原型链继承 将父类的实例作为子类的原型 优点 父类方法可以复用 缺点 父类的引用属性会被所有子类共享 子类创建实例不能向父类传递参数 构造函数继承 将父类构造函数内容复制给子类构造函数 优点 可以在 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 知识

暂无评论