您的位置:首页 > Web前端 > JavaScript

js中的面向对象程序设计(3)-继承

2016-07-23 22:17 344 查看
继承:

1.接口继承

只继承方法的签名。js中函数没有签名,所以只支持实现继承。

2.实现继承

继承实际的方法,主要依靠原型链来实现。

简单回顾下构造函数、原型和实例的关系:每个构造函数都有一个原型对象,原型对象都包含一个指向构造函数的指针,而实例都包含一个指向原型对象的内部指针。

一、原型链实现继承

原理:让一个原型对象等于一个类型的实例。

function SuperType(){
this.property = true;
}

SuperType.prototype.getSuperValue = function(){
return this.property;
}

function SubType(){
this.subproperty = false;
}

//继承SuperType
SubType.prototype = new SuperType();

SubType.prototype.getSubValue = function(){
return this.subproperty;
}

var instance = new SubType();
alert(instance.getSuperValue);//true


上述SubType继承SuperType的本质是重写了SubType的原型对象,代之以一个新类型的实例。

SubType的原型被重写了,所以instance.constructor指向的是SuperType.



通过实现原型链,属性的搜索机制扩展如下:

1>搜索实例

2>搜索SubType.prototype

3>搜索SuperType.prototype

下面注意几个问题

1.别忘记默认的原型

所有的引用类型都继承自Object对象,这个继承也是通过原型链实现的。



2.确定原型和实例的关系

*方式一:

instanceof操作符,只要用这个操作符来测试实例与原型链中出现过的构造函数。

alert(instance instanceof Object); //true
alert(instance instanceof SuperType); //true
alert(instance instanceof SubType); //true


*方式二:

isPrototypeOf():只要是原型链中出现过的原型,都可以说是该原型链所派生的实例的原型

alert(Object.prototype.isPrototypeOf(instance)); //true
alert(SuperType.prototype.isPrototypeOf(instance)); //true
alert(SubType.prototype.isPrototypeOf(instance)); //true


谨慎地定义方法

子类型有时候需要重写超类型中的某个方法,或者需要添加超类型中不存在的某个方法。但不管怎样,给原型添加方法的代码一定要放在替换原型的语句之后。

function SuperType(){
this.property = true;
}

SuperType.prototype.getSuperValue = function(){
return this.property;
};

function SubType(){
this.subproperty = false;
}

//继承了SuperType
SubType.prototype = new SuperType();

//添加新方法
SubType.prototype.getSubValue = function (){
return this.subproperty;
};

//重写超类型中的方法,屏蔽SuperType中的已存在的同名方法
SubType.prototype.getSuperValue = function (){
return false;
};

var instance = new SubType();
alert(instance.getSuperValue()); //false


注意:不能使用对象字面量的形式创建原型方法,因为这样会重写原型链。

function SuperType(){
this.property = true;
}

SuperType.prototype.getSuperValue = function(){
return this.property;
};

function SubType(){
his.subproperty = false;
}

//继承了SuperType
SubType.prototype = new SuperType();


//使用字面量添加新方法,会导致上一行代码无效。因为这样又重写了SubType的原型,该原型的constructor现在指向Object构造函数

SubType.prototype = {
getSubValue : function (){
return this.subproperty;
},

someOtherMethod : function (){
return false;
}
};

var instance = new SubType();
alert(instance.getSuperValue()); //error!


4.原型链的问题

在通过原型来实现继承时,原型实际上会变成另一个类型的实例。于是,原先的实例属性也就顺理成章地变成了现在的原型属性了。这就又涉及到前面所说的,原型中引用类型属性被所有实例共享的问题了。

function SuperType(){
this.colors = ["red", "blue", "green"];
}

function SubType(){
}

//继承了SuperType
SubType.prototype = new SuperType();

var instance1 = new SubType();
instance1.colors.push("black");
alert(instance1.colors);  //"red,blue,green,black"

var instance2 = new SubType();
alert(instance2.colors); //"red,blue,green,black"


原型链的缺点:

1>当父类的构造函数中有引用类型的属性时,在继承的时候,子类的原型实际就变成了父类对象的实例,所以,即子类的原型中包含有引用类型的属性。这样,该引用类型的属性被所有的子类对象实例所共有,这是不可取的。

2>在创建子类实例时,不能向父类的构造函数中传递参数。

所以,很少单独使用原型链。

二、借用构造函数(伪造对象、经典继承)

在子类的构造函数内部调用父类的构造函数。函数只不过是在特定环境中执行代码的对象而已,所以通过使用apply()或者call()方法也可以在新创建的对象上执行构造函数。

function SuperType(){
this.colors = ["red", "blue", "green"];
}

function SubType(){
//继承了SuperType
SuperType.call(this); //这里的this表示SubType对象的引用,这样SubType就具有colors属性。
}

