# 继承

# 原型链继承

# 实现

function Father() {
  this.text = "1";
}
Father.prototype.someFn = function() {
  console.log(1);
};
Father.prototype.someValue = "2";

function Son() {
  this.text1 = "text1";
}
// 函数原型指向构造函数的实例
Son.prototype = new Father();
1
2
3
4
5
6
7
8
9
10
11
12
13

# 优点

简单易操作。

# 缺点

  1. 父类使用 this 声明的属性被所有实例共享。原因是实例化是父类一次性赋值到子类实例的原型上,它会将父类通过 this 声明的属性也赋值到子类原型上。例如在父类中一个数组值,在子类的多个实例中,无论哪一个实例去修改这个数组的值,都会影响到其他子类实例。
  2. 创建子类实例时,无法向父类构造函数传参,不够灵活。

# 借用构造函数(call)

# 实现

function Father(...arr) {
  this.some = "父类属性";
  this.params = arr;
}
Father.prototype.someFn = function() {
  console.log(1);
};
Father.prototype.someValue = "2";
function Son(fatherParams, ...sonParams) {
  // Father的this指向Son的this
  // 使用call调用父类,Father将会立即被执行,并且将父类的Father的this执行Son
  // 的this。实例化子类,this将指向new期间创建的新对象,返回该新对象。
  Father.call(this, ...fatherParams);
  this.text = "子类属性";
  this.sonParams = sonParams;
}
var fatherParams = [];
var sonParams = [];
var sonInstance = new Son(fatherParams, ...sonParams);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

# 优点

  1. 可以向父类传递参数。
  2. 解决父类 this 声明的属性会被实例共享的问题。

# 缺点

  1. 只能继承父类通过 this 声明的属性/方法。不能继承父类 prototype 上的属性/方法。
  2. 父类方法无法复用。每次实例化子类,都要执行父类函数。重新声明父类所定义的方法,无法复用。

# 组合继承(call+new)

原理:通过原型链继承来将 this、prototype 上的属性和方法继承制子类的原型对象上。使用借用构造函数来继承父类通过 this 声明的属性和方法在之子类的实例属性上。

# 实现

function Father(...arr) {
  this.some = "父类属性";
  this.params = arr;
}
Father.prototype.someFn = function() {
  console.log(1);
};
Father.prototype.someValue = "2";
function Son(fatherParams, ...sonParams) {
  // 借用构造函数继承父类this什么的属性和方法到子类实例属性上
  Father.call(this, ...fatherParams);
  this.text = "子类属性";
  this.sonParams = sonParams;
}
// 原型链继承,将`this`和`prototype`声明的属性/方法继承至子类的`prototype`上
Son.prototype = new Father("xxxxx");
var fatherParams = [];
var sonParams = [];
var sonInstance = new Son(fatherParams, ...sonParams);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

# 优点

  1. 解决原型链继承父类 this 声明的属性或者方法被共享的问题。
  2. 解决借用构造函数解决不能继承父类 prototype 对象上的属性/方法问题。

# 缺点

  1. 调用了父类函数两次,造成一定的性能问题。
  2. 因调用两次父类,导出父类通过 this 声明的属性和方法被生成两份的问题。
  3. 原型链上下文丢失,子类和父类通过 prototype 声明的属性和方法都存在与子类 prototype 上。

# 原型式继承

# 实现

function cloneObj(obj) {
  function F() {}
  // 将被继承的对象作为空函数的prototype
  F.prototype = obj;
  // 返回new期间创建的新对象,此对象的原型为被继承的对象,
  // 通过原型链查找可以拿到被继承对象的属性
  return new F();
}
1
2
3
4
5
6
7
8

# 优点

兼容性好,最简单的对象继承。

# 缺点

多少实例共享被继承的属性,存在被篡改的情况,不能传递参数。

# 寄生式继承(继承过程封装)

创建一个仅用于封装继承过程的函数,改函数在内部已某种方式类增强对象,最后返回对象。在原型式继承的基础上进行增强对象。

