[深刻03] 继承

导航

[深刻01] 执行上下文
[深刻02] 原型链
[深刻03] 继承
[深刻04] 事件循环
[深刻05] 柯里化 偏函数 函数记忆
[深刻06] 隐式转换 和 运算符
[深刻07] 浏览器缓存机制(http缓存机制)
[深刻08] 前端安全
[深刻09] 深浅拷贝
[深刻10] Debounce Throttle
[深刻11] 前端路由
[深刻12] 前端模块化
[深刻13] 观察者模式 发布订阅模式 双向数据绑定
[深刻14] canvas
[深刻15] webSocket
[深刻16] webpack
[深刻17] http 和 https
[深刻18] CSS-interview
[react] Hooks前端

[部署01] Nginx
[部署02] Docker 部署vue项目
[部署03] gitlab-CIvue

[源码-webpack01-前置知识] AST抽象语法树
[源码-webpack02-前置知识] Tapable
[源码-webpack03] 手写webpack - compiler简单编译流程react

原型链继承

  • 将子类的prototype指向父类的实例,同时修改子类的constructor让其从新指向子类
  • 或者说:将父类的实例做为子类实例的隐式原型
  • 缺点:
    • 在建立子类实例的时候,不能向父类传参
    • 不能实现多继承(继承多个父类),由于是给prototype直接赋值
    • 多个实例共享父类的属性和父类原型上的属性,当属性是引用类型时,子类实例间修改会相互影响【特别是数组】
    • 在子类的prototype上挂属性和方法,必须在修改子类的prototype指向以后。
    • Sub.prototype = new Super('woow_wu7')以后Sub.prototype.sex = 'man',否则会被新的引用代替
原型链继承
- 原理:将子类的prototype指向父类的实例,同时要修改子类的constructor属性让其从新指向子类
    - 由于修改了子类prototype指向父类实例后,子类的prototype.constructor就指向了父类(修改改回来,防止引用出错)
- 缺点:
1. 生成子类实例时,不能向父类传参
2. 不能实现多继承
3. 属性共享,修改子类实例上的原型链上的引用类型的属性时,子类实例会相互影响
4. 在子类的prototype上挂属性和方法时,须要在子类的prototype指向父类的实例以后


代码示例:
// 父类
function Super(name) {
  this.name = name
}
Super.prototype.age = 20

// 子类
function Sub(address) {
  this.address = address
}
Sub.prototype = new Super('woow_wu7') // 原型链继承:将子类的prototype指向父类的实例,子类实例就能访问父类实例和父类实例原型链上的属性和方法,缺点:不能实现多继承
Sub.prototype.constructor = Sub // 记得在修改prototype后,须要修改constructor指向,防止引用出错
Sub.prototype.sex = 'man' // 缺点:挂载属性必须在上面步骤滞后
const sub = new Sub('hangzhou') // 缺点:只能向子类传参,不能向父类传参

console.log(sub.address, '子类实例自身属性')
console.log(sub.sex, '子类实例原型上的属性')
console.log(sub.name, '子类实例原型上的属性 => 父类实例上的属性')
console.log(sub.age, '子类实例原型的原型上的属性 => 父类实例原型上的属性') // 一层层上溯
复制代码
修改constructor也能够用下面的方式


Sub.prototype = Object.create(Super.prototype, { 
    // Oject.create第二个参数表示生成的原型上的属性
    // 不要忘了从新指定构造函数 
    constructor: {
        value: Student
    }
})
复制代码

借用构造函数继承(经典继承)

  • 原理:经过call(this, 参数)绑定父类中的this为子类的实例,并执行父类,就至关于把父类中的this换成了子类实例
  • 优势:
    • 能实现多继承(即调用多个父类)
    • 生成子类实例时,能够向父类传参
    • 继承的属性是直接生成在子类实例上的,各个子类实例之间修改引用类型的属性互相不受影响
  • 缺点:
    • 不能继承父类实例对象原型链上的属性和方法
      • (由于父类没有经过new命令生成父类实例,也没有改变子类prototype的指向,不存在原型链继承)
    • 就是构造函数的缺点,也是优势,做为缺点就是属性和方法都生成在实例上,每次new都会新生成一份,形成系统资源浪费(即不共享属性),对于能够共享的只读属性,应该方法原型链上
