当前浏览器过旧,页面样式无法完整呈现。请使用 Chrome、Edge、Firefox 或 Safari 访问。
全部文章
理解 js 的继承:原型链继承、构造函数继承、组合继承、原型继承、寄生继承、寄生组合继承和 ES6 class extend

理解 js 的继承:原型链继承、构造函数继承、组合继承、原型继承、寄生继承、寄生组合继承和 ES6 class extend

原型链继承 将父类的实例作为子类的原型 优点 父类方法可以复用 缺点 父类的引用属性会被所有子类共享 子类创建实例不能向父类传递参数 构造函数继承 将父类构造函数内容复制给子类构造函数 优点 可以在 Child 中向 Parent 传参 类的引用属性不会被共享 缺点 都在构造函数中定义,每次创建实例都会初始化方法或属性 父类方法不能复用 组合继承 构造函数继承与原型链继承结合 优点 父类的方法可以…

原型链继承

将父类的实例作为子类的原型

function Parent() {
  this.name = ['koa', 'express'];
}
Parent.prototype.getName = function() {
  console.log(this.name);
};
function Child() {}
Child.prototype = new Parent();
var child1 = new Child();
child1.name.push('egg');
child1.getName();
var child2 = new Child();
child2.getName();
// 输出
// ["koa", "express", "egg"]
// ["koa", "express", "egg"]
  • 优点
  1. 父类方法可以复用
  • 缺点
  1. 父类的引用属性会被所有子类共享
  2. 子类创建实例不能向父类传递参数

构造函数继承

将父类构造函数内容复制给子类构造函数

function Parent(name) {
  this.name = ['koa'];
  if (name) {
    this.name.push(name);
  }
  this.getName = function() {
    console.log(this.name);
  };
}

function Child(name) {
  Parent.call(this, name);
}
var child1 = new Child();
child1.name.push('express');
child1.getName();
var child2 = new Child();
child2.getName();
var child3 = new Child('egg');
child3.getName();
// 输出
// ["koa", "express"]
// ["koa"]
//  ["koa", "egg"]
  • 优点
  1. 可以在 Child 中向 Parent 传参
  2. 类的引用属性不会被共享
  • 缺点
  1. 都在构造函数中定义,每次创建实例都会初始化方法或属性
  2. 父类方法不能复用

组合继承

构造函数继承与原型链继承结合

function Parent(name) {
  this.name = name;
  this.age = [18, 16, 23];
}
Parent.prototype.getData = function() {
  console.log(this.name, this.age, this.sex);
};

function Child(name, sex) {
  Parent.call(this, name);
  this.sex = sex;
}

Child.prototype = new Parent();
Child.prototype.constructor = Child;

var child = new Child('koa', 1);
child.age.push(26);
child.getData();

var child2 = new Child('express', 0);
child2.getData();
// 输出
// koa  [18, 16, 23, 26] 1
// express  [18, 16, 23] 0
  • 优点
  1. 父类的方法可以复用
  2. 可以在 Child 中向 Parent 传参
  3. 类的引用属性不会被共享
  • 缺点
  1. 调用了两次父类的构造函数,第一次是 new Parent,第二次是 call 的时候,会覆盖子类的同名参数

原型继承

对参数对象的一种浅复制

var objCopy = {};
objCopy.create = function(o) {
  function C() {}
  C.prototype = o;
  return new C();
};
var person = {
  age: 18,
  sex: [1],
  getData: function() {
    console.log(this.age, this.sex);
  }
};

var person1 = objCopy.create(person);
var person2 = objCopy.create(person);

person1.age = 24;
person1.sex.push(0);
person1.getData();
person2.getData();
// 输出
// 24 [1,0]
// 24 [1,0]
  • 优点
  1. 父类方法可复用
  • 缺点
  1. 父类的引用属性会被所有子类共享
  2. 子类创建实例不能向父类传递参数
  • 原型继承的另一种方法
