与传统的面向对象语言不同,JavaScript 的继承次要是通过原型链和借用构造函数的形式实现。明天咱们就来学习下在 JavaScript 中常见的四种继承实现形式,别离是:原型链继承、借用构造函数继承、组合继承以及 Class 类继承。
原型链继承
原型链继承的核心思想是通过将子类的原型设置为父类实例的对象来实现对属性和办法的继承。
理论案例 1:
// 父类构造函数
function SuperType(){this.color=['红','橙','黄'];
this.name="大壮";
}
// 子类构造函数
function SubType(){}
// 将子类的原型指向父类的实例
SubType.prototype = new SuperType();
let instance1 = new SubType();
instance1.color.push("紫");
instance1.name ="大顺"
console.log(instance1.name); // 大顺
console.log(instance1.color);// ['红','橙','黄',"紫"]
var instance2 = new SubType();
console.log(instance2.color);// ['红','橙','黄',"紫"]
console.log(instance2.name);// 大壮
这里,咱们能够清晰的看到,因为原型属性中援用的类型会被实例共享,而根本根本数据类型不会被实例共享,所以导致咱们批改了原型上的 name 属性不会影响其余实例,而当批改了援用实例 (color) 却作用到了其余实例,这必定不是咱们想要的。
原型属性还有一个问题,就是咱们在实例化属性的时候咱们不能进行传参,比方咱们在实例化属性的时候想要给 name 传一个值,但因为子类的参数无奈共享给父类,所以是无奈做到的。原型链继承次要问题总结如下:
- 原型属性上的援用类型数据批改后会净化其余的实例;
- 实例化对象时无奈传参。
借用构造函数继承
借用构造函数继承的核心思想是通过 call()、apply()函数在 未来 实例化的对象上执行构造函数。
理论案例 2:
// 父类构造函数
function SuperType1(){this.color = ['red','orange','yellow'];
this.name = "DaZhuang";
}
// 子类构造函数
function SubType1(){SuperType1.call(this);
}
let instance3 = new SubType1();
instance3.color.push("purple");
instance3.name = "DaShun";
console.log(instance3.color); //["red", "orange", "yellow", "purple"]
console.log(instance3.name); // DaShun
let instance4 = new SubType1();
console.log(instance4.color); //['red','orange','yellow']
console.log(instance4.name); // DaZhuang
咱们能够分明地看到,通过借用构造函数,咱们能够在创立子类实例的时候执行 SuperType()上写好的初始化代码,这样每个实例都有一个 color 的正本了,很好的解决了援用类型属性净化其余实例的问题。
然而这种借用构造函数形式的继承也存在问题,那就是属性全都在父类中定义因而无奈进行函数的复用,而且在父类原型中定义的办法对子类也是不可见的,后果所有类型都只能应用构造函数模式。因而借用构造函数的形式也很少独自应用。
组合继承
组合继承是指将原型链继承和借用构造函数继承这两种形式联合起来,从而施展二者各自的短处的一种继承模式。
其外围思路是通过原型链实现对原型属性和办法的继承,而借用构造函数实现对实例属性和办法的继承。
理论案例 3:
// 父类构造函数
function Super (name) {
this.name = name;
this.colors = ["金", "木", "水"];
this.sayBigName = function () {console.log('父类的办法');
}
}
// 子类构造函数
function Sub (name) {
// 实现父类属性和办法的继承
Super.call(this, name);
}
// 实现实例属性和办法的继承
Sub.prototype = new Super();
// 修复 Sub 构造函数原型构造函数的指向,从 Super 变更为 Sub
Sub.prototype.constructor = Sub;
Sub.prototype.sayName = function () {console.log(this.name);
}
var sub1 = new Sub("大壮");
sub1.colors.push("火");
console.log(sub1.colors); //["金", "木", "水", "火"]
sub1.sayName(); // 大壮
sub1.sayBigName(); // 父类的办法
var sub2 = new Sub("大顺");
console.log(sub2.colors); // ["金", "木", "水"]
sub2.sayName(); // 大顺
这个案例比较复杂,读者须要细细品读。首先读者要明确哪些属性和办法是在父类中申明的(对应案例中 Super 外面的:name,colors,sayBigName()),这些属性和办法是怎么实例化的呢?是通过 13 行代码(借用构造函数)实例化的,通过 call 办法在 Sub 上下文执行了 Super 属性和办法的实例化,这样 Sub 就拿到了 Super 外面的属性和办法。然而做完这一步是齐全不够的,因为咱们在子类中也有一些办法须要子类继承,这些办法咱们也心愿它能在实例化的时候被实例对象共享,这时候就须要采纳原型链继承模式(代码 17 行)。
咱们通过将子类(Sub)的原型指向父类的实例,这样在咱们在子类中定义的办法和属性就会被寄存到这个实例化的父类对象外面。在后续实例化 Sub 子类创立实例过程中,实例对象就能够通过原型链继承的模式拿到子类中定义的办法(sub1._ proto_ 等于 Sub.prototype)。这样咱们就实现了实例对象对父类对象属性和办法的继承以及子类对象办法的继承。
仔细的同学可能会发现,咱们并没有通过原型链模式让实例对象继承子类的属性,其起因其实咱们在原型链继承模式局部曾经介绍,就是因为应用原型链继承援用类型数据的时候存在净化其余实例对象的问题,因而咱们采纳原型链模式仅继承子类的办法而不对子类的属性进行继承(属性能够放到父类构造函数中)。
ES6 Class 继承
在 es6 中,引入了 class 函数,通过 class 语法咱们能够很容易的实现继承
理论案例 4:
// 父类
class Super {getValue () {console.log(this.val)
}
}
// 子类
class Sub extends Super {constructor(value) {super(value)
this.val = value
}
}
let sub = new Sub(1)
sub.getValue() // 1
sub instanceof Super // true