[回炉计划]-JS七大继承实现

论坛 期权论坛 期权     
前端指南   2019-6-9 21:29   2052   0
[h1]js 有没有类的概念,js 的继承是怎样的?[/h1]
  1. javascript
复制代码
本身不提供一个
  1. class
复制代码
实现,(即使在
  1. es6
复制代码
中引入了
  1. class
复制代码
关键字,但那只是语法糖,
  1. javascript
复制代码
仍然是基于原型的),
  1. class
复制代码
本质上是一个函数。
  1. class Person {}Person instanceof Function; //true
复制代码
当谈到继承时,JavaScript 只有一种结构:对象。每个实例对象(
  1. object
复制代码
)都有一个私有属性(称之为
  1. __proto__
复制代码
)指向它的构造函数的原型对象(
  1. prototype
复制代码
)。该原型对象也有一个自己的原型对象(
  1. __proto__
复制代码
) ,层层向上直到一个对象的原型对象为
  1. null
复制代码
。根据定义,
  1. null
复制代码
没有原型,并作为这个原型链中的最后一个环节。继承与原型链-MDN
[h1]几种继承方式[/h1]先定义一个父类
  1. function Person(name) {  this.name = name || "小明";  this.color = ["blue", "yellow"];  this.sleep = function() {    console.log(this.name + "正在睡觉");  };}Person.prototype.eat = function(food) {  console.log(this.name + "正在吃:" + food);};
复制代码
[h2][/h2][h2]1 原型链继承[/h2]原型链实现继承的思想是利用原型让一个引用类型继承另一个引用类型的属性和方法。
  1. function Man() {}Man.prototype = new Person(); //实现了原型链继承let xiaoLi = new Man();Person.prototype.happy = function() {  console.log(this.name + "好()嗨哦");};xiaoLi.sleep(); // '小明正在睡觉'xiaoLi.happy(); //小明好()嗨哦Man.prototype.name = "小李";xiaoli.sleep(); // '小李正在睡觉'xiaoLi.eat("苹果"); // '小李正在吃'
复制代码
原型链继承实现简单,并且父类新增的实例和属性子类都能访问到
缺点:
  • 可以在子类中增加实例属性,如果要新增加原型属性和方法需要在 new 父类构造函数的后面
  • 无法实现多继承
  • 创建子类实例时不能向父类的构造函数传参数
    1. let xiaoLi = new Man('小李')
    复制代码
    是不行的,必须是通过
    1. Man.prototype.name = '小李';
    复制代码
    实现
[h2]2 借助构造函数(经典继承)[/h2]
  1. function Man(name) {  Person.call(this);  this.name = name || "小亮";}let xiaoLi = new Man("小李");xiaoLi.sleep(); //小李在睡觉xiaoLi.eat("苹果"); //VM525:1 Uncaught TypeError: xiaoLi.eat is not a function
复制代码
优点:
  • 解决了子类构造函数向父类构造函数中传递参数
  • 可以实现多继承(call 或者 apply 多个父类)
缺点:
  • 方法都在构造函数中定义,无法复用
  • 不能继承原型属性/方法,只能继承父类的实例属性和方法
