JavaScript是一种基于原型继承的语言,不包含传统的类继承模型,没有“类”和“实例”的概念,全靠原型链实现继承。
传统类继承,通过new命令创建实例,Brendan Eich也把new命令引入了JavaScript,通过new 构造函数来创建对象。
function Dog(name) { this.name = name; this.eat = function () { ...} } var daMao = new Dog("大毛"); var xiaoMao = new Dog("小毛");
上面创建了两个对象,但是有个问题就是,daMao和xiaoMao各有个eat function的副本,这有点浪费资源,怎么办呢。
Brendan Eich为构造函数设置了一个prototype属性,这个属性指向一个对象(原型对象),所有实例对象共享该对象的方法和属性。给出如下代码
function Dog(name) { this.name = name; } Dog.prototype = { eat: function() { //eat something } }
这就是原型继承的实现方式了,然后通过
var dogA = new Dog("大狗");
就可以创建新的dogA对象。
new运算符做了哪些工作呢,
(1) 创建一个空的对象,对象的__proto__属性指向Dog.prototype;
(2) 初始化对象,函数Dog被传入参数并调用,this指向该对象。
(3) 返回对象。
大多数JavaScript实现用一个__proto__属性来表示一个对象的原型链。当查找一个对象的属性时,JavaScript 会向上遍历原型链,直到找到给定名称的属性为止。—JavaScript秘密花园
Vjeux在Javascript – How Prototypal Inheritance really works中给出一段代码用于解释JS引擎如何查找属性:
function getProperty(obj, prop) { if (obj.hasOwnProperty(prop)) return obj[prop] else if (obj.__proto__ !== null) return getProperty(obj.__proto__, prop) else return undefined; }上述dogA的原型链如下:
dogA
Dog.prototype
{eat: function() {}}
Object.prototype
{toString: ... , hasOwnProperty ...}
dogA具有原型链上对象的所有方法。
所有的实例对象共享同一个prototype对象,实例对象就好像"继承"了prototype对象一样。下面给出一张图(出自 ECMAScript规范)来解释原型继承的工作原理。
Object.prototype.bar = 1; var foo = {goo: undefined}; foo.hasOwnProperty('bar'); // false foo.hasOwnProperty('goo'); // true注意: hasOwnProperty是可以被覆盖的,
var foo = { hasOwnProperty: function() { return false; }, bar: 'Here be dragons' }; foo.hasOwnProperty('bar'); // always returns false当hasOwnProperty被覆盖时,可以通过如下方式来调用外部的hasOwnProperty函数
({}).hasOwnProperty.call(foo, 'bar'); // true Object.prototype.hasOwnProperty.call(foo, 'bar'); // true当使用 for in 循环遍历对象的属性时,原型链上的所有属性都将被访问。可以通过Object.prototype原型上的hasOwnProperty函数来过滤不希望出现的属性。
for (var i in foo) { if (foo.hasOwnProperty(i)) { console.log(i); } }
不仅是过滤不希望出现的属性,当JS对象被扩展时,使用for in遍历属性时可能会出错。Prototype类库就扩展了内置对象,因此当页面包含这个类库时,不使用hasOwnProperty过滤的for in循环可能会出问题。
[1] Javascript继承机制的设计思想.
[2] JavaScript原型继承工作原理
[3] Prototypal Inheritance in JavaScript