Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

js-继承的八种方法 #6

Open
ahaow opened this issue May 5, 2022 · 0 comments
Open

js-继承的八种方法 #6

ahaow opened this issue May 5, 2022 · 0 comments

Comments

@ahaow
Copy link
Owner

ahaow commented May 5, 2022

js继承的八种方法

原型链继承

构造函数、原型和实例之间的关系:每个构造函数都有一个原型对象,原型对象都包含一个指向构造函数的指针,而实例都包含一个原型函数的指针

function Parent() {
  this.name = 'carpe'
}
Parent.prototype.getName = function() {
  return this.name
}
function Son() {
  this.age = '18'
}
// 将构造函数Son的原型挂载到 Parent的实例上
Son.prototype = new Parent()
Son.prototype.getAge = function() {
  return this.age
}
let son1 = new Son()
console.log(son1.name) // carpe
console.log(son1.age) // 18
console.log(son1.getName()) // carpe
console.log(son1.getAgee()) // 18

缺点: 多个实例对引用数据类型的操作会被篡改

function Parent() {
  this.colors = ['red', 'yellow', 'black']
}
Parent.prototype.getColors = function() {
  return this.colors
}
function Son() {}
Son.prototype = new Parent()
let son1 = new Son()
let son2 = new Son()
son1.colors.push('blue')
console.log(son1.colors) // ['red', 'yellow', 'black', 'blue']
console.log(son2.colors) // ['red', 'yellow', 'black', 'blue']

借用构造函数继承

使用父类的构造函数来增强子类实例,等同于复制父类的实例给子类(不使用原型)

function Parent() {
  this.numbers = [1,2,3,4,5]
}
function Son() {
  Parent.call(this)
}
let son1 = new Son()
let son2 = new Son()
son1.numbers.push(6) // [1,2,3,4,5,6]
son2.numbers.unshift(0) // [0,1,2,3,4,5]

核心就是Parent.call(this), 创建子类实例时调用Parent构造函数,于是Son的每个实例就会将Parent中的属性复制一份

缺点:

  • 只能继承父类的实例属性和方法,不能继承原型属性和方法
  • 无法实现复用,每个子类都有父类实例函数的副本,影响性能

组合继承

就是将原型链继承和借用构造函数继承方式结合, 使用原型链继承的方式对原型实行继承,用借用构造函数实现实例属性继承

function Parent(name) {
  this.name = name
  this.numbers = [1,2,3,4,5]
}
Parent.prototype.sayName = function() {
  console.log(this.name)
}
function Son(name, age) {
  // 借用构造函数继承
  Parent.call(this, name)
  this.age = age
}
Son.prototype.sayAge = function() {
  console.log(this.age)
}
// 原型链继承
Son.prototype = new Parent()
// 此时Son的constructor指向Parent, 需要指向Son
Son.prototype.constructor = Son
let son1 = new Son('carpe', 18)
let son2 = new Son('hanzo', 26)
son1.numbers.push(6)
son1.age = 19
son1.sayName()
console.log('son1', son1)
console.log('son2', son2)

缺点

  • 在第一次调用Parent会给Son原型对象写入numbersname属性
  • 在第二次生成Son实例的时候,给son1son2写入nameage属性
  • 实例对象son1上的两个属性就屏蔽了其原型对象Son.prototype的两个同名属性
  • 组合模式的缺点就是在使用子类创建实例对象时,其原型中会存在两份相同的属性/方法。

LKt6PK.png

原型式继承

利用一个空对象作为中介,将某个对象直接赋值给空对象构造函数的原型

function object(obj) {
  function fn() {}
  fn.prototype = obj
  return new fn()
}
let person = {
  name: 'carpe',
  numbers: [1,2,3,4,5]
}
let p1 = object(person)
let p2 = object(person)
p1.name = 'diem'
p1.numbers.push(6)
p1.numbers.unshift(0)
console.log('p1', p1)
console.log('p2', p2)
console.log('person', person)

