什么是继承?
作为OO语言,有两种继承,接口继承和实现继承。然而js只支持实现继承,实现继承主要是依靠原型链来实现的。我对继承的理解是:子函数具有父函数的属性和方法,就叫继承。
什么是原型链?
《Javascript 高级程序设计》里面这样子解释原型链:
js中,每个函数都有一个[[prototype]]属性,其中保存着函数能够实例化的属性和方法。然后[[prototype]]属性中保存的对象也有其原型,所以形成一个链式,叫原型链。
参考http://www.php100.com/html/webkaifa/javascript/2012/1015/11260.html
接下来我分析一下几个基础的继承。
假设:子函数继承父函数;
1,原型链继承
思想:父函数赋值给子函数的原型。因为父函数的原型中保存着父函数的属性和方法,我们把父函数赋值给子函数,那么父函数的属性和方法也就被赋值给子函数,那么相当于子函数继承了父函数的属性和方法。但是这中继承有一定的缺陷,当我们改变子函数的属性或者重写其方法时,是通过原型来改变的,因为父函数的原型与子函数的原型是一致的,所以父函数的属性和方法也会被改变。
demo:
function a(){
this.name="name";
}
a.prototype.sayName=function(){
alert(this.name);
};
function b(){
}
//继承 a
b.prototype=new a();
//实例化
var c=new b();
alert(c.name);//name
c.sayName();//name
原型被改变的情况:
function a(){
this.array=["1","2","3"]
}
function b(){
}
//继承 a
b.prototype=new a();
//实例化
var d=new b();
var c=new b();
c.push("4");
alert(c.array);//1,2,3,4
alert(d.array);//1,2,3,4
明显的,原型被改变了。
构造函数实现继承:
思想:在子函数中扩展其作用域,达到了继承父函数的属性的方法。运用call()函数与apply()函数能够完美的实现这点。用call函数和apply函数扩展的作用域是可以自由选择的,也就是能有选择的去继承属性,这点很重要,可以更加高效。 并且不会改变其原型链。但是无法继承父函数的方法。
PS:【call()与apply()函数的用法:
call(this 1,2,3);
apply(this ,[1,2,3]);】
demo:
function a(){
this.name="name";
this.array=["1","2","3"]
}
function b(){
a.call(this,this.array,this.name);
}
//实例化
var d=new b();
var c=new b();
c.array.push("4");
alert(c.array);//1,2,3,4
alert(d.array);//1,2,3
优点:不改变原型,可以有选择的继承属性。
缺点:不能继承方法。
组合继承:
思想:用原型链实现对属性和方法的继承,用构造函数法来实现对实例属性的继承,并且不会改变原型链。
demo:
function a(){
this.name="name";
this.array=["1","2","3"]
}
a.prototype.sayName=function(){
alert(a.name);
}
function b(){
a.call(this,this.name);//继承name属性 第二次调用父函数
}
b.prototype=new a();//继承属性和方法 第一次调用父函数
//实例化
var d=new b();
var c=new b();
alert(c.name)//name
c.array.push("4");
alert(c.array);//1,2,3,4
alert(d.array);//1,2,3 实现了实例属性的继承
子函数具有了父函数所有的属性和方法,但是在实例化属性时,子函数不会改变其原型。为什么会出现这中情况呢?我分析是这样的,在第一次原型链实现对属性和方法的继承时调用了父函数,继承了所有的属性和方法,然后再执行b()的实例化时,我们又调用了call()方法实例化属性,那么从原型链继承来的属性就会被call()继承的属性覆盖。
优点:不改变原型链。
缺点:两次调用父函数。
原型式继承:
思想:基于原型,可以利用已有的对象创建新的对象,而且不需要去创建自定义类型。为此,用这样的一个函数可以达到目的:
function object(o){
function F(){};
F.prototype=o;
return new F();
}
这种继承必须要先提供一个对象;
demo:
function object(o){
function F(){};
F.prototype=o;
return new F();
}
var person={
name:"name",
age:23,
array:["1","2"]
}
//实例化
var otherPerson=object(person);
otherPerson.array.push("3")
alert(otherPerson.name);
alert(otherPerson.age);//继承了age属性
alert(person.array);//1,2,3 会改变原对象
子对象的继承属性改变,也会改变父对象的属性值。
然而ECMAScript5通过新增Object.create()函数规范了原型式继承,这个函数接受两个参数,第一个参数一个用作新对象的对象(父对象),第二个新对象的额外属性
demo:
var person={
name:"name",
age:23,
array:["1","2"]
}
//实例化
var otherPerson=Object.create(person);
otherPerson.array.push("3")
alert(otherPerson.name);
alert(otherPerson.age);//继承了age属性
alert(person.array);//1,2,3 会改变原对象
otherPerson
支持Object.create()方法的浏览器有IE9+,FF4+,S5+,O12+,Chrome。
优点:没有?
缺点:只能用于对象,改变子对象的属性会改变付对象的属性。不能继承方法。
寄生式继承:
思想:与原型式继承类似,创建一个仅用于封装继承过程的函数,在该函数的内部通过某种方式来增强对象(也就是添加属性或者方法),最后像真的做了所有工作一样返回对象。
eg:
function object(o){
function F(){};
F.prototype=o;
return new F();
}
function createAnther(original){
var clone=object(original);//创建新对象
clone.sayHi= function () {
alert("hi!")
};//增强对象(添加了一个方法)
return clone;//返回对象
}
这种继承仅适用于对象而不是自定义类型和构造函数。
demo:
function object(o){
function F(){};
F.prototype=o;
return new F();
}
function createAnther(original){
var clone=object(original);
clone.sayHi= function () {
alert("hi!")
};
return clone;
}
var person={
name:"name",
age:24,
work:"student",
array:["1","2"]
}
var otherPerson=createAnther(person);
alert(otherPerson.name);
alert(otherPerson.age);
alert(otherPerson.work);
otherPerson.sayHi();
otherPerson.array.push("3");
alert(otherPerson.array);//1,2,3 会改变原对象
改变子对象的属性也会改变父对象的属性,而且createAnther函数中的object函数不是必须的,任何返回一个对象的函数都适用于这个模式。
缺点:只能适用对象。
寄生式组合继承:
思想:通过原型链的混成形式来继承方法,通过构造函数来继承属性。基本的思路是,不比为了子类型的原型而调用构造函数,我们需要的无非是父类型的一个副本而已,本质上,就是使用寄生式继承来继承父类型的原型,然后再将结果指定给子类型的原型。
其基本模式如下:
//b继承a
function inhertPrototype(a,b){
var prototype=object(a.prototype);
prototype.constructor=b;
b.prototype=prototype;
}
先创建父类型的原型的副本,再为创建的副本添加constructor属性,再把这个副本赋给子类型。这样我们就可以用inhertPrototype函数去替换为子类型原型赋值的语句。这样就可以用新创建的副本,避免两次调用父函数。弥补了组合继承的缺点。
demo:
//b继承a
function object(o){
function F(){};
F.prototype=o;
return new F();
}
function inhertPrototype(a,b){
var prototype=object(a.prototype);
prototype.constructor=b;
b.prototype=prototype;
}
function a(){
this.name="name";
this.array=["1","2","3"];
this.weight=268;
}
a.prototype.sayName= function () {
alert(this.name);
}
function b(name,array){
a.call(this,array,name);
this.age=29;
}
inhertPrototype(a,b);
b.prototype.sayAge=function(){
alert(this.age);
}
var person=new b();
alert(person.age);//29
alert(person.name);//name
person.sayAge();//29
person.sayName()//name
//验证不会影响原型属性
person.array.push("4");
alert(person.array);//1,2,3,4
var personTest=new b();
alert(personTest.array);//1,2,3
寄生式继承的原型链不会改变。被普遍认为是引用类型最理想的继承范式。
优点:完美。
缺点:不知道~
总结:
原型链,构造函数,组合继承,寄生组合式继承都适用于自定义类型,而原型式继承和寄生式继承只适用于对象,与原型链模式一样,改变子对象的属性,父对象的属性值也会改变。