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

栏目: JavaScript · 发布时间: 6年前

内容简介:当谈到继承时,JavaScript 只有一种结构:对象。每个实例对象(先定义一个父类

javascript 本身不提供一个 class 实现,(即使在 es6 中引入了 class 关键字,但那只是语法糖, javascript 仍然是基于原型的), class 本质上是一个函数。

class Person {}
Person instanceof Function; //true
复制代码

当谈到继承时,JavaScript 只有一种结构:对象。每个实例对象( object )都有一个私有属性(称之为 __proto__ )指向它的构造函数的原型对象( prototype )。该原型对象也有一个自己的原型对象( __proto__ ) ,层层向上直到一个对象的原型对象为 null 。根据定义, null 没有原型,并作为这个原型链中的最后一个环节。继承与原型链-MDN

几种继承方式

先定义一个父类

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);
};
复制代码

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("苹果"); // '小李正在吃'
复制代码

原型链继承实现简单,并且父类新增的实例和属性子类都能访问到

缺点:

  1. 可以在子类中增加实例属性,如果要新增加原型属性和方法需要在 new 父类构造函数的后面
  2. 无法实现多继承
  3. 创建子类实例时不能向父类的构造函数传参数 let xiaoLi = new Man('小李') 是不行的,必须是通过 Man.prototype.name = '小李'; 实现

2 借助构造函数(经典继承)

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
复制代码

优点:

  1. 解决了子类构造函数向父类构造函数中传递参数
  2. 可以实现多继承(call 或者 apply 多个父类)

缺点:

  1. 方法都在构造函数中定义,无法复用
  2. 不能继承原型属性/方法,只能继承父类的实例属性和方法

3 组合继承

也叫做伪经典继承,是利用原型链和借用构造函数的技术组合在一起从而发挥两者之长的一种继承模式。思路是使用原型链实现对原型属性和方法的继承,而通过构造函数来实现对实现对实例属性的继承,这样。既通过在原型上定义方法实现复用,又能保证每个实例都有它自己的属性

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(); //小李hello
xiaoLi.color.push("black");
console.log(xiaoLi.color); //["blue", "yellow","black"];

let xiaoqiang = new Man("小强");
iaoqiang.sleep(); //小强在睡觉
iaoqiang.sayHello(); //小强hello
console.log(iaoqiang.color); //["blue", "yellow"];
复制代码

优点:融合原型链继承和构造函数的优点,是 JavaScript 中最常用的继承模式。而且 instanceofisPrototypeOf() 能够用于识别基于组合继承创建的对象

xiaoLi instanceof Person; //true
复制代码

4 原型式继承

基于已有的对象创建新对象,同时还不必因此创建自定义类型

function createObj(o) {
  function F() {}
  F.prototype = o;
  return new F();
}
复制代码

上面这段函数就是 es5 Object.create 的模拟实现,将传入的对象作为创建对象的原型

缺点是:包含引用类型的属性值始终都会共享相应的值,这点跟原型链继承一样。 我们来看一个例子

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","篮球"]
复制代码

注意: 修改 xiaoLi.name 的值,x iaoWang.name 的值并未发生改变,并不是因为 xiaoLixiaoWang有 独立的 name 值,而是因为 xiaoLi.name = '小李' ,给 xiaoLi 添加了 name 值,并非修改了原型上的 name 值。

5 寄生式继承

封装一个仅用于封装继承过程的函数,该函数在内部以某种方式来增强对象,最后返回这个对象

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
复制代码

缺点:跟借用构造函数模式一样,每次创建对象都会创建一遍方法。

6. 寄生组合式继承

组合继承的最大缺点就是他会调用两次父类的构造函数

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("小李");
复制代码

第一次调用

Man.prototype = new Person();
复制代码

第二次调用

let xiaoLi = new Man("小李");
复制代码

主要是因为

Parent.call(this, name); //调用夫类构造函数
复制代码

如果我们不使用 Man.prototype = new Person(); ,而是间接的让 Man.prototype 访问到 Person.prototype ,就可以避免这种情况

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("小李");
复制代码

最后封装一下

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);
复制代码

简单调用一下

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
复制代码

寄生组合继承的高效率在于它只调用了一次父类构造函数,避免在 Person.prototype 上面创建不必要的、多余的属性,与此同时,原型链还能保持不变;因此,还能够正常使用 instanceofisPrototypeOf 。开发人员普遍认为寄生组合式继承是引用类型最理想的继承范式。

es6 Class 继承

class A {}

class B extends A {
  constructor() {
    super();
  }
}
复制代码

关于 es6 的继承,我们后续再做详细介绍。只需要记住,class 只是语法糖,本质上还是函数。ES6 要求,子类的构造函数必须执行一次 super 函数。在子类的构造函数中,只有调用 super 之后,才可以使用 this 关键字,否则会报错。这是因为子类实例的构建,基于父类实例,只有 super 方法才能调用父类实例。


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Design for Hackers

Design for Hackers

David Kadavy / Wiley / 2011-10-18 / USD 39.99

Discover the techniques behind beautiful design?by deconstructing designs to understand them The term ?hacker? has been redefined to consist of anyone who has an insatiable curiosity as to how thin......一起来看看 《Design for Hackers》 这本书的介绍吧!

在线进制转换器
在线进制转换器

各进制数互转换器

随机密码生成器
随机密码生成器

多种字符组合密码

UNIX 时间戳转换
UNIX 时间戳转换

UNIX 时间戳转换