Underscore源碼解析(四)

本文同步自我得博客:http://www.joeray61.comjavascript

我在這個系列的第一篇文章說過,我學underscore是爲了在學backbone的時候少一些阻礙,從第一篇的寫做時間到今天,大概也有個十幾二十天,感受拖得有點久,因此今天將會是underscore源碼解析系列的最後一篇文章,我會在這篇文章中介紹underscore剩下的全部函數。
先附上前三篇文章的地址:Underscore源碼解析(一)Underscore源碼解析(二)Underscore源碼解析(三)前端

_.zip

_.zip = function() {
    // 將參數轉換爲數組, 此時args是一個二維數組
    var args = slice.call(arguments);
    // 計算每個數組的長度, 並返回其中最大長度值
    var length = _.max(_.pluck(args, 'length'));
    // 依照最大長度值建立一個新的空數組, 該數組用於存儲處理結果
    var results = new Array(length);
    // 循環最大長度, 在每次循環將調用pluck方法獲取每一個數組中相同位置的數據(依次從0到最後位置)
    // 將獲取到的數據存儲在一個新的數組, 放入results並返回
    for(var i = 0; i < length; i++)
    results[i] = _.pluck(args, "" + i);
    // 返回的結果是一個二維數組
    return results;
};

這個函數將每一個數組的相同位置的數據做爲一個新的二維數組返回, 返回的數組長度以傳入參數中最大的數組長度爲準, 其它數組的空白位置使用undefined填充。zip函數應該包含多個參數, 且每一個參數應該均爲數組。java

_.indexOf

_.indexOf = function(array, item, isSorted) {
    if(array == null)
        return -1;
    var i, l;
    // 若數組已經通過排序,則調用sortedIndex方法,獲取元素插入數組中所處位置的索引號
    if(isSorted) {
        i = _.sortedIndex(array, item);
        return array[i] === item ? i : -1;
    }
    // 優先調用宿主環境提供的indexOf方法
    if(nativeIndexOf && array.indexOf === nativeIndexOf)
        return array.indexOf(item);
    // 循環並返回元素首次出現的位置
    for( i = 0, l = array.length; i < l; i++)
    if( i in array && array[i] === item)
        return i;
    // 沒有找到元素, 返回-1
    return -1;
};

這個函數的做用是搜索一個元素在數組中首次出現的位置, 若是元素不存在則返回 -1,搜索時使用 === 對元素進行匹配node

_.lastIndexOf

_.lastIndexOf = function(array, item) {
    if(array == null)
        return -1;
    // 優先調用宿主環境提供的lastIndexOf方法
    if(nativeLastIndexOf && array.lastIndexOf === nativeLastIndexOf)
        return array.lastIndexOf(item);
    var i = array.length;
    // 循環並返回元素最後出現的位置
    while(i--)
    if( i in array && array[i] === item)
        return i;
    // 沒有找到元素, 返回-1
    return -1;
};

這個函數返回一個元素在數組中最後一次出現的位置, 若是元素不存在則返回 -1,搜索時使用 === 對元素進行匹配正則表達式

_.range

_.range = function(start, stop, step) {
    // 參數控制
    if(arguments.length <= 1) {
        // 若是沒有參數, 則start = 0, stop = 0, 在循環中不會生成任何數據, 將返回一個空數組
        // 若是有1個參數, 則參數指定給stop, start = 0
        stop = start || 0;
        start = 0;
    }
    // 生成整數的步長值, 默認爲1
    step = arguments[2] || 1;

    // 根據區間和步長計算將生成的最大值
    var len = Math.max(Math.ceil((stop - start) / step), 0);
    var idx = 0;
    var range = new Array(len);

    // 生成整數列表, 並存儲到range數組
    while(idx < len) {
        range[idx++] = start;
        start += step;
    }

    // 返回列表結果
    return range;
};

這個函數根據區間和步長, 生成一系列整數, 並做爲數組返回,start參數表示最小數,stop參數表示最大數,step參數表示步長數組

_.bind

