重点分析四个常用的继承方式,并且由最次到最佳的递进过程,分析每个继承的优缺点。
原型继承
主要利用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 = name
- this.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 />代码实例:
```javascript
class 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.length
- for(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扩展处理Xquery
- class Xquery extends Jquery{
- constructor(selector){
- super(selector)
- }
- // 扩展自己的方法
- addClass(){}
- getClass(){}
- }
 
 
 
 
 
                         
                                

