链模式讲解

链模式
通常情况下,通过对构造函数使用 new 会返回一个绑定到 this 上的新实例,所以我们可以在 new 出来的对象上直接用 . 访问其属性和方法。如果在普通函数中也返回当前实例,那么我们就可以使用 . 在单行代码中一次性连续调用多个方法,就好像它们被链接在一起一样,这就是链式调用,又称链模式。

之前建造者模式、组合模式等文章已经用到了链模式,日常使用的 jQuery、Promise 等也使用了链模式,我们对使用形式已经很熟悉了,下面一起来看看链模式的原理。

  1. 什么是链模式 1.1 链模式的实现

在 jQuery 时代,下面这样的用法我们很熟悉了:

// 使用链模式
$('div')
  .show()
  .addClass('active')
  .height('100px')
  .css('color', 'red')
  .on('click', function(e) {
      // ... 
  })

源码中的链模式 3.1 jQuery 中的链模式 1. jQuery 构造函数

jQuery 方法看似复杂,可以简写如下:


var jQuery = function(selector, context) {
   // jQuery 方法返回的是 jQuery.fn.init 所 new 出来的对象
   return new jQuery.fn.init(selector, context, rootjQuery)
}

jQuery.fn = jQuery.prototype = {
   constructor: jQuery,
   // jQuery 对象的构造函数
   init: function(selector, context, rootjQuery) {
       // ... 一顿匹配操作,返回一个拼装好的伪数组的自身实例
       // 是 jQuery.fn.init 的实例,也就是我们常用的 jQuery 对象
       return this
   },
   selector: '',
   eq: function() { ... },
   end: function() { ... },
   map: function() { ... },
   last: function() { ... },
   first: function() { ... },
   // ... 其他方法
}

jQuery.fn.init 的实例都拥有 jQuery.fn 相应的方法jQuery.fn.init.prototype = jQuery.fn
// 此处源码位于 src/core.jsreturn new jQuery.fn.init(...) 这句看似复杂,其实也就是下面的这个 init 方法,
这个方法最后返回的是我们常用的 jQuery 对象,下面还有一句 jQuery.fn.init.prototype = jQuery.fn,
因此最上面的 jQuery 方法返回的 new 出来的 jQuery.fn.init 实例将继承 jQuery.fn` 上的方法:

const p = $("<p/>")
$.fn === p.__proto__   // true

因此返回出来的实例也将继承 eq、end、map、last 等 jQuery.fn 上的方法。

1、jQuery 实例方法

下面我们一起看看,show、hide、toggle 这些方法是如何实现链模式的呢 :

jQuery.fn.extend({
   show: function() {
       var elem
       
       for (i = 0; i < this.length; i++) {
           // ... 
           elem = this[i]
           
           if (elem.style.display === 'none') {
               elem.style.display = 'block'
           }
       }

       return this
   },
   hide: function() { ... },
   toggle: function() { ... }
})
这里首先使用了一个方法 jQuery.fn.extend(),简单看一下这个方法做啥的:

jQuery.extend = jQuery.fn.extend = function(options) {
   // ... 一系列啰啰嗦嗦的判断
 
   for (name in options) {
       this[name] = options[ name ]  // 此处 this === jQuery.fn
   }
}

// 此处源码位于 src/core.js

这个方法就是把传参的对象的值赋值给 jQuery.fn,因为这时候这个方法是通过上下文对象 jQuery.fn.extend()方式来调用,属于隐式绑定。(对 this 绑定规则的同学参看本专栏第 2 篇文章)
show 方法为例,此时这个方法被赋到 jQuery.fn 对象上,而通过上文我们知道,jQuery.fn.init.prototype = jQuery.fn,而jQuery.fn.init这个方法是作为构造函数被 jQuery 函数 new 出来并返回,因此 show 方法此时可以被jQuery.fn.init实例访问到,也就可以被$('selector')访问到,因此此时我们已经可以: $('p').show()了。
那么我们再回头来看看show方法的实现,show方法将匹配的元素的 display 置为 block之后返回了 this。注意了,此时的 this也是隐式绑定,而且是通过$('p')点出来的,因此返回的值就是$('p') 的引用。
经过以上步骤,我们知道 show方法返回的仍然是 $('p') 的引用,我们可以继续在之后点出来其他 jQuery.fn对象上的方法,css、hide、toggle、addClasson等等方法同理,至此,jQuery 的链模式就形成了。

2、Underscore 中的链模式

如果你用过 Underscore,那么你可能知道 Underscore 提供的一个链模式实现 _.chain。通过这个方法,可以方便地使用Underscore提供的一些方法链模式地对数据进行处理。另外,Lodashchain 实现和 Underscore 的基本一样,可以自行去LodashGitHub仓库阅读。
比如这里我们需要对一个用户对象数组进行一系列操作,首先按年龄排序,去掉年龄为奇数的人,再将这些用户的名字列成数组:

var users = [
    { 'name': 'barney', 'age': 26 },
    { 'name': 'fred', 'age': 21 },
    { 'name': 'pebbles', 'age': 28 },
    { 'name': 'negolas', 'age': 23 }
]

_.chain(users)
  .sortBy('age')
  .reject(user => user.age % 2)
  .map(user => user.name)
  .value()

// 输出: ["barney", "pebbles"]

经过 _.chain方法处理后,就可以使用 Underscore 提供的其他方法对这个数据进行操作,下面一起来看看源码是如何实现链模式。

首先是 _.chain方法:

_.chain = function(obj) {
    var instance = _(obj)  // 获得一个经 underscore 包裹后的实例
    instance._chain = true // 标记是否使用链式操作
    return instance
}

此处源码位于 underscore.js#L1615-L1619

这里通过_(obj)的方式把数据进行了包装,并返回了一个对象,结构如下:

{
    _chain: true,
    _wrapped: [...],
    __proto__:  ...
}

返回的对象的隐式原型可以访问到 Undersocre 提供的很多方法,如下图:

image

这个 chain 方法的作用就是创建一个包裹了 objUnderscore 实例对象,并标记该实例是使用链模式,最后返回这个包装好的链式化实例(叫链式化是因为可以继续调用 underscore 上的方法)。

我们一起看看 sort 方法是如何实现的:

var chainResult = function (instance, obj) {
    return instance._chain ? _(obj).chain() : obj;  // 这里 _chain 为 true
};

_.each(['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift'], function(name) {
    var method = Array.prototype[name];
    _.prototype[name] = function() {
        var obj = this._wrapped;
        method.apply(obj, arguments);   // 执行方法

        return chainResult(this, obj);
    };
});

此处源码位于 underscore.js#L1649-L1657

  • sort 方法执行之后,把结果重新放在 _wrapped 里,并执行 chainResult 方法,这个方法里由于 _chain 之前已经置为 true,因此会继续对结果调用 chain() 方法,包装成链式化实例并返回。
  • 最后的这个_.value 方法比较简单,就是返回链式化实例的_wrapped 值:
_.prototype.value = function() {
   return this._wrapped;
};

此处源码位于 underscore.js#L1668-L1670

总结一下,只要一开始调用了 chain 方法, _chain 这个标志位就会被置为 true,在类似的方法中,返回的值都用 chainResult 包裹一遍,并判断这个 _chain 这个标志位,为 true 则返回链式化实例,供给下一次方法调用,由此形成了链式化调用

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

推荐阅读更多精彩内容