_.bind = function bind(func, context) {
    var bound, args;
    // 優先調用宿主環境提供的bind方法
    if(func.bind === nativeBind && nativeBind)
        return nativeBind.apply(func, slice.call(arguments, 1));
    // func參數必須是一個函數(Function)類型
    if(!_.isFunction(func))
        throw new TypeError;
    // args變量存儲了bind方法第三個開始的參數列表, 每次調用時都將傳遞給func函數
    args = slice.call(arguments, 2);
    return bound = function() {
        if(!(this instanceof bound))
            return func.apply(context, args.concat(slice.call(arguments)));
        ctor.prototype = func.prototype;
        var self = new ctor;
        var result = func.apply(self, args.concat(slice.call(arguments)));
        if(Object(result) === result)
            return result;
        return self;
    };
};

這個函數爲一個函數綁定執行上下文, 任何狀況下調用該函數, 函數中的this均指向context對象,綁定函數時, 能夠同時給函數傳遞調用形參瀏覽器

_.bindAll

_.bindAll = function(obj) {
    // 第二個參數開始表示須要綁定的函數名稱
    var funcs = slice.call(arguments, 1);
    // 若是沒有指定特定的函數名稱, 則默認綁定對象自己全部類型爲Function的屬性
    if(funcs.length == 0)
        funcs = _.functions(obj);
    // 循環並將全部的函數上下本設置爲obj對象自己
    // each方法自己不會遍歷對象原型鏈中的方法, 但此處的funcs列表是經過_.functions方法獲取的, 它已經包含了原型鏈中的方法
    each(funcs, function(f) {
        obj[f] = _.bind(obj[f], obj);
    });
    return obj;
};

這個函數將指定的函數, 或對象自己的全部函數上下本綁定到對象自己, 被綁定的函數在被調用時, 上下文對象始終指向對象自己緩存

_.memoize

_.memoize = function(func, hasher) {
    // 用於存儲緩存結果的memo對象
    var memo = {};
    // hasher參數應該是一個function, 它用於返回一個key, 該key做爲讀取緩存的標識
    // 若是沒有指定key, 則默認使用函數的第一個參數做爲key, 若是函數的第一個參數是複合數據類型, 可能會返回相似[Object object]的key, 這個key可能會形成後續計算的數據不正確
    hasher || ( hasher = _.identity);
    // 返回一個函數, 該函數首先經過檢查緩存, 再對沒有緩存過的數據進行調用
    return function() {
        var key = hasher.apply(this, arguments);
        return _.has(memo, key) ? memo[key] : (memo[key] = func.apply(this, arguments));
    };
};

這個函數將返回一個函數, 該函數集成了緩存功能, 將通過計算的值緩存到局部變量並在下次調用時直接返回架構

_.delay

_.delay = function(func, wait) {
    var args = slice.call(arguments, 2);
    // 經過setTimeout來延時執行
    return setTimeout(function() {
        return func.apply(null, args);
    }, wait);
};

這個函數的做用是延時執行一個函數,wait單位爲ms, 第3個參數開始將被依次傳遞給執行函數app

_.defer

_.defer = function(func) {
    // 至關於_.delay(func, 1, [arguments]);
    return _.delay.apply(_, [func, 1].concat(slice.call(arguments, 1)));
};

這個函數的做用是延遲1ms執行函數,javascript是一個單線程的程序,setTimeout(func, time)做用是把func放處處理任務的隊列末尾,在其餘任務都完成以後的time ms 後執行func

_.throttle

