js实现继承的方式
实现继承的方式有:原型链、构造器、组合继承、原型式继承、寄生继承、寄生组合继承以及ES6中的class
(1)原型链
原型链的构建是通过将一个类型的实例赋值给另一个类型的原型实现的,如subObj.prototype=new superObj();子类的prototype为父类对象的一个实例,因此subObj.prototype.__proto__=superObj.prototype;
这样,子类型就能够访问超类型中所有的属性和方法,原型链的问题是对象实例共享所有继承的属性和方法,因此不适合单独使用,解决这一问题的方法是借助构造函数。即在子类型构造函数内部调用超类型构造函数,这样就可以做到每个实例都具有自己的属性。
function superType(){ this.name="super"; this.color=['red','blue','yellow']; } function subType(){ this.name='sub' } subType.prototype=new superType(); var instance1=new subType(); instance.color.push('green'); console.log(instance1.color);//["red", "blue", "yellow", "green"] var instance2=new subTyp1(); console.log(instance2.color);//["red", "blue", "yellow", "green"]
原型链的缺点:创建子类实例时,无法向父类构造函数传参
来自原型对象的所有属性被所有实例共享
(2)构造函数
借助构造函数的基本思想:即在子类型构造函数的内部调用超类型构造函数,函数只不过是在特定环境中执行代码的对象,因此通过使用apply()和call()方法可以在创建新对象上执行构造函数。
function superType(){ this.name='123'; this.color=['red','blue','yellow']; } superType.prototype.sayName=function(){ console.log(this.name); } function subType(){ //继承了superType superType.call(this); } var instance1=new subType(); instance1.color.push('green'); console.log(instance1.color);//["red", "blue", "yellow", "green"] instance1.sayName();//Uncaught TypeError: instance1.sayName is not a function var instance2=new subType(); console.log(instance2.color);//["red", "blue", "yellow"]
缺点:在父类型的原型中定义的方法,对于子类型是不可见的
(3)组合继承
指将原型链和构造函数的技术结合在一起。使用原型链实现对原型方法的继承,通过构造函数实现对实例属性的继承。这样,既可以实现函数的复用,又能保证每个实例都有自己的属性。
function superType(name){ this.name=name; this.color=['red','blue','green']; this.sayhello=function(){ console.log(this.name); } } superType.prototype.sayName=function(){ console.log(this.name); } function subType(name,age){ //继承属性 使用构造函数实现对实例属性的继承 superType.call(this,name); //第二次调用父类构造函数 this.age=age; } //继承方法 使用原型链继承 subType.prototype=new superType(); //第一次调用父类构造函数 subType.prototype.sayAge=function(){ console.log(this.age); } var instance1=new subType('mary',22); instance1.color.push('yellow'); console.log(instance1.color);//['red','blue','green','yellow'] instance1.sayName();//mary instance1.sayAge();//22 var instance2=new subType('liny',18); console.log(instance2.color);//['red','blue','green'] instance2.sayName();//liny instance2.sayAge();//18
优点:1、子类可以向父类传参
2、使每个实例有自己的属性
缺点:无论在什么情况下,都会调用两次构造函数:一次是在创建子类型原型时,另一次是在子类型构造函数内部。
在第一次调用superType构造函数时,subType.prototype会得到两个属性:name和color;他们都是superType的实例属性,只不过现在位于subType的原型中。
当调用subType构造函数时,又会调用一次superType构造函数,这一次又在新对象上创建了实例属性name和color。于是这两个属性就屏蔽了原型中的两个同名属性。
寄生组合式继承就是为了解决这一问题。
(4)原型继承
基本思想:借助原型可以基于已有的对象创建新对象,同时还不必因此创建自定义类型。
在object()函数内部,先创建一个临时性的构造函数,然后将传入的对象作为这个构造函数的原型,最后返回了这个临时类型的一个新实例。从本质上讲,object()对传入的对象执行了一次浅拷贝。
在es2015中通过Object.create()方法规范了原型式继承。这个方法接受两个参数:一个用作新对象原型的对象和一个为新对象定义额外属性的对象(可选)。
var person={ name:'xiaoming', friends:['an','lina'] } var p1=Object.create(person); p1.name='jack'; p1.friends.push('wne'); var p2=Object.create(person); p2.name='Bob'; p2.friends.push('xiao'); console.log(p1.friends);//["an", "lina", "wne", "xiao"] console.log(p2.friends);//["an", "lina", "wne", "xiao"]
缺点:同原型链实现继承一样,包含引用类型值的属性会被所有实例共享。
优点:在没必要创建构造函数,进让一个对象与另外一个对象保持相似的情况下,原型式继承是可以胜任的。
(5)寄生式继承
与寄生构造函数和工厂模式类似,创建一个仅用于封装继承过程的函数,该函数在内部以某种方式来增强对象,最后返回对象。
function createAnother(original){ var clone=Object.create(original); clone.sayHi=function(){ console.log('hi'); console.log(this.name); } return clone; } var person={ name:'bob', friends:['anny','liming','limei'] } var anotherPerson=createAnother(person); anotherPerson.sayHi();
在上例中createAnother函数接受一个参数,这个参数就是将要作为新对象基础的对象。
anotherPerson是基于person创建的新对象,新对象不仅具有person的所有属性和方法,还有自己的sayHi()方法。
缺点:
1、使用寄生式继承来为对象添加函数,会由于不能做到函数复用而效率低下;
2、同原型链实现继承一样,包含引用类型值的属性会被所有实例共享。
(6)寄生组合式继承
思想:不必为了指定子类型的原型而多new了一次超类型的构造函数,如subType.prototype=new superType();我们所需要的无非就是复制一个父类原型的副本给子类原型即可。
本质上,就是使用寄生式继承来继承超类型的原型,然后再将结果指定给子类型的原型。
function inheritPrototype(subType,superType){ var obj=Object.create(superType.prototype);//基于父类原型创建对象 创建父类原型的副本 obj.constructor=subType; //为创建的副本添加constructor,弥补因重写原型而失去默认的constructor subType.prototype=obj; //将新创建的对象赋值给子类型的原型。 } function superType(name){ this.name=name; this.color=['red','blue','yellow']; } superType.prototype.sayName=function(){ return this.name; } function subType(name,age){ superType.call(this,name); this.age=age; } inheritPrototype(subType,superType); subType.prototype.sayAge=function(){ return this.age; } var ins1=new subType('an',18); console.log(ins1.sayName());//an console.log(ins1.sayAge());//18
(7)ES6的Class继承
class superType{ constructor(name){ this.name=name; this.color=['red','blue','green']; } sayhello(){ console.log(this.name); } } class subType extends superType{ constructor(name,age){ super(name); this.age=age; } sayAge(){ console.log(this.age); } } var ins1=new subType('an',23); ins1.sayAge();//23 ins1.sayhello();//an
为了简化原型链继承,ES6的class出现大大减少了相关的代码。不用再去构建有序的原型链,直接用class extends就能实现继承。
附:class中super的用法
在es6中class实现继承,子类必须在constructor方法中调用super方法,否则新建实例时会报错。因为子类没有自己的this对象,而是继承父类的this对象,然后对其进行加工,如果不调用super方法,子类就得不到this对象。
super这个关键字既可以当做函数使用,也可以当作对象使用。在这两种情况下,用法完全不同。
第一种:super作为函数调用时,代表父类构造函数。
class A{ constructor(){ console.log(new.target.name); } } class B extends A{ constructor(){ super(); } new A();//A new B();//B
super虽然代表父类的构造函数,但是返回的是子类的实例,即super内部的this指向的是子类B。因此super()在这里相当于:
A.prototype.constructor.call(this);
作为函数时,super()只能在子类的构造函数中,用在其他地方会报错。
ES6规定,通过super调用父类的方法时,super会绑定到子类的this。
class A { constructor() { this.x = 1; } print() { console.log(this.x); } } class B extends A { constructor() { super(); this.x = 2; } m() { super.print(); } } let b = new B(); b.m() // 2
第二种:super作为对象时,在普通方法中,指向父类的原型对象;在静态方法中,指向父类。