美文网首页
继承:非构造函数式

继承:非构造函数式

作者: JSoon | 来源:发表于2017-01-08 00:14 被阅读0次

    Javascript中,一切可变的键控集合(keyed collections),称为对象。(好拗口,实际上就是指引用数据类型)

    在一个纯粹的原型模式中,应当抛弃类的概念,拥抱于对象。由于没有了constructor的概念,继承会显得更为简单——子对象可以继承父对象的属性/方法。


    方法一:通过创建一个中介空构造函数去继承父对象,然后子对象通过中介构造函数生成的实例来继承父对象

    /**
     * @param {object} o - 需要继承的父对象
     */
    function extendsObject(o) {
      var F = function() {};
      F.prototype = o;
      return new F;
    }
    // 父对象
    var parent_1 = { // 由于已经不在通过构造函数的方式,所以这里的父对象首字母不再以大写形式出现
      name: 'parent_1',
      sayName: function () {
        return this.name;
      }
    };
    // 子对象
    var c1 = extendsObject(parent_1);
    c1.uber = parent_1; // 访问父对象的通道
    // 进而再对子对象进行扩展,所以,这种继承方法,也叫做差异化继承(differential inheritance)
    // 即通过创建一个新的子对象,然后指明它与其继承的父对象之间的区别
    c1.name = 'child_1';
    // 访问子对象方法
    console.log(c1.sayName()); // child_1
    // 还可以方便地访问父对象的方法
    console.log(c1.uber.sayName()); // parent_1
    

    方法二:子对象直接浅拷贝父对象上的属性

    /**
     * @param {object} o - 需要继承的父对象
     */
    function shallowCopyObject(o) {
      var c = {};
      for(var k in o) {
        c[k] = o[k];
      }
      c.uber = o;
      return c;
    }
    var parent_2 = {
      name: 'parent_2',
      children: ['foo', 'bar'], // 本意是给父对象创建一个“私有属性”,但是这里却会被继承的子对象所篡改,等会会说到如何解决这个问题(转方法四)
      sayName: function() {
        return this.name;
      }
    };
    var c2 = shallowCopyObject(parent_2); // 自此,c2继承了parent_2上的所有属性
    // 不幸的是,由于父对象中的children属性是一个数组对象,是引用数据类型,
    // 故parent_2.children只是存储了该数组对象在堆中的地址,也即是说,对子对象上children的任何改动,
    // 都会影响到父对象,非常糟糕,例如:
    c2.children[0] = 'ugly_foo'; // 父对象的“私有属性”被非法篡改了
    console.log(parent_2.children); // ['ugly_foo', 'bar']
    

    方法三:子对象直接深拷贝父对象上的属性

    function deepCopyObject(o) {
      var c = {}, t;
      for (var k in o) {
        if (typeof o[k] === 'object') {
          c[k] = (o[k].constructor === Array) ? [] : {};
          c[k] = deepCopyObject(o[k]);
        } else {
          c[k] = o[k];
        }
      }
      c.uber = o;
      return c;
    }
    

    以上代码有一个问题,就是父对象中的每一个引用类型的属性,都会产生一个uber属性,并指向该引用类型的属性(好拗口> <)。
    下面改进一下:

    function deepCopyObject() {
      // 使用闭包(closure),在闭包中创建root变量作为根父对象的判断标识
      var root = true;
      return function _deepCopyObject(o) {
          var c = {};
          if (root) { // 如此,子对象中便只有第一层会产生uber属性,并指向父对象
              c.uber = o;
              root = false;
          }
          for (var k in o) {
              if (typeof o[k] === 'object') {
                  i += 1;
                  c[k] = (o[k].constructor === Array) ? [] : {};
                  c[k] = _deepCopyObject(o[k]);
              } else {
                  c[k] = o[k];
              }
          }
          return c;
      }
    }
    var parent_3 = {
      name: 'parent_3',
      children: ['foo', 'bar'],
      sayName: function () {
          return this.name;
      }
    };
    var c3 = deepCopyObject()(parent_3);
    c3.name = 'child_3';
    c3.children[0] = 'ugly_foo';
    // 我们可以看到,对于子对象中引用类型的改动,没有影响到父对象
    // 即是说,子对象中的引用类型属性,已经完全地存储到了不同的内存地址中,这就是深拷贝的作用
    console.log(parent_3.children); // ['foo', 'bar']
    

    方法四:模块化继承

    到此为止,我们所看到的继承模式,无论是父还是子,都没有私有属性和方法,所有的属性和方法都是对外可见的,且能够被篡改。为此我们引进另一种好的方法——模块模式,如下:

    // 父对象
    var parent_4 = function(o) {
      var that = {};
      // 其他的私有属性
      var children = ['foo', 'bar'];
      // ...
      var sayName = function() {
        return 'parent sayName: ' + o.name;
      };
      var sayChildren = function () {
        return children;
      };
      // 对外接口,暴露出可被继承的方法
      // 且这里将函数的定义与暴露给that分两步开写的好处是:
      // 1. 如果其他方法想要调用sayName,可以直接调用sayName()而不是that.sayName()
      // 2. 如果该对象实例被篡改,比如that.sayName已经被替换掉,sayName将同样起作用,
      //    因为sayName方法是私有的,重写that.sayName只会重新赋值,不会破坏到私有方法
      that.sayName = sayName;
      that.sayChildren = sayChildren;
      // 最后返回包含了可被继承的属性和方法的对象
      return that; 
    };
    // 子对象
    var child_4 = function(o) {
      var that = parent_4(o); // 先继承父对象
      // 同时我们还可以在子对象中调用父对象的方法,尽管上下文环境已经变化成子对象了(即this的指向)
      var uberSayName = that.sayName;
      // 或者也可以创建整个锚来指向父对象(浪费内存的做法)
      that.uber = parent_4(o);
      // 子对象自身的属性/方法
      var sayName = function() {
        return o.name;
      };
      var sayHi = function() {
        return o.saying;
      };
      // 对外暴露子对象的方法/属性
      that.sayName = sayName;
      that.uberSayName = uberSayName;
      that.sayHi = sayHi;
      return that;
    };
    // 创建子对象实例
    var c4 = child_4({
      // name和saying属性现在完全是私有属性了,除非调用对外接口sayName和sayHi,否则无法对其进行访问,
      // 这样,我们拥有了真正意义上的私有属性,而不是那些有着稀奇古怪名称的“伪私有属性”
      name: 'child_4',
      saying: 'hello world!'
    });
    console.log(c4.sayName()); // 'child_4'
    console.log(c4.uberSayName()); // 'parent sayName: child_4'
    console.log(c4.uber.sayName()); // 'parent sayName: child_4'
    console.log(c4.sayHi()); // 'hello world!'
    

    结论:使用模块化继承的好处很多,其中最重要的就是对私有属性的保护(对象封装),以及对外暴露接口(对象间通信),以及访问父对象方法的能力

    欢迎交流,完。兄弟篇——继承:构造函数式

    相关文章

      网友评论

          本文标题:继承:非构造函数式

          本文链接:https://www.haomeiwen.com/subject/deyfbttx.html