_.throttle = function(func, wait) {
    var context, args, timeout, throttling, more, result;
    // whenDone變量調用了debounce方法, 所以在屢次連續調用函數時, 最後一次調用會覆蓋以前調用的定時器, 清除狀態函數也僅會被執行一次
    // whenDone函數在最後一次函數執行的時間間隔截止時調用, 清除節流和調用過程當中記錄的一些狀態
    var whenDone = _.debounce(function() {
        more = throttling = false;
    }, wait);
    // 返回一個函數, 並在函數內進行節流控制
    return function() {
        // 保存函數的執行上下文和參數
        context = this;
        args = arguments;
        // later函數在上一次函數調用時間間隔截止時執行
        var later = function() {
            // 清除timeout句柄, 方便下一次函數調用
            timeout = null;
            // more記錄了在上一次調用至時間間隔截止之間, 是否重複調用了函數
            // 若是重複調用了函數, 在時間間隔截止時將自動再次調用函數
            if(more)
                func.apply(context, args);
            // 調用whenDone, 用於在時間間隔後清除節流狀態
            whenDone();
        };
        // timeout記錄了上一次函數執行的時間間隔句柄
        // timeout時間間隔截止時調用later函數, later中將清除timeout, 並檢查是否須要再次調用函數
        if(!timeout)
            timeout = setTimeout(later, wait);
        // throttling變量記錄上次調用的時間間隔是否已經結束, 便是否處於節流過程當中
        // throttling在每次函數調用時設爲true, 表示須要進行節流, 在時間間隔截止時設置爲false(在whenDone函數中實現)
        if(throttling) {
            // 節流過程當中進行了屢次調用, 在more中記錄一個狀態, 表示在時間間隔截止時須要再次自動調用函數
            more = true;
        } else {
            // 沒有處於節流過程, 多是第一次調用函數, 或已經超過上一次調用的間隔, 能夠直接調用函數
            result = func.apply(context, args);
        }
        // 調用whenDone, 用於在時間間隔後清除節流狀態
        whenDone();
        // throttling變量記錄函數調用時的節流狀態
        throttling = true;
        // 返回調用結果
        return result;
    };
};

這是函數節流方法, throttle方法主要用於控制函數的執行頻率, 在被控制的時間間隔內, 頻繁調用函數不會被屢次執行,在時間間隔內若是屢次調用了函數, 時間隔截止時會自動調用一次, 不須要等到時間截止後再手動調用(自動調用時不會有返回值),throttle函數通常用於處理複雜和調用頻繁的函數, 經過節流控制函數的調用頻率, 節省處理資源

_.debounce

_.debounce = function(func, wait, immediate) {
    // timeout用於記錄函數上一次調用的執行狀態(定時器句柄)
    // 當timeout爲null時, 表示上一次調用已經結束
    var timeout;
    // 返回一個函數, 並在函數內進行節流控制
    return function() {
        // 保持函數的上下文對象和參數
        var context = this, args = arguments;
        var later = function() {
            // 設置timeout爲null
            // later函數會在容許的時間截止時被調用
            // 調用該函數時, 代表上一次函數執行時間已經超過了約定的時間間隔, 此時以後再進行調用都是被容許的
            timeout = null;
            if(!immediate)
                func.apply(context, args);
        };
        // 若是函數被設定爲當即執行, 且上一次調用的時間間隔已通過去, 則當即調用函數
        if(immediate && !timeout)
            func.apply(context, args);
        // 建立一個定時器用於檢查和設置函數的調用狀態
        // 建立定時器以前先清空上一次setTimeout句柄, 不管上一次綁定的函數是否已經被執行
        // 若是本次函數在調用時, 上一次函數執行尚未開始(通常是immediate設置爲false時), 則函數的執行時間會被推遲, 所以timeout句柄會被從新建立
        clearTimeout(timeout);
        // 在容許的時間截止時調用later函數
        timeout = setTimeout(later, wait);
    };
};

debounce與throttle方法相似, 用於函數節流, 它們的不一樣之處在於:

-- throttle關注函數的執行頻率, 在指定頻率內函數只會被執行一次
-- debounce函數更關注函數執行的間隔, 即函數兩次的調用時間不能小於指定時間

若是兩次函數的執行間隔小於wait, 定時器會被清除並從新建立, 這意味着連續頻繁地調用函數, 函數一直不會被執行, 直到某一次調用與上一次調用的時間不小於wait毫秒

_.once

_.once = function(func) {
    // ran記錄函數是否被執行過
    // memo記錄函數最後一次執行的結果
    var ran = false, memo;
    return function() {
        // 若是函數已被執行過, 則直接返回第一次執行的結果
        if(ran)
            return memo;
        ran = true;
        return memo = func.apply(this, arguments);
    };
};

這個函數建立一個只會被執行一次的函數, 若是該函數被重複調用, 將返回第一次執行的結果

_.wrap

_.wrap = function(func, wrapper) {
    return function() {
        // 將當前函數做爲第一個參數, 傳遞給wrapper函數
        var args = [func].concat(slice.call(arguments, 0));
        // 返回wrapper函數的處理結果
        return wrapper.apply(this, args);
    };
};

