javascript不包含传统的类继承模型,而是使用 prototype 原型模型。
虽然这经常被当作是 JavaScript 的缺点被提及,其实基于原型的继承模型比传统的类继承还要强大。实现传统的类继承模型是很简单,但是实现 JavaScript 中的原型继承则要困难的多。ES6新增加的class语法糖使得这种继承方式实现起来更加简单,但其原理还是原型继承,而不是类继承。
由于 JavaScript 是唯一一个被广泛使用的基于原型继承的语言,所以理解两种继承模式的差异是需要一定时间的,今天我们就来了解一下原型和原型链。
函数对象和普通对象
javascript中的对象分为函数对象和普通对象。其区分起来也很简单,所有Function的实例都是函数对象,其他的都是普通对象,Object和Function都是函数对象。
函数对象在javascript中起到模拟类的作用,我们可以使用new来创建一个函数对象的实例,这个实例是一个普通对象,它通过__proto__从函数对象的prototype中继承属性。
1 | var Person = function(){}; |
prototype和__proto__
由上面的例子可以得知,原型继承是由prototype和__proto__两个属性来完成的,只要知道这两个属性分别指向哪儿,他们是如何工作的,那么就能弄明白原型继承了。
这里依旧使用上面的例子:
1 | var Person = function(){}; |
由上述的例子可以得知:
- 普通对象没有prototype属性,只有__proto__属性,而函数对象都有
- 普通对象的__proto__属性都指向其原型函数对象的prototype
- 函数对象的__proto__属性都指向Function.prototype,Object的__proto__属性也是
- 函数对象的prototype.__proto__属性都指向Object.prototype,除了Object.prototype.__proto__ –> null
我们通过new来创建函数对象的实例,而一个实例不是不能再实例化的,所以普通对象没有prototype就很好理解了。
而对于函数对象来说
1 | typeof Person.prototype //"object" |
这里把原型对象prototype单独拿出来,它其实是一个object,它存放了函数对象需要被继承的属性,以及构造函数。
1 | Person.__proto__ = Funtion.prototype |
所有函数对象的__proto__属性都指向Function.prototype,包括Function自身的__proto__属性和Object的__proto__属性
作为所有对象的原型Object,普通对象的__proto__属性都指向Object.prototype。而Object.prototype.__proto__ = null,万物都是由无到有,原型链的末端就是null了。
1 | p1.__proto__ == Person.prototype; //true |
由此可以再得到一个结论
__proto__和prototype在原型链中是自动分配的,__proto__用于连接对象和原型对象,prototype用于保存原型属性,构造函数和__proto__属性
这里再引用一张图
通过这张图应该就能很清楚的看到对象之间的继承关系了。
原型链
1 | var Person = function(name,age){ |
这里在Person上建立构造函数,在执行new操作时会执行这个构造函数。现在得到的p1是这样的:
1 | { |
现在来试试多重继承
1 | var A = function(){}; |
这里我们让B继承于A,再实例化一个B。执行c.a时,c自身并没有这个属性,于是它顺着原型链去寻找a属性,首先是a.__proto__即B.prototype,B的原型对象上有这个属性,于是它便不再继续寻找,返回了3。执行c.b时同理,在B的原型对象上不存在b属性,于是继续顺着原型链寻找,在A的原型对象上找到了b属性,返回2。
constructor
constructor永远指向函数本身,所以在上面的例子中可以使用函数本身作为构造函数,构造函数中属性会直接赋值在实例上。
1 | var Person = function(name,age){ |
此时p1对象结构为:
1 | { |
1 | Person.prototype.constructor == Person //true |
总结
对象通过__proto__来连接原型对象prototype,也通过__proto__来进行原型链上的属性查找,直到原型链的末端:Object.prototype.__proto__–>null。