深入浅出 javascript prototype 继承
最近又重温了 js 的继承关系, 看了几篇帖子, 终于被我悟道了, 在此分享一下.
如果您没有耐心阅读完本文章, 请直接跳转至最后一段, 复制代码.
http://www.51testing.com/html/90/n-814890-2.html
推荐大家先看看这篇帖子, 等云里雾里的时候再看我下面的解释就豁然开朗了.
关键词: function, prototype, __proto__
javascript 对象是原型继承的, 当访问实例对象的某个属性而没有找到的时候, js 引擎会追溯这个对象的 prototype 链条, 尝试从超类, 超类的超类... 找到想要的属性; 如果失败返回 undefined, 成功则返回超类 prototype 对象的属性引用. 需要额外注意的是, 这个属性是只读的, "实例"是不可能改变"原型"中的属性的. 这段话相信大家都耳熟能详, 但它却是暗藏玄机, 需要细细的去解读一番.
1 prototype 只有类才具备, 实例类型是不具备的, 例如:
function First() {
}
var f = new First();
console.log(f.prototype);
会输出 undefined, 怎么样是不是同你的直觉相悖呢? 如果你打开 chrome 浏览器的调试功能, 你会发现 f 这个"实例"就没有 prototype 这个属性, 只有 __proto__ . 但这完全不矛盾, 定义只说:属性查找的时候会追溯原型链, 但幷没有说 prototype 是对象自身的. 它只属于对象的"类".
2 __proto__ 只属于实例, 或者说当一个对象可以作为"实例"时就具有了 __proto__ 例如
function First() {
}
启用调试模式就发现 First 同时具有 prototype 和 __proto__, 这是因为, 如果定义
var f = new First();// First 作为类型使用, 所以它具有 prototype, 而 First 本身是函数类型 Function 的一个实例, 所以也具有 __proto__ 属性.
3 对象的建立顺序是解释原型继承的关键. 援引帖子:
// json 只是用来表示结构, 不是说"类"真的就是一个jsonvar someclass = { prototype : { constuctor : function used when init newly created object. __proto__ : point to parent }}
上面的三个步骤转化为伪码就是:
function A() {}var a = {};// empty object [1]a.__proto__ = A.prototype;// [2]A.call(a, arguments);//[3]function A() { this.name = 'Jack';}function B() {}// 如果我们想让 B 继承 A 的属性 name, 那么只要var b = new B();b.__proto__ = A.prototype;function B() {}var b = new B();b:{ __proto__: { constructor: function B() {...} __proto__: Object }}b:{ __proto__: { constructor: function B() {...} __proto__: A }}function A() {}function B() {}function F() {}F.prototype = A.prototype;var f = new F();B.prototype = f;B.prototype.constructor = B;function A() {}function F() {}// 清晰起见, 我们设置var APROTO = A.prototypeF.prototype = A.prototype;// F.prototype = APROTO;var f = new F();// f.__proto__ = F.prototype = APROTO;function B() {}B.prototype = f;// js 默认会为每个类型创建一个 prototype 对象, // 当然我们也可以指定 prototype 对象, 像这样 B.prototype = f, // 则有, B.prototype = f = {__proto__ : APROTO} 这里尤其关键, 因为它折腾出了一个 __proto__ 指向 A 的对象 f.var b = new B();/* 根据实例建立的三个步骤 b.__proto__ = B.prototype = {__proto__ : APROTO}; b 的结构为:b:{ __proto__: { __proto__: APROTO }}而之前的目标结构为:b:{ __proto__: { constructor: function B() {...} __proto__: A }}比较发现, 只要设置B.prototyp.constructor = B;目标和结果就完全一致了, 就实现了完美的继承.*/// 清晰起见我们定义tool函数function Extends(superobj) { function F() { } F.prototype = superobj; return new F();}function A() {}// 如果要实现 B extends A, 则只要function B() {}B.prototype = Extends(A.prototype);B.prototype.constructor = B;var b = new B();// successful? 这里忽略了一件事情, 就是 A 的私有属性没有在 B 中被初始化, 也就是没有 A.call(b, arguments);// 让我们添加一点日志再次执行上面的代码function A() { console.log('A.constructor'); this.name = 'Jack';}function Extends(superobj) { function F() { console.log('F.constructor'); } F.prototype = superobj; return new F();}function B() { console.log('B.constructor'); this.company = 'top secret';}B.prototype = Extends(A.prototype);B.prototype.constructor = B;var b = new B();console.log(b.name);console.log(b.company);/*输出:F.constructorB.constructorundefinedtop secret */// 这时继承结构正确, 但变量初始化不正确, 所以需要修改 Bfunction B() { A.call(this, arguments);// <------- console.log('B.constructor'); this.company = 'top secret';}// 再次执行var b = new B();console.log(b.name);console.log(b.company);/*输出:F.constructorA.constructorB.constructorJacktop secret */function A() {};function B() {A.call(this, arguments)};B.prototype = Object.create(A.prototype);B.prototype.constructor = B;