// Sub是子类,Sup是超类
function _extend(Sub, Sup) {
  // 定义一个空函数
  var F = function() {};
  // 空函数原型为超类原型
  F.prototype = Sup.prototype;
  // 实例化空函数,把超类原型引用传递给子类原型
  Sub.prototype = new F();
  // 重置子类原型构造函数为子类本身
  Sub.prototype.constructor = Sub;
  // 在子类保存超类原型,避免子类与超类耦合
  Sub.sup = Sup.prototype;
  // 检测超类原型的构造函数是否为原型本身,如果是原型本身,重置超类原型构造函数为超类本身
  if (Sup.prototype.constructor === Object.prototype.constructor) {
    Sup.prototype.constructor = Sup;
  }
}

寄生继承

使用原型式继承一个目标对象的浅复制,增强这个浅复制的能力

var objCopy = {};
objCopy.create = function(o) {
  function C() {}
  C.prototype = o;
  return new C();
};

function clone(o) {
  var clone = Object.create(o);
  clone.getData = function() {
    console.log(this.age);
  };
  return clone;
}

var person = {
  age: 16
};
var child1 = clone(person);
child1.getData();
// 输出
// 16

寄生组合继承

创建子类实例,然后对其增强

var objCopy = {};
objCopy.create = function(o) {
  function C() {}
  C.prototype = o;
  return new C();
};

function inherit(child, parent) {
  // 创建了父类原型的浅复制
  var prototype = Object.create(parent.prototype);
  // 修正了原型的构造函数
  prototype.constructor = child;
  // 将子类原型替换为prototype
  child.prototype = prototype;
}

function Parent(name) {
  this.name = name;
}
Parent.prototype.getName = function() {
  console.log(this.name);
};

function Child(name, age) {
  Parent.call(this, name);
  this.age = age;
}
inherit(Child, Parent);

Child.prototype.getAge = function() {
  console.log(this.age);
};
var child = new Child('he', 23);
child.getName();
child.getAge();
// 输出
// he
// 23
  • 优点
  1. 对父类原型的复制,不包括父类的构造函数,没有两次调用父类的构造函数
  2. 父类的方法可以复用
  3. 可以在 Child 中向 Parent 传参
  4. 类的引用属性不会被共享

ES6 class extends

先把父类实例的属性和方法加到 this 上,然后再用子类的构造函数修改 this,如果要继承父类的属性和方法,就要在子类 constructor()中调用 super()

new 操作符做了什么

class A {
  constructor() {
    this.data = 'A';
  }
  getData() {
    console.log(this.data);
  }
}
class B extends A {
  constructor() {
    super();
    this.data = 'B';
  }
}
var f = new A();
var c = new B();

extend 的实现

__proto__是什么

class A {}
class B {}
// 设置一个指定对象的原型到另一个对象
Object.setPrototypeOf = function(child, parent) {
  child.__proto__ = parent;
  return child;
};
// B的实例继承A的实例
Object.setPrototypeOf(B.prototype, A.prototype);
// B继承A的静态属性
Object.setPrototypeOf(B, A);

class 做了什么

function Point(x, y) {
  this.x = x;
  this.y = y;
}
Point.prototype.toString = function() {};
var p = new Point(1, 2);

// 等价于===>

class Point {
  constuctor(x, y) {
    this.x = x;
    this.y = y;
  }
  toString() {}
}

ES6 的 class 继承与 ES5 的 prototype 的区别

  1. extends 是 ES5 继承的语法糖,不是新方法
  2. ES6 的继承是基于父类实例的
  3. ES5 是使用构造函数赋值,没有__proto__指向,ES6 的子类构造函数的原型的__proto__是指向父类的构造函数的

参考

js 继承、构造函数继承、原型链继承、组合继承、组合继承优化、寄生组合继承

JavaScript 高级程序设计

原型污染:最新:Lodash 严重安全漏洞背后你不得不知道的 JavaScript 知识

评论

在这里阅读读者对这篇文章的讨论。

暂无评论