js中的继承

Posted fraudulentartists

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了js中的继承相关的知识,希望对你有一定的参考价值。

在es6出来之后,js多了一个关键词:class。其仿照java的类进行了一系列的封装,其中class的继承就只需要一个关键词:extend 就ok了 。

但是在es6之前,js中没有类(js 中的 class 也不是类,只是仿造的伪类,其实际还是构造函数),所以构造函数的继承就是一个问题,所以就出现了形形色色的继承方式。

作为一个逼乎er(滑稽),沉浸逼乎这么多年也看到不少大佬的教程,我这颗爱学习的心(滑稽)促使我把这些教程给copy下来以备后用。

从事前端工作加上实习也有快一年了,反正我是没有遇见过需要继承的时候,唯一用到继承的还是react中的被封装好的组件继承。

但是作为一个爱学习的人,我一直谨记古人说的“以史为镜,可以明得失”,所以我就把这些用不到的继承方式copy过来,说不定哪天面试用到了呢(这要是遇见了,我能掐死面试官,什么鬼题)。

 

根据大佬们的各种文章,各种博客,各种。。。反正我复制粘贴一把梭,把东西先拿过来,然后再尘封。然而,现在是时候出世了。

 

js中的构造函数的继承我看了一下,大概分为6种(我是看见了这6种,其他的不清楚):

 

1. 原型链继承:

原型链继承核心是将父类的实例作为子类的原型

看一段代码:

function Animal(){}
function Cat(){}
Cat.prototype = new Animal()
Cat.prototype.name = ‘cat‘
Cat.prototype.constructor = Cat

 

上面先定义两个构造函数 Animal 和 Cat,然后将 父类Animal 的实例赋值给 子类Cat 的原型,这样 子类Cat 的实例就有了 父类Animal 的所有属性。

因为 Cat.prototype.__proto__ = Animal.prototype

定义一个 Cat 的实例 :cat

var cat = new Cat()

则 cat.__proto__ = Cat.prototype

这样 cat 在获取属性的时候可以沿着 __proto__ 找到 Animal.prototyoe

 

这个继承方式的优点在于三点:

  1)子类的实例同时也是父类的实例

  2)父类新增原型属性和原型方法,子类都能访问到

  3)简单,最简单的继承方式

但是这个继承方式自然也有缺点,比优点还多一点:

  1)想要给子类添加原型属性和原型方法,必须在 Cat.prototype = new Animal() 这句代码之后,因为这句代码会把子类的原型重新赋值

  2)无法实现多继承,也就是一个子类只能继承一个父类,不能继承多个父类

  3)父类无法进行传参操作,因为子类的实例无法通过传参改变父类的属性

  4)来自原型对象的引用属性是所有实例共享的(因为子类实例也是父类的实例)

针对第三、四点看段代码:

function A(name){
  this.name = name || ‘Tom‘  
  this.arr = []
}
function B(name){}

B.prototype = new A()

var a = new B(‘a‘)
var b = new B(‘b‘)

console.log(‘a.name‘,a.name)
console.log(‘b.name‘,b.name)
console.log(‘a.arr‘,a.arr)
console.log(‘b.arr‘,b.arr)

a.name = ‘a‘
a.arr.push(‘This is a‘)

console.log(‘a.name‘,a.name)
console.log(‘b.name‘,b.name)
console.log(‘a.arr‘,a.arr)
console.log(‘b.arr‘,b.arr)

上答案:

技术分享图片

从上面可以看到第一次打印出来的 name 都是 Tom,这就是父类无法传参的问题,因为父类的实例是子类的原型,子类的实例无法改变子类的原型。而在下面我给 a.arr.push(‘This is a‘),但是b.arr也同步添加了,这是因为arr位于B的原型上,a和b 的arr是同一个arr,也就是来自原型对象的引用属性是所有实例共享的。

 

2.构造继承:

构造继承的核心是使用父类的构造函数来增强子类实例,等于是复制父类的实例属性给子类(没用到原型)

上代码:

function Animal(name){
  this.name = name || ‘Tom‘  
}

function Cat(name,age){
  Animal.call(this,name)
  this.age = age
}

 

