【Javascript高级知识】深刻理解原型链之继承的多种实现方式和优缺点

Javascript 之继承的多种实现方式和优缺点

一、 原型链继承

    function Parent() {
      this.name = 'zhangsan';
      this.children = ['A', 'B', 'C'];
    }
    Parent.prototype.getName = function() {
      console.log(this.name);
    }
    
    function Child() {
      
    }
    Child.prototype = new Parent();
    var child = new Child();
    console.log(child.getName());

主要问题:
1. 引用类型的属性被所有实例共享(this.children.push(‘name’))
2. 在创建Child的实例的时候,不能向Parent传参

二、借用构造函数(经典继承)

    function Parent(age) {
      this.names = ['zhangsan', 'lisi'];
      this.age = age;
      
      this.getName = function() {
        return this.names;
      }
      
      this.getAge = function() {
        return this.age;
      }
    }
    
    function Child(age) {
      Parent.call(this, age);
    }
    var child = new Child(18);
    child.names.push('haha');
    console.log(child.names);
    
    var child2 = new Child(20);
    child2.names.push('yaya');
    console.log(child2.names);

优点:
1. 避免了引用类型的属性被所有实例共享
2. 可以直接在Child中向Parent传参
缺点:
方法都在构造函数中定义了,每次创建实例都会创建一遍方法

三、组合继承(原型链继承和经典继承双剑合璧)

    /**
    * 父类构造函数
    * @param name
    * @constructor
    */
    function Parent(name) {
      this.name = name;
      this.colors = ['red', 'green', 'blue'];
    }
    
    Parent.prototype.getName = function() {
      console.log(this.name);
    }
    
    // child
    function Child(name, age) {
      Parent.call(this, name);
      this.age = age;
    }
    
    Child.prototype = new Parent();
    // 校正child的构造函数
    Child.prototype.constructor = Child;
    
    // 创建实例
    var child1 = new Child('zhangsan', 18);
    child1.colors.push('orange');
    console.log(child1.name, child1.age, child1.colors);    // zhangsan 18 (4) ["red", "green", "blue", "orange"]
    
    var child2 = new Child('lisi', 28);
    console.log(child2.name, child2.age, child2.colors);    // lisi 28 (3) ["red", "green", "blue"]

优点: 融合了原型链继承和构造函数的优点,是Javascript中最常用的继承模式

高级继承的实现

四、原型式继承

    function createObj(o) {
      function F(){};
      // 关键:将传入的对象作为创建对象的原型
      F.prototype = o;
      return new F();
    }
    
    // test
    var person = {
        name: 'zhangsan',
        friends: ['lisi', 'wangwu']
    }
    var person1 = createObj(person);
    var person2 = createObj(person);
    
    person1.name = 'wangdachui';
    console.log(person1.name, person2.name);  // wangdachui, zhangsan
    
    person1.friends.push('songxiaobao');
    console.log(person2.friends);       // lisi wangwu songxiaobao

缺点:
对于引用类型的属性值始终都会共享相应的值,和原型链继承一样

五、寄生式继承

    // 创建一个用于封装继承过程的函数,这个函数在内部以某种形式来增强对象
    function createObj(o) {
      var clone = Object.create(o);
      clone.sayName = function() {
        console.log('say HelloWorld');
      }
      return clone;
    }

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

六、寄生组合式继承

6.1 基础版本

    function Parent(name) {
      this.name = name;
      this.colors = ['red', 'green', 'blue'];
    }
    
    Parent.prototype.getName = function() {
      console.log(this, name);
    }
    
    function Child(name, age) {
      Parent.call(this, name);
      this.age = age;
    }
    
    // test1:
    // 1. 设置子类实例的时候会调用父类的构造函数
    Child.prototype = new Parent();
    // 2. 创建子类实例的时候也会调用父类的构造函数
    var child1 = new Child('zhangsan', 18);   // Parent.call(this, name);
    
    
    // 思考:如何减少父类构造函数的调用次数呢?
    var F = function(){};
    F.prototype = Parent.prototype;
    Child.prototype = new F();
    
    // 思考:下面的这一句话可以吗?
    /* 分析:因为此时Child.prototype和Parent.prototype此时指向的是同一个对象,
            因此部分数据相当于此时是共享的(引用)。
            比如此时增加 Child.prototype.testProp = 1; 
            同时会影响 Parent.prototype 的属性的。
          如果不模拟,直接上 es5 的话应该是下面这样吧
          Child.prototype = Object.create(Parent.prototype);*/
    Child.prototype = Parent.prototype;
    
    // 上面的三句话可以简化为下面的一句话
    Child.prototype = Object.create(Parent.prototype);
    
    
    
    // test2:
    var child2 = new Child('lisi', 24);

6.2优化版本

    // 自封装一个继承的方法
    function object(o) {
      // 下面的三句话实际上就是类似于:var o = Object.create(o.prototype)
      function F(){};
      F.prototype = o.prototype;
      return new F();
    }
    
    function prototype(child, parent) {
      var prototype = object(parent.prototype);
      // 维护原型对象prototype里面的constructor属性
      prototype.constructor = child;
      child.prototype = prototype;
    }
    
    // 调用的时候
    prototype(Child, Parent)

补充知识

创建对象的方法

  • 字面量创建
  • 构造函数创建
  • Object.create()
var o1 = {name: 'value'};
var o2 = new Object({name: 'value'});

var M = function() {this.name = 'o3'};
var o3 = new M();

var P = {name: 'o4'};
var o4 = Object.create(P)

原型

  • JavaScript 的所有对象中都包含了一个 __proto__ 内部属性,这个属性所对应的就是该对象的原型
  • JavaScript 的函数对象,除了原型 __proto__ 之外,还预置了 prototype 属性
  • 当函数对象作为构造函数创建实例时,该 prototype 属性值将被作为实例对象的原型 __proto__

原型链

任何一个实例对象通过原型链可以找到它对应的原型对象,原型对象上面的实例和方法都是实例所共享的。

一个对象在查找以一个方法或属性时,他会先在自己的对象上去找,找不到时,他会沿着原型链依次向上查找。

注意: 函数才有prototype,实例对象只有有__proto__, 而函数有的__proto__是因为函数是Function的实例对象

instanceof原理

判断实例对象的__proto__属性与构造函数的prototype是不是用一个引用。如果不是,他会沿着对象的__proto__向上查找的,直到顶端Object。

判断对象是哪个类的直接实例

使用对象.construcor直接可判断

你可能感兴趣的:(JS基础,JavaScript学习,Javascript高级)