lgy的博客


  • 首页

  • 标签

  • 分类

  • 归档

JS中的继承

发表于 2018-11-25 | 分类于 JS

1.原型链

ECMAScript中描述了原型链的概念,并将原型链作为实现继承的主要方法。构造函数、原型和实例的关系:每个构造函数都有一个原型对象,原型对象都包含一个指向构造函数的指针,而实例都包含一个指向原型对象的指针。我们让原型对象等于另一个另一个类型的实例,就构成了实例与原型的链条,也就是原型链。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function SuperType(){
this.property = true;
}
SuperType.prototype.getSuperValue = function(){
return this.property;
};

function SubType(){
this.subproperty = false;
}

SubType.prototype = new SuperType(); //继承了SuperType
SubType.prototype.getSubValue = function(){
return this.subproperty;
};

var instance = new SubType();
alert(instance.getSuperValue()); // true

该例子中,SubType继承了SuperType,而继承是通过创建SuperType的实例,并将该实例赋给SubType.prototype实现的,如下图所示。
实例与构造函数的关系
图中,instance指向SubType的原型,SubType的原型又指向SuperType的原型。getSuperValue()方法仍然还在SuperType.prototype中,但property则位于SubType.prototype中。这是因为property是一个实例属性,getSuperValue()则是一个原型方法。
调用instance.getSuperValue()方法,会经历3个步骤,1)搜索实例,2)搜索SubType.prototype,3)搜索SuperType.prototype,最后一步才会找到该方法。
原型链也存在着一些问题,第一个问题是包含引用类型值的原型属性会被所有实例所共享,第二个问题是没有办法在不影响所有对象实例的情况下,给超类型的构造函数传递参数。所以说实践中很少会单独使用原型链。

2.借用构造函数

这种基本思想是在子类型构造函数的内部调用超类型构造函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
function SuperType(){
this.colors = ["red","blue","green"];
}
function SubType(){
SuperType.call(this); // 继承了SuperType
}

var instance1 = new SubType();
instance1.colors.push("black");
alert(instance1.colors); // "red,blue,green,black"

var instance2 = new SubType();
alert(instance2.colors); // "red,blue,green"

借用构造函数存在的问题是方法都在构造函数中定义,函数复用就无从谈起了,所以也很少使用。

3.组合继承

组合继承也叫作伪经典继承,指的是将原型链和借用构造函数的技术组合到一块儿,从而发挥二者之长的一种继承模式。其背后的思路是,使用原型链实现对原型属性和方法的继承,而通过借用构造函数来实现对实例属性的继承。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
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); // 继承属性,第二次调用SuperType()
this.age = age;
}

SubType.prototype = new SuperType(); // 继承方法,第一次调用SuperType()
SubType.prototype.constructor = SubType;
SubType.prototype.sayAge = function(){
alert(this.age);
};

var instance1 = new SubType("Nicholas", 29);
instance1.colors.push("black");
alert(instance1.colors);
instance1.sayName();
instance1.sayAge();

var instance2 = new SubType("Greg", 27);
alert(instance2.colors);
instance2.sayName();
instance2.sayAge();

在这个例子中,SuperType构造函数定义了两个属性:name和colors。SuperType的原型定义了一个方法sayName().SubType构造函数在调用SuperType构造函数时传入了name参数,紧接着又定义了它自己的属性age。然后,将SuperType的实例赋值给SubType的原型,然后又在该新原型上定义了方法sayAge()。这样就两个不同的SubType实例既分别拥有自己的属性,又可以使用相同的方法。
组合继承是JavaScript中最常用的继承模式。
不过组合继承每次都要调用两次超类型的构造函数。

4.原型式继承

道格拉斯.克罗克福德提出了一种实现继承的方法,他的想法是借助原型可以基于已有的对象创建新对象,同时还不必因此创建自定义类型,为此他给出如下函数:

1
2
3
4
5
function object(o){
function F(){}
F.prototype = o;
return new F();
}

在object()函数内部,先创建了一个临时的构造函数,然后将传入的对象作为这个构造函数的原型,最后返回了这个临时类型的一个新实例。看下面的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var person = {
name: "Nicholas",
friends: ["Shelby","Court","Van"]
};

var anotherPerson = object(person);
anotherPerson.name = "Gerg";
anotherPerson.friends.push("Rob");

var yetAnotherPerson = object(person);
yetAnotherPerson.name = "Linda";
yetAnotherPerson.friends.push("Barbie");

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

原型式继承要求你必须有一个对象作为另一个对象的基础,把这个对象传给object()函数。ECMAScript通过新增的Object.create()方法规范了原型式继承,这个方法接收两个参数,一个是用作新对象原型的对象,另一个(可选)是为新对象定义额外属性的对象,在传入一个参数时和object()是一样的。

1
2
3
4
5
var anotherPerson = Object.create(person,{
name: {
value: "Greg"
}
});

在只想让一个对象与另一个对象保持类似的情况下,原型式继承是可以胜任的。不过包含引用类型值的属性始终会共享相应的值,就像使用原型模式一样。

5.寄生式继承

寄生式继承与寄生构造函数和工厂模式类似,即创建一个仅用于封装继承过程的函数,该函数在其内部以某种方式增强对象,最后再想它做了所有工作一样返回对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function createAnother(original){
var clone = object(original);
clone.sayHi = function(){
alert("hi");
};
return clone;
}

var person = {
name: "Nicholas",
friends: ["Shelby","Court","van"]
};
var anotherPerson = createAnother(person);
anotherPerson.sayHi(); //"hi"

前面示范的object()函数不是必须的,任何能够返回新对象的函数都适用于此模式。
使用寄生式继承来为对象添加函数,会由于不能做到函数复用而降低效率,这一点与构造函数模式类似。

6.寄生组合式继承

为了解决组合继承调用两次超类型构造函数的问题,我们可以使用寄生组合式继承。
所谓寄生组合式继承,即通过借用构造函数来继承属性,通过原型链的混成形式来继承方法。本质上,就是使用寄生式继承来继承超类型的原型,然后再将结果指定给子类型的原型。
寄生组合式继承的基本模式如下:

1
2
3
4
5
function inheritPrototype(subType, superType){
var prototype = object(superType.prototype); //创建超类型原型副本
prototype.constructor = subType; // 为副本添加constructor属性
subType.prototype = prototype; // 将新创建的对象赋值给子类型的原型
}

这个函数接收两个参数:子类型构造函数和超类型构造函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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;
}
inheritPrototype(SubType, SuperType);

SubType.prototype.sayAge = function(){
alert(this.age);
};

这个例子的高效率体现在它只调用了一次SuperType构造函数,并且因此避免了在SubType.prototype上创建多余的属性。开发人员普遍认为寄生组合式继承是引用类型最理想的继承范式。

参考书籍:《JavaScript高级程序设计》,作者:【美】 Nicholas C.Zakas

1…13141516
ligaoyan

ligaoyan

16 日志
5 分类
1 标签
© 2019 ligaoyan
总访问量次 | 本站访客数人
由 Hexo 强力驱动
|
主题 — NexT.Pisces v5.1.4