JavaScript 中的继承
原型链继承
function SuperType() {
this.property = true;
}
SuperType.prototype.showValue = function() {
console.log(this.property);
};
function SubType(val) {
this.sub = val;
}
// 子类原型指向父类实例
SubType.prototype = new SuperType();
SubType.prototype.showSub = function() {
console.log(this.sub);
};
问题:
- 引用类型属性被所有实例共享
- 创建子类的实例时,不能像超类构造函数中传递参数
构造函数继承
(也叫经典继承)
基本思想:即在子类型构造函数的内部调用超类型构造函数:
function Father(val) {
this.colors = ["red", "blue", "green"];
this.colors.push(val);
}
Father.prototype.count = 1;
Father.prototype.show = function() {
console.log(this.count);
};
function Son() {
Father.apply(this, arguments); //继承了Father,且向父类型传递参数
}
var instance1 = new Son("black");
console.log(instance1.colors); //"red,blue,green,black"
var instance2 = new Son();
console.log(instance2.colors); //"red,blue,green" 可见引用类型值是独立的
// 无法访问父类的原型对象上的属性和方法
console.log(instance2.count); // undefined
console.log(instance2.show); // undefined
解决了原型链的两大问题:
- 保证了原型链中引用类型值的独立,不再被所有实例共享;
- 子类型创建时也能够向父类型传递参数.
存在的问题:
- 每次创建实例都会在对应实例里创建方法,无法对方法进行复用
- 没有拼接原型链,无法继承原型对象上的属性和方法
考虑此,借用构造函数的技术也很少单独使用.
组合继承
(伪经典继承)
指的是将原型链和借用构造函数的技术组合到一块,从而发挥两者之长的一种继承模式.
基本思路: 使用原型链实现对原型属性和方法的继承,通过借用构造函数来实现对实例属性的继承:
function Father(name) {
this.name = name;
this.colors = ["red", "blue", "green"];
}
Father.prototype.sayName = function() {
alert(this.name);
};
function Son(name, age) {
Father.call(this, name); //继承实例属性,第一次调用Father()
this.age = age;
}
Son.prototype = new Father(); //继承父类方法,第二次调用Father()
Son.prototype.sayAge = function() {
alert(this.age);
};
var instance1 = new Son("louis", 5);
instance1.colors.push("black");
console.log(instance1.colors); //"red,blue,green,black"
instance1.sayName(); //louis
instance1.sayAge(); //5
var instance1 = new Son("zhai", 10);
console.log(instance1.colors); //"red,blue,green"
instance1.sayName(); //zhai
instance1.sayAge(); //10
组合继承避免了原型链和借用构造函数的缺陷,融合了它们的优点,成为 JavaScript 中最常用的继承模式. 而且, instanceof 和 isPrototypeOf( )也能用于识别基于组合继承创建的对象.
同时我们还注意到组合继承其实调用了两次父类构造函数, 造成了不必要的消耗.
原型继承
在object()函数内部, 先创建一个临时性的构造函数, 然后将传入的对象作为这个构造函数的原型,最后返回了这个临时类型的一个新实例:
function object(o) {
function F() {}
F.prototype = o;
return new F();
}
从本质上讲, object() 对传入其中的对象执行了一次浅复制
原型式继承中, 包含引用类型值的属性始终都会共享相应的值, 就像使用原型模式一样.
寄生式继承
寄生式继承的思路与(寄生)构造函数和工厂模式类似, 即创建一个仅用于封装继承过程的函数,该函数在内部以某种方式来增强对象,最后再像真的是它做了所有工作一样返回对象. 如下:
function createAnother(original) {
var clone = object(original); //通过调用object函数创建一个新对象
clone.sayHi = function() {
//以某种方式来增强这个对象
alert("hi");
};
return clone; //返回这个对象
}
寄生组合式继承
组合继承是 JavaScript 最常用的继承模式; 不过, 它也有自己的不足. 组合继承最大的问题就是无论什么情况下,都会调用两次父类构造函数
寄生组合式继承就是为了降低调用父类构造函数的开销而出现的:
// 借用构造函数 继承父类上私有属性和方法
function subClass() {
superClass.apply(this, arugments);
}
// 寄生 继承父类上公有的属性和方法
function extend(subClass, superClass) {
var prototype = object(superClass.prototype); //创建对象
prototype.constructor = subClass; //增强对象
subClass.prototype = prototype; //指定对象
}
寄生组合式继承,集寄生式继承和组合继承的优点于一身,是实现基于类型继承的最有效方法
es6 class 继承
extends 实现类的继承:
class Person {
constructor(name) {
this.name = name;
this.color = ["red", "blue", "green"];
}
sayName() {
console.log(this.name);
}
}
class Student extends Person {
constructor(name, score) {
super(name);
this.score = score;
}
showScore() {
alert(this.score);
}
}
let s1 = new Student("s1", 99);
s1.sayName(); // s1
s1.showScore(); // 99
- super 和借用构造函数类似
- 内部会有寄生继承
- 其实就是寄生组合继承的语法糖