与传统的面向对象语言不同,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); // DaShunlet 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变更为SubSub.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() // 1sub instanceof Super // true