构造继承的优点有三点:

  1)解决了原型链继承中子类实例共享父类引用属性的问题

  2)创建子类实例时,可以向父类传递参数

  3)可以实现多继承(call多个父类对象)

自然也有三个缺点:

  1)实例不是父类的实例,只是子类的实例

  2)只能继承父类的实例属性和方法,不能继承父类的原型属性和原型方法

  3)无法实现函数复用,每个子类都有父类实例函数的副本,影响性能

针对第二点看一段代码:

function Animal(name){
  this.name = name || ‘Tom‘  
}
Animal.prototype.sex = ‘male‘

function Cat(name,age){
  Animal.call(this,name)
  this.age = age
}

var cat  = new Cat(‘cat‘,10)

console.log(cat.name,cat.age,cat.sex)

看结果:

技术分享图片

从cat.name可以看出来是可以给父类传参的,但是cat.sex是undefiend,但是父类的实力属性中是有sex属性的,说明子类的实例无法继承到父类的原型属性

 

3.实例继承:

实例继承的核心是为父类实例添加新特性,作为子类的实例返回

上代码:

function Animal(name){
  this.name = name  
}

function Cat(name,age){
  var result = new Animal(name)
  result.age = age
  return result        
}

 

实例继承的优点:

  1)不限制调用方式,无论是 new 函数 还是直接调用函数都会返回同样的结果

缺点:

  1)实例是父类的实例,不是子类的实例

  2)不支持多继承

4.拷贝继承:

拷贝继承的核心是将父类实例的属性拷贝到子类的原型上

上代码:

function Animal(name){
  this.name = name  
  this.sex = ‘male‘
}

function Cat(name,age){
  var animal = new Animal(name)
  for(var attr in animal){
    Cat.prototype[attr] = animal[attr]
  }
  Cat.prototype.age = age
}

 

拷贝继承的优点是:

  1)支持多继承

缺点:

  1)效率较低,内存占用高(因为要遍历复制父类实例的属性)

  2)无法获取父类实例不可枚举的方法

5.组合继承:

组合继承的核心是通过调用父类构造,继承父类的属性并保留传参的优点,然后通过将父类实例作为子类原型,实现函数复用

上代码:

function Animal(name){
  this.name = name || ‘Tom‘
}

function Cat(name,age){
  Animal.call(this,name)
  this.age = age  
}
Cat.prototype = new Animal()
Cat.prototype.constructor = Cat

 

优点:

  1)弥补了构造继承的缺陷,可以继承原型实例属性/方法和原型属性/方法

  2)子类实例同时也是父类的实例

  3)不存在父类引用属性共享的问题

  4)可以给父类传参

  5)函数可以复用

缺点:

  1)调用了两次父类构造函数,生成了两份实例(子类实例将子类原型上的重复属性覆盖了,可以忽略不计)

 

6.寄生组合继承:

寄生组合继承的核心是通过寄生方式,砍掉父类的实例属性,这样,在调用两次父类的构造的时候,就不会初始化两次实例方法/属性,避免的组合继承的缺点

直接上代码:

function Animal(name){
  this.name = name || ‘Tom‘
}

function Cat(name, age){
  Animal.call(this, name)
  this.age = age  
}

(function(){
  // 创建一个没有实例方法的
  var Super = function(){};
  Super.prototype = Animal.prototype;
  //将实例作为子类的原型
  Cat.prototype = new Super();
})();

Cat.prototype.constructor = Cat;

 

优点:

  1)堪称完美

缺点:

  1)实现优点复杂(强行添加缺点)

 

ok,以上就是我总(fu)结(zhi)过来的关于js构造函数继承的六个方法,我强力推荐最后一个,虽然几乎不会用到,但是了解一下人家大佬的脑回路也是不错的。

 

以上是关于js中的继承的主要内容,如果未能解决你的问题,请参考以下文章

VSCode自定义代码片段9——JS中的面向对象编程

Chrome-Devtools代码片段中的多个JS库

Node.js JavaScript 片段中的跳过代码

XSS:如何从 C# 中的字符串中删除 JS 片段?

JavaScript笔试题(js高级代码片段)

JS常用代码片段-127个常用罗列-值得收藏