# 实现

function createAnother(original) {
  var clone = cloneObject(original); // 继承一个对象 返回新函数
  // do something 以某种方式来增强对象
  clone.some = function() {}; // 方法
  clone.obkoro1 = "封装继承过程"; // 属性
  return clone; // 返回这个对象
}
1
2
3
4
5
6
7

# 优点

兼容性好,最简单的对象继承。 缺点

# 缺点

1、多少实例共享被继承的属性,存在被篡改的情况,不能传递参数。

# 寄生组合式继承(call+寄生式封装)

使用借用构造函数来继承父类 this 声明的属性和方法。2、使用寄生式继承来设置父类 prototype 为子类 prototype 的原型来继承父类的属性和方法。

# 实现

function Father(...arr) {
  this.some = "父类属性";
  this.params = arr;
}
Father.prototype.someFn = function() {
  console.log(1);
};
Father.prototype.someValue = "2";
function Son() {
  Father.call(this, "xxxx");
  this.text = "2222";
}
function inhertPro(son, father) {
  // 原型式继承
  var fatherPrototype = Object.create(father.prototype);
  // 设置Son.prototype的原型是Father.prototype
  son.prototype = fatherPrototype;
  // 修正constructor 指向
  // constructor的作用:返回创建实例对象的Object构造函数的引用。
  // 在这里保持constructor指向的一致性
  son.prototype.constructor = son;
}
inhertPro(Son, Father);
var sonInstance = new Son();
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

# 优点

  1. 寄生组合式继承是当前最成熟的继承方法,也是先也常用的继承方法,在大多数 Js 框架中都是用这个作为继承方案。 寄生组合式继承相对组合继承的优点:
  2. 只调用了父类构造函数一次,节约了性能。
  3. 避免生成了不必要的属性。
  4. 使用原型式继承保证了原型链上下文不变,子类的 prototype 只有子类通过 prototype 声明的属性和方法,父类的 prototype 只有父类通过 prototype 声明的属性和方法。

# ES6-extends 继承

# 实现

class Point {}
class ColorPoint extends Point {}
1
2

# 注意

子类必须在 constructor 方法中代用 super 方法,否则新建实例将会报错,这是因为子类自己的 this 对象,必须先通过父类的构造函数完成塑性,得到父类的属性和方法,然后对其加工,加上子类自己的属性和方法。如果不调用 super 方法,子类将得不到 this 对象。如果没有定义 constructor 方法,这个方法会被默认的添加。

# 转换

ES6 继承的原理跟寄生组合式继承是一样的。优缺点也相仿。 把 ES6 的代码装换为 ES5 www.babeljs.cn/repl 转换前:

class Point {}
class ColorPoint extends Point {}
1
2

转换后: 转换的结果核心代码如下:用于子类的 prototype 继承父类的 prototype 方法。

function _inherits(subClass, superClass) {
  if (typeof superClass !== "function" && superClass !== null) {
    throw new TypeError("Super expression must either be null or a function");
  }
  subClass.prototype = Object.create(superClass && superClass.prototype, {
    constructor: {
      value: subClass,
      writable: true,
      configurable: true
    }
  });
  if (superClass) _setPrototypeOf(subClass, superClass);
}
1
2
3
4
5
6
7
8
9
10
11
12
13

# 区别

ES5 的继承实质是先创建子类的实例对象 this,然后将父类的方法添加到 this 上。 ES6 的继承实质是先将父类实例对象的方法和属性加到 this 上面,然后在用子类的构造函数修改 this。

# 绝杀

function Rectangle(length, width) {
  this.l = length;
  this.w = width;
}
Rectangle.prototype.getArea = function() {
  return this.l * this.w;
};
function Square(length) {
  Rectangle.call(this, length, length);
}
Square.prototype = Object.create(Rectangle.prototype, {
  constructor: {
    value: Square
  }
});

var square = new Square(3);
console.log(square.getArea());
console.log(square instanceof Square);
console.log(square instanceof Rectangle);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

yuanxinglian