zoukankan      html  css  js  c++  java
  • 【前端知识体系-JS相关】深入理解JavaScript原型(继承)和原型链

    1. Javascript继承

    1.1 原型链继承

        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());
    

    [!NOTE]
    主要问题:

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

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

        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);
    

    [!NOTE]
    优点:

    1. 避免了引用类型的属性被所有实例共享
    2. 可以直接在Child中向Parent传参

    [!DANGER]
    缺点:

    • 方法都在构造函数中定义了,每次创建实例都会创建一遍方法

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

        /**
        * 父类构造函数
        * @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"]
    

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

    2. 多种方式实现继承及优缺点总结

    2.1 原型式继承

        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
    

    [!DANGER]
    缺点:

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

    2.2 寄生式继承

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

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

    2.3 寄生组合式继承

    2.3.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);
    

    2.3.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)
    

    3. JS创建对象的方法

    • 字面量创建
    • 构造函数创建
    • 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)
    

    4. 原型和原型链

    4.1 原型

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

    原型

    4.2 原型链

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

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

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

    4.3 instanceof原理

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

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

    [!NOTE]
    使用对象.construcor直接可判断

    4.5 构造函数,new时发生了什么?

       var obj  = {}; 
       obj.__proto__ = Base.prototype;
       Base.call(obj);  
    
    1. 创建一个新的对象 obj;
    2. 将这个空对象的__proto__成员指向了Base函数对象prototype成员对象
    3. Base函数对象的this指针替换成obj, 相当于执行了Base.call(obj);
    4. 如果构造函数显示的返回一个对象,那么则这个实例为这个返回的对象。 否则返回这个新创建的对象

    4.6 类

    // 普通写法
    function Animal() {
      this.name = 'name'
    }
    
    // ES6
    class Animal2 {
      constructor () {
        this.name = 'name';
      }
    }
    
  • 相关阅读:
    stm32串口通讯
    Java中日期处理
    Java中synchronized同步的理解
    由代理模式到AOP的实例分析
    基数排序(RadixSort)
    桶排序(BucketSort)
    计数排序
    快速排序
    6.5 k个已排好序链表合并为一个排序链表
    优先队列 (堆实现)
  • 原文地址:https://www.cnblogs.com/fecommunity/p/11922158.html
Copyright © 2011-2022 走看看