在了解了js 中的原型链之后 (https://www.jianshu.com/p/1e683a9771c3),我们再来看看js 中的几种实现继承的方式
一 借用构造函数实现继承
为了解决包含引用类型值的原型属性会被所有实例共享的问题,大神们发明了在子类型构造函数的内部调用超类型构造函数然后通过apply()和call()方法在(将来)新创建的对象上执行构造函数的方式来实现继承,如下
function SuperType() {
this.colors = ["red", "blue", "green"];
}
function SubType() {
//调用SuperType 并且通过call()方法修正this指向
SuperType.call(this);
}
var instance1 = new SubType();
instance1.colors.push("black");
//"red,blue,green,black"
alert(instance1.colors);
//"red,blue,green"
var instance2 = new SubType();
alert(instance2.colors);
以上例子中在SubType()调用了SuperType()构造函数。通过使用call()方法(或apply()方法也可以),我们实际上是在(未来将要)新创建的SubType实例的环境下调用了SuperType构造函数。这样一来,就会在新SubType对象上执行SuperType()函数中定义的所有对象初始化代码。结果,SubType的每个实例就都会具有自己的colors属性的副本了(互不影响)。
使用这种方式继承的好处:
可以在子类型构造函数中向超类型构造函数传递参数。如下
function SuperType(name) {
this.name = name;
}
function SubType() {
//继承了SuperType,同时还传递了参数
SuperType.call(this, "Nicholas");
//实例属性
this.age = 29;
}
var instance = new SubType();
//"Nicholas";
alert(instance.name);
//29
alert(instance.age);
SuperType只接受一个参数name,该参数会直接赋给一个属性。在SubType构造函数内部调用SuperType构造函数时,实际上是为SubType的实例设置了name属性。为了确保SuperType构造函数不会重写子类型的属性,可以在调用超类型构造函数后,再添加应该在子类型中定义的属性。
使用这种方式继承的坏处:
1.方法都在构造函数中定义
2.在超类型的原型中定义的方法,对子类型而言也是不可见的 如下
function SuperType(name) {
this.name = name;
}
SuperType.prototype.a=function(){
alert("aaaa");
}
function SubType() {
//继承了SuperType,同时还传递了参数
SuperType.call(this, "Nicholas");
//实例属性
this.age = 29;
}
var instance = new SubType();
console.log(instance);
我们在控制台可以看到子类型原型中无法获取超类型的a方法
二 组合继承
将原型链和借用构造函数的技术组合到一块,从而发挥二者之长的一种继承模式,主要的思路是使用原型链实现对原型属性和方法的继承,而通过借用构造函数来实现对实例属性的继承。这样,既通过在原型上定义方法实现了函数复用,又能够保证每个实例都有它自己的属性。下面来看一个例子
function SuperType(name) {
this.name = name;
this.colors = ["red", "blue", "green"];
}
SuperType.prototype.sayName = function() {
alert(this.name);
};
function SubType(name, age) {
//继承name属性
SuperType.call(this, name);
this.age = age;
}
//继承方法 (拼接原型链)
SubType.prototype = new SuperType();
SubType.prototype.sayAge = function() {
alert(this.age);
};
var instance1 = new SubType("Nicholas", 29);
instance1.colors.push("black");
//"red,blue,green,black"
alert(instance1.colors);
//"Nicholas";
instance1.sayName();
//29
instance1.sayAge();
var instance2 = new SubType("Greg", 27);
//"red,blue,green"
alert(instance2.colors);
//"27";
instance2.sayAge();
//"Greg";
instance2.sayName();
我们看到现在实例可以访问的超类型的原型上的方法了
SuperType构造函数定义了两个属性:name和colors。SuperType的原型定义了一个方法sayName()。Sub-Type构造函数在调用SuperType构造函数时传入了name参数,紧接着又定义了它自己的属性age。然后,将SuperType的实例赋值给SubType的原型,然后又在该新原型上定义了方法sayAge()。这样一来,就可以让两个不同的SubType实例既分别拥有自己属性——包括colors属性,又可以使用相同的方法了这种方式是目前js实现继承使用的最常见的方式
使用这种继承方式的不足
SubType.prototype = new SuperType()的确会创建一个关联到SubType.prototype 的新对象。但是它使用了SubType(..)的“构造函数调用”,如果函数SubType有一些副作用(比如写日志、修改状态、注册到其他对象、给this添加数据属性,等等)的话,就会影响到SubType()的“后代”。
改进方法
function SuperType(name) {
this.name = name;
this.colors = ["red", "blue", "green"];
}
SuperType.prototype.sayName = function() {
alert(this.name);
};
function SubType(name, age) {
//继承name属性
SuperType.call(this, name);
this.age = age;
}
//使用Object.create 生成对象来代替new SuperType()生成的对象
SubType.prototype = Object.create(SuperType.prototype);
SubType.prototype.sayAge = function() {
alert(this.age);
};
var instance1 = new SubType("Nicholas", 29);
console.log(instance1 );
这样可以避免对SubType后代的影响
注
// ES6之前需要抛弃默认的SubType.prototype
SubType.ptototype = Object.create( SuperType.prototype );
// ES6开始可以直接修改现有的
SubType.prototypeObject.setPrototypeOf( SubType.prototype, SuperType.prototype );