继承:非构造函数式

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

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

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,588评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,456评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,146评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,387评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,481评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,510评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,522评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,296评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,745评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,039评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,202评论 1 343
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,901评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,538评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,165评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,415评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,081评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,085评论 2 352

推荐阅读更多精彩内容

  • 博客内容:什么是面向对象为什么要面向对象面向对象编程的特性和原则理解对象属性创建对象继承 什么是面向对象 面向对象...
    _Dot912阅读 1,409评论 3 12
  • importUIKit classViewController:UITabBarController{ enumD...
    明哥_Young阅读 3,793评论 1 10
  • 前言 人生苦多,快来 Kotlin ,快速学习Kotlin! 什么是Kotlin? Kotlin 是种静态类型编程...
    任半生嚣狂阅读 26,191评论 9 118
  • 今日临完二遍,根据大家建议,写得稍微小了些,尽量注意了转折和留白,还不是特别到位。请大家指教。【本文由“写点小书法...
    写点小书法阅读 572评论 0 0
  • 《最好的告别》,作者是美国著名外科医生阿图·葛文德。看到这个书名的时候让我有一种莫名的感动,可能因为我学的是医药专...
    Lynette_C阅读 464评论 2 16