通过原型理解 javascript 中 new 命令的原理
引出原型
开始之前,我们先看一段代码
const obj = {}
obj.toString() //[object Object]
const obj = {}
obj.toString() //[object Object]
这段代码做了两件事情:
- 创建一个名为 obj 的对象
- 调用 obj 的 toString 方法,返回这个对象的字符串形式
我们创建了一个空对象,并没有在它身上声明 toString 方法,但是却成功调用了 toString 并返回了一个字符串。这里我们就要明白,我们调用 toString 方法都经历了哪些过程:
- 浏览器首先检查,obj 对象是否具有可用的 toString() 方法。
- 如果没有,则浏览器检查 obj 对象的原型对象是否具有可用的 toString() 方法,这里有这个方法(为什么会有,请看下文),于是该方法被调用。
所以,在这里 obj 调用的并不是自身的 toString 方法,而是找到了它的原型对象中的 toString ,然后再调用。那么,原型对象又是怎么来的?
什么是原型对象
JavaScript 规定,每个函数都有一个 prototype 属性,指向一个对象
每个函数都有一个 prototype 属性,指向一个对象,但是这些和 obj 有什么关系?我们知道,obj 对象是 Object 构造函数生成的实例,构造函数也是函数。那么 Object 就有一个 prototype 属性,指向一个对象,而这个对象,就是 obj 要找的原型对象。我们不妨在控制台打印一下 Object.prototype :
所以,obj 调用的其实就是它的原型对象(Object.prototype)上的 toString 方法。 注意上图中 prototype 对象还有一个 constructor 属性,默认指向实例对象所在的构造函数。也就是说,obj 的原型对象上的 constructor 指向 Object ,打印 __Object.prototype.constructor = Object 会返回 true 。 到现在,实例对象、原型对象、构造函数三者之间的关系应该是很明确的,如下图所示:
Object.getPrototypeOf() 方法返回指定对象的原型
new 命令的原理
知道什么是原型和实例对象、原型对象、构造函数三者之间的关系后,可以自己实现一个 _new 函数来模仿 new 命令的操作。 前置知识:Object.create
Object.create() 方法用于创建一个新对象,使用现有的对象来作为新创建对象的原型(prototype)
const person = { name: 'xxx' }
const person2 = Object.create(person)
person2.name //xxx
const person = { name: 'xxx' }
const person2 = Object.create(person)
person2.name //xxx
自定义构造函数
首先,我们自定义一个 Dog 构造函数,并生成一个实例:
function Dog(breed) {
this.breed = breed
}
const myDog = new Dog('中华田园犬')
function Dog(breed) {
this.breed = breed
}
const myDog = new Dog('中华田园犬')
有一点需要注意的是,如果构造函数内部有 return 语句,而且 return 后面跟着一个对象,new 命令会返回 return 语句指定的对象,否则,直接返回实例对象:
function Dog(breed) {
this.breed = breed
return { name: 'xxx' }
}
const myDog = new Dog('中华田园犬')
myDog.name //xxx
function Dog(breed) {
this.breed = breed
return { name: 'xxx' }
}
const myDog = new Dog('中华田园犬')
myDog.name //xxx
自定义 _new 函数
那么不通过 new 命令来创造实例,如何自己去实现 new 命令的操作。其实,主要有三个地方需要考虑:
1.以构造函数的 prototype 属性为原型创建一个空对象,这样空对象才能正确的继承属性和方法 2.将这个空对象绑定为构造函数内部的 this 并且执行,这样才能实现构造函数内部的操作(比如附值操作:this.name = 'xxx') 3.判断构造函数的返回值并返回
具体实现如下:
function _new(Constructor, ...arg) {
// 以构造函数的 prototype 属性为原型创建一个空对象
const newObject = Object.create(Constructor.prototype)
// 将这个空对象绑定为构造函数内部的 this 并且执行
// arg 是构造函数需要的参数,也应该传入
const result = Constructor.apply(newObject, arg)
// 判断返回值
return typeof result __= 'object' && result != null ? result : newObject
}
const myDog = _new(Dog, '中华田园犬')
function _new(Constructor, ...arg) {
// 以构造函数的 prototype 属性为原型创建一个空对象
const newObject = Object.create(Constructor.prototype)
// 将这个空对象绑定为构造函数内部的 this 并且执行
// arg 是构造函数需要的参数,也应该传入
const result = Constructor.apply(newObject, arg)
// 判断返回值
return typeof result __= 'object' && result != null ? result : newObject
}
const myDog = _new(Dog, '中华田园犬')