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) { |