• JavaScript的封装和继承


    提到JavaScript“面向对象编程”,主要就是封装和继承,这里主要依据阮一峰及其他博客的系列文章做个总结。

    继承机制的设计思想

    • 所有实例对象需要共享的属性和方法,都放在这个对象里面;那些不需要共享的属性和方法,就放在构造函数里面。

    • 由于所有的实例对象共享同一个prototype对象,那么从外界看起来,prototype对象就好像是实例对象的原型,而实例对象则好像”继承”了prototype对象一样。

    封装

    主要介绍了如何”封装”数据和方法,以及如何从原型对象生成实例。

    (1) 封装:把属性和方法封装成一个对象。

    (2)如何根据某一规格(原型)生成对象:构造函数。相较于原始方法(用字面量一个一个手动生成对象),其优点在于:

    • 减少代码重复,将对象普遍具有的属性先用构造函数定义好,使用构造函数生成对象时传参即可为产生的对象的属性赋值;

    • 体现生成的对象与原型对象之间的联系。

    • 构造函数具有的prototype属性的优点:将所有对象具有的值相同的属性,挂在prototype上,无论生成多少个对象,它们都共用这一份prototype,在内存中只有一份,减少不必要的资源浪费。isPrototypeOf, hasOwnProperty()检测是本地属性还是原型属性,in遍历对象的所有属性,包括原型属性。

    【注意】:构造函数中的属性是本地属性,每个对象都维护各自的值,prototype中的属性是引用属性,所有对象共有一份。原型属性与实例属性同名时,原型属性会被实例属性覆盖

    构造函数的继承 (五种方法)

    方法一:构造函数绑定

    在子对象构造函数Cat()中加一行:

    1
    Animal.apply(this, arguments);

    优点:可以实现多继承(call/apply多个对象);并且,创建子类实例时,可以向父类传递参数。

    缺点:只能继承父类的实例属性和方法,不能继承父类的原型属性/方法。(这里引申出【组合继承】:在本方法基础上,加上方法二的原型继承,即call+prototype方法,组合继承既可以继承原型属性/方法,又可以继承实例属性/方法,而且还可传参)。

    方法二:prototype模式(又称原型链继承,注意需要对constructor修正)

    将父类的实例作为子类的原型:如果”猫”的prototype对象,指向一个Animal的实例,那么所有”猫”的实例,就能继承Animal了。

    1
    2
    Cat.prototype = new Animal(); //它相当于完全删除了prototype 对象原先的值,然后赋予一个新值。包括改写Cat.prototype.constructor为Animal
    Cat.prototype.constructor = Cat; //如果不改回去,会导致继承链的混乱

    【注意】:

    • 任何一个prototype对象(cat.prototype)都有一个constructor属性,指向它的构造函数。

    • 更重要的是,每一个实例(cat1)也有一个constructor属性,默认调用prototype对象的constructor属性。

    =>

    【普适性的规则】:如果替换了prototype对象,下一步必然是为新的prototype对象加上constructor属性,并将这个属性指回原来的构造函数:

    1
    2
    o.prototype = {};
    o.prototype.constructor = o;

    缺点:无法实现多继承;创建子类实例时,无法向父类构造函数传参。

    方法三:直接继承prototype

    优点是 效率比较高(不用执行和建立Animal的实例了),比较省内存。

    缺点是 Cat.prototypeAnimal.prototype现在指向了同一个对象,那么任何对Cat.prototype的修改,都会反映到Animal.prototype

    1
    2
    Cat.prototype = Animal.prototype;
    Cat.prototype.constructor = Cat; //这样导致`Animal.prototype.const 大专栏  JavaScript的封装和继承ructor`也被改掉了。下面的方法四针对这个问题用空函数作中介做了改进。

    方法四:利用空对象作为中介 (又称寄生组合继承,对方法三的改良):完美

    1
    2
    3
    4
    5
    6
    7
    function (Child, Parent) {
      var F = function(){};
      F.prototype = Parent.prototype;
      Child.prototype = new F();
      Child.prototype.constructor = Child;
      Child.uber = Parent.prototype; //在子对象上打开一条通道,可以直接调用父对象的方法,纯属备用性质,实现继承的完备性
    }

    方法五:拷贝继承:把Parent.prototype的所有属性和方法,拷贝进Child.prototype

    1
    2
    3
    4
    5
    6
    7
    8
    9
    function (Child, Parent) {
    var p = Parent.prototype;
      var c = Child.prototype;
      for (var i in p) {
        c[i] = p[i];
      }
      c.uber = p;
    }

    拷贝继承支持多继承。

    非构造函数的继承

    继承某个具体的对象。

    方法一:object()(实质上还是原型继承)

    1
    2
    3
    4
    5
    function object(o) {
      function F() {}
      F.prototype = o;
      return new F();
    }

    方法二:浅拷贝(只能拷贝数据全是基本类型的对象)

    存在问题:如果父对象的属性等于数组或另一个对象,那么实际上,子对象获得的只是一个内存地址,而不是真正拷贝,因此存在父对象被篡改的可能。

    方法三:深拷贝

    递归调用浅拷贝

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    function deepCopy(p, c) {
    var c = c || {};
    for (var i in p) {
    if (typeof p[i] === 'object' ) {
    c[i] = (p[i].constructor === Array) ? [] : {};
    deepCopy(p[i], c[i]);
    } else {
    c[i] = p[i];
    }
    }
    return c;
    }

    优点:支持多继承。

    缺点:效率较低,内存占用高(因为要拷贝父类的属性);无法获取父类不可枚举的方法(不可枚举方法,不能使用for in 访问到)。

  • 相关阅读:
    STL中队列queue的常见用法
    牛客网剑指offer第17题——树的子结构
    《剑指offer》网络大神所有习题题解
    牛客网剑指offer第4题——重建二叉树
    牛客网剑指offer第56题——删除链表中重复的节点
    图像处理中的求导问题
    hash_set和hash_map
    hashtable初步——一文初探哈希表
    数据结构-链表的那些事(下)(二)
    数据结构-链表的那些事(上)(一)
  • 原文地址:https://www.cnblogs.com/lijianming180/p/12258934.html
Copyright © 2020-2023  润新知