整理《javascript高级程序设计》中继承的方法以及优缺点。
1. 原型链
ECMAScript中描述了原型链的概念,并将原型链作为实现继承的主要方法。
原型链继承的基本思想是利用原型让一个引用类型继承另一个引用类型的属性和方法。
简单回顾一下构造函数、原型和实例的关系:每个构造函数都有一个原型对象,原型对象都包含一个指向构造函数的指针,而实例都包含一个指向原型对象的内部指针。
那么,假如我们让原型对象等于另一个类型的实例,结果会怎么样呢?显然,此时的原型对象将包含一个指向另一个原型的指针,相应地,另一个原型中也包含着一个指向另一个构造函数的指针。假如另一个原型又是另一个类型的实例,那么上述关系依然成立,如此层层递进,就构成了实例与原型的链条。这就是所谓原型链的基本概念。
function SuperType() { this.property = true; } SuperType.prototype.getSuperValue = function () { return this.property; } function SubType() { this.subProperty = false; } SubType.prototype = new SuperType(); SubType.prototype.getSubValue = function (){ return this.subproperty; }; var instance = new SubType(); console.log(instance.getSuperValue()); //true
以上代码定义了两个类型:SuperType和SubType。每个类型分别有一个属性和一个方法。它们的主要区别是SubType继承了SuperType,而继承是通过创建SuperType的实例,并将该实例赋给SubType.prototype实现的。实现的本质是重写原型对象,代之以一个新类型的实例。换句话说,原来存在于SuperType的实例中的所有属性和方法,现在也存在于SubType.prototype中了。在确立了继承关系之后,我们给SubType.prototype添加了一个方法,这样就在继承了SuperType的属性和方法的基础上又添加了一个新方法
要注意instance.constructor现在指向的是SuperType,这是因为原来SubType.prototype中的constructor被重写了的缘故。实际上,不是SubType的原型的constructor属性被重写了,而是SubType的原型指向了另一个对象——SuperType的原型,而这个原型对象的constructor属性指向的是SuperType
别忘记默认的原型
事实上,前面例子中展示的原型链还少一环。我们知道,所有引用类型默认都继承了Object,而这个继承也是通过原型链实现的。大家要记住,所有函数的默认原型都是Object的实例,因此默认原型都会包含一个内部指针,指向Object.prototype。这也正是所有自定义类型都会继承toString()、valueOf()等默认方法的根本原因
原型链的问题
原型链虽然很强大,可以用它来实现继承,但它也存在一些问题。其中,最主要的问题来自包含引用类型值的原型
function SuperType(){ this.colors = ["red", "blue", "green"]; } function SubType(){ } //继承了SuperType SubType.prototype = new SuperType(); 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,black"
原型链的第二个问题是:在创建子类型的实例时,不能向超类型的构造函数中传递参数。实际上,应该说是没有办法在不影响所有对象实例的情况下,给超类型的构造函数传递参数。有鉴于此,再加上前面刚刚讨论过的由于原型中包含引用类型值所带来的问题,实践中很少会单独使用原型链
2. 借用构造函数
在子类型构造函数的内部调用超类型构造函数
function SuperType() { this.colors = ['red', 'blue', 'green']; } function SubType() { SuperType.call(this); } 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()方法也可以),我们实际上是在(未来将要)新创建的SubType实例的环境下调用了SuperType构造函数。这样一来,就会在新SubType对象上执行SuperType()函数中定义的所有对象初始化代码。结果,SubType的每个实例就都会具有自己的colors属性的副本了
对于原型链而言,借用构造函数有一个很大的优势,即可以在子类型构造函数中向超类型构造函数传递参数
function SuperType() { this.colors = ['red', 'blue', 'green']; } function SubType() { SuperType.call(this); } 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"
借用构造函数问题: