js常见的6种继承方式

构造函数、原型与实例之间的关系

每创建一个函数,该函数就会自动带有一个 prototype 属性。该属性是个指针,指向了一个对象,我们称之为 原型对象。什么是指针?指针就好比学生的学号,原型对象则是那个学生。我们通过学号找到唯一的那个学生。假设突然,指针设置 null, 学号重置空了,不要慌,对象还存在,学生也没消失。只是不好找了。
原型对象上默认有一个属性 constructor,该属性也是一个指针,指向其相关联的构造函数。
通过调用构造函数产生的实例,都有一个内部属性,指向了原型对象。所以实例能够访问原型对象上的所有属性和方法。
所以三者的关系是,每个构造函数都有一个原型对象,原型对象都包含一个指向构造函数的指针,而实例都包含一个指向原型对象的内部指针。通俗点说就是,实例通过内部指针可以访问到原型对象,原型对象通过constructor指针,又可以找到构造函数。

function Dog (name) {
    this.name = name;
    this.type = 'Dog'; 
}
Dog.prototype.speak = function () {
  console.log('wang');
}
var doggie = new Dog('jiwawa');
doggie.speak();  //wang 

以上代码定义了一个构造函数 Dog(), Dog.prototype 指向的原型对象,其自带的属性construtor又指回了 Dog,即 Dog.prototype.constructor == Dog. 实例doggie由于其内部指针指向了该原型对象,所以可以访问到 speak方法。
在这里插入图片描述

Dog.prototype 只是一个指针,指向的是原型对象,但是这个原型对象并不特别,它也只是一个普通对象。假设说,这时候,我们让 Dog.protptype 不再指向最初的原型对象,而是另一个类 (Animal)的实例,情况会怎样呢?
在这里插入图片描述

原型链

面我们说到,所有的实例有一个内部指针,指向它的原型对象,并且可以访问原型对象上的所有属性和方法。doggie实例指向了Dog的原型对象,可以访问Dog原型对象上的所有属性和方法;如果Dog原型对象变成了某一个类的实例 aaa,这个实例又会指向一个新的原型对象 AAA,那么 doggie 此时就能访问 aaa 的实例属性和 AA A原型对象上的所有属性和方法了。同理,新的原型对象AAA碰巧又是另外一个对象的实例bbb,这个实例bbb又会指向新的原型对象 BBB,那么doggie此时就能访问 bbb 的实例属性和 BBB 原型对象上的所有属性和方法了。
这就是JS通过原型链实现继承的方法了。看下面一个例子:

/定义一个 Animal 构造函数,作为 Dog 的父类
function Animal () {
    this.superType = 'Animal';
}

Animal.prototype.superSpeak = function () {
    alert(this.superType);
}

function Dog (name) {
    this.name = name;
    this.type = 'Dog';  
}
//改变Dog的prototype指针,指向一个 Animal 实例
Dog.prototype = new Animal();
//手动挂载构造器,指向自己的构造函数
Dog.prototype.constructor = Dog
//上面那行就相当于这么写

Dog.prototype.speak = function () {
  alert(this.type);
}
var doggie = new Dog('jiwawa');
doggie.superSpeak();  //Animal 

解释一下。以上代码,首先定义了一个 Animal 构造函数,通过new Animal()得到实例,会包含一个实例属性 superType 和一个原型属性 superSpeak。另外又定义了一个Dog构造函数。然后情况发生变化,代码中加粗那一行,将Dog的原型对象覆盖成了 animal 实例。当 doggie 去访问superSpeak属性时,js会先在doggie的实例属性中查找,发现找不到,然后,js就会去doggie 的原型对象上去找,doggie的原型对象已经被我们改成了一个animal实例,那就是去animal实例上去找。先找animal的实例属性,发现还是没有 superSpeack, 最后去 animal 的原型对象上去找,诶,这才找到。
在这里插入图片描述
这就说明,我们可以通过原型链的方式,实现 Dog 继承 Animal 的所有属性和方法。
总结来说:就是当重写了Dog.prototype指向的原型对象后,实例的内部指针也发生了改变,指向了新的原型对象,然后就能实现类与类之间的继承了。

js常见的6种继承方式

原型链继承

原型链继承是比较常见的继承方式之一,其中涉及的构造函数、原型和实例,三者之间存在着一定的关系,即每一个构造函数都有一个原型对象,原型对象又包含一个指向构造函数的指针,而实例则包含一个原型对象的指针。

function inherit1(){
   this.name = "冰墩墩"
   this.arr = [1,2,3,4]
}
function inherit2(){
   this.age = "男"
}
inherit2.prototype = new inherit1()
let new1 = new inherit2()
let new2 = new inherit2()
new1.arr.push(5)
console.log(new1.arr) // [1,2,3,4,5]
console.log(new2.arr) // [1,2,3,4,5]

明明我只改变了new1的play属性,为什么new2也跟着变了呢?原因很简单,因为两个实例使用的是同一个原型对象。它们的内存空间是共享的,当一个发生变化的时候,另外一个也随之进行了变化,这就是使用原型链继承方式的一个缺点。
在这里插入图片描述

构造函数继承

function inherit1(){
  this.name = "冰墩墩"
  this.arr = [1,2,3,4]
}
inherit1.prototype.getName = function(){
   console.log(this.name)
}
function inherit2(){
   inherit1.call(this)
   this.age = "男"
}
let new1 = new inherit2()
let new2 = new inherit2()
new1.arr.push(5)
console.log(new1.arr) // [1,2,3,4,5]
console.log(new2.arr) // [1,2,3,4]
console.log(new1.name2) // undefined
console.log(new1.getName()) // 报错

可以看到我改变了new1的arr属性,new2的arr属性没有发生改变,解决了原型链继承的弊端。但他也有一个弊端就是,只能继承父类的实例属性和方法,不能继承原型属性或者方法

组合继承(结合原型链继承和构造函数继承)

function inherit1(){
   this.name = "冰墩墩"
   this.arr = [1,2,3,4]
}
inherit1.prototype.getName = function(){
   return this.name
}
inherit1.prototype.name2 = "雪融融"
// 使用构造函数继承
function inherit2(){
   inherit1.call(this)
   this.age = "男"
}
// 使用原型链继承 解决原型属性和方法无法继承的问题
inherit2.prototype = new inherit1()
inherit1.prototype.constructor = inherit2
let new1 = new inherit2()
let new2 = new inherit2()
new1.arr.push(5)
console.log(new1.arr) // [1,2,3,4,5]
console.log(new2.arr) // [1,2,3,4]
console.log(new1.name2) // undefined
console.log(new1.getName()) // 报错

可以看到这次解决了前两种方式产生的问题,但是inherit1被执行了两次,第一次是改变inherit2的prototype的时候,第二十是通过call方法调用inherit1的时候,所以就多进行了一次性能开销。

原型式继承

这里不得不提到的就是ES5里面的Object.create方法,这个方法接收两个参数:一是用作新对象原型的对象、二是为新对象定义额外属性的对象(可选参数)。

let inherit1 = {
   name: "冰墩墩",
   date: "2022",
   arr: [1,2,3,4]
}
let new1 = Object.create(inherit1)
new1.name = "冰墩墩可爱"
new1.arr.push(5)
let new2 = Object.create(inherit1)
new2.name1 = "雪融融可爱"
new2.arr.push(6)
console.log(new1.name) //冰墩墩可爱
console.log(new1.arr) //[1,2,3,4,5,6]
console.log(new2.name1) //雪融融可爱
console.log(new2.arr) //[1,2,3,4,5,6]

可以看到使用原型式继承出现了类似于前拷贝的问题

寄生式继承

let inherit1 = {
  name: "冰墩墩",
  date: "2022",
  arr: [1, 2, 3, 4],
  getName: function () {
     console.log(this.name)
  }
}
function clone(inherit) {
  let inherit2 = Object.create(inherit)
    inherit2.getDate = function () {
    console.log(this.date)
  }
  return inherit2
}
let inherit2 = clone(inherit1)
inherit2.name = "冰墩墩可爱"
inherit2.arr.push(5)
let inherit3 = clone(inherit1)
inherit3.name1 = "雪融融可爱"
inherit3.arr.push(6)
console.log(inherit2.name) //冰墩墩可爱
console.log(inherit2.arr) //[1,2,3,4,5,6]
console.log(inherit3.name1) //雪融融可爱
console.log(inherit3.arr) //[1,2,3,4,5,6]

寄生式组合继承

结合第四种中提及的继承方式,解决普通对象的继承问题的Object.create方法,我们在前面这几种继承方式的优缺点基础上进行改造,得出了寄生组合式的继承方式,这也是所有继承方式里面相对最优的继承方式,代码如下。

function clone(inherit, child) {
  child.prototype = Object.create(inherit.prototype)
  child.prototype.constructor = child
}
function inherit1() {
  this.name = "张三"
  this.sex = "男"
  this.arr = [1,2,3,4]
}
inherit1.prototype.seeName = function () {
  console.log(this.name) 
}

function inherit2() {
  inherit1.call(this)
  this.age = 18
} 
clone(inherit1, inherit2)
inherit2.prototype.seeAge = function () {
  console.log(this.age)
}
let new1 = new inherit2()
new1.arr.push(5)
let new2 = new inherit2()
new2.arr.push(6)
console.log(new1.arr) // [1,2,3,4,5]
console.log(new2.arr) // [1,2,3,4,6]
new1.seeAge()
new1.seeName()

通过这段代码可以看出来,这种寄生组合式继承方式,基本可以解决前几种继承方式的缺点,较好地实现了继承想要的结果,同时也减少了构造次数,减少了性能的开销,我们来看一下上面这一段代码的执行结果。

ES6的extends关键字实现逻辑

ES6的extends关键字实现逻辑
我们可以利用ES6里的extends 的语法糖,使用关键词很容易直接实现JavaScript的继承,但是如果想深入了解extends 语法糖是怎么实现的,就得深入研究extends的底层逻辑(就是使用寄生式组合继承实现的)

    class animal {
      constructor (name) {
        this.name = name
      }
      getName(){
        return this.name
      }
    }

    class dog extends animal {
      constructor(name,age){
        super(name)
        this.age = age
      }
    }

    let example = new dog('狗',20)
    console.log(example.getName())
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
JavaScript有多继承方式,以下是几常见方式: 1. 原型链继承:通过将父类实例赋值给子类的原型来实现继承。 ``` function Parent() { this.name = 'Parent'; } Parent.prototype.sayName = function() { console.log(this.name); } function Child() {} Child.prototype = new Parent(); var child = new Child(); child.sayName(); // 输出 Parent ``` 2. 构造函数继承:通过在子类构造函数中调用父类构造函数来实现继承。 ``` function Parent() { this.name = 'Parent'; } Parent.prototype.sayName = function() { console.log(this.name); } function Child() { Parent.call(this); } var child = new Child(); child.sayName(); // 报错,因为子类没有继承父类的原型方法 ``` 3. 组合继承:结合原型链继承和构造函数继承方式。 ``` function Parent() { this.name = 'Parent'; } Parent.prototype.sayName = function() { console.log(this.name); } function Child() { Parent.call(this); } Child.prototype = new Parent(); Child.prototype.constructor = Child; var child = new Child(); child.sayName(); // 输出 Parent ``` 4. 寄生组合继承:对组合继承进行优化,避免了在子类原型上创建不必要的父类实例。 ``` function Parent() { this.name = 'Parent'; } Parent.prototype.sayName = function() { console.log(this.name); } function Child() { Parent.call(this); } Child.prototype = Object.create(Parent.prototype); Child.prototype.constructor = Child; var child = new Child(); child.sayName(); // 输出 Parent ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

兢兢业业的码农

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值