漫谈JS 的继承方式
一.原型链
原型链的基本思想是利用原型让一个引用类型继承另一个引用类型的属性和方法。每一个构造函数都有一个原型对象,原型对象都包含一个指向构造函数的指针,而实例都包含一个指向原型对象的指针。如果:我们让原型对象A等于另一个类型B的实例,那么原型对象A就会有一个指针指向B的原型对象,相应的B的原型对象中保存着指向其构造函数的指针。假如B的原型对象又是另一个类型的实例,那么上述的关系依旧成立,如此层层递进,就构成了实例与原型的链条
1、默认的原型
前面的例子中展示的原型链少了一环,所有引用类型默认都继承了Object,而这个继承也是通过原型链实现的。因此默认的原型都包含一个内部指针,指向Object.prototype,这也正是所有自定义类型会继承toString()、ValueOf()等默认方法的根本原因。换句话说Object.prototype就是原型链的末端。
2、确定原型和实例的关系
通过两种方式可以确定原型和实例之间的关系,第一种是使用instanceOf操作符,第二种是使用isPrototypeOf()方法。
实例 instanceOf 原型链 中出现过的构造函数,都会返回true
JavaScript中isPrototypeOf函数方法是返回一个布尔值,指出对象是否存在于另一个对象的原型链中。使用方法:
object1.isPrototypeOf(object2)
3、谨慎地定义方法
子类型有时候需要覆盖超类型中的某个方法,或者需要添加超类型中不存在的莫个方法,注意:给原型添加方法的代码一定要放在替换原型的语句之后。
当通过Child的实例调用getParentValue()时,调用的是这个重新定义过的方法,但是通过Parent的实例调用getParentValue()时,调用的还是原来的方法。
格外需要注意的是:必须要在Parent的实例替换原型之后,再定义这两个方法。
还有一点需要特别注意的是:通过原型链实现继承时,不能使用对象字面量创建原型方法,因为这样做会重写原型链。
4、原型链的问题
原型链很强大,可以利用它来实现继承,但是也有一些问题,主要的问题还是包含引用类型值的原型属性会被所有实例共享。因此我们在构造函数中定义实例属性。但是在通过原型来实现继承时,原型对象其实变成了另一个类型的实例。于是原先定义在构造函数中的实例属性变成了原型属性了。
2.借用构造函数
为了解决原型中包含引用类型值带来的一些问题,引入了借用构造函数的技术。这种技术的基础思想是:在子类型构造函数的内部调用超类型构造函数。
Parent.call(this)在新创建的Child实例的环境下调用了Parent构造函数。在新创建的Child实例环境下调用Parent构造函数。这样,就在新的Child对象上,此处的kid1和kid2对象上执行Parent()函数中定义的对象初始化代码。这样,每个Child实例就都会具有自己的friends属性的副本了。
借用构造函数的方式可以在子类型的构造函数中向超类型构造函数传递参数
为了确保子类型的熟悉不会被父类的构造函数重写,可以在调用父类构造函数之后,再添加子类型的属性。
构造函数的问题:
构造函数模式的问题,在于方法都在构造函数中定义,函数复用无从谈起,因此,借用构造函数的模式也很少单独使用。
3.组合继承
组合继承指的是将原型链和借用构造函数的技术组合在一块,从而发挥二者之长。即:使用原型链实现对原型属性和方法的继承,而通过借用构造函数来实现对实例属性的继承。
Person构造函数定义了两个属性:name和friends。Person的原型定义了一个方法sayName()。Child构造函数在调用Parent构造函数时,传入了name参数,紧接着又定义了自己的属性age。然后将Person的实例赋值给Child的原型,然后又在该原型上定义了方法sayAge().这样,两个不同的Child实例既分别拥有自己的属性,包括引用类型的属性,又可以使用相同的方法了。
组合继承避免了原型链和构造函数的缺陷,融合了他们的有点,成为JavaScript中最常用的继承模式。而且,instanceOf和isPropertyOf()也能够识别基于组合继承创建的对象。
4.对象冒充
对象冒充包括三种:临时属性方式、call()及apply()方式
1.临时属性方式:
1 function A(x){ 2 this.x=x; this.say = function(){ alert('My name is '+this.name); } 3 } 4 function B(x,y){ 5 this.tmpObj=A; 6 this.tmpObj(x); 7 delete this.tmpObj; 8 this.id = id; this.showId = function(){ alert('Good morning,Sir,My work number is '+this.id); } 9 }
var simon = new B('Simon',9527);
simon.say();
simon.showId();
/**/第5、6、7行:创建临时属性tmpObj引用构造函数A,然后在B内部执行,执行完后删除。当在B内部执行了 this.x=x后(这里的this是B的对象),B当然就拥有了x属性,当然B的x属性和A的x属性两者是独立,所以并不能算严格的继承。第5、6、7行有更简单的实现,就是通过call(apply)方法:A.call(this,x);
2.call()/apply()方式:实质上是改变了this指针的指向、
1 function Person(name){ 2 this.name = name; 3 this.say = function(){ 4 alert('My name is '+this.name); 5 } 6 } 7 function F2E(name,id){ 8 Person.call(this,name); //apply()方式改成Person.apply(this,[name]); 9 this.id = id; 10 this.showId = function(){ 11 alert('Good morning,Sir,My work number is '+this.id); 12 } 13 } 14 var simon = new F2E('Simon',9527); 15 simon.say(); 16 simon.showId();
/**/call和apply都可以实现继承,唯一的一点参数不同,func.call(func1,var1,var2,var3)对应的apply写法为:func.apply(func1,[var1,var2,var3])。区别在于 call 的第二个参数可以是任意类型,而apply的第二个参数必须是数组,也可以是arguments。
通过对象冒充的方式,无法继承通过prototype方式定义的变量和方法:
5.原型式继承
基本想法:借助原型可以基于已有的对象创建新对象,同时还不必须因此创建自定义的类型。
原型式继承的思想可用以下函数来说明:
1 function object(o) { 2 function F(){} 3 F.prototype = o; 4 return new F(); 5 } 6 例子: 7 var person = { 8 name:"EvanChen", 9 friends:["Shelby","Court","Van"]; 10 }; 11 var anotherPerson = object(person); 12 anotherPerson.name = "Greg"; 13 anotherPerson.friends.push("Rob"); 14 var yetAnotherPerson = object(person); 15 yetAnotherPerson.name = "Linda"; 16 yetAnotherPerson.friends.push("Barbie"); 17 console.log(person.friends);//"Shelby","Court","Van","Rob","Barbie"
ECMAScript5通过新增Object.create()方法规范化了原型式继承,这个方法接收两个参数:一个用作新对象原型的对象和一个作为新对象定义额外属性的对象。
var person = { name:"EvanChen", friends:["Shelby","Court","Van"]; }; var anotherPerson = Object.create(person); anotherPerson.name = "Greg"; anotherPerson.friends.push("Rob"); var yetAnotherPerson = Object.create(person); yetAnotherPerson.name = "Linda"; yetAnotherPerson.friends.push("Barbie"); console.log(person.friends);//"Shelby","Court","Van","Rob","Barbie"
6.寄生式继承
基本思想:创建一个仅用于封装继承过程的函数,该函数在内部以某种方式来增强对象,最后再像真正是它做了所有工作一样返回对象。
例子:
1 function createAnother(original) { 2 var clone = object(original); 3 clone.sayHi = function () { 4 alert("hi"); 5 }; 6 return clone; 7 } 8 var person = { 9 name:"EvanChen", 10 friends:["Shelby","Court","Van"]; 11 }; 12 var anotherPerson = createAnother(person); 13 anotherPerson.sayHi();///"hi"
7.寄生组合式继承
基本思想:通过借用函数来继承属性,通过原型链的混成形式来继承方法
其基本模型如下所示:
function inheritProperty(subType, superType) { var prototype = object(superType.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; } inheritProperty(SubType,SuperType); SubType.prototype.sayAge = function() { alert(this.age); }