继承是OO语言中一个最为人津津乐道的概念,也是初接触Javascript的初学者难理解的概念=。=继承主要分为两种:一种是接口继承,另一种是实现继承。而在ECMAScript中只支持实现继承,所以我们今天来讨论讨论实现继承。实现继承就是继承实际的方法,主要依靠原型链来实现。讲到这里我们就需要讨论讨论什么是原型链。
1、什么是原型
要理解原型链我们首先要知道什么是原型。我们知道每个函数都有一个prototype属性,这个属性是一个指针,指向一个对象,这个对象包含所有实例共享的属性和方法。所以我个人觉得可以这么简单的理解:原型就是prototype属性。
同时prototype属性有自己的prototype对象,而pototype对象肯定也有自己的constuct属性,construct属性有自己的constuctor对象,神奇的事情要发生了,这最后一个constructor对象就是我们构造出来的function函数本身!
2、关于prototype和_proto_
(1).每个对象都具有一个名为__proto__的属性;
(2).每个构造函数(构造函数标准为大写开头,如Function(),Object()等等JS中自带的构造函数,以及自己创建的)都具有一个名为prototype的方法(注意:既然是方法,那么就是一个对象(JS中函数同样是对象),所以prototype同样带有__proto__属性);
(3).每个对象的__proto__属性指向自身构造函数的prototype;
所以在大多数情况下我们可以这么认为:_proto_===constructor.prototype(某些情况除外,比如:通过Object.create()创建的对象不适用此等式)
3、原型链
讲到这,到底什么是原型链呢?其实已经很明了了。由于每个对象都有_proto_属性,而在Javascript中万物皆对象,所以最终会形成一条由_proto_连起来的链条,递归访问最终会到头,最终的值为NULL。那么这条链条就是原型链。
当Javascript引擎查找对象的属性时,先查找对象本身是否存在该属性,如果不存在,那么会在原型链上逐级查找。(但不会查找自身的prototype)
4、继承
一、原型链式继承
实现原型链继承有一种基础模式,代码如下:
function SuperType(){ this.property=ture; }SuperType.prototype.getSuperValue=function(){ return this.prototype;};function SubType(){ this.subproperty=false;}SubType.prototype=new SubType();//继承了SuperTypeSubType.prototype.getSubValue=function(){ return this.subproperty;};var instance=new SubType();alert(instance.getSuperValue());//true
基础原型链继承虽然简单但是它有两个问题:1、在通过原型链继承时,原型实际上会变成另一个类型的实例。于是,原先的实例属性也就顺理成章地变成了现在的原型属性了。2、在创建子类型的实例时,不能向超类型的构造函数中传递函数。
二、借用构造函数继承
在子类型构造函数的内部调用超类构造函数,通过使用call()和apply()方法可以在新创建的对象上执行构造函数。
function SuperType() { this.colors = ["red","blue","green"]; } function SubType() { SuperType.call(this);//继承了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"
借用构造函数的问题:放大都在构造函数中,因此函数的复用就无从谈起。而且,在超类型的原型中定义的方法,对子类型而言也是不可见的,结果所有类型都只能使用构造函数模式。
三、组合继承
function Super(){ // 只在此处声明基本属性和引用属性 this.val = 1; this.arr = [1]; } // 在此处声明函数 Super.prototype.fun1 = function(){}; Super.prototype.fun2 = function(){}; //Super.prototype.fun3... function Sub(){ Super.call(this); // 核心 // ... } Sub.prototype = new Super(); // 核心 var sub1 = new Sub(1); var sub2 = new Sub(2); alert(sub1.fun === sub2.fun); // true
为了解决借用构造函数继承的问题,组合继承应运而生。
组合继承把实例函数都放在原型对象上,以实现函数复用。同时还要保留借用构造函数方式的优点,通过Super.call(this);继承父类的基本属性和引用属性并保留能传参的优点;通过Sub.prototype = new Super();继承父类函数,实现函数复用。
组合继承避免了原型链继承和借用构造函数继承的缺点,融合了他们的优点:不存在引用属性共享问题,可传参,函数可复用。因此成为Javascript中最常见的继承模式。
缺点:组合集成的最大问题是在无论什么情况下都会调用两次超类型构造函数。
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=function(){ alert(this.age); };
四、寄生式组合继承
既然组合继承也有不足之处,那当然就需要弥补,于是就出现了寄生式组合继承。
寄生式组合继承的基本模式如下:
function inheritPrototype(subType,superType){ var prototype=Object(superType.prototype);//创建对象 prototypr.constructor=subType;//增强对象 弥补因重写原型失去的默认的constructor属性 subType.prototype=prototype;//指定对象 将新创建的对象赋值给子类型的原型}
这个示例中的inheritPrototype()函数实现了组合继承的最简单形式。我们可以调用inheritPrototype()函数区替换前面例子中的子类型原型赋值函数:
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 } inheritPrototype(Subtype,Supertype); SubType.prototype.sayAge=function(){ alert(this.age); };
这个例子的搞笑体现在只调用了一次Supertype构造函数,避免了在SubType.prototype上创建的不必要、多余的属性。与此同时还能保持原型链的不变;因此,还能正常使用instanceof和isPrototypeOf()。所以现在寄生式组合继承是最理想的继承方式。
PS:关于原型式继承和寄生式继承在这里就不多做解释了。大家可以自己去查相关的资料,不太推荐使用(个人观点)。