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

继承:
1.接口继承
只继承方法的签名。js中函数没有签名,所以只支持实现继承。
2.实现继承
继承实际的方法,主要依靠原型链来实现。

简单回顾下构造函数、原型和实例的关系:每个构造函数(其实就是普通函数,你把它当成构造函数那就是构造函数)都有一个原型属性(prototype属性),该属性执行一个对象(称为原型对象),原型对象包含一个指向该构造函数的指针,而实例都包含一个指向原型对象的内部指针([[prototype]],无法使用js方法访问)。

一、原型链实现继承

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

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

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

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

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

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

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

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

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

下图中的 SubType Prototype 就是 super1 (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 
  1. 谨慎地定义方法

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

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. 借用构造函数的问题

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上面创建不必要的、多余的属性。

开发人员普遍认为寄生组合式继承是引用类型最理想的继承范式。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值