這個函數返回一個函數, 該函數會將當前函數做爲參數傳遞給一個包裹函數,在包裹函數中能夠經過第一個參數調用當前函數, 並返回結果

_.compose

_.compose = function() {
    // 獲取函數列表, 全部參數需均爲Function類型
    var funcs = arguments;
    // 返回一個供調用的函數句柄
    return function() {
        // 從後向前依次執行函數, 並將記錄的返回值做爲參數傳遞給前一個函數繼續處理
        var args = arguments;
        for(var i = funcs.length - 1; i >= 0; i--) {
            args = [funcs[i].apply(this, args)];
        }
        // 返回最後一次調用函數的返回值
        return args[0];
    };
};

這個函數將多個函數組合到一塊兒, 按照參數傳遞的順序, 後一個函數的返回值會被依次做爲參數傳遞給前一個函數做爲參數繼續處理,_.compose(A, B, C)等同於 A(B(C()))

_.after

_.after = function(times, func) {
    // 若是沒有指定或指定無效次數, 則func被直接調用
    if(times <= 0)
        return func();
    // 返回一個計數器函數
    return function() {
        // 每次調用計數器函數times減1, 調用times次以後執行func函數並返回func函數的返回值
        if(--times < 1) {
            return func.apply(this, arguments);
        }
    };
};

after返回一個函數, 該函數做爲調用計數器, 當該函數被調用times次(或超過times次)後, func函數將被執行

_.keys

_.keys = nativeKeys ||
function(obj) {
    if(obj !== Object(obj))
        throw new TypeError('Invalid object');
    var keys = [];
    // 記錄並返回對象的全部屬性名
    for(var key in obj)
    if(_.has(obj, key))
        keys[keys.length] = key;
    return keys;
};

這個函數用於獲取一個對象的屬性名列表(不包含原型鏈中的屬性)

_.values

_.values = function(obj) {
    return _.map(obj, _.identity);
};

這個函數返回一個對象中全部屬性的值列表(不包含原型鏈中的屬性)

_.functions / _.methods

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

這個函數獲取一個對象中全部屬性值爲Function類型的key列表, 並按key名進行排序(包含原型鏈中的屬性)

_.extend

_.extend = function(obj) {
    // each循環參數中的一個或多個對象
    each(slice.call(arguments, 1), function(source) {
        // 將對象中的所有屬性複製或覆蓋到obj對象
        for(var prop in source) {
            obj[prop] = source[prop];
        }
    });
    return obj;
};

這個函數將一個或多個對象的屬性(包含原型鏈中的屬性), 複製到obj對象, 若是存在同名屬性則覆蓋

_.pick

_.pick = function(obj) {
    // 建立一個對象, 存放複製的指定屬性
    var result = {};
    // 從第二個參數開始合併爲一個存放屬性名列表的數組
    each(_.flatten(slice.call(arguments, 1)), function(key) {
        // 循環屬性名列表, 若是obj中存在該屬性, 則將其複製到result對象
        if( key in obj)
            result[key] = obj[key];
    });
    // 返回複製結果
    return result;
};

這個函數返回一個新對象, 並從obj中複製指定的屬性到新對象中,第2個參數開始爲指定的須要複製的屬性名

_.defaults

_.defaults = function(obj) {
    // 從第二個參數開始可指定多個對象, 這些對象中的屬性將被依次複製到obj對象中(若是obj對象中不存在該屬性的話)
    each(slice.call(arguments, 1), function(source) {
        // 遍歷每一個對象中的全部屬性
        for(var prop in source) {
            // 若是obj中不存在或屬性值轉換爲Boolean類型後值爲false, 則將屬性複製到obj中
            if(obj[prop] == null)
                obj[prop] = source[prop];
        }
    });
    return obj;
};

這個函數將obj中不存在或轉換爲Boolean類型後值爲false的屬性, 從參數中指定的一個或多個對象中複製到obj,通常用於給對象指定默認值

_.clone

_.clone = function(obj) {
    // 不支持非數組和對象類型的數據
    if(!_.isObject(obj))
        return obj;
    // 複製並返回數組或對象
    return _.isArray(obj) ? obj.slice() : _.extend({}, obj);
};

這個函數建立一個obj的副本, 返回一個新的對象, 該對象包含obj中的全部屬性和值的狀態

