JavaScript 不包含传统的类继承模型,而是使用 prototype 原型模型。javascript
虽然这常常被看成是 JavaScript 的缺点被说起,其实基于原型的继承模型比传统的类继承还要强大。 实现传统的类继承模型是很简单,可是实现 JavaScript 中的原型继承则要困难的多。 (It is for example fairly trivial to build a classic model on top of it, while the other way around is a far more difficult task.)java
因为 JavaScript 是惟一一个被普遍使用的基于原型继承的语言,因此理解两种继承模式的差别是须要必定时间的。git
第一个不一样之处在于 JavaScript 使用原型链的继承方式。程序员
注意: 简单的使用Bar.prototype = Foo.prototype
将会致使两个对象共享
相同的原型。 所以,改变任意一个对象的原型都会影响到另外一个对象的原型,在大多数状况下这不是但愿的结果。
function Foo() { this.value = 42; } Foo.prototype = { method: function() {} }; function Bar() {} // 设置Bar的prototype属性为Foo的实例对象 Bar.prototype = new Foo(); Bar.prototype.foo = 'Hello World'; // 修正Bar.prototype.constructor为Bar自己 Bar.prototype.constructor = Bar; var test = new Bar() // 建立Bar的一个新实例 // 原型链 test [Bar的实例] Bar.prototype [Foo的实例] { foo: 'Hello World' } Foo.prototype {method: ...}; Object.prototype {toString: ... /* etc. */};
上面的例子中,test
对象从 Bar.prototype
和 Foo.prototype
继承下来;所以, 它能访问 Foo
的原型方法 method
。同时,它也可以访问那个定义在原型上的 Foo
实例属性 value
。 须要注意的是 new Bar()
不会创造出一个新的 Foo
实例,而是 重复使用它原型上的那个实例;所以,全部的 Bar
实例都会共享相同的 value
属性。github
Bar.prototype = Foo
,由于这不会执行
Foo
的原型,而是指向函数
Foo
。 所以原型链将会回溯到
Function.prototype
而不是
Foo.prototype
,所以
method
将不会在 Bar 的原型链上。
当查找一个对象的属性时,JavaScript 会向上遍历原型链,直到找到给定名称的属性为止。编程
到查找到达原型链的顶部 – 也就是 Object.prototype
– 可是仍然没有找到指定的属性,就会返回 undefined。ide
当原型属性用来建立原型链时,能够把任何类型的值赋给它(prototype)。 然而将原子类型赋给 prototype 的操做将会被忽略。函数
function Foo() {} Foo.prototype = 1; // 无效
而将对象赋值给 prototype,正如上面的例子所示,将会动态的建立原型链。oop
若是一个属性在原型链的上端,则对于查找时间将带来不利影响。特别的,试图获取一个不存在的属性将会遍历整个原型链。性能
而且,当使用 for in
循环遍历对象的属性时,原型链上的全部属性都将被访问。
一个错误特性被常常使用,那就是扩展 Object.prototype
或者其余内置类型的原型对象。
这种技术被称之为 monkey patching 而且会破坏封装。虽然它被普遍的应用到一些 JavaScript 类库中好比 Prototype, 可是我仍然不认为为内置类型添加一些非标准的函数是个好主意。
扩展内置类型的惟一理由是为了和新的 JavaScript 保持一致,好比 Array.forEach
。
在写复杂的 JavaScript 应用以前,充分理解原型链继承的工做方式是每一个 JavaScript 程序员必修的功课。 要提防原型链过长带来的性能问题,并知道如何经过缩短原型链来提升性能。 更进一步,绝对不要扩展内置类型的原型,除非是为了和新的 JavaScript 引擎兼容。