深入浅出 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 对象的建立顺序是解释原型继承的关键. 援引帖子:
引用
先了解下new运算符,如下:
var a1 = new A;
var a2 = new A;
这是通过构造函数来创建对象的方式,那么创建对象为什么要这样创建而不是直接var a1 = {};呢?这就涉及new的具体步骤了,这里的new操作可以分成三步(以a1的创建为例):
1、新建一个对象并赋值给变量a1:var a1 = {};
2、把这个对象的[[Prototype]]属性指向函数A的原型对象:a1.[[Prototype]] = A.prototype
3、调用函数A,同时把this指向1中创建的对象a1,对对象进行初始化:A.apply(a1,arguments)
按照上面所说, 对象建立是分为建立和初始化两个步骤的, 而初始化是通过 constructor 来完成的. 每个"类"类型就具有这样的结构
// json 只是用来表示结构, 不是说"类"真的就是一个json
var 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]
由于 js 的继承是实例继承, 所以 A.prototype 对象本身也是一个实例, 它自然具有 __proto__ 属性.
4 基于 1, 2, 3 我来解释下为什么继承的实现这么啰嗦:
function A() {
this.name = 'Jack';
}
function B() {
}
// 如果我们想让 B 继承 A 的属性 name, 那么只要
var b = new B();
b.__proto__ = A.prototype;
整件事情就极其简单的被搞定了. 可遗憾的是你不能这么做, __proto__ 这个指针只是 FF 和 chrome 浏览器实现提的, 在规范中它叫做 [[Prototype]], 是不可以被改变的, 所以以上代码只是 hack 行为, 自己想想就行了.
再次基于以上所有知识, 让我们想法来绕过去
function B() {
}
var b = new B();
这时 b 对象的结构是:
b:{
__proto__: {
constructor: function B() {...}
__proto__: Object
}
}
b 的超类是 Object, 而我们需要的结构是:
b:{
__proto__: {
constructor: function B() {...}
__proto__: A
}
}
有牛人(sorry 名字没记住)在 2006 就帮我们把这个死脑细胞的问题搞定了, 让我们一起来看看吧:
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.prototype
F.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;
目标和结果就完全一致了, 就实现了完美的继承.
*/
5 更多细节:
// 清晰起见我们定义tool函数
function Extends(supe