您的位置:首页 > Web前端 > JavaScript

彻底理解原型链 (prototype chain)和Simple JavaScript Inheritance

2017-02-07 00:00 302 查看
摘要: Simple JavaScript Inheritance

彻底理解原型链 (prototype chain)

在 ECMAScript 中,每个由构造器创建的对象拥有一个指向构造器 prototype 属性值的 隐式引用(implicit reference,这个引用称之为 原型(prototype。进一步,每个原型可以拥有指向自己原型的 隐式引用(即该原型的原型),如此下去,这就是所谓的 原型链(prototype chain参考资源)。在具体的语言实现中,每个对象都有一个 __proto__ 属性来实现对原型的 隐式引用程序清单 4说明了这一点。

清单 4. 对象的 __proto__ 属性和隐式引用

function Person( name ) { this.name = name; }
var p = new Person();
//对象的隐式引用指向了构造器的 prototype 属性,所以此处打印 true
console.log( p.__proto__ === Person.prototype );
//原型本身是一个 Object 对象,所以他的隐式引用指向了
// Object 构造器的 prototype 属性 , 故而打印 true
console.log(Person.prototype.__proto__ === Object.prototype );
// 构造器 Person 本身是一个函数对象,所以此处打印 true
console.log( Person.__proto__ === Function.prototype );


有了 原型链,便可以定义一种所谓的 属性隐藏机制,并通过这种机制实现继承。ECMAScript 规定,当要给某个对象的属性赋值时,解释器会查找该对象原型链中第一个含有该属性的对象(注:原型本身就是一个对象,那么原型链即为一组对象的链。对象的原型链中的第一个对象是该对象本身)进行赋值。反之,如果要获取某个对象属性的值,解释器自然是返回该对象原型链中首先具有该属性的对象属性值。图 1说名了这中隐藏机制:

图 1. 原型链中的属性隐藏机制



在图 1 中,object1->prototype1->prototype2 构成了 对象 object1 的原型链,根据上述属性隐藏机制,可以清楚地看到 prototype1 对象中的 property4 属性和 prototype2 对象中的 property3 属性皆被隐藏。理解了原型链,那么将非常容易理解 JS 中基于原型的继承实现原理,程序清单 5 是利用原型链实现继承的简单例子。

清单 5. 利用原型链 Horse->Mammal->Animal 实现继承

// 声明 Animal 对象构造器
function Animal() { }
// 将 Animal 的 prototype 属性指向一个对象,
// 亦可直接理解为指定 Animal 对象的原型
Animal.prototype = {
name: "animal",
weight: 0,
eat: function() {
alert( "Animal is eating!" );
}
}
// 声明 Mammal 对象构造器
function Mammal() { this.name = "mammal"; }
// 指定 Mammal 对象的原型为一个 Animal 对象。
// 实际上此处便是在创建 Mammal 对象和Animal 对象之间的原型链
Mammal.prototype = new Animal();
// 声明 Horse 对象构造器
function Horse(height,weight){
this.name = "horse";
this.height = height;
this.weight = weight;
}
// 将 Horse对象的原型指定为一个 Mamal 对象,继续构建 Horse 与 Mammal 之间的原型链
Horse.prototype = new Mammal();
// 重新指定 eat方法 , 此方法将覆盖从 Animal 原型继承过来的 eat 方法
Horse.prototype.eat = function() {
alert( "Horse is eating grass!" );
}
// 验证并理解原型链
var horse = new Horse( 100, 300 );
console.log(horse.__proto__ === Horse.prototype);
console.log( Horse.prototype.__proto__ === Mammal.prototype );
console.log( Mammal.prototype.__proto__ === Animal.prototype );*

理解清单 5 中对象原型继承逻辑实现的关键在于 Horse.prototype = new Mammal() 和 Mammal.prototype = new Animal() 这两句代码。首先,等式右边的结果是构造出一个临时对象,然后将这个对象赋值给等式左边对象的 prototype 属性。也就是说将右边新建的对象作为左边对象的原型。读者可以将这两个等式替换到相应的程序清单 5 代码最后两行的等式中自行领悟。

JavaScript 类式继承的实现方法

从代码清单 5 可以看出,基于原型的继承方式,虽然实现了代码复用,但其行文松散且不够流畅,可阅读性差,不利于实现扩展和对源代码进行有效地组织管理。不得不承认,类式继承方式在语言实现上更具健壮性,且在构建可复用代码和组织架构程序方面具有明显的优势。这使得程序员们希望寻找到一种能够在 JavaScript 中以类式继承风格进行编码的方法途径。从抽象的角度来讲,既然类式继承和原型继承都是为实现面向对象而设计的,并且他们各自实现的载体语言在计算能力上是等价的 ( 因为图灵机的计算能力与 Lambda 演算的计算能力是等价的 ),那么能不能找到一种变换,使得原型式继承语言通过该变换实现具有类式继承编码的风格呢?

目前一些主流的 JS 框架都提供了这种转换机制,也即类式声明方法,比如 Dojo.declare()、Ext.entend() 等等。用户使用这些框架,可以轻易而友好地组织自己的 JS 代码。其实,在众多框架出现之前,JavaScript 大师 Douglas Crockford 最早利用三个函数对 Function 对象进行扩展,实现了这种变换,关于它的实现细节可以(参考资源)。此外还有由 Dean Edwards实现的著名的 Base.js(参考资源)。值得一提的是,jQuery 之父 John Resig 在搏众家之长之后,用不到 30 行代码便实现了自己的 Simple Inheritance。使用其提供的 extend 方法声明类非常简单。程序清单 6是使用了 Simple Inheritance库实现类的声明的例子。其中最后一句打印输出语句是对 Simple Inheritance实现类式继承的最好说明。

清单 6. 使用 Simple Inheritance 实现类式继承

// 声明 Person 类
var Person = Class.extend( {
_issleeping: true,
init: function( name ) { this._name = name; },
isSleeping: function() {
return this._issleeping;
}
} );
// 声明 Programmer 类,并继承 Person
var Programmer = Person.extend( {
init: function( name, issleeping ) {
// 调用父类构造函数
this._super( name );
// 设置自己的状态
this._issleeping = issleeping;
}
} );
var person = new Person( "张三" );
var diors = new Programmer( "张江男", false );
// 打印 true
console.log( person.isSleeping() );
// 打印 false
console.log( diors.isSleeping() );
// 此处全为 true,故打印 true
console.log( person instanceof Person && person instanceof Class && diors instanceof Programmer &&
diors instanceof Person && diors instanceof Class );

如果您已对原型、函数构造器、闭包和基于上下文的 this 有了充分的理解,那么理解 Simple Inheritance 的实现原理也并非相当困难。从本质上讲,var Person = Class.extend(...)该语句中,左边的 Person 实际上是获得了由 Class 调用 extend 方法返回的一个构造器,也即一个 function 对象的引用。顺着这个思路,我们继续介绍 Simple Inheritance 是如何做到这一点,进而实现了由原型继承方式到类式继承方式的转换的。图 2是 Simple Inheritance 的源码及其附带注释。为了方便理解,用中文对代码逐行补充说明。

图 2.Simple Inheritance 源码解析



抛开代码第二部分,整体连贯地考察第一和第三部分会发现,extend 函数的根本目的就是要构造一个具有新原型属性的新构造器。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: