`

深入浅出 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(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.constructor
B.constructor
undefined
top secret 
 */
// 这时继承结构正确, 但变量初始化不正确, 所以需要修改 B
function 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.constructor
A.constructor
B.constructor
Jack
top secret  
 */


6 万事大吉. 让我们再来考虑如何把事情做的更优雅, 实际上在 ECMAScript 5th Edition 提供了函数 Object.create 能替代上面的 Extends. 基于这点, code 可以简化为:
function A() {};
function B() {A.call(this, arguments)};
B.prototype = Object.create(A.prototype);
B.prototype.constructor = B;

Object.create 也支持额外的参数, 可以自行查阅相关资料.

以上所有痛苦是为了更优雅的解决问题, 而不单单只是为了解决问题. copy, paste 也要知其所以然.
分享到:
评论

相关推荐

    深入浅出javascript pdf与实例

    "深入浅出JavaScript" 是一套专门针对JavaScript初学者和进阶者设计的学习资料,旨在帮助读者全面理解这门语言,并通过实例加深对概念的理解。这本书的中文版——"Head First JavaScript",以其独特的视觉和互动式...

    深入浅出javascript源代码

    《深入浅出JavaScript源代码》是一本旨在帮助开发者深入理解JavaScript核心机制的书籍,通过源代码分析来提升对语言本质的理解。这个压缩包包含了这本书籍中的源代码示例,为学习者提供了实际操作和研究的素材。 在...

    深入浅出JavaScript对象模型

    ### 深入浅出JavaScript对象模型 #### JavaScript对象的本质 根据ECMA262规范,ECMAScript被定义为一种基于对象的语言而非传统的面向对象语言。这意味着在JavaScript中,对象被视为存储数据的一种大型数组形式,...

    深入浅出javascript

    "深入浅出JavaScript"这本书旨在帮助开发者,无论初学者还是有一定基础的人,提升他们的JavaScript编程技能。 本书可能涵盖了以下几个核心知识点: 1. **基础语法**:讲解JavaScript的基础元素,包括变量、数据...

    Javascript之深入浅出prototype

    JavaScript中的原型(Prototype)是语言的核心特性之一,它在对象创建和继承机制中扮演着重要角色。通过原型,我们可以实现代码的复用和高效的对象共享。让我们深入探讨一下`prototype`这一概念及其相关知识点。 ...

    深入浅出理解javaScript原型链

    JavaScript的原型链是其面向...理解`prototype`和`__proto__`的关系以及`new`操作符的工作原理,是深入学习JavaScript面向对象编程的基础。通过不断实践和理解这些概念,我们可以更有效地编写和维护JavaScript代码。

    prototype开发手册以及PDF

    这份文档深入浅出地解释了如何利用prototype进行面向对象编程,是开发者掌握JavaScript面向对象编程基础的重要参考资料。在JavaScript中,prototype是实现对象继承的关键机制,它允许我们为对象添加新的属性和方法,...

    Prototype 1.6.0.3 中文参考手册

    《Prototype 1.6.0.3 中文参考手册》是一部深入浅出的JavaScript库学习资料,特别适合JavaScript初学者及对Ajax技术有需求的开发者。Prototype库是JavaScript的一个强大扩展,它提供了一系列实用的函数,简化了DOM...

    ASP.NET AJAX深入浅出系列课程(30):ASP.NET AJAX的相关扩展(下)

    【ASP.NET AJAX深入浅出系列课程(30):ASP.NET AJAX的相关扩展(下)】 在本课程中,我们将深入探讨ASP.NET AJAX的扩展性,这是Microsoft为Web开发人员提供的一种强大工具,允许他们创建更动态、响应更快的网页应用。...

    面向对象JavaScript精要(英文原版pdf)

    作者深入浅出地介绍了面向对象编程的基本原理以及如何将这些原理应用于JavaScript中。 #### 二、面向对象编程基础 面向对象编程(OOP)是一种软件开发方法,它通过将数据和处理这些数据的方法捆绑在一起形成“对象”...

    prototype1.4开发者手册(中文PDF)

    《Prototype 1.4开发者手册》是一本专为JavaScript库Prototype设计的中文参考资料,它深入浅出地介绍了如何利用Prototype进行Ajax开发。Prototype是JavaScript的一个强大框架,它为Web开发提供了一系列实用的功能,...

    ASP.NET AJAX深入浅出系列课程(9):使用Microsoft AJAX Library中的面向对象特性来进行开发

    ASP.NET AJAX技术是微软推出的一种增强Web应用程序交互性和用户体验的技术,它通过JavaScript库与服务器端的ASP.NET框架相结合,实现了页面的部分更新和异步通信。本课程的重点在于讲解如何利用Microsoft AJAX ...

    《JAVASCRIPT语言精髓与编程实践》.周爱民PDF

    总的来说,《JAVASCRIPT语言精髓与编程实践》是一本深入浅出的JavaScript教程,无论你是初学者还是有经验的开发者,都能从中受益匪浅。通过学习这本书,你将能够更全面地掌握JavaScript,从而在Web开发的世界中...

    李炎恢在线课堂JavaScript讲义代码

    李炎恢是一位知名的IT教育专家,他的在线课堂深入浅出地讲解了JavaScript的相关知识,旨在帮助学习者掌握这一重要技能。这个压缩包文件包含了他在课堂上讲解的JavaScript讲义和代码示例,是学习JavaScript的宝贵资源...

    [JavaScript学习指南(美)鲍尔斯].源代码

    《JavaScript学习指南(美)鲍尔斯》是一本深入浅出的JavaScript编程教程,作者通过丰富的实例和详尽的解释,帮助读者掌握这门强大的脚本语言。源代码部分提供了书中各个章节练习和示例的实现,是理解并实践...

    javascript权威指南与实例

    "javascript权威指南与实例"这本书深入浅出地阐述了JavaScript语言的基础与高级特性,并结合实例帮助读者更好地理解和运用这些知识。 首先,JavaScript的核心语言特性包括变量、数据类型、运算符、流程控制(如条件...

    prototype1.4中文开发者手册

    《Prototype 1.4中文开发者手册》是一本深入解析Prototype JavaScript库的重要参考资料,旨在帮助开发者更好地理解和使用这个强大的JavaScript框架。Prototype是一个广泛应用于Web开发的开源JavaScript库,它极大地...

    JavaScript 权威指南第五版

    总结,《JavaScript权威指南第五版》是一本覆盖全面、深入浅出的JavaScript学习资料,无论你是初学者还是经验丰富的开发者,都能从中受益匪浅。通过阅读这本书,你将能够掌握JavaScript的核心概念,以及在客户端和...

Global site tag (gtag.js) - Google Analytics