underScore專題-源碼分析迭代器

下面會用到undefined判斷,所以這裏先來跟underScore學習一下undefined的處理

在javaScript中我們判斷一個變量是否是undefined通常會這樣寫

var a;
if (a === undefined) {
    console.log(1) // 1
}

但是在javaScript中undefined並不可靠,因爲undefined可以作爲變量名使用:

var a;
var undefined = 2
console.log(undefined) // 2
if (a === undefined) {
    console.log(1)
}

underScore中通過這樣的方法來獲取undefined:

console.log(void 0) // undefined
console.log(void (0)) //undefined

好了,切入正題:

就像數組原型上又map方法,允許對數組處理並返回,underScore中也有map方法,並且比Array.prototype.map更加健全,不能叫健全吧,應該說是健壯。下面從map方法的使用上來分析源碼。

 var arr = [4, 6, 1]
    _.map(arr, function(item, index) {
        console.log(item, index)
    })

如果我不傳遞迭代器呢?會將原對象返回:

 var arr = [4, 6, 1]
    var a = _.map(arr)
    console.log(a)

如果我傳遞一個一個對象呢?

    var arr = [4, 6, 1]
    var obj = [{ name: '麥樂' }, { name: 'maile' }]
    var a = _.map(obj, { name: 'maile' })
    console.log(a)

也支持傳遞字符串

    var arr = [4, 6, 1]
    var obj = [{ name: '麥樂' }, { name: 'maile' }]
    var a = _.map(obj, 'name')
    console.log(a)

可以分析出,map可以根據傳遞的參數不同,返回不同的值,具體分以下幾種情況:

  • 當迭代器也就是iteratee不傳遞時,返回第一個參數
  • 當iteratee傳遞爲函數時,正常處理
  • 當iteratee傳遞爲對象時,返回匹配結果
  • iteratee傳遞爲字符串時,返回匹配到的鍵值數組

看下源碼是怎麼實現的?

  // Return the results of applying the iteratee to each element.
    function map(obj, iteratee, context) {
        iteratee = cb(iteratee, context);
        var _keys = !isArrayLike(obj) && keys(obj),
            length = (_keys || obj).length,
            results = Array(length);
        for (var index = 0; index < length; index++) {
            var currentKey = _keys ? _keys[index] : index;
            results[index] = iteratee(obj[currentKey], currentKey, obj);
        }
        return results;
    }

函數接受三個參數,對象,迭代器,上下文對象也就是this。

這裏將我們傳遞進去的迭代器做了處理,就是上面出現的幾種情況,cb函數是underScore內部的一個函數,就是負責處理不同的迭代器參數的。源碼內部多處用到了這個函數。

iteratee = cb(iteratee, context);

cb 

function cb(value, context, argCount) {
        if (_.iteratee !== iteratee) return _.iteratee(value, context);
        return baseIteratee(value, context, argCount);
    }

 _.iteratee

 _.iteratee = iteratee;
    function iteratee(value, context) {
        return baseIteratee(value, context, Infinity);
    }

可以看到一般情況下_.iteratee === iteratee 是true,_.iteratee(value, context);不會執行,但是當我們在外部自定義一個這樣的函數,迭代器函數被修改這時返回的下面這個函數的調用結果,也就是說允許我們修改map函數的功能。

 _.iteratee = function(value, context) {
        
    }

比如,如果不想考慮那麼多,只允許第二個參數是函數,就可以這麼寫

    var arr = [4, 6, 1]
    _.iteratee = function(value, context) {
        if (typeof value !== 'function') throw Error('第二個參數必須是函數')
        return function(...agrs) {
            value.call(context, agrs)
        }
    };
    _.map(arr, 'aa')

baseIteratee

大多數情況下,不會去修改iteratee,cb函數一般都會走到下面這一步:

return baseIteratee(value, context, argCount);

這裏調用了baseIteratee函數,源碼中這個函數如下:

 function baseIteratee(value, context, argCount) {
        if (value == null) return identity;
        if (isFunction(value)) return optimizeCb(value, context, argCount);
        if (isObject(value) && !isArray(value)) return matcher(value);
        return property(value);
    }

可以看到,實在這個函數中對傳遞進來的第二個參數做了處理:

identity

if (value == null) return identity;
 function identity(value) {
        return value;
    }

初始化了一個迭代器,返回傳遞進來的參數。


optimizeCb

 if (isFunction(value)) return optimizeCb(value, context, argCount);

如果是函數,就交給內部的optimizeCb函數去處理:

  function optimizeCb(func, context, argCount) {
        if (context === void 0) return func;
        switch (argCount == null ? 3 : argCount) {
            case 1: return function(value) {
                return func.call(context, value);
            };
            // The 2-argument case is omitted because we’re not using it.
            case 3: return function(value, index, collection) {
                return func.call(context, value, index, collection);
            };
            case 4: return function(accumulator, value, index, collection) {
                return func.call(context, accumulator, value, index, collection);
            };
        }
        return function() {
            return func.apply(context, arguments);
        };
    }

大多數情況下,都不會傳遞context,直接返回了func,也就是傳遞進來的迭代器。如果傳遞了context,只需這樣返回就可以滿足需求:

var optimizeCb = function(func, context) {
    
    if (context === void 0) return func;
    return function() {
        return func.apply(context, arguments);
    };
};

但是 underScore中卻對傳遞的參數個數進行分類處理。之所以這麼做就是爲了避免使用arguments。

比如map中,迭代器的調用就傳遞了三個參數:

results[index] = iteratee(obj[currentKey], currentKey, obj);

這裏沒有傳遞argCount,optimizeCb中,argCount默認是3。返回

case 3: return function(value, index, collection) {
           return func.call(context, value, index, collection);
          };

正好對應iteratee(obj[currentKey], currentKey, obj);這裏傳遞的3個參數。

matcher

if (isObject(value) && !isArray(value)) return matcher(value);

第二個參數是對象,並且不是數組,也就是這樣的情況:

    var arr = [4, 6, 1]
    var obj = [{ name: '麥樂' }, { name: 'maile' }]
    var a = _.map(obj, { name: 'maile' })
    console.log(a) // [false, true]

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章