JavaScript 中的继承
1 原型链继承
让原型对象等于另一个类型的实例。
function Father(){
this.val=1;
}
father.prototype.getVal=function(){
return this.val;
}
function Son(){
this.val=2;
}
//实现继承
Son.prototype=new Father();
var s1=new Son();
alert(s1.getVal());//1
实现原型链
创建父亲实例,并将父亲赋值给儿子的原型而实现的。本质是重写儿子的原型对象,代之以父亲的实例。
原型链中的搜索机制:一环一环地搜索,直到原型链末端才停下来。
谨慎地定义方法
给原型添加方法的代码一定要放在替换原型的语句之后。
不能用对象字面量创建原型方法,否则会切断原型链。
问题
父亲的属性值容易被儿子修改,而修改之后所有的儿子都会受到影响。
实践中很少会直接使用原型链。
2 构造函数继承
function Father(){
this.val=1;
}
function Son(){
Father.call(this);
}
var s1=new Son();
思想:在子类构造函数的内部调用父类构造函数。
构造父亲的时候传递参数,使得每一个子类可以拥有自己想要的父亲。
实现
在子类构造函数中,调用father.call(this,arguments);
问题:子类失去了复用性,每一个都需要独自创建。
借用构造函数的方式很少单独使用。
3 组合继承
也叫伪经典继承
结合原型链和借用构造函数,融合了它们的优点,最常用。
思路
使用原型链继承原型,使用构造函数继承实例的属性。
也就是把要共享的部分,加到原型链中。而私有的部分,则加到实例中。
属性是通过实例屏蔽原型的方法来实现私有的,也就是说,同名属性既存在于实例中,又存在于原型中。
function father(name){
this.name=name;
}
father.prototype.sayName=function(){
alert(this.name);
}
function son(name,age){
father.call(this,name);
this.age=age;
}
son.prototype=new father();
4 原生式继承
思想
借助已有的对象创建新对象,也就是少掉了new一个父亲的过程。
称为原型式继承,是因为将儿子的原型直接设有已有的对象以作父亲。
function object(father){
function son(){};
son.prototype=father;
return new son();
}
- Object.create()方法
该方法规范了原型式继承。接受两个参数:一个用作新对象原型,(可选)一个为新对象定义额外属性的对象。
缺点:跟原型链类似,作为参数传入的饿父亲,可以被其每一个儿子修改。
5 寄生式继承
思路
创建一个仅用于封装继承过程的函数。
function createSon(father){
var son=Object.create(father);
son.sayHi=function(){
alert("hello world!");
}
return son;
}
缺点:每个儿子都具有自己的sayHi函数。跟构造函数继承类似,调用一次函数就得创建一遍方法,无法实现函数复用,效率较低。
6 寄生组合式继承
寄生组合式继承式引用类型最理想的继承范式。
用于解决组合继承中最大的问题:无论在什么情况下,总会调用超过两次的父亲构造函数。
思路
通过借用构造函数继承属性,通过原型链的混成形式来继承方法。
实现
function inherit(son,father){
var prototype=Object.create(father.prototype);
prototype.constructor=son;
son.prototype=prototype;
}
funtion father(){
}
father.prototype.sayName(){
}
function son(){
}
inherit(son,father);
7 ES6 中的class extends
class ColorPoint extends Point {
constructor(x, y, color) {
super(x, y); // 调用父类的constructor(x, y)
this.color = color;
}
toString() {
return this.color + ' ' + super.toString(); // 调用父类的toString()
}
}