JavaScript实现继承的方式和原理

JavaScript实现继承的方式和原理

 说明:参考了很多帖子,以这个为蓝本:http://www.jb51.net/article/81766.htm


    对于从C++Java等转来的程序员,理解JavaScript的“类-继承”思想可能不是显而易见的。简而言之,JavaScript实现的是实现继承而非接口继承,主要依靠原型链来实现。实现继承的方式也有多种。

0、构造函数、原型、实例之间的关系

每个构造函数(constructor)都有一个原型对象(prototype);

原型对象(prototype)包含一个指向构造函数的指针(constructor

实例(instance)都包含一个指向原型对象(prototype)的内部指针。

1、原型链模式(默认)

基本思想:利用原型prototype让一个引用类型继承另一个引用类型属性方法

原型链实现继承的例子:

// SuperType就是一个构造函数(constructor

function SuperType() {

this.property = true;

}

// 构造函数SuperType有一个原型对象(prototype

SuperType.prototype.getSuperValue = function() {

return this.property;

}

// SubType是“子类”构造函数(constructor

function SubType() {

this.property = false;

}

// !!!将构造函数SubType的原型对象(prototype)指向SuperType的一个实例

SubType.prototype = new SuperType(); //SuperType实例包含指向prototype的指针

SubType.prototype.getSubValue = function () {

return this.property;

}

var instance = new SubType();

console.log(instance.getSuperValue()); //实例instance实际上指向SubType.prototype

分析:

生成SuperType实例时,会自动生成一个prototype。利用该prototype,可以定义“类”的函数(SuperType.prototype.getSuperValue)。

“子类”SubType构造时也生成一个prototype,但紧接着就将SubType.prototype指向SuperType的一个实例。而从使用的角度来看,对实例(instance)的引用就是对实例中prototype指针的引用:比如instance.getSuperValue()实际上就是对SuperType.prototype.getSuperValue的调用。

SubType.prototype=new SuperType();中,将SubType.prototype指向了SuperType的一个实例——进而指向SuperTypeprototype。这样,SubType就继承了SuperType期中的所有内容。接下来定义SubType.prototype.getSubValue,就是在SubType.prototype中添加了更多内容。

所以这其中的关键是prototype机制,需要详细研究一下。

2、借用构造函数

基本思想:在子类型构造函数的内部调用超类构造函数,使用call()apply()新建对象上执行构造函数

function SuperType() {

this.colors = ["red","blue","green"];

}

// 子类型构造函数

function SubType()  {

SuperType.call(this); //用call调用SuperType的构造函数

// this作为参数:用子类型的this替换SuperTypethis

// 使SuperType的构造函数作用在SubType的实例上

}

var instance1 = new SubType();

instance1.colors.push("black");

console.log(instance1.colors); //"red","blue","green","black"

var instance2 = new SubType();

console.log(instance2.colors); //"red","blue","green"

call()apply()也可以用于别的函数。

这里,SuperType是写死(hardcode)的,不灵活,实际上可以利用自定义的parentconstructor来替代。

3、组合继承

基本思想:将原型链借用构造函数技术组合起来,发挥两者之长。

function SuperType(name) { this.name = name; this.colors = ["red","blue","green"]; }

SuperType.prototype.sayName = function() { console.log(this.name); }

function SubType(name, age)  {

SuperType.call(this,name); // “父类”构造函数,name作为SuperType参数

this.age = age;  // “子类”自己的构造内容

}

SubType.prototype = new SuperType(); // 继承SuperTypeprototype

// -子类prototype中的构造函数constructor指针指向构造函数SubType

// 在第一种方法中,由于将SubType.prototype指向了SuperType.prototype

// 所以SubType.prototype.constructor就指向了SuperType.prototype.constructor

// 也就是SuperType()

// 但是,经过测试发现:是否有下面这一行,结果都一样,还不理解!

Subtype.prototype.constructor = SubType;

Subtype.prototype.sayAge = function() { console.log(this.age); }

// 生成SubType的实例

var instance1 = new SubType("EvanChen",18);

instance1.colors.push("black");

consol.log(instance1.colors); //"red","blue","green","black"

instance1.sayName(); //"EvanChen"

instance1.sayAge(); //18

var instance2 = new SubType("EvanChen666",20);

console.log(instance2.colors); //"red","blue","green"

instance2.sayName(); //"EvanChen666"

instance2.sayAge(); //20

4原型继承模式(有待研究)

基本想法:借助原型可以基于已有的对象创建新对象,同时还不必因此创建自定义的类型。原型式继承的思想可用以下函数来说明:

function object(o) { // object是一个“壳”构造函数,o是父类型

function F(){} // F是一个空的构造函数(临时的)

F.prototype = o; // Fprototype指向期望的“父类”的实例(?)o

return new F(); // 返回F的实例(也就是object的实例)

}

例子:

var person = {

name:"EvanChen",

friends:["Shelby","Court","Van"];

};

var anotherPerson = object(person);

anotherPerson.name = "Greg";

anotherPerson.friends.push("Rob");

var yetAnotherPerson = object(person);

yetAnotherPerson.name = "Linda";

yetAnotherPerson.friends.push("Barbie");

console.log(person.friends); //"Shelby","Court","Van","Rob","Barbie"

ECMAScript5通过新增Object.create()方法规范化了原型式继承,这个方法接收两个参数:一个用作新对象原型的对象和一个作为新对象定义额外属性的对象

var person = {

name:"EvanChen",

friends:["Shelby","Court","Van"];

};

var anotherPerson = Object.create(person);

anotherPerson.name = "Greg";

anotherPerson.friends.push("Rob");

var yetAnotherPerson = Object.create(person);

yetAnotherPerson.name = "Linda";

yetAnotherPerson.friends.push("Barbie");

console.log(person.friends); //"Shelby","Court","Van","Rob","Barbie"

 

5、寄生式继承(有待研究)

基本思想:创建一个仅用于封装继承过程的函数,该函数在内部以某种方式来增强对象,最后再像真正是它做了所有工作一样返回对象

function createAnother(original) { // 函数createAnother仅用于封装继承过程

var clone = object(original); //

clone.sayHi = function () { // 增强对象

alert("hi");

};

return clone; // 返回增强对象

}

var person = {

name:"EvanChen",

friends:["Shelby","Court","Van"];

};

var anotherPerson = createAnother(person);

anotherPerson.sayHi(); // "hi"

6、寄生组合式继承

基本思想:通过借用函数来继承属性,通过原型链的混成形式来继承方法。基本模型如下所示:

function inheritProperty(subType, superType) { // 用函数来继承属性

var prototype = object(superType.prototype); // 创建对象

prototype.constructor = subType; // 构造函数指向subType

subType.prototype = prototype; // subTypeprototype指向superType的原型

}

例子:

function SuperType(name) {

this.name = name;

this.colors = ["red","blue","green"];

}

SuperType.prototype.sayName = function (){

alert(this.name);

};

function SubType(name,age) {

SuperType.call(this,name); // 继承构造函数

this.age = age;

}

// SubType.prototype指向SubType.prototype

// 同时将SubType.prototypeconstructor指向SubType

inheritProperty(SubType,SuperType);

SubType.prototype.sayAge = function() {

alert(this.age);

}

总结,以及如何添加parent来提高继承的灵活性

(1)继承的核心在于prototype,要让子类型的prototype指向父类型实例prototype

① SubType.prototype = new SupperType();

(2)prototype中有构造函数指针constructor,应该正确设置

① 指向子类型构造函数:SubType.prototype.constructor = SubType;

② 之前SubType.prototype等于SupperTypeprototype,构造函数也是SupperType

(3)子类型构造函数中,可借用父类型构造函数

① SupertType.call(this,...),这个是写死的

② 可以在prototype中添加parent,使其指向父类型的prototype

1) SubType.prototype.parent = SupperType.prototype;