[h2]3 组合继承[/h2]也叫做伪经典继承,是利用原型链和借用构造函数的技术组合在一起从而发挥两者之长的一种继承模式。思路是使用原型链实现对原型属性和方法的继承,而通过构造函数来实现对实现对实例属性的继承,这样。既通过在原型上定义方法实现复用,又能保证每个实例都有它自己的属性
  1. function Man(name) {  // 继承属性  Person.call(this);  this.name = name || "小亮";}// 继承方法Man.prototype = new Person();Man.prototype.constructor = Person;Man.prototype.sayHello = function() {  console.log(this.name + "hello");};let xiaoLi = new Man("小李");xiaoLi.sleep(); //小李在睡觉xiaoLi.sayHello(); //小李helloxiaoLi.color.push("black");console.log(xiaoLi.color); //["blue", "yellow","black"];let xiaoqiang = new Man("小强");iaoqiang.sleep(); //小强在睡觉iaoqiang.sayHello(); //小强helloconsole.log(iaoqiang.color); //["blue", "yellow"];
复制代码
优点:融合原型链继承和构造函数的优点,是 JavaScript 中最常用的继承模式。而且
  1. instanceof
复制代码
  1. isPrototypeOf()
复制代码
能够用于识别基于组合继承创建的对象
  1. xiaoLi instanceof Person; //true
复制代码
[h2]4 原型式继承[/h2]基于已有的对象创建新对象,同时还不必因此创建自定义类型
  1. function createObj(o) {  function F() {}  F.prototype = o;  return new F();}
复制代码
上面这段函数就是 es5
  1. Object.create
复制代码
的模拟实现,将传入的对象作为创建对象的原型
缺点是:包含引用类型的属性值始终都会共享相应的值,这点跟原型链继承一样。我们来看一个例子
  1. let person = {  name: "小蔡",  like: ["唱", "跳", "rap"]};let xiaoLi = createObj(person);let xiaoWang = createObj(person);xiaoLi.name = "小李";console.log(xiaoWang.name); //小蔡xiaoLi.like.push("篮球");console.log(xiaoWang.like); //["唱", "跳", "rap","篮球"]
复制代码
注意: 修改
  1. xiaoLi.name
复制代码
的值,x
  1. iaoWang.name
复制代码
的值并未发生改变,并不是因为
  1. xiaoLi
复制代码
  1. xiaoWang有
复制代码
独立的
  1. name
复制代码
值,而是因为
  1. xiaoLi.name = '小李'
复制代码
,给
  1. xiaoLi
复制代码
添加了
  1. name
复制代码
值,并非修改了原型上的
  1. name
复制代码
值。
[h2]5 寄生式继承[/h2]封装一个仅用于封装继承过程的函数,该函数在内部以某种方式来增强对象,最后返回这个对象
  1. function createObj(obj) {  var clone = Object.create(obj);  clone.sayHi = function() {    console.log("hi");  };  return clone;}let person = {  name: "小蔡",  like: ["唱", "跳", "rap"]};let xiaoLi = createObj(person);xiaoLi.sayHi(); //hi
复制代码
缺点:跟借用构造函数模式一样,每次创建对象都会创建一遍方法。
[h2]6. 寄生组合式继承[/h2]组合继承的最大缺点就是他会调用两次父类的构造函数
  1. function Man(name) {  // 继承属性  Person.call(this);  this.name = name || "小亮";}// 继承方法Man.prototype = new Person();Man.prototype.constructor = Person;Man.prototype.sayHello = function() {  console.log(this.name + "hello");};let xiaoLi = new Man("小李");
复制代码
第一次调用
  1. Man.prototype = new Person();
复制代码
第二次调用
  1. let xiaoLi = new Man("小李");
复制代码
主要是因为
  1. Parent.call(this, name); //调用夫类构造函数
复制代码
如果我们不使用
  1. Man.prototype = new Person();
复制代码
,而是间接的让
  1. Man.prototype
复制代码
访问到
  1. Person.prototype
复制代码
,就可以避免这种情况
  1. function Person(name) {  this.name = name || "小明";  this.color = ["blue", "yellow"];}Person.prototype.sleep = function() {  console.log(this.name + "正在睡觉");};function Man(name, age) {  Person.call(this, name);  this.age = age;}// 关键的三步var F = function() {};F.prototype = Person.prototype;Man.prototype = new F();var xiaoLi = new Man("小李");
复制代码
最后封装一下
  1. function object(o) {  function F() {}  F.prototype = o;  return new F();}function prototype(child, parent) {  var prototype = object(parent.prototype);  prototype.constructor = child;  child.prototype = prototype;}// 使用:prototype(Child, Parent);
复制代码
简单调用一下
  1. function Person(name) {  this.name = name || "小明";  this.color = ["blue", "yellow"];}Person.prototype.sleep = function() {  console.log(this.name + "正在睡觉");};function Man(name, age) {  Person.call(this, name);  this.age = age;}prototype(Man, Person);let xiaoXia = new Man("小霞", 22);xiaoxia.sleep(); //小霞正在睡觉xiaoxia.age; //22
复制代码
寄生组合继承的高效率在于它只调用了一次父类构造函数,避免在
  1. Person.prototype
复制代码
上面创建不必要的、多余的属性,与此同时,原型链还能保持不变;因此,还能够正常使用
  1. instanceof
复制代码
  1. isPrototypeOf
复制代码
。开发人员普遍认为寄生组合式继承是引用类型最理想的继承范式。
[h2]7 es6 Class 继承[/h2]
  1. class A {}class B extends A {  constructor() {    super();  }}
复制代码
关于 es6 的继承,我们后续再做详细介绍。只需要记住,class 只是语法糖,本质上还是函数。ES6 要求,子类的构造函数必须执行一次 super 函数。在子类的构造函数中,只有调用 super 之后,才可以使用 this 关键字,否则会报错。这是因为子类实例的构建,基于父类实例,只有 super 方法才能调用父类实例。

[h1]总结[/h1]以上就是 Js 继承的所有实现了,如文中有误,欢迎指出
[h1]参考资料[/h1]
  • Class 的继承-阮一峰
  • js 继承的几种方式- 任博
  • JavaScript 深入之继承的多种方式和优缺点
  • javascript 高级程序设计 162-174


觉得写的不错的老哥点个好看吧!
分享到 :
0 人收藏
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

积分:
帖子:
精华:
期权论坛 期权论坛
发布
内容

下载期权论坛手机APP