## 6.3 继承
由于函数没有签名,在ECMAScript中无法实现接口继承,ECMAScript只支持**实现继承**,而且其实现继承主要是依靠**原型链**来实现的。
### 6.3.1 原型链
基本思想:利用原型让一个引用类型继承另一个引用类型。
~~~
function SuperType(){
this.property = true;
}
SuperType.property.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
~~~
此时`instance.constructor`现在指向的是`SuperType`,这是因为`SubType`的原型指向了另一个对象`SuperType的原型`,而这个原型对象的constructor属性指向的是SuperType。
**1. 别忘记默认的原型**
所有函数的默认原型都是Object的实例,因此默认原型都会包含一个**内部指针**,指向`Object.prototype`,这正是所有自定义类型都会继承toString()、valueOf等默认方法的根本原因。
**2. 确定原型和实例的关系**
通过两种方式来确定原型与实例之间的关系。第一种方式是使用`instanceof`操作符;第二种方式是使用`isPrototypeOf()`方法。
~~~
alert(SubType.prototype.isPrototypeOf(instance)); //true;
~~~
**3. 谨慎地定义方法**
给原型添加方法的代码一定要放在**替换**原型的语句**之后**。
提醒:在通过原型链实现继承时,不能使用对象字面量创建原型方法,因为这样会**重写原型链**。
**4. 原型链的问题**
* 包含引用类型值的原型,其属性会被所有实例共享。
* 在创建子类型的实例时,不能向超类型的构造函数中传递参数。
### 6.3.2 借用构造函数(伪构造对象或经典继承)
**基本思想:在子类型构造函数的内部调用超类型构造函数。**
~~~
function SuperType(){
this.colors = ['red','yellow','blue'];
}
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"
~~~
通过使用`call()或者apply()`方法,我们实际上是在(未来将要)创建的SubType实例的环境下调用了SuperType构造函数。这样一来,就会在新SubType()对象上执行SuperType()函数中定义的所有对象初始化代码。结果,SubType的每个实例就都会具有自己的colors属性的副本。
**1. 传递参数**
相对于原型链而言,借用构造函数有一个很大的优势,即**可以在子类型构造函数中向超类型构造函数传递参数。**
~~~
function SuperType(name){
this.name = name;
}
function SubType(){
SuperType.call(this,'ken');
this.age = 28;
}
~~~
**2. 借用构造函数的问题**
* 方法都在构造函数中定义,无法函数复用
* 在超类型的原型中定义的方法(使用prototype定义的),对于子类型而言也是不可见的,结果所有类型都只能使用构造函数模式。
### 6.3.3 组合继承(伪经典继承)
思路:**使用原型链实现对原型属性和方法的继承,而通过借用构造函数来实现对实例属性的继承**。这样,既在原型上定义方法实现了函数复用,又能够保证每个实例都有自己的属性。
~~~
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 = new SuperType(); //将SuperType的实例复制给SubType的原型
SubType.prototype.constructor = SubType;
SubType.prototype.sayAge = function(){
alert(this.age);
}
var instance1 = new SubType('ken',28);
instance1.colors.push('black);
alert(instance1.colors); //'red,blue,green,black'
instance1.sayName(); //'ken'
instance1.sayAge(); //28
var instance2 = new SubType('Greg',27);
alert(instance2.colors); //'red,blue,green'
instance2.sayName(); //'Greg'
instance2.sayAge(); //27
~~~
组合继承避免了原型链和借用构造函数的缺陷,融合了他们的优点,成为JavaScript中最常用的继承模式。而且,instanceof和isPrototypeOf()也能够用于识别基于组合继承创建的对象。
**缺点**:由于多次调用超类型构造函数而导致低效率。
### 6.3.4 原型式继承
原型式继承没有严格意义上的使用构造函数,它是借助原型可以基于已有的对象创建新对象,同时还不必因此创建自定义类型。
~~~
function object(o){
function F(){}
F.prototype = o;
return new F();
}
~~~
在object()函数内部,先创建了一个临时性的构造函数,然后将传入的对象作为这个构造函数的原型,最后返回这个临时类型的新实例。从本质上讲,object()对传入其中的对象执行了一次**浅复制**。例:
~~~
var person = {
name: 'Nicholas',
friends: ['wesley','fox']
};
var anotherPerson = object(person);
anotherPerson.name = 'Greg';
anotherPerson.friends.push('Rob');
var yetAnotherPerson = object(person);
yetAnotherPerson.name = 'Linda';
yetAnotherPerson.friends.push('Barbie');
alert(person.friends); //'wesley,fox,Rob,Barbie'
~~~
ES5通过新增`Object.create()`方法规范了原型式继承。这个方法接收两个参数:一个用作新对象原型的对象和(可选的)一个为新对象定义额外属性的对象。
注:第二个参数格式与`Object.defineProperties()`方法的第二个参数格式相同:每个属性都是通过自己的描述符定义的。以这种方式指定的任何属性都会覆盖原型对象上的同名属性。
~~~
var person = {
name: 'Nicholas',
friends: ['wesley','fox']
};
var anotherPerson = Object.create(person, {
name:{
value: 'Greg'
}
});
~~~
在没有必要兴师动众地创建构造函数,而只是想让一个对象与另个对象保持类似的情况下,原型式继承是完全可以胜任的。不过别忘了,**包含引用类型值的属性始终都会共享相应的值,就像使用原型模式一样。**
### 6.3.5 寄生式继承
寄生式继承是与原型式继承密写相关的一种思路:**创建一个仅用于封装继承过程的函数,该函数在内部以某种方式来增强对象**,最后返回对象。
~~~
function object(o){
function F(){}
F.prototype = o;
return new F();
}
function createAnother(original) {
var clone = object(original);
clone.sayHi = function(){
alert('hi');
};
return clone;
}
~~~
object(o)函数不是必须的,任何能够返回新对象的函数都适用于此模式。
这样创建的新对象不仅具有original的所有属性和方法,而且还有自己的sayHi()方法。在主要考虑对象而不是自定义类型和构造函数的情况下,寄生式继承也是一种有用的模式。
### 6.3.6 寄生组合式继承(最有效理想)
寄生组合式继承,即通过借用构造函数来继承属性,通过原型链的混成形式来继承方法。
思路是:不必为了指定子类型的原型调用超类型的构造函数,我们所需要的无非就是超类型原型的一个副本而已。本质上,就是**使用寄生式继承来继承超类型的原型,然后再将结果指定给子类型的原型**。
~~~
function inheritPrototype(subType,SuperType){
var prototype = object(subType.prototype);
prototype.constructor = subType;
subType.prototype = prototype;
}
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);
}
~~~
寄生组合式继承与组合继承的区别在于,**前者不将超类型中的实例属性继承为子类型的原型属性。**
- 前言
- 第一章 JavaScript简介
- 第三章 基本概念
- 3.1-3.3 语法、关键字和变量
- 3.4 数据类型
- 3.5-3.6 操作符、流控制语句(暂略)
- 3.7函数
- 第四章 变量的值、作用域与内存问题
- 第五章 引用类型
- 5.1 Object类型
- 5.2 Array类型
- 5.3 Date类型
- 5.4 基本包装类型
- 5.5 单体内置对象
- 第六章 面向对象的程序设计
- 6.1 理解对象
- 6.2 创建对象
- 6.3 继承
- 第七章 函数
- 7.1 函数概述
- 7.2 闭包
- 7.3 私有变量
- 第八章 BOM
- 8.1 window对象
- 8.2 location对象
- 8.3 navigator、screen与history对象
- 第九章 DOM
- 9.1 节点层次
- 9.2 DOM操作技术
- 9.3 DOM扩展
- 9.4 DOM2和DOM3
- 第十章 事件
- 10.1 事件流
- 10.2 事件处理程序
- 10.3 事件对象
- 10.4 事件类型
- 第十一章 JSON
- 11.1-11.2 语法与序列化选项
- 第十二章 正则表达式
- 12.1 创建正则表达式
- 12.2-12.3 模式匹配与RegExp对象
- 第十三章 Ajax
- 13.1 XMLHttpRequest对象
- 你不知道的JavaScript
- 一、作用域与闭包
- 1.1 作用域
- 1.2 词法作用域
- 1.3 函数作用域与块作用域
- 1.4 提升
- 1.5 作用域闭包
- 二、this与对象原型
- 2.1 关于this
- 2.2 全面解析this
- 2.3 对象
- 2.4 混合对象“类”
- 2.5 原型
- 2.6 行为委托
- 三、类型与语法
- 3.1 类型
- 3.2 值
- 3.3 原生函数