继承是面向对象(OOP)语言中的一个最为人津津乐道的概念。许多面对对象(OOP)语言都支持两种继承方式::接口继承 和 实现继承 。
接口继承只继承方法签名,而实现继承则继承实际的方法。由于js中方法没有签名,在ECMAScript中无法实现接口继承。ECMAScript只支持实现继承,而且其 实现继承 主要是依靠原型链来实现的。
二、概念
2.1简单回顾下构造函数,原型和实例的关系:
每个构造函数(constructor)都有一个原型对象(prototype),原型对象都包含一个指向构造函数的指针,而实例(instance)都包含一个指向原型对象的内部指针。
关系如下图所示:
2.2什么是原型链
每一个对象拥有一个原型对象,通过__proto__指针指向上一个原型,并从中继承方法和属性,同时原型对象也可能拥有原型,这样一层层的,最终指向null。这种关系成为原型链(prototype chain)。
假如有这样一个要求:instance实例通过原型链找到了Father原型中的getFatherValue方法.
function Father(){ this.property = true; } Father.prototype.getFatherValue = function(){ return this.property; } function Son(){ this.sonProperty = false; } //继承 Father Son.prototype = new Father();//Son.prototype被重写,导致Son.prototype.constructor也一同被重写 Son.prototype.getSonVaule = function(){ return this.sonProperty; } var instance = new Son(); console.log(instance.getFatherValue());//true注意: 此时instance.constructor指向的是Father,这是因为Son.prototype中的constructor被重写的缘故.
2.3确定原型和实例之间的关系
使用原型链后, 我们怎么去判断原型和实例的这种继承关系呢? 方法一般有两种.
1、第一种是使用 instanceof 操作符。只要用这个操作符来测试实例(instance)与原型链中出现过的构造函数,结果就会返回true. 以下几行代码就说明了这点。
console.log(instance instanceof Object);//true console.log(instance instanceof Father);//true console.log(instance instanceof Son);//true由于原型链的关系, 我们可以说instance 是 Object, Father 或 Son中任何一个类型的实例. 因此, 这三个构造函数的结果都返回了true.
2、第二种是使用 isPrototypeOf() 方法,。同样只要是原型链中出现过的原型,isPrototypeOf() 方法就会返回true
console.log(Object.prototype.isPrototypeOf(instance));//true console.log(Father.prototype.isPrototypeOf(instance));//true console.log(Son.prototype.isPrototypeOf(instance));//true
2.4原型链的问题
原型链并非十分完美, 它包含如下两个问题:
问题一: 当原型链中包含引用类型值的原型时,该引用类型值会被所有实例共享;
问题二: 在创建子类型(例如创建Son的实例)时,不能向超类型(例如Father)的构造函数中传递参数.
有鉴于此, 实践中很少会单独使用原型链。为此,下面将有一些尝试以弥补原型链的不足。
三、借用构造函数(经典继承)
为解决原型链中上述两个问题, 我们开始使用一种叫做借用构造函数(constructor stealing)的技术(也叫经典继承).
基本思想:即在子类型构造函数的内部调用超类型构造函数。
function Father(){ this.colors = ["red","blue","green"]; } function Son(){ Father.call(this);//继承了Father,且向父类型传递参数 } var instance1 = new Son(); instance1.colors.push("black"); console.log(instance1.colors);//"red,blue,green,black" var instance2 = new Son(); console.log(instance2.colors);//"red,blue,green" 可见引用类型值是独立的很明显,借用构造函数一举解决了原型链的两大问题:
其一, 保证了原型链中引用类型值的独立,不再被所有实例共享;
其二, 子类型创建时也能够向父类型传递参数.