js 六种继承方式介绍及优缺点
前言
在JavaScript高级程序设计一书中,提到js中有六种继承方式,但阅读后,个人觉得这六种方式,其实最终也只是对原型和构造函数通过不同的技巧实现的继承设计方式罢了,为了满足需要其实我们也可以自己去定义技巧实现继承,那么如此说来继承方式岂非不是仅仅只有六种乎?
继承方式
- 原型链继承
- 借用构造函数继承
- 组合继承
- 原型式继承
- 寄生继承
- 寄生组合继承
详情介绍
1.原型链继承
缺点:Parent 中的引用属性会被每个子类示例共享
1 //原型链继承 2 function Parent() { 3 this.parentPrototype = "parent prototype" 4 //验证这种继承方法的确定,如果父类示例中存在一个引用类型的属性,将会被所有子类共享 5 this.parentObj = { 6 info: "我是 parent 引用属性parentObj中的 info" 7 } 8 } 9 10 function Children() { 11 12 } 13 //将Children的原型对象指定为Parent的示例,通过原型链,将Parent中的属性赋值给Children示例 14 Children.prototype = new Parent(); 15 const a = new Children(); 16 console.log(a.parentPrototype); // parent prototype 17 //缺点 18 const b = new Children(); 19 //在a示例中改动继承的引用属性 20 a.parentObj.info = "我是a示例中 引用属性parentObj中的 info" 21 //b与a示例共享引用属性 22 console.log(b.parentObj.info); // 我是a示例中 引用属性parentObj中的 info
2.借用构造函数继承
优点:
1避免了子类示例共享引用属性的情况
2可以在实例化时给Parent构造函数传递参数
缺点:
1如果Parent中存在一个函数,那么每次实例化Children的时候,都会创建一个同样函数,函数的复用性就难以体现
1 function Parent() { 2 this.parentPrototype = "parent prototype" 3 this.obj = { 4 info: "parent obj info" 5 } 6 this.fn = function () { 7 console.log("打印功能") 8 } 9 10 } 11 12 function Children() { 13 Parent.call(this); 14 } 15 16 const a = new Children(); 17 console.log(a.parentPrototype); // parent ptototype 18 19 //缺点 此时Parent()会再次创建一个fn函数,这个是没有必要的 20 const b = new Children(); 21 a.obj.info = "a obj info"; 22 //优点 避免了子类实例共享引用属性 23 console.log(b.obj.info) // parent obj info;
3组合继承:原型链 + 构造函数 (这是js中最常见的继承方式)
优点:
1 避免了子类共享引用属性同时避免了父类构造函数重复对function属性的创建
1 function Parent() { 2 this.parentPrototype = "我是Parent 中的属性" 3 } 4 //Parent中的方法,在原型上定义 5 Parent.prototype.pFn = function () { 6 console.log('我是Parent中的方法'); 7 } 8 9 function Children() { 10 //Parent中的属性仍然在构造函数中继承 11 Parent.call(this); 12 } 13 //将Children的原型对象赋值为 Parent实例,这样Parent中的方法也能够被Children继承 14 Children.prototype = new Parent(); 15 const c = new Children(); 16 console.log(c.parentPrototype); //我是Parent 中的属性 17 c.pFn(); //我是Parent中的方法
4.原型式继承(注意:是原型式而非原型链,这种方法使用较少)
缺点:
1和原型链继承一样,后代实例会共享父类引用属性
1 function objFn(o) { 2 o.objFnPrototype = "我是 objFnPrototype" 3 function F() {} 4 F.prototype = o; 5 return new F(); 6 } 7 8 let a = objFn({ 9 name: "name1" 10 }); 11 console.log(a.name); //name1 12 console.log(a.objFnPrototype); //我是 objFnPrototype
5寄生式继承(个人感觉就是定义了一个方法,复制了一个对象,让后在复制的对象上添加属性和方法,然后return)
缺点:
1和原型链继承一样,parent中的引用属性,会被所有示例共享
function createObje(obj) { let clone = Object.assign(obj); //接受到对象后,原封不动的创建一个新对象 clone.prototype1 = "我是新增的prototype1"; //在新对象上新增属性,这就是所谓的寄生 return clone; //返回新对象 } const parent = { parentPrototype: "parentPrototype" } //c实例,就继承了parent的所有属性 let c = createObje(parent); console.log(c.parentPrototype); //parentPrototype
6寄生组合式继承 (寄生+组合(原型链+借用构造函数))
优点:
1和组合继承一样,只不过没有组合继承的调用两次父类构造函数的缺点
1 function inherProto(superType, subType) { 2 //拷贝一个超类的原型副本 3 let proto = { 4 ...superType.prototype 5 }; 6 //将原型的超类副本作为子类的原型对象,也就是第一种中的原型链继承方式,只不过继承的是超类原型的副本 7 subType.prototype = proto; 8 //这一步比较迷,官方的说法是,我们在拷贝超类的原型的时候,拷贝的proto对象,将会丢失默认自己的构造函数,也就是superType, 9 //所以我们这里将它的构造函数补全为subType。貌似不做这一步也没啥问题,但是缺了点东西可能会有其他的副作用,所以还是补上 10 proto.constructor = subType; 11 12 } 13 14 function Super() { 15 this.superProto = "super proto"; 16 this.colors = ["red", "yelloy"]; 17 } 18 19 function Sub() { 20 this.subProto = "sub proto"; 21 this.name = "sub name"; 22 //这里还是借用构造函数的套路 23 Super.call(this); 24 } 25 Super.prototype.getName = function () { 26 console.log(this.name); 27 } 28 //这里要在定义完Super的属性后执行,因为继承的是超类原型的副本,与Super.prototype是两个对象,在这之后再改变Super.prototype,就已经不会在影响到Sub所继承的副本超类原型对象了 29 inherProto(Super, Sub); 30 31 let a = new Sub(); 32 console.log(a.getName); 33