_.tap

_.tap = function(obj, interceptor) {
    interceptor(obj);
    return obj;
};

這個函數執行一個函數, 並將obj做爲參數傳遞給該函數, 函數執行完畢後最終返回obj對象

eq

function eq(a, b, stack) {
    // 檢查兩個簡單數據類型的值是否相等
    // 對於複合數據類型, 若是它們來自同一個引用, 則認爲其相等
    // 若是被比較的值其中包含0, 則檢查另外一個值是否爲-0, 由於 0 === -0 是成立的
    // 而 1 / 0 == 1 / -0 是不成立的(1 / 0值爲Infinity, 1 / -0值爲-Infinity, 而Infinity不等於-Infinity)
    if(a === b)
        return a !== 0 || 1 / a == 1 / b;
    // 將數據轉換爲布爾類型後若是值爲false, 將判斷兩個值的數據類型是否相等(由於null與undefined, false, 0, 空字符串, 在非嚴格比較下值是相等的)
    if(a == null || b == null)
        return a === b;
    // 若是進行比較的數據是一個Underscore封裝的對象(具備_chain屬性的對象被認爲是Underscore對象)
    // 則將對象解封后獲取自己的數據(經過_wrapped訪問), 而後再對自己的數據進行比較
    // 它們的關係相似與一個jQuery封裝的DOM對象, 和瀏覽器自己建立的DOM對象
    if(a._chain)
        a = a._wrapped;
    if(b._chain)
        b = b._wrapped;
    // 若是對象提供了自定義的isEqual方法(此處的isEqual方法並不是Undersocre對象的isEqual方法, 由於在上一步已經對Undersocre對象進行了解封)
    // 則使用對象自定義的isEqual方法與另外一個對象進行比較
    if(a.isEqual && _.isFunction(a.isEqual))
        return a.isEqual(b);
    if(b.isEqual && _.isFunction(b.isEqual))
        return b.isEqual(a);
    // 對兩個數據的數據類型進行驗證
    // 獲取對象a的數據類型(經過Object.prototype.toString方法)
    var className = toString.call(a);
    // 若是對象a的數據類型與對象b不匹配, 則認爲兩個數據值也不匹配
    if(className != toString.call(b))
        return false;
    // 執行到此處, 能夠確保須要比較的兩個數據均爲複合數據類型, 且數據類型相等
    // 經過switch檢查數據的數據類型, 針對不一樣數據類型進行不一樣的比較
    // (此處不包括對數組和對象類型, 由於它們可能包含更深層次的數據, 將在後面進行深層比較)
    switch (className) {
        case '[object String]':
            // 若是被比較的是字符串類型(其中a的是經過new String()建立的字符串)
            // 則將B轉換爲String對象後進行匹配(這裏匹配並不是進行嚴格的數據類型檢查, 由於它們並不是來自同一個對象的引用)
            // 在調用 == 進行比較時, 會自動調用對象的toString()方法, 返回兩個簡單數據類型的字符串
            return a == String(b);
        case '[object Number]':
            // 經過+a將a轉成一個Number, 若是a被轉換以前與轉換以後不相等, 則認爲a是一個NaN類型
            // 由於NaN與NaN是不相等的, 所以當a值爲NaN時, 沒法簡單地使用a == b進行匹配, 而是用相同的方法檢查b是否爲NaN(即 b != +b)
            // 當a值是一個非NaN的數據時, 則檢查a是否爲0, 由於當b爲-0時, 0 === -0是成立的(實際上它們在邏輯上屬於兩個不一樣的數據)
            return a != +a ? b != +b : (a == 0 ? 1 / a == 1 / b : a == +b);
        case '[object Date]':
        // 對日期類型沒有使用return或break, 所以會繼續執行到下一步(不管數據類型是否爲Boolean類型, 由於下一步將對Boolean類型進行檢查)
        case '[object Boolean]':
            // 將日期或布爾類型轉換爲數字
            // 日期類型將轉換爲數值類型的時間戳(無效的日期格式將被換轉爲NaN)
            // 布爾類型中, true被轉換爲1, false被轉換爲0
            // 比較兩個日期或布爾類型被轉換爲數字後是否相等
            return +a == +b;
        case '[object RegExp]':
            // 正則表達式類型, 經過source訪問表達式的字符串形式
            // 檢查兩個表達式的字符串形式是否相等
            // 檢查兩個表達式的全局屬性是否相同(包括g, i, m)
            // 若是徹底相等, 則認爲兩個數據相等
            return a.source == b.source && a.global == b.global && a.multiline == b.multiline && a.ignoreCase == b.ignoreCase;
    }
    // 當執行到此時, ab兩個數據應該爲類型相同的對象或數組類型
    if( typeof a != 'object' || typeof b != 'object')
        return false;
    // stack(堆)是在isEqual調用eq函數時內部傳遞的空數組, 在後面比較對象和數據的內部迭代中調用eq方法也會傳遞
    // length記錄堆的長度
    var length = stack.length;
    while(length--) {
        // 若是堆中的某個對象與數據a匹配, 則認爲相等
        if(stack[length] == a)
            return true;
    }
    // 將數據a添加到堆中
    stack.push(a);
    // 定義一些局部變量
    var size = 0, result = true;
    // 經過遞歸深層比較對象和數組
    if(className == '[object Array]') {
        // 被比較的數據爲數組類型
        // size記錄數組的長度
        // result比較兩個數組的長度是否一致, 若是長度不一致, 則方法的最後將返回result(即false)
        size = a.length;
        result = size == b.length;
        // 若是兩個數組的長度一致
        if(result) {
            // 調用eq方法對數組中的元素進行迭代比較(若是數組中包含二維數組或對象, eq方法會進行深層比較)
            while(size--) {
                // 在確保兩個數組都存在當前索引的元素時, 調用eq方法深層比較(將堆數據傳遞給eq方法)
                // 將比較的結果存儲到result變量, 若是result爲false(即在比較中獲得某個元素的數據不一致), 則中止迭代
                if(!( result = size in a == size in b && eq(a[size], b[size], stack)))
                    break;
            }
        }
    } else {
        // 被比較的數據爲對象類型
        // 若是兩個對象不是同一個類的實例(經過constructor屬性比較), 則認爲兩個對象不相等
        if('constructor' in a != 'constructor' in b || a.constructor != b.constructor)
            return false;
        // 深層比較兩個對象中的數據
        for(var key in a) {
            if(_.has(a, key)) {
                // size用於記錄比較過的屬性數量, 由於這裏遍歷的是a對象的屬性, 並比較b對象中該屬性的數據
                // 當b對象中的屬性數量多餘a對象時, 此處的邏輯成立, 但兩個對象並不相等
                size++;
                // 迭代調用eq方法, 深層比較兩個對象中的屬性值
                // 將比較的結果記錄到result變量, 當比較到不相等的數據時中止迭代
                if(!( result = _.has(b, key) && eq(a[key], b[key], stack)))
                    break;
            }
        }
        // 深層比較完畢, 這裏已經能夠確保在對象a中的全部數據, 對象b中也存在相同的數據
        // 根據size(對象屬性長度)檢查對象b中的屬性數量是否與對象a相等
        if(result) {
            // 遍歷對象b中的全部屬性
            for(key in b) {
                // 當size已經到0時(即對象a中的屬性數量已經遍歷完畢), 而對象b中還存在有屬性, 則對象b中的屬性多於對象a
                if(_.has(b, key) && !(size--))
                    break;
            }
            // 當對象b中的屬性多於對象a, 則認爲兩個對象不相等
            result = !size;
        }
    }
    // 函數執行完畢時, 從堆中移除第一個數據(在比較對象或數組時, 會迭代eq方法, 堆中可能存在多個數據)
    stack.pop();
    // 返回的result記錄了最終的比較結果
    return result;
}