2) 可以在子类型构造函数中调用this.parent.constructor.call(this, ...)

3) 比SupertType.call(this,...)优雅一点。不过无伤大雅——类之间继承关系很明确

最终的例子

// 定义父类型

function SuperType(name) { // 构造函数

this.name = name;

}

SuperType.prototype.getNameVal = function () { return this.name; } // 父类型函数

// 定义子类型

function SubType(type, name) { // 构造函数

    this.parent.constructor.call(this, name); // 通过parent调用父类型构造函数

    this.type = type;

}

SubType.prototype = new SuperType(); // 将子类型的prototype指向父类型实例!!!

// 此处子类型prototype完全等于父类型的

SubType.prototype.constructor = SubType; // 使子类型构造函数指向子类型构造函数

SubType.prototype.parent = SuperType.prototype; // 自定义parent,指向父类型的prototype

SubType.prototype.getType = function () { // 子类型函数

    return this.type;

}

SubType.prototype.toString = function() {

    return '[SubType "'+ this.objectIndex + '"]';

}

 

var subInstance = new SubType(‘type1’, ‘name1’);

alert(subInstance.getName());

alert(subInstance.geType());

alert(subInstance.toString());

 


 

结果分析

// 父类型中的内容

SuperType = {

this.name: name;

prototype: [getNameVal, toString, ...]

}

 

// new一个SubType实例: var inst = new SubType(type,name);

// 第一步:

SubType = { prototype: [construct: SubType, toString, ...] }

// 执行this.parent.constructor.call(this, name);继承父类型的属性(方法?)

SubType = { this.name: name; prototype: [construct: SubType, toString, ...] }

(有没有SubType的方法?)

// 执行this.type = type;添加新属性

SubType = { this.name: name; this.type: type; prototype: [construct: SubType, toString, ...] }

// 执行SubType.prototype = new SuperType();

SubType = { this.name: name; this.type: type;

prototype: [construct: SuperType, toString, getNameVal, ...] }

// 执行SubType.prototype.constructor = SubType;

SubType = { this.name: name; this.type: type;

prototype: [construct: SubType, getNameVal, toString, ...] }

// SubType.prototype.parent = SuperType.prototype;

SubType = { this.name: name; this.type: type;

prototype: [construct: SubType, getNameVal, toString,

parent: SuperType.prototype, ...] }

// SubType.prototype.getType = function () {   return this.type; }

// SubType.prototype.toString = function() { return '[SubType "'+ this.objectIndex + '"]'; }

SubType = { this.name: name; this.type: type;

prototype: [construct: SubType, getNameVal, toString,

parent: SuperType.prototype, getType, toString, ...] }

有两个toString,这时候子类型的方法会覆盖父类型的方法。



猜你喜欢

转载自blog.csdn.net/xingyanchao/article/details/79421158