原型
引出原型
开始之前,我们先看一段代码
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
自定义构造函数
首先,我们自定义一个 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
自定义 _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, '中华田园犬')