eq函數只在isEqual方法中調用, 用於比較兩個數據的值是否相等,與 === 不一樣在於, eq更關注數據的值,若是進行比較的是兩個複合數據類型, 不只僅比較是否來自同一個引用, 且會進行深層比較(對兩個對象的結構和數據進行比較)

_.isEqual

_.isEqual = function(a, b) {
    return eq(a, b, []);
};

很少說了,就是內部函數eq的外部方法

_.isEmpty

_.isEmpty = function(obj) {
    // obj被轉換爲Boolean類型後值爲false
    if(obj == null)
        return true;
    // 檢查對象或字符串長度是否爲0
    if(_.isArray(obj) || _.isString(obj))
        return obj.length === 0;
    // 檢查對象(使用for in循環時將首先循環對象自己的屬性, 其次是原型鏈中的屬性), 所以若是第一個屬性是屬於對象自己的, 那麼該對象不是一個空對象
    for(var key in obj)
    if(_.has(obj, key))
        return false;
    // 全部數據類型均沒有經過驗證, 是一個空數據
    return true;
};

這個函數用於檢查數據是否爲空值, 包含'', false, 0, null, undefined, NaN, 空數組(數組長度爲0)和空對象(對象自己沒有任何屬性)

_.isElement

_.isElement = function(obj) {
    return !!(obj && obj.nodeType == 1);
};

