原型与原型链
简述原型与原型链
在 JavaScript 中,函数可以有属性。 每个函数都有一个特殊的属性叫作原型(prototype)。
通常,我们会通过构造函数对原型进行操作:
1 | function Foo() {} |
打印出来的对象就是Foo
函数的原型,将其展开,发现里面有两个属性constructor
和__proto__
。constructor
指向Foo
函数本身,__proto__
是一个对象。
可以在函数的原型上添加一些其他属性,比如:
1 | Foo.prototype.name = 'nick' |
再次查看Foo
的原型:
1 | console.log(Foo.prototype) |
发现刚刚添加的name
与age
已经在原型中了。
现在创建一个Foo
的实例:
1 | var foo = new Foo() |
将其打印,发现里面有一个__proto__
属性,而这个属性里面包含了之前在构造函数Foo
的原型中创建的name
和age
属性。也就是说,构造函数的实例都可以通过__proto__
属性找到创建它的构造函数的原型对象。
由此可以得到下面这张关系图:
在之前打印Foo
的原型时,发现里面的__proto__
是一个对象,将其展开,发现有一个constructor
属性是指向Object
构造函数的。那么Foo
原型上的__proto__
会不会就是构造函数Object
的原型呢?
带着疑问,试着打印对比一下:
1 | console.log(Foo.prototype.__proto__ === Object.prototype) // true |
结果为true
。并且我们知道在JavaScript
中,函数都有一个特殊的属性prototype
,那么构造函数Object
的内部结构与之前创建的构造函数Foo
的内部结构是差不多的。
现在再来更新一下刚刚的关系图:
可以看出,函数的__proto__
属性就像一根线条一样一直往上衔接,那么问题又来了?Object
原型上的__proto__
又是个啥?会不会是其他构造函数的原型?
带着疑问,再次打印看看结果:
1 | console.log(Object.prototype.__proto__) // null |
结果是一个特殊值null
,这就表明,原型链的最顶层就是到构造函数Object
原型的__proto__
,也就是null
。
更新一下刚才的关系图:
到这里,基本上把原型与原型链的关系理清楚了。
但是,还可以延伸一点点内容。
我们知道,在JavaScript
中,函数实际上是一个Function
对象。这是不是就表明构造函数Foo
与Object
是Function
的实例?
可以通过instanceof
来测试一下:
1 | Foo instanceof Function // true |
发现都为true
,按照之前的结论,实例的__proto__
属性都指向创建它的构造函数的prototype
对象,
这就说明,构造函数的__proto__
都是指向Function
的prototype
对象。
1 | console.log(Foo.__proto__ === Function.prototype) // true |
而Function
的__proto__
应该也是指向自身的prototype
对象的:
1 | console.log(Function.__proto__ === Function.prototype) // true |
那么Function
的prototype
对象中的__proto__
又指向哪里呢?
当然是Object
的原型呐:
1 | console.log(Function.prototype.__proto__ === Object.prototype) // true |
这样一来,再更新一下刚刚的关系图:
总结
- 任何函数都有一个
prototype
属性,它是一个对象,直观的说,这个对象就是当前函数的原型。 prototype
中的constructor
通常指向函数自身。- 实例对象的
__proto__
指向创建它的构造函数的原型。 - 构造函数的
__proto__
指向Function
的原型。 Function
的__proto__
指向自身的原型(和上一条类似)。