var instance1 = new SubType();
instance1.colors.push("black");
alert(instance1.colors); //"red,blue,green,black"

var instance2 = new SubType();
alert(instance2.colors); //"red,blue,green"


需要注意的几个问题:

1.传递参数

function SuperType(name){
this.name = name;
}

function SubType(){
//继承了SuperType,同时还传递了参数
SuperType.call(this, "Nicholas");

//实例属性
this.age = 29;
}

var instance = new SubType();
alert(instance.name); //"Nicholas";
alert(instance.age); //29


借用构造函数的问题

1>方法都在构造函数中定义,无法复用

2>在父类的原型中定义的方法,对子类来说是不可见的。

三、组合继承(伪经典继承)

用原型链实现对原型方法的继承,而通过借用构造函数来实现对实例属性的继承

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;
}

//继承方法
SubType.prototype = new SuperType();
SubType.prototype.constructor = SubType;
SubType.prototype.sayAge = function(){
alert(this.age);
};

var instance1 = new SubType("Nicholas", 29);
instance1.colors.push("black");
alert(instance1.colors); //"red,blue,green,black"
instance1.sayName(); //"Nicholas";
instance1.sayAge(); //29

var instance2 = new SubType("Greg", 27);
alert(instance2.colors); //"red,blue,green"
instance2.sayName(); //"Greg";
instance2.sayAge(); //27


组合继承避免了原型链和借用构造函数的缺陷,融合了它们的优点,成为js中最常用的继承模式。

四、原型式继承

借助原型可以基于已有的对象创建新对象,同时还可以创建自定义类型。

function object(o){

function F(){}
F.prototype = o;
return new F();

}


从本质上讲,object()对传入的对象o作了一次浅复制。

var person = {
name:'Nicholas',
friends:['Shelby','Court','Van']
};

var anotherPerson = object(person);
anotherPerson.name = 'Greg';
anotherPerson.friends.push('Rob');

var yetAnotherPerson = object(person);
yetAnotherPerson.name = "Linda";
yetAnotherPerson.friends.push("Barbie");

alert(person.friends); //"Shelby,Court,Van,Rob,Barbie"


ECMAScript 5通过新增Object.create()方法规范化了原型式继承

var person = {
name: "Nicholas",
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");
alert(person.friends); //"Shelby,Court,Van,Rob,Barbie"


Object.create()方法接收两个参数:一个用作新对象原型的对象和(可选的)一个为新对象定义额外属性的对象。

var person = {
name: "Nicholas",
friends: ["Shelby", "Court", "Van"]
};

var anotherPerson = Object.create(person, {
name: {
value: "Greg"
}
});

alert(anotherPerson.name); //"Greg"


在没有必要兴师动众地创建构造函数,而只想让一个对象与另一个对象保持类似的情况下,原型式继承是完全可以胜任的。

不过别忘了,包含引用类型值的属性始终都会共享相应的值,就像使用原型模式一样。

五、寄生式继承

寄生式继承的思路与寄生构造函数和工厂模式类似,即创建一个仅用于封装继承过程的函数,该函数在内部以某种方式来增强对象,最后再像真地是它做了所有工作一样返回对象。

function createAnother(original){

var clone = object(original); //通过调用函数创建一个新对象
clone.sayHi = function(){ //以某种方式来增强这个对象
alert("hi");
};

return clone;  //返回这个对象
}

var person = {
name: "Nicholas",
friends: ["Shelby", "Court", "Van"]
};

var anotherPerson = createAnother(person);
anotherPerson.sayHi(); //"hi"


使用寄生式继承来为对象添加函数,会由于不能做到函数复用而降低效率;这一点与构造函数模式类似。

六、寄生组合式继承

组合继承最大的问题就是无论什么情况下,都会调用两次超类型构造函数:一次是在创建子类型原型的时候,另一次是在子类型构造函数内部。

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 = SubType;
SubType.prototype.sayAge = function(){
alert(this.age);
};


其实调用两次SuperType,在SubType的原型中和SubType的实例中都创建了SuperType的实例属性,不过,SubType的实例中的属性会屏蔽其原型中的同名属性。



基于以上缺点,我们找到了解决问题的方法–寄生组合式继承。

即通过借用构造函数来继承属性,通过原型链的混成形式来继承方法。本质上,就是使用寄生式继承来继承超类型的原型,然后再将结果指定给子类型

的原型。

function inheritPrototype(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;
}

inheritPrototype(SubType, SuperType);

SubType.prototype.sayAge = function(){
alert(this.age);
};


这个例子的高效率体现在它只调用了一次SuperType 构造函数,并且因此避免了在SubType.prototype上面创建不必要的、多余的属性。

开发人员普遍认为寄生组合式继承是引用类型最理想的继承范式。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  面向对象 js