這個函數用於驗證對象是不是一個DOM對象

_.isArray

_.isArray = nativeIsArray ||
function(obj) {
    return toString.call(obj) == '[object Array]';
};

這個函數用於驗證一個變量是不是數組

_.isObject

_.isObject = function(obj) {
    return obj === Object(obj);
};

這個函數用於驗證對象是不是一個複合數據類型的對象(即非基本數據類型String, Boolean, Number, null, undefined)

_.isArguments

_.isArguments = function(obj) {
    return toString.call(obj) == '[object Arguments]';
};
// 驗證isArguments函數, 若是運行環境沒法正常驗證arguments類型的數據, 則從新定義isArguments方法
if(!_.isArguments(arguments)) {
    // 對於環境沒法經過toString驗證arguments類型的, 則經過調用arguments獨有的callee方法來進行驗證
    _.isArguments = function(obj) {
        // callee是arguments的一個屬性, 指向對arguments所屬函數自身的引用
        return !!(obj && _.has(obj, 'callee'));
    };
}

這個函數用於檢查一個數據是不是一個arguments參數對象

_.isFunction / _.isString / _.isNumber / _.isDate / _.isRegExp

這幾個我就放在一塊兒說了,他們都是經過Object.prototype.toString.call(obj)的值來進行判斷的

_.isFinite

_.isFinite = function(obj) {
    return _.isNumber(obj) && isFinite(obj);
};

這個函數用於檢查一個數字是否爲有效數字且有效範圍(Number類型, 值在負無窮大 - 正無窮大之間)

_.isNaN

_.isNaN = function(obj) {
    return obj !== obj;
};

在js裏,全部數據中只有NaN與NaN不相等

_.isBoolean

_.isBoolean = function(obj) {
    // 支持字面量和對象形式的Boolean數據
    return obj === true || obj === false || toString.call(obj) == '[object Boolean]';
};

這個函數用於檢查數據是不是Boolean類型

_.isNull

_.isNull = function(obj) {
    return obj === null;
};

這個函數用於檢查數據是不是Null值

_.isUndefined

_.isUndefined = function(obj) {
    return obj === void 0;
};

這個函數用於檢查數據是不是Undefined值

_.has

_.has = function(obj, key) {
    return hasOwnProperty.call(obj, key);
};

這個函數檢查一個屬性是否屬於對象自己, 而非原型鏈中

_.noConflict

_.noConflict = function() {
    // previousUnderscore變量記錄了Underscore定義前_(下劃線)的值
    root._ = previousUnderscore;
    return this;
};

這個函數通常用於避免命名衝突或規範命名方式,放棄_(下劃線)命名的Underscore對象, 並返回Underscore對象

_.identity

_.identity = function(value) {
    return value;
};

這個函數返回與參數相同的值, 通常用於將一個數據的獲取方式轉換爲函數獲取方式(內部用於構建方法時做爲默認處理器函數)

_.times

_.times = function(n, iterator, context) {
    for(var i = 0; i < n; i++)
    iterator.call(context, i);
};

這個函數的做用是使指定的函數迭代執行n次(無參數)

_.escape

_.escape = function(string) {
    return ('' + string).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;').replace(/'/g, '&#x27;').replace(/\//g, '&#x2F;');
};

這個函數用於將HTML字符串中的特殊字符轉換爲HTML實體, 包含 & < > " ' \

_.result

