重点分析四个常用的继承方式,并且由最次到最佳的递进过程,分析每个继承的优缺点。
原型继承
主要利用JavaScript的原型链。
代码演示
function Parent(sex){this.sex = sex}Parent.prototype.setSex = function(){}function Son(name){this.name = name}Son.prototype = new Parent()var s1 = new Son("bobo")console.log(s1)
关键实现
把子类的原型指向父类的实例,完成了子继承父类的私有属性和原型属性。
优点:
- 父类新增的原型属性和方法,子类实例都能访问到
-
缺点:
无法实现多继承,一个子类继承多个父类
- 创建子类实例的时候,无法向父类构造函数传参
存在子类实例共享父类引用属性的问题(子类的原型同时指向父类的一个实例,假如父类的私有属性是一个引用类型,那么任何一个子类操作该引用类型属性的时,会导致其他子类使用的这个属性发生变化)
借用父类构造函数继承
代码实例
function Parent(sex){this.sex = sex}Parent.prototype.setSex = function (){}function Son(name, age, sex){Parent.call(this, sex)this.name = namethis.age = age}var son = new Son("bobo",18,'male')console.log(son)
关键实现:
在子类构造函数中使用call或者apply调用父类的构造函数,继承父类私有属性。
优点:
创建子类实例时,可以向父类传递参数
- 可以实现多继承
解决原型继承中子类实例共享父类引用类型属性的问题。(在创建子类实例时,都会重新调用父类构造函数重新创建一个引用类型属性的数据)
缺点:
每次创建子类实例,都要调用一次父类构造函数,影响性能
- 只继承了父类的私有属性,没有继承父类的原型属性
组合式继承(原型链+借用构造函数)
代码实例: ```javascript function Parent(sex){ this.sex = sex } Parent.prototype.setSex = function(){}
function Son(name, age, sex){ Parent.call(this, sex) this.name = name this.age = age } Son.prototype = Object.create(Parent.prototype)
// 修正Son的构造函数 Son.prototype.constructor = Son var son = new Son(“bobo”, 18, “boy”) console.log(son)
执行结果:<br /><a name="AH5Ks"></a>### 关键实现:通过调用父类构造函数,继承父类的属性并保留传参的优点,通过Object.create(Parent.prototype)来继承父类原型属性的对象,并把这个对象赋值给子类的原型。<br />既能保证父类构造函数不用执行两次,又能让子类能继承到父类的原型方法。<a name="9eBZY"></a>### 优点:- 创建子类实例时,可以向父类传递参数- 可以实现多继承- 解决了原型链继承中子类实例共享父类是引用类型属性的问题。- 父类构造函数只执行一次。<br /><a name="4dSvY"></a>## Es6中class的继承ES6中引入了**class关键字**,class可以通过extends关键字实现继承,还可以通过**static**关键字定义类的静态方法,这比 ES5 的通过修改原型链实现继承,要**清晰和方便**很多。<br />**注意:ES5 的继承**,实质是**先创造子类的实例对象this**,然后**再将父类的方法添加到this**上面(Parent.apply(this))。<br />**ES6 的继承机制完全不同**,实质是**先将父类实例对象的属性和方法加到this上面(所以必须先调用super方法)**,**然后再用子类的构造函数修改this**。<br />代码实例:```javascriptclass A{constructor(sex){this.sex = sex}showSex(){console.log("父类中的方法")}}class B extends A{constructor(name, age, sex){super(sex);this.name = name;this.age =age;}showSex(){console.log("这里是子类的方法")}}let b = new B("bbb",12,"boy")console.log(b)
关键实现原理
使用extends关键字继承父类的原型属性,调用super来继承父类的实例属性,并且保留向父类构造函数传参的优点
优点:
简单易用,书写方便。不用自己来修改原型链完成继承
接下来将代码从ES6编译到ES5来看看到底class继承的代码最终会被编译成什么样
从上图分析得到:
- 上述代码示例中的super指的就是父类构造函数
- 子类继承父类的实例属性最终还是通过call或者apply来实现继承的
- 通过extends方法的调用来修改子类和父类的原型链关系
再看经过编译后的extends方法,如下
1、注意Object.setPrototypeOf()方法设置一个指定的对象的原型 ( 即, 内部[[Prototype]]属性)到另一个对象或 null。
2、(.prototype = b.prototype, new ())表达式的执行执行顺序是先执行前者,再返回后者
从上图可知,extends做了以下几件事:
- 定义了一个function __() {}函数,并把该函数的constructor指向了子类
- 紧接着,把function __() {} 函数的原型指向了父类的原型
- 最后再把function () {} 函数的实例赋给了子类函数,就这样子类的实例就能沿着proto.proto获取到父类的原型属性了,这种继承模式俗称圣杯模式
基于class实现简版Jquery
class Jquery{constructor(selector){const selectList = document.querySelectorAll(selector)let length = selectList.lengthfor(let i = 0;i< length;i++){this[i] = selectList[i]}this.length = length}getIndex(index){return this[index]}each(fn){for(let i=0;i<this.length; i++){fn(this[i])}}on(type, fn){return this.each(elem=>{elem.addEventListener(type, fn, false)})}// ......}// 扩展插件Jquery.prototype.dialog = function(str){alert(str)}// 基于Jquery扩展处理Xqueryclass Xquery extends Jquery{constructor(selector){super(selector)}// 扩展自己的方法addClass(){}getClass(){}}
