Introduction
本来用英语写了一篇,结果看起来乱糟糟的,还是用中文写一下吧。
原型是JavaScript中非常重要的概念,面试中也常常会问到这个问题。那么,原型到底是什么?
原型的英文是prototype,他是函数的一个属性,是一个对象。
下面来看一张图,这个图乍一看非常复杂,但是理解之后,就会发现原型其实非常简单。
在理解这张图之前,我们先明确几个概念:
- 对象具有
__proto__和constructor属性。 - 函数具有
prototype属性。 - 因为JS中函数也是对象,所以函数也有
__proto__和constructor属性。
所以,我们可以得出以下结论:
- 对象具有
__proto__和constructor属性。 - 函数具有
prototype,__proto__和constructor属性。
这两条结论反映在上面的图中就是:
- 所有蓝色节点都向外发出两条线,分别对应
__proto__和constructor属性。 - 所有绿色节点都向外发出三条线,分别对应
prototype,__proto__和constructor属性。
我们以一个简单的Person函数阐述上面这张图。
1 | function Person(name) { |
__proto__属性
在这个例子中,Person函数是一个构造函数,p是Person的一个实例。为了方便,我们将上图简化一下,只保留__proto__属性。
先看左边纵向的这条线。
- 因为对象
p是Person函数的一个实例,所以p的__proto__属性指向Person的prototype属性。 - 而
Person继承自Object,所以Person的__proto__属性指向Object的prototype属性。 Object的__proto__属性指向null。
p, Person, Object, null之间通过__proto__属性形成了一条链,这就是所谓的原型链。当我们在某个对象上查找一个属性时,如果该对象没有这个属性,那么JS会通过原型链向上查找,直到找到这个属性或者到达null。举几个例子:
p.printName()-p对象上没有printName属性,所以JS会通过原型链向上查找,找到Person的prototype属性,从而找到printName方法。p.hasOwnProperty('name')-p对象上没有hasOwnProperty属性,向上查找到Person,Person的原型对象上也没有hasOwnProperty属性,继续向上查找到Object,Object原型上有hasOwnProperty属性,于是调用之,所以返回true。p.foo()-p对象上没有foo属性,向上查找到Person,Person的原型对象上也没有foo属性,继续向上查找到Object,Object的原型上还是没有foo属性,继续向上查找到null,而null上也没有foo属性,所以会报错。—TypeError: p.foo is not a function
再看右侧部分,Person, Function, Object这三者皆是函数,而JS中函数也是对象,故而这三者亦有__proto__属性,而且都指向Function的prototype属性。而Function的__proto__属性指向Object的prototype属性。
由此我们可以得出结论:
- 所有对象的
__proto__属性指向其构造函数的prototype属性。 - 所有函数的
__proto__属性指向Function的prototype属性。 Function的__proto__属性指向Object的prototype属性。Object的__proto__属性指向null。
所有__prototype__的终点都是null.
prototype属性
照例现简化一下上图,只保留prototype属性。
因为prototype是函数的一个属性(对象没有prototype),所以图中只有绿色节点有prototype属性。对于一个函数Foo来说,他的prototype属性是一个对象,指向Foo.prototype
constructor
最后来看constructor属性。
constructor就是某个对象的构造函数,因为JS中函数也是对象,所以函数也有constructor属性。
- 对于普通对象,其
constructor属性指向其构造函数。 - 对于函数,其
constructor属性指向Function。 Function的constructor属性指向其自身。
根据以上结论,不难得出如下结论:
- 对象
p的constructor属性指向Person函数, 因为p是Person的一个实例。 Person.prototype的constructor指向Person函数,这个是为啥?- 硬性规定Person和Object的constructor都指向Function,因为Person和Object都是函数。Function的constructor属性指向Function。
由以上结论可以推导出:Function是所有constructor的终点。
总结
我们把上面的结论公式化一下,对于如下代码:
1 | function Person() {} |
我们有如下结论成立:
1 | console.log(p.__proto__ === Person.prototype); // true |
其他杂项
- 箭头函数没有
prototype属性,因为箭头函数不可以做构造函数。 - Bound函数(使用
bind创建的函数)也没有prototype属性。why?
Introduction
prototype is an very important concept in JavaScript. It’s used to implement inheritance in JavaScript.
prototype is a property of a function, it’s an object. When you create a function, JavaScript engine will automatically create a prototype object for you. This object has a property called constructor, which points back to the function.
The prototype data property of a Function instance is used when the function is used as a constructor with the new operator. It will become the new object’s prototype.
Take the following code as an example, its prototype is an empty object {}.
1 | function Person(name) { |
Let’s create a functions on its prototype, and it’s prototype now has a property(function) called printName.
1 | function Person(name) { |
Create an instance of Person
When you create an instance of Person, the instance will have a property called __proto__, which points to the prototype of the constructor function(the Person function).
1 | function Person(name) { |
Functions defined on the prototype are shared by all instances of the constructor function.
1 | function Person(name) { |
But, functions defined in the constructor function are not shared by all instances.
1 | function Person(name) { |