_.result = function(object, property) {
    if(object == null)
        return null;
    // 獲取對象的值
    var value = object[property];
    // 若是值是一個函數, 則執行並返回, 不然將直接返回
    return _.isFunction(value) ? value.call(object) : value;
};

這個函數指定一個對象的屬性, 返回該屬性對應的值, 若是該屬性對應的是一個函數, 則會執行該函數並返回結果

_.mixin

_.mixin = function(obj) {
    // obj是一個集合一系列自定義方法的對象, 此處經過each遍歷對象的方法
    each(_.functions(obj), function(name) {
        // 經過addToWrapper函數將自定義方法添加到Underscore構建的對象中, 用於支持對象式調用
        // 同時將方法添加到 _ 自己, 用於支持函數式調用
        addToWrapper(name, _[name] = obj[name]);
    });
};

這個函數添加一系列自定義方法到Underscore對象中, 用於擴展Underscore插件

_.template

_.template = function(text, data, settings) {
    // 模板配置, 若是沒有指定配置項, 則使用templateSettings中指定的配置項
    settings = _.defaults(settings || {}, _.templateSettings);

    // 開始將模板解析爲可執行源碼
    var source = "__p+='" + text.replace(escaper, function(match) {
        // 將特殊符號轉移爲字符串形式
        return '\\' + escapes[match];
    }).replace(settings.escape || noMatch, function(match, code) {
        // 解析escape形式標籤 <%- %>, 將變量中包含的HTML經過_.escape函數轉換爲HTML實體
        return "'+\n_.escape(" + unescape(code) + ")+\n'";
    }).replace(settings.interpolate || noMatch, function(match, code) {
        // 解析interpolate形式標籤 <%= %>, 將模板內容做爲一個變量與其它字符串鏈接起來, 則會做爲一個變量輸出
        return "'+\n(" + unescape(code) + ")+\n'";
    }).replace(settings.evaluate || noMatch, function(match, code) {
        // 解析evaluate形式標籤 <% %>, evaluate標籤中存儲了須要執行的JavaScript代碼, 這裏結束當前的字符串拼接, 並在新的一行做爲JavaScript語法執行, 並將後面的內容再次做爲字符串的開始, 所以evaluate標籤內的JavaScript代碼就能被正常執行
        return "';\n" + unescape(code) + "\n;__p+='";
    }) + "';\n";
    if(!settings.variable)
        source = 'with(obj||{}){\n' + source + '}\n';
    source = "var __p='';" + "var print=function(){__p+=Array.prototype.join.call(arguments, '')};\n" + source + "return __p;\n";

    // 建立一個函數, 將源碼做爲函數執行體, 將obj和Underscore做爲參數傳遞給該函數
    var render = new Function(settings.variable || 'obj', '_', source);
    // 若是指定了模板的填充數據, 則替換模板內容, 並返回替換後的結果
    if(data)
        return render(data, _);
    // 若是沒有指定填充數據, 則返回一個函數, 該函數用於將接收到的數據替換到模板
    // 若是在程序中會屢次填充相同模板, 那麼在第一次調用時建議不指定填充數據, 在得到處理函數的引用後, 再直接調用會提升運行效率
    var template = function(data) {
        return render.call(this, data, _);
    };
    // 將建立的源碼字符串添加到函數對象中, 通常用於調試和測試
    template.source = 'function(' + (settings.variable || 'obj') + '){\n' + source + '}';
    // 沒有指定填充數據的狀況下, 返回處理函數句柄
    return template;
};

這個我要介紹的最後一個函數,也是我我的認爲比較重要的,它是Underscore模板解析方法, 用於將數據填充到一個模板字符串中,在模板體內, 可經過argments獲取2個參數, 分別爲填充數據(名稱爲obj)和Underscore對象(名稱爲_)

小結

今天一口氣把剩下的全部函數都介紹完了,真是累感不愛啊,不過在寫做這幾篇博客的過程當中,我也從Underscore這個框架中學到了不少東西,包括它的優雅的代碼風格(至少比我本身寫的優雅),還有一個優秀的庫整個的架構是怎麼搭建起來的。之後我還會繼續爲你們分享其餘的前端知識和學習心得,thx for reading, hope u enjoy

相關文章
相關標籤/搜索