借用构造函数继承


function Super1(name) {
  this.name = name
}
function Super2(age) {
  this.age = age
}
Super1.prototype.sex = 'man'

function Sub(name, age, address) {
  Super1.call(this, name) // 经过call,绑定super1的this为子类实例,并执行Super1(),至关于 this.name = name
  Super2.call(this, age) // 优势:能够多继承,同时继承了Super1和Super2中的属性,且在子类实例上修改属性相互不受影响
  this.address = address // 缺点:不能继承父类实例原型链上的属性和方法
}
const sub = new Sub('woow_wu7', 20, 'hangzhou') // 优势:能够向父类传参
console.log(sub)
复制代码

组合式继承(原型链继承+借用构造函数继承)

  • 组合继承:即原型链继承 + 借用构造函数继承
  • 优势:
    • 既具备借用构造函数继承的优势(向父类传参,多继承,不存在属性共享)
    • 又具备原型链继承的优势(继承父类实例原型链上的属性和方法,而且是共享)
  • 缺点:
    • 会调用两次父构造函数,致使子类实例和子类实例原型链上都有同一个属性或方法
    • 父类被调用了两次,一次是借用构造函数是的call调用,一次是原型链继承时的new调用
    • 由于父类两次调用,因此子类和父类实例原型链上有相同的属性和方法,形成浪费
组合式继承
- 借用构造函数继承 + 原型链继承
- 优势:多继承,将父类传参,某些属性不共享,继承父类实例原型链上的属性和方法
- 缺点:!!!!!! 
    - 父类被调用了两次,一次是借用构造函数是的call调用,一次是原型链继承时的new调用
    - 由于父类两次调用,因此子类和父类实例原型链上有相同的属性和方法,形成浪费
    
    
    
代码:
function Super1(name) {
  this.name = name
}
function Super2(age) {
  this.age = age
}
Super1.prototype.getName = function() {
  return 'Super1' + this.name
}
Super2.prototype.getAge = function() {
  return 'Super2' + this.age
}

function Sub(name, age, address) {
  Super1.call(this, name) // 借用构造函数,多继承,但不能继承原型链上的属性
  Super2.call(this, age)
  this.address = address
}
Sub.prototype = new Super1() 
// 注意:这里没有传参,在原型链继承这条线上,父类实例上的nane属性是undefined
// 注意:原型链继承这条线,仍是不能多继承,(如不能同时继承Super1和Super2所在的prototye)由于是直接赋值
Sub.constructor = Sub // 记得修改constructor指向
Sub.prototype.getAddress = function() {
  return 'Sub' + this.address
}

const sub = new Sub('woow_wu7', 20, 'hangzhou')
console.log(sub)



组合继承最大的缺点:
1. 父类执行了两次
  - 1. 在new Sub('woow_wu7', 20, 'hangzhou')是会执行Super.call(this, name)------- 生成一次name // 'woow_wu7'
  - 2. 在Sub.prototype = new Super1() 执行了一次,又会生成一次name // undefined
复制代码

寄生组合式继承

  • 寄生组合继承:主要解决了在组合继承中两次调用父类的问题,这致使子类实例的自身属性中有父类实例的属性,子类的原型链中也有父类实例原型中的属性
寄生组合式继承
- 主要解决:
    - 组合式继承中,父类被屡次调用,致使子类实例属性和子类实例原型链上有相同的属性的问题
    - 由于父类两次被调用,call和new,构造函数中的属性会两次生成,形成资源的浪费
    
    
