继承:非构造函数式

2017-01-08  本文已影响0人  JSoon

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!'

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

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

上一篇下一篇

猜你喜欢

热点阅读