关于javascript:underscore源码阅读之如何实现链式调用

11次阅读

共计 3025 个字符,预计需要花费 8 分钟才能阅读完成。

最近又跟着冴羽大大的博客浏览了 underscore 的源码,为了加深本人的了解,梳理了一遍学习的重点,之后打算写 underscore 源码浏览系列文章,那么就进入注释吧!
在理解 underscore 是如何实现链式调用的之前,咱们先来看一下 underscore 中函数调用的两种形式。

const arr = [1, 2, 3]
// 函数式格调
_.map(arr, item => item * 2)
// 面向对象格调
_(arr).map(item => item * 2)

实现函数式与面向对象两种调用形式

思考一下,咱们该怎么同时实现这两种调用形式?

  1. 实现函数式调用很直观,咱们能够定义var _ = {}, 将函数间接挂载在字面量空对象上。
  2. 既然以 _([1, 2, 3]) 的模式能够执行,就阐明 _ 是一个函数对象,而非一般字面量对象。因而,咱们能够定义
var _ = function(){}
_.map = () => { // function code}

但问题在于,如何做到 _([1, 2, 3]).map(...)?也就是说,_([1, 2, 3]) 返回的后果怎么能力调用挂载在 _ 函数上的办法呢?咱们来看 underscore 的解决形式:

var _ = function (obj) {if (obj instanceof _) return obj;
    if (!this instanceOf _) return new _(obj)
    this._wrapped = obj
} 

剖析 _([1, 2, 3]) 这段代码的执行过程:
1. 首次调用_函数,obj instanceof _显然为假,继续执行
2.this 指向全局对象,if 语句判断为真,执行new _(obj)
3. 对于 new 操作符, 其工作过程能够了解为:


function _() {}
var f = new _()

等价于

function _() {}
function newFunc(_, ...args) {var newobj = {}
    newobj.__proto__ = _.prototype
    var res = _.call(newobj, ...args)
    return isObject(res) ? res : newobj
}
var f = newFfunc(_)

因而执行 new _(obj) 过程中,当执行 var res = _.call(newobj, ...args) 语句时,将调用_函数,其 this 理论指向 newobj,而 newobj.__proto__ = _.prototype,因而_函数中的 if 判断语句为假,继续执行this.wrapped = obj,最初返回var res = undefined,因而 newFunc(_) 最初返回了 newobj 对象,即{_wrapped: [1, 2, 3]},该对象的原型指向_.prototype

但目前函数只是挂载在_函数对象上,并没有挂载在_.prototype 上,_([1, 2, 3])返回的实例也无奈调用_函数对象上所挂载的办法。因而,underscore 通过 mixin 将_函数对象上的所有办法复制到_.prototype 上。

_.functions

首先,通过 _.functions 函数获取挂载在_上的所有办法。

// Return a sorted list of the function names available on the object.

_.functions(obj) {var names = [];
    for (var key in obj) {if (isFunction(obj[key])) names.push(key);
    }
    return names.sort();}

isFunction 细节暂且不谈,总之是用来判断所传参数是否为一个函数。

mixin

function mixin(obj) {
    //_.functions 返回所传对象上的所有函数名
    _.each(_.functions(obj), name => {var func = _[name] = obj[name]
        _.prototype[name] = function() {var args = [this._wrapped];
            Array.prototype.push.apply(args, arguments);
            return func.apply(_, args)
        };
    })
    return _
}

到目前为止,咱们曾经实现了函数式与面向对象两种调用形式。接下来,咱们实现链式调用。

链式调用

在 underscore 中默认不应用链式调用,但有须要时也能够通过_.chain 函数实现。例如:

_.filter = function (arr, callback, context) {return arr.filter(callback, context)
}
_.map = function (arr, callback, context) {return arr.map(callback, context)
}
_.chain([1, 2, 3])
 .filter(num => num % 2 === 0)
 .map(num => num * num)
 .value(); // [4]

咱们来看_.chain 函数做了什么。

_.chain = function (obj) {var instance = _(obj);
instance._chain = true;
return instance;
}

_.chain([1, 2, 3])外部通过面向对象格调调用了_,返回了一个对象为 {_wrapped: [1,2,3], _chain: true}, 该对象原型指向_.prototype。_.chain([1,2,3]).filter(num => num % 2 === 0)调用了通过 mixin 后挂载在_.prototype 上的 filter 办法,但该办法返回值为 [2],而不是返回一个原型指向_.prototype 的实例对象,因而无奈持续调用其余办法。
你可能会想将返回值再传入_.chain()函数,不就能够持续调用了吗?比方:

var res = _.chain([1,2,3]).filter(num => num % 2 === 0)
var res2 = _.chain(res).map(num => num * num)
var res3 = _.chain(res2).otherfunc(...)

没错,然而这个反复_.chain()的过程是不是能够通过某种形式主动实现?
那么具体怎么做呢?咱们能够通过 函数调用对象._chain判断是否为链式调用,进而判断是返回一个原型指向_.prototype 的实例对象还是返回 obj 自身。
实现形式如下:

var chainResult = function(instance, obj) {return instance._chain ? _.chain(obj) : obj
}
function mixin(obj) {
    //_.functions 返回所传对象上的所有函数名
    _.each(_.functions(obj), name => {var func = _[name] = obj[name]
        _.prototype[name] = function() {var args = [this._wrapped];
            Array.prototype.push.apply(args, arguments);
            return chainResult(this, func.apply(_, args))
        };
    })
    return _
}
_.prototype.value = function () {return this._wrapped;};

到此为止,咱们曾经齐全实现了链式调用。在文章的最初,咱们来做一个小测试吧,链式调用你真的学会了吗?

正文完
 0