function Super(name) {
  this.name = name
}
Super.prototype.getName = function() {
  return 'Super' + this.name
}
function Sub(name, age) {
  Super.call(this, name) // 借用构造函数
  this.age = age
}
// Sub.prototype = new Super() ---------------- 原型链继承,(没用寄生组合继承以前,即没有使用过渡函数Parasitic)
function Parasitic(){}
Parasitic.prototype = Super.prototype
Sub.prototype = new Parasitic() 
// Parasitic内没有任何属性
// 这样就没有执行父类(Super构造函数),而是间接的只继承了父类实例原型上的属性
Sub.constructor = Sub // 修改prototype要同时修改conscrutor指向
Sub.prototype.getAge = function() {
  return 'Sub' + this.age
}
const sub = new Sub('woow_wu7', 20)
console.log(sub)
复制代码

class

  • class能够经过 extends关键字实现继承
  • 子类必须在constructor方法中调用super方法,否在新建实例时会报错
    • 由于子类的this须要经过父类的构造函数获取,不调用super方法就得不到this对象
  • 子类没有定义constructor会被默认添加
  • 在子类的constructor中必须先调用super()后才能使用this
  • 父类的静态方法也会被子类所继承
es5的继承(借用构造函数式继承)
- es5的借用构造函数式继承:
- 是先建立子类的this,而后将父类的属性和方法帮到子类的this对象上


es6的继承:
- 是将父类实例的属性和方法添加到this上,而后用子类的构造函数修改this
复制代码

super关键字

  • 能够做为函数,也能够做为对象

super做为函数

  • super做为函数只能用于构造函数中,表示父类的构造函数,this指向子类的实例
  • 注意:
    super做为函数,虽然表示父类的构造函数,但返回的是子类的实例 ,即super内部的this指向的是子类的实例!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
super做为函数
- super做为函数:只能用于构造函数中,表示父类的构造函数
- super做为函数:内部的this指向的是子类的实例


class A {
  constructor() {
    console.log(this, 'this')
  }
}
class B extends A {
  constructor() {
    super() // 注意:super最为函数,只能用于构造函数中表示父类的构造函数,内部this指向子类的实例
  }
}

new B() // B this ========> super做为函数,内部this指向子类的实例
复制代码

super做为对象

  • super做为对象
  • 在普通方法中:指向父类的原型,this指向当前子类的实例(实例上的属性和方法没法经过该super获取)
  • 在静态方法中:指向父类,this指向子类
因为this指向子类实例,因此若是经过super对某个属性赋值,这时super就是this,赋值的属性会变成子类实例的属性。

class A {
  constructor() {
    this.x = 1;
  }
}

class B extends A {
  constructor() {
    super();
    this.x = 2;
    super.x = 3; // !!!!!super对某个属性赋值,super就表示this,即子类的实例!!!!!
    console.log(super.x); // undefined // super在普通函数中是对象时,表示父类的原型
    console.log(this.x); // 3
  }
}

let b = new B();
复制代码
super做为对象,在静态方法中:表示父类

class Parent {
  static myMethod(msg) {
    console.log('static', msg);
  }

  myMethod(msg) {
    console.log('instance', msg);
  }
}

class Child extends Parent {
  static myMethod(msg) {
    super.myMethod(msg); // super做为对象,在静态方法中,表示父类,调用父类的静态方法myMethod
  }
  myMethod(msg) {
    super.myMethod(msg);
  }
}

Child.myMethod(1); 
// static 1
// Child.myMethod()是调用Child的静态方法,静态方法中的super对象表示父类

var child = new Child();
child.myMethod(2); 
// instance 2
// 实例上调用myMethod,没构造函数中没有,就去原型上查找,super对象在普通方法中表示父类的原型
复制代码

super总结

  • super做为函数,表示父类的构造函数,this指向子类实例(此时this只能用于)
  • super做为对象,在普通方法中,表示父类的原型,this指向子类实例
  • super做为对象,在静态方法中,表示父类,this指向子类

es6继承

  • class做为构造函数的语法糖,同时具备__proto__ 和 prototype
  • 因此 class 同时具备两条继承链:
    • 子类的__proto__老是指向父类(表示构造函数的继承)
    • 子类的prototype.__proto__老是指向父类的prototype(表示方法的继承)

图片来源于网络

个人简书:www.jianshu.com/p/d8809038c…
川神:juejin.im/post/5c433e…
www.jianshu.com/p/a8844b28f…webpack

相关文章
相关标签/搜索