LKDJc6.png

缺点:

  • 原型式继承多个实例的引用类型属性指向相同,存在篡改的可能
  • 无法传递参数

ES5中存在Object.create()的方法,能够代替上面的object方法

寄生式继承

在原型式继承的基础上,增强对象,返回构造函数

function object(obj) {
  function fn() {}
  fn.prototype = obj
  return new fn()
}

function createAnother(original) {
  let clone = object(original) // 通过调用 object() 函数创建一个新对象
  clone.sayHi = function() { // 以某种方式来增强对象
    alert("hi");
  }
  return clone // 返回这个对象
}
let person = {
  name: 'carpe',
  numbers: [1, 2, 3, 4, 5]
} 
let p1 = createAhother(person)
let p2 = createAhother(person)
p1.name = 'diem'
p1.numbers.push(6)
p1.numbers.unshift(0)
console.log('p1', p1)
console.log('p2', p2)
console.log('person', person)

LKyV1I.png

缺点 和 原型式继承 一样

寄生组合式继承

借用构造函数传递参数和寄生模式实现继承

function object(obj) {
  function fn() {}
  fn.prototype = obj
  return new fn()
}

function inheritPrototype(son, parent) {
  let clonePrototype = Object.create(parent.prototype) // 1. 寄生复制一份父类的原型
  son.prototype = clonePrototype // 2. 将clonePrototype原型挂到子类的原型上
  // 此时的son的原型上的构造函数是clonePrototype的构造函数,也就是parent
  son.prototype.constructor = son // 3. 手动强制更改son的构造函数
}

function Parent(name) {
  this.name = name
  this.numbers = [1,2,3,4,5]
}
Parent.prototype.sayName = function() {
  console.log(this.name)
}
function Son(name, age) {
  // 借用构造函数继承
  Parent.call(this, name)
  this.age = age
}
inheritPrototype(Son, Parent)
Son.prototype.sayAge = function() {
  console.log(this.age)
}
let son1 = new Son('carpe', 18)
let son2 = new Son('hanzo', 23)
son1.numbers.push(6)
son1.sayName()
son2.numbers.unshift(0)
son2.sayName()
console.log(son1)
console.log(son2)

LK5rdI.png

要点

  • 这个例子的高效率体现在它只调用了一次Parent 构造函数,并且因此避免了在Son.prototype 上创建不必要的、多余的属性。
  • 于此同时,原型链还能保持不变;因此,还能够正常使用instanceofisPrototypeOf()

这是最成熟的方法,也是现在库实现的方法

ES6类继承extends

class Parent {
  constructor(name) {
    this.name = name
  }
  sayName() {
    console.log(this.name)
  }
}
let p1 = new Parent('carpe')
class Son extends Parent {
  constructor(name, age) {
    // 在 子类里 必须要调用super      
    super(name)
    this.age = age
  }
  sayAge() {
    console.log(this.age)
  }
}
let son1 = new Son('hanzo', 35)
let son2 = new Son('genji', 31)
son1.numbers.push(6)
son2.numbers.unshift(0)
console.log('son1', son1)
console.log('son2', son2)

LKTcJx.png

混入方式继承多个对象(只做了解)

function MyClass() {
     SuperClass.call(this);
     OtherSuperClass.call(this);
}

// 继承一个类
MyClass.prototype = Object.create(SuperClass.prototype);
// 混合其它
Object.assign(MyClass.prototype, OtherSuperClass.prototype);
// 重新指定constructor
MyClass.prototype.constructor = MyClass;

MyClass.prototype.myMethod = function() {
     // do something
};

Object.assign会把 OtherSuperClass原型上的函数拷贝到 MyClass原型上,使 MyClass 的所有实例都可用 OtherSuperClass 的方法。

@ahaow ahaow changed the title js继承的八种方法 js-继承的八种方法 May 5, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant