发布网友 发布时间:2024-10-03 01:21
共1个回答
热心网友 时间:2024-11-17 06:20
前言本文介绍了javascript中多种继承方式及其优缺点,读完本文将收获:
知道javascript中6种继承方式的实现原理
知道各种继承方式的优缺点
理解各种继承方式之间的联系
一、原型链继承顾名思义,原型链继承利用了构造函数的原型对象来实现继承。如下代码中有两个类,子类Child想要继承父类Father的name属性该怎么做?
function?Father(){????this.name?=?'William'}function?Child(){}每一个构造函数都有一个prototype属性,它指向构造函数的原型对象。我们可以让子类构造函数的prototype属性等于父类的一个实例,即:
Child.prototype?=?new?Father()然后我们生成一个子类的实例,并且去访问子类实例的name属性:
let?child1?=?new?Child()console.log(child1.name)????//?Willam可以看到打印出William,访问child1的name属性的工作机制是这样的:
查找child1自身发现没有name属性
查找child1.__proto__属性指向的对象,该对象也就是Child.prototype指向的对象,它是父类Father的一个实例,查找该对象,发现有name属性,于是返回。用一个图来表示上面的关系:途中红色箭头部分就是所谓的原型链,利用原型链我们也可以实现方法的继承,如下所示:
Father.prototype.getName?=?function?()?{????console.log(this.name)}child1.getName()??//?william上面示例的整体代码:
function?Father()?{????this.name?=?'Willam'}Father.prototype.getName?=?function?()?{????console.log(this.name)}function?Child()?{}Child.prototype?=?new?Father()let?child1?=?new?Child()console.log(child1.name)child1.getName()?//?william利用原型链实现继承简单直观,那么修改子类实例的属性会怎样呢?
let?child2?=?new?Child()console.log(child2.name)????//?Williamconsole.log(child1.name)????//?Williamchild2.name?=?'Jane'console.log(child2.name)????//?Janeconsole.log(child1.name)????//?William看上面代码似乎没有问题,实例之间的属性并没有相互影响,那是因为这里是直接给child2增加了一个自身的name属性,而不是去修改它继承的那个属性:
console.log(child2.__proto__.name)??//?William如果所继承的属性是引用类型,那修改实例属性时就需要注意了:
function?Father()?{????this.names?=?['William']}function?Child()?{}Child.prototype?=?new?Father()let?child1?=?new?Child()let?child2?=?new?Child()console.log(child1.names)?//?[?'William'?]console.log(child2.names)?//?[?'William'?]child1.names.push('Jane')console.log(child1.names)?//?[?'William',?'Jane'?]console.log(child2.names)?//?[?'William',?'Jane'?]二、借用构造函数(经典继承)直接在子类构造函数中调用父类构造函数并改变其this指向。
function?Father()?{????this.names?=?['William',?'Jane']????this.getName?=?function?()?{????????console.log(this.names)????}}function?Child()?{????Father.call(this)}let?child1?=?new?Child()let?child2?=?new?Child()console.log(child1.names)?//?[?'William',?'Jane'?]console.log(child2.names)?//?[?'William',?'Jane'?]child1.getName()??//?[?'William',?'Jane'?]child2.getName()??//?[?'William',?'Jane'?]child1.names.pop()console.log(child1.names)?//?[?'William'?]console.log(child2.names)?//?[?'William',?'Jane'?]child1.getName()??//?[?'William'?]child2.getName()??//?[?'William',?'Jane'?]优点:
子类实例的方法和属性都是独有的
可以向父类构造函数传参,举个例子
function?Father(name)?{????this.name?=?name}function?Child(name)?{????Father.call(this,?name)}let?child1?=?new?Child('William')console.log(child1.name)??//?William缺点:
在每一次创建子类实例时,继承的方法总是要被重新创建。
三、组合继承组合继承是javascrip最常用的继承模式,它将一和二两种方法组合起来:
利用原型链继承方法
借助构造函数继承属性
function?Father()?{????this.names?=?['William',?'Jane']}Father.prototype.getNames?=?function?()?{????console.log(this.names)}function?Child()?{????Child.prototype?=?new?Father()8}Child.prototype?=?new?Father()Child.prototype.constructor?=?Childlet?child1?=?new?Child()let?child2?=?new?Child()console.log(child1.names)?//?[?'William',?'Jane'?]console.log(child2.names)?//?[?'William',?'Jane'?]child1.getNames()?//?[?'William',?'Jane'?]child2.getNames()?//?[?'William',?'Jane'?]child1.names.push('Amy')console.log(child1.names)?//?[?'William',?'Jane',?'Amy'?]console.log(child2.names)?//?[?'William',?'Jane'?]child1.getNames()?//?[?'William',?'Jane',?'Amy'?]child2.getNames()?//?[?'William',?'Jane'?]优点:
子类实例的属性是独有的
子类实例的方法都是继承父类原型上的方法,不用每次都创建
创建子类实例时可以向父类构造函数传参缺点:
要调用两次父类的构造函数,效率低。且在newFather()创建的对象上也有并不需要的属性。
function?Child()?{????Child.prototype?=?new?Father()8???//?第一次调用}Child.prototype?=?new?Father()??//?第二次调用四、原型式继承当我们想要在一个对象的基础之上去构造另一个对象而不想创建额外的构造函数,就非常适合使用原型式继承。
Child.prototype?=?new?Father()2上面的createObj()其实就是ES5中Object.create()的模拟实现,其中Object.create还可以接收第二个参数,它是一个对象,对象里可以定义要创建的对象独有的属性和方法。缺点:
和原型链继承一样,实例的引用类型的属性都是共享的。
五、寄生式继承创建一个实现继承的函数,在函数里以某种方式增强对象。
Child.prototype?=?new?Father()3缺点:
和借用构造函数的继承方式一样,每个实例都要重新创建同一方法。
六、寄生组合式继承回顾一下前面使用的组合式继承
function?Father(name)?{????this.name?=?name}Father.prototype.getName?=?function?()?{????console.log(this.name)}function?Child(name,?age)?{????Father.call(this,?name)??this.age?=?age}Child.prototype?=?new?Father()上面代码的最后一步是为了继承父类的方法,但是newFather()又被调用了一次且它得到的对象上面也有name属性,而我们并不需要这些属性,我们只是需要这个对像把实例对象与Father.prototype对象联系起来。
所以我们可以直接创建一个对象,让这个对象的构造函数的prototype指向Father.prototype
function?Father(name)?{????this.name?=?name}Father.prototype.getName?=?function?()?{????console.log(this.name)}function?Child(name,?age)?{????Father.call(this,?name)????this.age?=?age}//?Child.prototype?=?new?Father()function?F()?{}F.prototype?=?Father.prototypeChild.prototype?=?new?F()let?child1?=?new?Child('William',?19)console.log(child1.name)?//?Williamchild1.getName()?//?William将上面的过程封装一下:
Child.prototype?=?new?Father()6优点:
只调用了一次父类构造函数且避免了新增不必要的属性,效率更高。
寄生式组合继承可以算是引用类型继承的最佳模式。
总结?原型链继承:Child.prototype?=?new?Father()优点:简单直观,子类的实例方法是共享的,不用每次都创建。
缺点:子类实例继承的引用属性都是共享的。
?借用构造函数:Child.prototype?=?new?Father()8优点:子类继承的属性不共享
缺点:实例的方法每次都要重新创建。
?组合继承:Child.prototype?=?new?Father()8Child.prototype?=?new?Father()优点:子类的实例方法是共享的,不用每次都创建。子类继承的属性独有不共享。
缺点:调用了两次父类构造函数,且newFather()得到的对象上的属性我们并不需要。
?原型式继承let?child1?=?new?Child()console.log(child1.name)????//?Willam0优点:直接实现对象继承另一个对象而无需构造函数。
缺点:实例继承的属性都是共享的。
?寄生式继承在原型式继承的基础上增强得到的对象。
缺点:和借用构造函数的继承方式一样,每个实例都要重新创建同一方法。
?寄生组合式继承组合继承与寄生继承的结合
优点:只调用了一次父类构造函数且避免了新增不必要的属性,效率更高。
原文:https://juejin.cn/post/7094858433647771661