1.原型链继承

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

  1. function Parent () {
  2. this.names = ['kevin', 'daisy'];
  3. }
  4. function Child () {
  5. }
  6. Child.prototype = new Parent(); // 不同的 Child 实例的 proto 会引用同一 Parent 的实例
  7. var child1 = new Child();
  8. child1.names.push('yayu');
  9. var child2 = new Child();
  10. console.log(child1.names); // ["kevin", "daisy", "yayu"]
  11. console.log(child2.names); // ["kevin", "daisy", "yayu"]
  12. // 两个实例使用的是同一个原型对象。它们的内存空间是共享的,当一个发生变化的时候,
  13. // 另外一个也随之进行了变化,这就是使用原型链继承方式的一个缺点。
  • 实例之间共享引用类型的值
  • 创建子类实例时,没法像向父类的构造函数中传递参数

2.构造函数继承

  1. function Parent () {
  2. this.names = ['kevin', 'daisy'];
  3. }
  4. Parent.prototype.getName = function () {
  5. return this.names;
  6. }
  7. function Child () {
  8. Parent.call(this); // here
  9. }
  10. var child1 = new Child();
  11. child1.names.push('yayu');
  12. console.log(child1.names); // ["kevin", "daisy", "yayu"]
  13. var child2 = new Child();
  14. console.log(child2.names); // ["kevin", "daisy"]
  15. console.log(child1.getName()); // 会报错
  • 它使父类的引用属性不会被共享,优化了第一种继承方式的弊端;
  • 但是随之而来的缺点也比较明显——只能继承父类的实例属性和方法,不能继承原型属性或者方法。

可以在 Child 中向 Parent 传参

  1. function Parent (name) {
  2. this.name = name;
  3. }
  4. function Child (name) {
  5. Parent.call(this, name);
  6. }
  7. var child1 = new Child('kevin');
  8. console.log(child1.name); // kevin
  9. var child2 = new Child('daisy');
  10. console.log(child2.name); // daisy

3.组合继承

  1. function Parent (name) {
  2. this.name = name;
  3. this.colors = ['red', 'blue', 'green'];
  4. }
  5. Parent.prototype.getName = function () {
  6. console.log(this.name)
  7. }
  8. function Child (name, age) {
  9. Parent.call(this, name); // 第二次调用 Parent()
  10. this.age = age;
  11. }
  12. Child.prototype = new Parent(); // // 第一次调用 Parent()
  13. Child.prototype.constructor = Child; //// 手动挂上构造器,指向自己的构造函数
  14. var child1 = new Child('kevin', '18');
  15. child1.colors.push('black');
  16. console.log(child1.getName()); // kevin
  17. console.log(child1.age); // 18
  18. console.log(child1.colors); // ["red", "blue", "green", "black"]
  19. var child2 = new Child('daisy', '20');
  20. console.log(child2.getName()); // daisy
  21. console.log(child2.age); // 20
  22. console.log(child2.colors); // ["red", "blue", "green"]

有个问题:构造函数多调用了一次

4.原型式继承

ES5 里面的 Object.create 方法,这个方法接收两个参数:一是用作新对象原型的对象、二是为新对象定义额外属性的对象(可选参数)。

  1. let parent4 = {
  2. name: "parent4",
  3. friends: ["p1", "p2", "p3"],
  4. getName: function() {
  5. return this.name;
  6. }
  7. };
  8. let person4 = Object.create(parent4);
  9. person4.name = "tom";
  10. person4.friends.push("jerry");
  11. let person5 = Object.create(parent4);
  12. person5.friends.push("lucy");
  13. console.log(person4.name); //tom
  14. console.log(person4.name === person4.getName()); //true
  15. console.log(person5.name); //parent4
  16. console.log(person4.friends); //p1,p2,p3,jerry,lucy
  17. console.log(person5.friends); //p1,p2,p3,jerry,lucy
  • 浅拷贝,多个实例的引用类型属性指向相同的内存

    5.寄生式继承

    使用原型式继承可以获得一份目标对象的浅拷贝,然后利用这个浅拷贝的能力再进行增强,添加一些方法,这样的继承方式就叫作寄生式继承

    1. let parent5 = {
    2. name: "parent5",
    3. friends: ["p1", "p2", "p3"],
    4. getName: function() {
    5. return this.name;
    6. }
    7. };
    8. function clone(original) {
    9. let clone = Object.create(original);
    10. clone.getFriends = function() {
    11. return this.friends;
    12. };
    13. return clone;
    14. }
    15. let person5 = clone(parent5);
    16. console.log(person5.getName());
    17. console.log(person5.getFriends());

    6.寄生组合式继承

    组合继承最大的缺点是会调用两次父构造函数。
    一次是设置子类型实例的原型的时候:

    1. Child.prototype = new Parent();

    一次在创建子类型实例的时候:

    1. var child1 = new Child('kevin', '18');
    2. // 回想下 new 的模拟实现,其实在这句中,我们会执行:
    3. // Parent.call(this, name);
    4. // 在这里,我们又会调用了一次 Parent 构造函数。

如果我们不使用 Child.prototype = new Parent() ,而是间接让Child.prototype 访问到Parent.prototype 呢?

o = new Constructor(); 相当于 o = Object.create(Constructor.prototype);

  1. function clone (parent, child) {
  2. // 这里改用 Object.create 就可以减少组合继承中多进行一次构造的过程
  3. child.prototype = Object.create(parent.prototype);
  4. child.prototype.constructor = child;
  5. }
  6. function Parent() {
  7. this.name = 'parent';
  8. this.play = [1, 2, 3];
  9. }
  10. Parent.prototype.getName = function () {
  11. return this.name;
  12. }
  13. function Child6() {
  14. Parent.call(this);
  15. this.friends = 'child';
  16. }
  17. clone(Parent6, Child6);
  18. Child.prototype.getFriends = function () {
  19. return this.friends;
  20. }
  21. let person = new Child();
  22. console.log(person);
  23. console.log(person.getName());
  24. console.log(person.getFriends());

ES6的extend

  1. class Person {
  2. constructor(name) {
  3. this.name = name
  4. }
  5. // 原型方法
  6. // 即 Person.prototype.getName = function() { }
  7. // 下面可以简写为 getName() {...}
  8. getName = function () {
  9. console.log('Person:', this.name)
  10. }
  11. }
  12. class Gamer extends Person {
  13. constructor(name, age) {
  14. // 子类中存在构造函数,则需要在使用“this”之前首先调用 super()。
  15. super(name)
  16. this.age = age
  17. }
  18. }
  19. const asuna = new Gamer('Asuna', 20)
  20. asuna.getName() // 成功访问到父类的方法

利用 babel 这个编译工具,将 ES6 的代码编译成 ES5

https://github.com/logan70/Blog/issues/22

  1. function _possibleConstructorReturn (self, call) {
  2. // ...
  3. return call && (typeof call === 'object' || typeof call === 'function') ? call : self;
  4. }
  5. function _inherits (subClass, superClass) {
  6. // 这里可以看到
  7. subClass.prototype = Object.create(superClass && superClass.prototype, {
  8. constructor: {
  9. value: subClass,
  10. enumerable: false,
  11. writable: true,
  12. configurable: true
  13. }
  14. });
  15. if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass;
  16. }
  17. var Parent = function Parent () {
  18. // 验证是否是 Parent 构造出来的 this
  19. _classCallCheck(this, Parent);
  20. };
  21. var Child = (function (_Parent) {
  22. _inherits(Child, _Parent);
  23. function Child () {
  24. _classCallCheck(this, Child);
  25. return _possibleConstructorReturn(this, (Child.__proto__ || Object.getPrototypeOf(Child)).apply(this, arguments));
  26. }
  27. return Child;
  28. }(Parent));

image.png

JavaScript 核心原理精讲