Underscore源碼中文註釋(轉)

// Underscore.js 1.3.3node

// (c) 2009-2012 Jeremy Ashkenas, DocumentCloud Inc.git

// Underscore is freely distributable under the MIT license.github

// Portions of Underscore are inspired or borrowed from Prototype,正則表達式

// Oliver Steele's Functional, and John Resig's Micro-Templating.數組

// For all details and documentation:瀏覽器

// http://documentcloud.github.com/underscore緩存

(function() {app

// 建立一個全局對象, 在瀏覽器中表示爲window對象, 在Node.js中表示global對象

var root = this;
// 保存"_"(下劃線變量)被覆蓋以前的值

// 若是出現命名衝突或考慮到規範, 可經過_.noConflict()方法恢復"_"被Underscore佔用以前的值, 並返回Underscore對象以便從新命名

var previousUnderscore = root._;
// 建立一個空的對象常量, 便於內部共享使用

var breaker = {};
// 將內置對象的原型鏈緩存在局部變量, 方便快速調用

var ArrayProto = Array.prototype, //

ObjProto = Object.prototype, //

FuncProto = Function.prototype;
// 將內置對象原型中的經常使用方法緩存在局部變量, 方便快速調用

var slice = ArrayProto.slice, //

unshift = ArrayProto.unshift, //

toString = ObjProto.toString, //

hasOwnProperty = ObjProto.hasOwnProperty;
// 這裏定義了一些JavaScript 1.6提供的新方法

// 若是宿主環境中支持這些方法則優先調用, 若是宿主環境中沒有提供, 則會由Underscore實現

var nativeForEach = ArrayProto.forEach, //

nativeMap = ArrayProto.map, //

nativeReduce = ArrayProto.reduce, //

nativeReduceRight = ArrayProto.reduceRight, //

nativeFilter = ArrayProto.filter, //

nativeEvery = ArrayProto.every, //

nativeSome = ArrayProto.some, //

nativeIndexOf = ArrayProto.indexOf, //

nativeLastIndexOf = ArrayProto.lastIndexOf, //

nativeIsArray = Array.isArray, //

nativeKeys = Object.keys, //

nativeBind = FuncProto.bind;
// 建立對象式的調用方式, 將返回一個Underscore包裝器, 包裝器對象的原型中包含Underscore全部方法(相似與將DOM對象包裝爲一個jQuery對象)

var _ = function(obj) {

    // 全部Underscore對象在內部均經過wrapper對象進行構造

    return new wrapper(obj);

};

// 針對不一樣的宿主環境, 將Undersocre的命名變量存放到不一樣的對象中

if( typeof exports !== 'undefined') {// Node.js環境

    if( typeof module !== 'undefined' && module.exports) {

        exports = module.exports = _;

    }

    exports._ = _;

} else {// 瀏覽器環境中Underscore的命名變量被掛在window對象中

    root['_'] = _;

}
// 版本聲明

_.VERSION = '1.3.3';
// 集合相關的方法(數據和對象的通用處理方法)

// --------------------
// 迭代處理器, 對集合中每個元素執行處理器方法

var each = _.each = _.forEach = function(obj, iterator, context) {

    // 不處理空值

    if(obj == null)

        return;

    if(nativeForEach && obj.forEach === nativeForEach) {

        // 若是宿主環境支持, 則優先調用JavaScript 1.6提供的forEach方法

        obj.forEach(iterator, context);

    } else if(obj.length === +obj.length) {

        // 對<數組>中每個元素執行處理器方法

        for(var i = 0, l = obj.length; i < l; i++) {

            if( i in obj && iterator.call(context, obj[i], i, obj) === breaker)

                return;

        }

    } else {

        // 對<對象>中每個元素執行處理器方法

        for(var key in obj) {

            if(_.has(obj, key)) {

                if(iterator.call(context, obj[key], key, obj) === breaker)

                    return;

            }

        }

    }

};

// 迭代處理器, 與each方法的差別在於map會存儲每次迭代的返回值, 並做爲一個新的數組返回

_.map = _.collect = function(obj, iterator, context) {

    // 用於存放返回值的數組

    var results = [];

    if(obj == null)

        return results;

    // 優先調用宿主環境提供的map方法

    if(nativeMap && obj.map === nativeMap)

        return obj.map(iterator, context);

    // 迭代處理集合中的元素

    each(obj, function(value, index, list) {

        // 將每次迭代處理的返回值存儲到results數組

        results[results.length] = iterator.call(context, value, index, list);

    });

    // 返回處理結果

    if(obj.length === +obj.length)

        results.length = obj.length;

    return results;

};

// 將集合中每一個元素放入迭代處理器, 並將本次迭代的返回值做爲"memo"傳遞到下一次迭代, 通常用於累計結果或鏈接數據

_.reduce = _.foldl = _.inject = function(obj, iterator, memo, context) {

    // 經過參數數量檢查是否存在初始值

    var initial = arguments.length > 2;

    if(obj == null)

        obj = [];

    // 優先調用宿主環境提供的reduce方法

    if(nativeReduce && obj.reduce === nativeReduce && false) {

        if(context)

            iterator = _.bind(iterator, context);

        return initial ? obj.reduce(iterator, memo) : obj.reduce(iterator);

    }

    // 迭代處理集合中的元素

    each(obj, function(value, index, list) {

        if(!initial) {

            // 若是沒有初始值, 則將第一個元素做爲初始值; 若是被處理的是對象集合, 則默認值爲第一個屬性的值

            memo = value;

            initial = true;

        } else {

            // 記錄處理結果, 並將結果傳遞給下一次迭代

            memo = iterator.call(context, memo, value, index, list);

        }

    });

    if(!initial)

        throw new TypeError('Reduce of empty array with no initial value');

    return memo;

};

// 與reduce做用類似, 將逆向迭代集合中的元素(即從最後一個元素開始直到第一個元素)

_.reduceRight = _.foldr = function(obj, iterator, memo, context) {

    var initial = arguments.length > 2;

    if(obj == null)

        obj = [];

    // 優先調用宿主環境提供的reduceRight方法

    if(nativeReduceRight && obj.reduceRight === nativeReduceRight) {

        if(context)

            iterator = _.bind(iterator, context);

        return initial ? obj.reduceRight(iterator, memo) : obj.reduceRight(iterator);

    }

    // 逆轉集合中的元素順序

    var reversed = _.toArray(obj).reverse();

    if(context && !initial)

        iterator = _.bind(iterator, context);

    // 經過reduce方法處理數據

    return initial ? _.reduce(reversed, iterator, memo, context) : _.reduce(reversed, iterator);

};

// 遍歷集合中的元素, 返回第一個可以經過處理器驗證的元素

_.find = _.detect = function(obj, iterator, context) {

    // result存放第一個可以經過驗證的元素

    var result;

    // 經過any方法遍歷數據, 並記錄經過驗證的元素

    // (若是是在迭代中檢查處理器返回狀態, 這裏使用each方法會更合適)

    any(obj, function(value, index, list) {

        // 若是處理器返回的結果被轉換爲Boolean類型後值爲true, 則當前記錄並返回當前元素

        if(iterator.call(context, value, index, list)) {

            result = value;

            return true;

        }

    });

    return result;

};

// 與find方法做用相似, 但filter方法會記錄下集合中全部經過驗證的元素

_.filter = _.select = function(obj, iterator, context) {

    // 用於存儲經過驗證的元素數組

    var results = [];

    if(obj == null)

        return results;

    // 優先調用宿主環境提供的filter方法

    if(nativeFilter && obj.filter === nativeFilter)

        return obj.filter(iterator, context);

    // 迭代集合中的元素, 並將經過處理器驗證的元素放到數組中並返回

    each(obj, function(value, index, list) {

        if(iterator.call(context, value, index, list))

            results[results.length] = value;

    });

    return results;

};

// 與filter方法做用相反, 即返回沒有經過處理器驗證的元素列表

_.reject = function(obj, iterator, context) {

    var results = [];

    if(obj == null)

        return results;

    each(obj, function(value, index, list) {

        if(!iterator.call(context, value, index, list))

            results[results.length] = value;

    });

    return results;

};

// 若是集合中全部元素均能經過處理器驗證, 則返回true

_.every = _.all = function(obj, iterator, context) {

    var result = true;

    if(obj == null)

        return result;

    // 優先調用宿主環境提供的every方法

    if(nativeEvery && obj.every === nativeEvery)

        return obj.every(iterator, context);

    // 迭代集合中的元素

    each(obj, function(value, index, list) {

        // 這裏理解爲 result = (result && iterator.call(context, value, index, list))

        // 驗證處理器的結果被轉換爲Boolean類型後是否爲true值

        if(!( result = result && iterator.call(context, value, index, list)))

            return breaker;

    });

    return !!result;

};

// 檢查集合中任何一個元素在被轉換爲Boolean類型時, 是否爲true值?或者經過處理器處理後, 是否值爲true?

var any = _.some = _.any = function(obj, iterator, context) {

    // 若是沒有指定處理器參數, 則默認的處理器函數會返回元素自己, 並在迭代時經過將元素轉換爲Boolean類型來判斷是否爲true值

    iterator || ( iterator = _.identity);

    var result = false;

    if(obj == null)

        return result;

    // 優先調用宿主環境提供的some方法

    if(nativeSome && obj.some === nativeSome)

        return obj.some(iterator, context);

    // 迭代集合中的元素

    each(obj, function(value, index, list) {

        if(result || ( result = iterator.call(context, value, index, list)))

            return breaker;

    });

    return !!result;

};

// 檢查集合中是否有值與目標參數徹底匹配(同時將匹配數據類型)

_.include = _.contains = function(obj, target) {

    var found = false;

    if(obj == null)

        return found;

    // 優先調用宿主環境提供的Array.prototype.indexOf方法

    if(nativeIndexOf && obj.indexOf === nativeIndexOf)

        return obj.indexOf(target) != -1;

    // 經過any方法迭代集合中的元素, 驗證元素的值和類型與目標是否徹底匹配

    found = any(obj, function(value) {

        return value === target;

    });

    return found;

};

// 依次調用集合中全部元素的同名方法, 從第3個參數開始, 將被以此傳入到元素的調用方法中

// 返回一個數組, 存儲了全部方法的處理結果

_.invoke = function(obj, method) {

    // 調用同名方法時傳遞的參數(從第3個參數開始)

    var args = slice.call(arguments, 2);

    // 依次調用每一個元素的方法, 並將結果放入數組中返回

    return _.map(obj, function(value) {

        return (_.isFunction(method) ? method || value : value[method]).apply(value, args);

    });

};

// 遍歷一個由對象列表組成的數組, 並返回每一個對象中的指定屬性的值列表

_.pluck = function(obj, key) {

    // 若是某一個對象中不存在該屬性, 則返回undefined

    return _.map(obj, function(value) {

        return value[key];

    });

};

// 返回集合中的最大值, 若是不存在可比較的值, 則返回undefined

_.max = function(obj, iterator, context) {

    // 若是集合是一個數組, 且沒有使用處理器, 則使用Math.max獲取最大值

    // 通常會是在一個數組存儲了一系列Number類型的數據

    if(!iterator && _.isArray(obj) && obj[0] === +obj[0])

        return Math.max.apply(Math, obj);

    // 對於空值, 直接返回負無窮大

    if(!iterator && _.isEmpty(obj))

        return -Infinity;

    // 一個臨時的對象, computed用於在比較過程當中存儲最大值(臨時的)

    var result = {

        computed : -Infinity

    };

    // 迭代集合中的元素

    each(obj, function(value, index, list) {

        // 若是指定了處理器參數, 則比較的數據爲處理器返回的值, 不然直接使用each遍歷時的默認值

        var computed = iterator ? iterator.call(context, value, index, list) : value;

        // 若是比較值相比上一個值要大, 則將當前值放入result.value

        computed >= result.computed && ( result = {

            value : value,

            computed : computed

        });

    });

    // 返回最大值

    return result.value;

};

// 返回集合中的最小值, 處理過程與max方法一致

_.min = function(obj, iterator, context) {

    if(!iterator && _.isArray(obj) && obj[0] === +obj[0])

        return Math.min.apply(Math, obj);

    if(!iterator && _.isEmpty(obj))

        return Infinity;

    var result = {

        computed : Infinity

    };

    each(obj, function(value, index, list) {

        var computed = iterator ? iterator.call(context, value, index, list) : value;

        computed < result.computed && ( result = {

            value : value,

            computed : computed

        });

    });

    return result.value;

};

// 經過隨機數, 讓數組無須排列

_.shuffle = function(obj) {

    // shuffled變量存儲處理過程及最終的結果數據

    var shuffled = [], rand;

    // 迭代集合中的元素

    each(obj, function(value, index, list) {

        // 生成一個隨機數, 隨機數在<0-當前已處理的數量>之間

        rand = Math.floor(Math.random() * (index + 1));

        // 將已經隨機獲得的元素放到shuffled數組末尾

        shuffled[index] = shuffled[rand];

        // 在前面獲得的隨機數的位置插入最新值

        shuffled[rand] = value;

    });

    // 返回一個數組, 該數組中存儲了通過隨機混排的集合元素

    return shuffled;

};

// 對集合中元素, 按照特定的字段或值進行排列

// 相比Array.prototype.sort方法, sortBy方法支持對對象排序

_.sortBy = function(obj, val, context) {

    // val應該是對象的一個屬性, 或一個處理器函數, 若是是一個處理器, 則應該返回須要進行比較的數據

    var iterator = _.isFunction(val) ? val : function(obj) {

        return obj[val];

    };

    // 調用順序: _.pluck(_.map().sort());

    // 調用_.map()方法遍歷集合, 並將集合中的元素放到value節點, 將元素中須要進行比較的數據放到criteria屬性中

    // 調用sort()方法將集合中的元素按照criteria屬性中的數據進行順序排序

    // 調用pluck獲取排序後的對象集合並返回

    return _.pluck(_.map(obj, function(value, index, list) {

        return {

            value : value,

            criteria : iterator.call(context, value, index, list)

        };

    }).sort(function(left, right) {

        var a = left.criteria, b = right.criteria;

        if(a ===

            void 0)

            return 1;

        if(b ===

            void 0)

            return -1;

        return a < b ? -1 : a > b ? 1 : 0;

    }), 'value');

};

// 將集合中的元素, 按處理器返回的key分爲多個數組

_.groupBy = function(obj, val) {

    var result = {};

    // val將被轉換爲進行分組的處理器函數, 若是val不是一個Function類型的數據, 則將被做爲篩選元素時的key值

    var iterator = _.isFunction(val) ? val : function(obj) {

        return obj[val];

    };

    // 迭代集合中的元素

    each(obj, function(value, index) {

        // 將處理器的返回值做爲key, 並將相同的key元素放到一個新的數組

        var key = iterator(value, index);

        (result[key] || (result[key] = [])).push(value);

    });

    // 返回已分組的數據

    return result;

};

_.sortedIndex = function(array, obj, iterator) {

    iterator || ( iterator = _.identity);

    var low = 0, high = array.length;

    while(low < high) {

        var mid = (low + high) >> 1;

        iterator(array[mid]) < iterator(obj) ? low = mid + 1 : high = mid;

    }

    return low;

};

// 將一個集合轉換一個數組並返回

// 通常用於將arguments轉換爲數組, 或將對象無序集合轉換爲數據形式的有序集合

_.toArray = function(obj) {

    if(!obj)

        return [];

    if(_.isArray(obj))

        return slice.call(obj);

    // 將arguments轉換爲數組

    if(_.isArguments(obj))

        return slice.call(obj);

    if(obj.toArray && _.isFunction(obj.toArray))

        return obj.toArray();

    // 將對象轉換爲數組, 數組中包含對象中全部屬性的值列表(不包含對象原型鏈中的屬性)

    return _.values(obj);

};

// 計算集合中元素的數量

_.size = function(obj) {

    // 若是集合是一個數組, 則計算數組元素數量

    // 若是集合是一個對象, 則計算對象中的屬性數量(不包含對象原型鏈中的屬性)

    return _.isArray(obj) ? obj.length : _.keys(obj).length;

};

// 數組相關的方法

// ---------------
// 返回一個數組的第一個或順序指定的n個元素

_.first = _.head = _.take = function(array, n, guard) {

    // 若是沒有指定參數n, 則返回第一個元素

    // 若是指定了n, 則返回一個新的數組, 包含順序指定數量n個元素

    // guard參數用於肯定只返回第一個元素, 當guard爲true時, 指定數量n無效

    return (n != null) && !guard ? slice.call(array, 0, n) : array[0];

};

// 返回一個新數組, 包含除第一個元素外的其它元素, 或排除從最後一個元素開始向前指定n個元素

// 與first方法不一樣在於, first肯定須要的元素在數組以前的位置, initial肯定能排除的元素在數組最後的位置

_.initial = function(array, n, guard) {

    // 若是沒有傳遞參數n, 則默認返回除最後一個元素外的其它元素

    // 若是傳遞參數n, 則返回從最後一個元素開始向前的n個元素外的其它元素

    // guard用於肯定只返回一個元素, 當guard爲true時, 指定數量n無效

    return slice.call(array, 0, array.length - ((n == null) || guard ? 1 : n));

};

// 返回數組的最後一個或倒序指定的n個元素

_.last = function(array, n, guard) {

    if((n != null) && !guard) {

        // 計算並指定獲取的元素位置n, 直到數組末尾, 做爲一個新的數組返回

        return slice.call(array, Math.max(array.length - n, 0));

    } else {

        // 若是沒有指定數量, 或guard爲true時, 只返回最後一個元素

        return array[array.length - 1];

    }

};

// 獲取除了第一個或指定前n個元素外的其它元素

_.rest = _.tail = function(array, index, guard) {

    // 計算slice的第二個位置參數, 直到數組末尾

    // 若是沒有指定index, 或guard值爲true, 則返回除第一個元素外的其它元素

    // (index == null)值爲true時, 做爲參數傳遞給slice函數將被自動轉換爲1

    return slice.call(array, (index == null) || guard ? 1 : index);

};

// 返回數組中全部值能被轉換爲true的元素, 返回一個新的數組

// 不能被轉換的值包括 false, 0, '', null, undefined, NaN, 這些值將被轉換爲false

_.compact = function(array) {

    return _.filter(array, function(value) {

        return !!value;

    });

};

// 將一個多維數組合成爲一維數組, 支持深層合併

// shallow參數用於控制合併深度, 當shallow爲true時, 只合並第一層, 默認進行深層合併

_.flatten = function(array, shallow) {

    // 迭代數組中的每個元素, 並將返回值做爲demo傳遞給下一次迭代

    return _.reduce(array, function(memo, value) {

        // 若是元素依然是一個數組, 進行如下判斷:

        // - 若是不進行深層合併, 則使用Array.prototype.concat將當前數組和以前的數據進行鏈接

        // - 若是支持深層合併, 則迭代調用flatten方法, 直到底層元素再也不是數組類型

        if(_.isArray(value))

            return memo.concat( shallow ? value : _.flatten(value));

        // 數據(value)已經處於底層, 再也不是數組類型, 則將數據合併到memo中並返回

        memo[memo.length] = value;

        return memo;

    }, []);

};

// 篩選並返回當前數組中與指定數據不相等的差別數據(可參考difference方法註釋)

_.without = function(array) {

    return _.difference(array, slice.call(arguments, 1));

};

// 對數組中的數據進行去重(使用===進行比較)

// 當isSorted參數不爲false時, 將依次對數組中的元素調用include方法, 檢查相同元素是否已經被添加到返回值(數組)中

// 若是調用以前確保數組中數據按順序排列, 則能夠將isSorted設爲true, 它將經過與最後一個元素進行對比來排除相同值, 使用isSorted效率會高於默認的include方式

// uniq方法默認將以數組中的數據進行對比, 若是聲明iterator處理器, 則會根據處理器建立一個對比數組, 比較時以該數組中的數據爲準, 但最終返回的惟一數據仍然是原始數組

_.uniq = _.unique = function(array, isSorted, iterator) {

    // 若是使用了iterator處理器, 則先將當前數組中的數據會先通過按迭代器處理, 並返回一個處理後的新數組

    // 新數組用於做爲比較的基準

    var initial = iterator ? _.map(array, iterator) : array;

    // 用於記錄處理結果的臨時數組

    var results = [];

    // 若是數組中只有2個值, 則不須要使用include方法進行比較, 將isSorted設置爲true能提升運行效率

    if(array.length < 3)

        isSorted = true;

    // 使用reduce方法迭代並累加處理結果

    // initial變量是須要進行比較的基準數據, 它多是原始數組, 也多是處理器的結果集合(若是設置過iterator)

    _.reduce(initial, function(memo, value, index) {

        // 若是isSorted參數爲true, 則直接使用===比較記錄中的最後一個數據

        // 若是isSorted參數爲false, 則使用include方法與集合中的每個數據進行對比

        if( isSorted ? _.last(memo) !== value || !memo.length : !_.include(memo, value)) {

            // memo記錄了已經比較過的無重複數據

            // 根據iterator參數的狀態, memo中記錄的數據多是原始數據, 也多是處理器處理後的數據

            memo.push(value);

            // 處理結果數組中保存的始終爲原始數組中的數據

            results.push(array[index]);

        }

        return memo;

    }, []);

    // 返回處理結果, 它只包含數組中無重複的數據

    return results;

};

// union方法與uniq方法做用一致, 不一樣之處在於union容許在參數中傳入多個數組

_.union = function() {

    // union對參數中的多個數組進行淺層合併爲一個數組對象傳遞給uniq方法進行處理

    return _.uniq(_.flatten(arguments, true));

};

// 獲取當前數組與其它一個或多個數組的交集元素

// 從第二個參數開始爲須要進行比較的一個或多個數組

_.intersection = _.intersect = function(array) {

    // rest變量記錄須要進行比較的其它數組對象

    var rest = slice.call(arguments, 1);

    // 使用uniq方法去除當前數組中的重複數據, 避免重複計算

    // 對當前數組的數據經過處理器進行過濾, 並返回符合條件(比較相同元素)的數據

    return _.filter(_.uniq(array), function(item) {

        // 使用every方法驗證每個數組中都包含了須要對比的數據

        // 若是全部數組中均包含對比數據, 則所有返回true, 若是任意一個數組沒有包含該元素, 則返回false

        return _.every(rest, function(other) {

            // other參數存儲了每個須要進行對比的數組

            // item存儲了當前數組中須要進行對比的數據

            // 使用indexOf方法搜索數組中是否存在該元素(可參考indexOf方法註釋)

            return _.indexOf(other, item) >= 0;

        });

    });

};

// 篩選並返回當前數組中與指定數據不相等的差別數據

// 該函數通常用於刪除數組中指定的數據, 並獲得刪除後的新數組

// 該方法的做用與without相等, without方法參數形式上不容許數據被包含在數組中, 而difference方法參數形式上建議是數組(也能夠和without使用相同形式的參數)

_.difference = function(array) {

    // 對第2個參數開始的全部參數, 做爲一個數組進行合併(僅合併第一層, 而並不是深層合併)

    // rest變量存儲驗證數據, 在本方法中用於與原數據對比

    var rest = _.flatten(slice.call(arguments, 1), true);

    // 對合並後的數組數據進行過濾, 過濾條件是當前數組中不包含參數指定的驗證數據的內容

    // 將符合過濾條件的數據組合爲一個新的數組並返回

    return _.filter(array, function(value) {

        return !_.include(rest, value);

    });

};

// 將每一個數組的相同位置的數據做爲一個新的二維數組返回, 返回的數組長度以傳入參數中最大的數組長度爲準, 其它數組的空白位置使用undefined填充

// 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;

};

// 搜索一個元素在數組中首次出現的位置, 若是元素不存在則返回 -1

// 搜索時使用 === 對元素進行匹配

_.indexOf = function(array, item, isSorted) {

    if(array == null)

        return -1;

    var i, l;

    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

// 搜索時使用 === 對元素進行匹配

_.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;

};

// 根據區間和步長, 生成一系列整數, 並做爲數組返回

// start參數表示最小數

// stop參數表示最大數

// step參數表示生成多個數值之間的步長值

_.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;

};

// 函數相關方法

// ------------------
// 建立一個用於設置prototype的公共函數對象

var ctor = function() {

};

// 爲一個函數綁定執行上下文, 任何狀況下調用該函數, 函數中的this均指向context對象

// 綁定函數時, 能夠同時給函數傳遞調用形參

_.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, sargs.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;

    };

};

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

// 該方法通常在處理對象事件時使用, 例如:

// _(obj).bindAll(); // 或_(obj).bindAll('handlerClick');

// document.addEventListener('click', obj.handlerClick);

// 在handlerClick方法中, 上下文依然是obj對象

_.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));

    };

};

// 延時執行一個函數

// wait單位爲ms, 第3個參數開始將被依次傳遞給執行函數

_.delay = function(func, wait) {

    var args = slice.call(arguments, 2);

    return setTimeout(function() {

        return func.apply(null, args);

    }, wait);

};

// 延遲執行函數

// JavaScript中的setTimeout會被放到一個單獨的函數堆棧中執行, 執行時間是在當前堆棧中調用的函數都被執行完畢以後

// defer設置函數在1ms後執行, 目的是將func函數放到單獨的堆棧中, 等待當前函數執行完成後再執行

// defer方法通常用於處理DOM操做的優先級, 實現正確的邏輯流程和更流暢的交互體驗

_.defer = function(func) {

    return _.delay.apply(_, [func, 1].concat(slice.call(arguments, 1)));

};

// 函數節流方法, throttle方法主要用於控制函數的執行頻率, 在被控制的時間間隔內, 頻繁調用函數不會被屢次執行

// 在時間間隔內若是屢次調用了函數, 時間隔截止時會自動調用一次, 不須要等到時間截止後再手動調用(自動調用時不會有返回值)

// throttle函數通常用於處理複雜和調用頻繁的函數, 經過節流控制函數的調用頻率, 節省處理資源

// 例如window.onresize綁定的事件函數, 或element.onmousemove綁定的事件函數, 能夠用throttle進行包裝

// throttle方法返回一個函數, 該函數會自動調用func並進行節流控制

_.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;

    };

};

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

// -- throttle關注函數的執行頻率, 在指定頻率內函數只會被執行一次;

// -- debounce函數更關注函數執行的間隔, 即函數兩次的調用時間不能小於指定時間;

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

// debounce函數通常用於控制須要一段時間以後才能執行的操做, 例如在用戶輸入完畢200ms後提示用戶, 可使用debounce包裝一個函數, 綁定到onkeyup事件

// ----------------------------------------------------------------

// @param {Function} func 表示被執行的函數

// @param {Number} wait 表示容許的時間間隔, 在該時間範圍內重複調用會被從新推遲wait毫秒

// @param {Boolean} immediate 表示函數調用後是否當即執行, true爲當即調用, false爲在時間截止時調用

// debounce方法返回一個函數, 該函數會自動調用func並進行節流控制

_.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);

    };

};

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

// 該函數用於獲取和計算固定數據的邏輯, 如獲取用戶所用的瀏覽器類型

_.once = function(func) {

    // ran記錄函數是否被執行過

    // memo記錄函數最後一次執行的結果

    var ran = false, memo;

    return function() {

        // 若是函數已被執行過, 則直接返回第一次執行的結果

        if(ran)

            return memo;

        ran = true;

        return memo = func.apply(this, arguments);

    };

};

// 返回一個函數, 該函數會將當前函數做爲參數傳遞給一個包裹函數

// 在包裹函數中能夠經過第一個參數調用當前函數, 並返回結果

// 通常用於多個流程處理函數的低耦合組合調用

_.wrap = function(func, wrapper) {

    return function() {

        // 將當前函數做爲第一個參數, 傳遞給wrapper函數

        var args = [func].concat(slice.call(arguments, 0));

        // 返回wrapper函數的處理結果

        return wrapper.apply(this, args);

    };

};

// 將多個函數組合到一塊兒, 按照參數傳遞的順序, 後一個函數的返回值會被一次做爲參數傳遞給前一個函數做爲參數繼續處理

// _.compose(A, B, C); 等同於 A(B(C()));

// 該方法的缺點在於被關聯的函數處理的參數數量只能有一個, 若是須要傳遞多個參數, 能夠經過Array或Object複合數據類型進行組裝

_.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];

    };

};

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

// after方法通常用做異步的計數器, 例如在多個AJAX請求所有完成後須要執行一個函數, 則可使用after在每一個AJAX請求完成後調用

_.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);

        }

    };

};

// 對象相關方法

// ----------------
// 獲取一個對象的屬性名列表(不包含原型鏈中的屬性)

_.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 = function(obj) {

    return _.map(obj, _.identity);

};

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

_.functions = _.methods = function(obj) {

    var names = [];

    for(var key in obj) {

        if(_.isFunction(obj[key]))

            names.push(key);

    }

    return names.sort();

};

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

_.extend = function(obj) {

    // each循環參數中的一個或多個對象

    each(slice.call(arguments, 1), function(source) {

        // 將對象中的所有屬性複製或覆蓋到obj對象

        for(var prop in source) {

            obj[prop] = source[prop];

        }

    });

    return obj;

};

// 返回一個新對象, 並從obj中複製指定的屬性到新對象中

// 第2個參數開始爲指定的須要複製的屬性名(支持多個參數和深層數組)

_.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中不存在或轉換爲Boolean類型後值爲false的屬性, 從參數中指定的一個或多個對象中複製到obj

// 通常用於給對象指定默認值

_.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的副本, 返回一個新的對象, 該對象包含obj中的全部屬性和值的狀態

// clone函數不支持深層複製, 例如obj中的某個屬性存放着一個對象, 則該對象不會被複制

// 若是obj是一個數組, 則會建立一個相同的數組對象

_.clone = function(obj) {

    // 不支持非數組和對象類型的數據

    if(!_.isObject(obj))

        return obj;

    // 複製並返回數組或對象

    return _.isArray(obj) ? obj.slice() : _.extend({}, obj);

};

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

// 通常在建立一個方法鏈的時候會使用tap方法, 例如:

// _(obj).chain().tap(click).tap(mouseover).tap(mouseout);

_.tap = function(obj, interceptor) {

    interceptor(obj);

    return obj;

};

// eq函數只在isEqual方法中調用, 用於比較兩個數據的值是否相等

// 與 === 不一樣在於, 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 = function(a, b) {

    return eq(a, b, []);

};

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

_.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;

};

// 驗證對象是不是一個DOM對象

_.isElement = function(obj) {

    return !!(obj && obj.nodeType == 1);

};

// 驗證對象是不是一個數組類型, 優先調用宿主環境提供的isArray方法

_.isArray = nativeIsArray ||

function(obj) {

    return toString.call(obj) == '[object Array]';

};
// 驗證對象是不是一個複合數據類型的對象(即非基本數據類型String, Boolean, Number, null, undefined)

// 若是基本數據類型經過new進行建立, 則也屬於對象類型

_.isObject = function(obj) {

    return obj === Object(obj);

};

// 檢查一個數據是不是一個arguments參數對象

_.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'));

    };

}
// 驗證對象是不是一個函數類型

_.isFunction = function(obj) {

    return toString.call(obj) == '[object Function]';

};

// 驗證對象是不是一個字符串類型

_.isString = function(obj) {

    return toString.call(obj) == '[object String]';

};

// 驗證對象是不是一個數字類型

_.isNumber = function(obj) {

    return toString.call(obj) == '[object Number]';

};

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

_.isFinite = function(obj) {

    return _.isNumber(obj) && isFinite(obj);

};

// 檢查數據是否爲NaN類型(全部數據中只有NaN與NaN不相等)

_.isNaN = function(obj) {

    return obj !== obj;

};

// 檢查數據是否時Boolean類型

_.isBoolean = function(obj) {

    // 支持字面量和對象形式的Boolean數據

    return obj === true || obj === false || toString.call(obj) == '[object Boolean]';

};

// 檢查數據是不是一個Date類型

_.isDate = function(obj) {

    return toString.call(obj) == '[object Date]';

};

// 檢查數據是不是一個正則表達式類型

_.isRegExp = function(obj) {

    return toString.call(obj) == '[object RegExp]';

};

// 檢查數據是不是Null值

_.isNull = function(obj) {

    return obj === null;

};

// 檢查數據是不是Undefined(未定義的)值

_.isUndefined = function(obj) {

    return obj ===

    void 0;

};

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

_.has = function(obj, key) {

    return hasOwnProperty.call(obj, key);

};

// 工具函數

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

// 例如:

// var us = _.noConflict(); // 取消_(下劃線)命名, 並將Underscore對象存放於us變量中

// console.log(_); // _(下劃線)已經沒法再訪問Underscore對象, 而恢復爲Underscore定義前的值

_.noConflict = function() {

    // previousUnderscore變量記錄了Underscore定義前_(下劃線)的值

    root._ = previousUnderscore;

    return this;

};

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

_.identity = function(value) {

    return value;

};

// 使指定的函數迭代執行n次(無參數)

_.times = function(n, iterator, context) {

    for(var i = 0; i < n; i++)

    iterator.call(context, i);

};

// 將HTML字符串中的特殊字符轉換爲HTML實體, 包含 & < > " ' \

_.escape = function(string) {

    return ('' + string).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"').replace(/'/g, ''').replace(/\//g, '/');

};

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

_.result = function(object, property) {

    if(object == null)

        return null;

    // 獲取對象的值

    var value = object[property];

    // 若是值是一個函數, 則執行並返回, 不然將直接返回

    return _.isFunction(value) ? value.call(object) : value;

};

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

_.mixin = function(obj) {

    // obj是一個集合一系列自定義方法的對象, 此處經過each遍歷對象的方法

    each(_.functions(obj), function(name) {

        // 經過addToWrapper函數將自定義方法添加到Underscore構建的對象中, 用於支持對象式調用

        // 同時將方法添加到 _ 自己, 用於支持函數式調用

        addToWrapper(name, _[name] = obj[name]);

    });

};

// 獲取一個全局惟一標識, 標識從0開始累加

var idCounter = 0;

// prefix表示標識的前綴, 若是沒有指定前綴則直接返回標識, 通常用於給對象或DOM建立惟一ID

_.uniqueId = function(prefix) {

    var id = idCounter++;

    return prefix ? prefix + id : id;

};

// 定義模板的界定符號, 在template方法中使用

_.templateSettings = {

    // JavaScript可執行代碼的界定符

    evaluate : /<%([\s\S]+?)%>/g,

    // 直接輸出變量的界定符

    interpolate : /<%=([\s\S]+?)%>/g,

    // 須要將HTML輸出爲字符串(將特殊符號轉換爲字符串形式)的界定符

    escape : /<%-([\s\S]+?)%>/g

};
var noMatch = /.^/;
// escapes對象記錄了須要進行相互換轉的特殊符號與字符串形式的對應關係, 在二者進行相互轉換時做爲索引使用

// 首先根據字符串形式定義特殊字符

var escapes = {

    '\\' : '\\',

    "'" : "'",

    'r' : '\r',

    'n' : '\n',

    't' : '\t',

    'u2028' : '\u2028',

    'u2029' : '\u2029'

};

// 遍歷全部特殊字符字符串, 並以特殊字符做爲key記錄字符串形式

for(var p in escapes)

escapes[escapes[p]] = p;

// 定義模板中須要替換的特殊符號, 包含反斜槓, 單引號, 回車符, 換行符, 製表符, 行分隔符, 段落分隔符

// 在將字符串中的特殊符號轉換爲字符串形式時使用

var escaper = /\\|'|\r|\n|\t|\u2028|\u2029/g;

// 在將字符串形式的特殊符號進行反轉(替換)時使用

var unescaper = /\\(\\|'|r|n|t|u2028|u2029)/g;
// 反轉字符串中的特殊符號

// 在模板中涉及到須要執行的JavaScript源碼, 須要進行特殊符號反轉, 不然若是以HTML實體或字符串形式出現, 會拋出語法錯誤

var unescape = function(code) {

    return code.replace(unescaper, function(match, escape) {

        return escapes[escape];

    });

};

// Underscore模板解析方法, 用於將數據填充到一個模板字符串中

// 模板解析流程:

// 1. 將模板中的特殊符號轉換爲字符串

// 2. 解析escape形式標籤, 將內容解析爲HTML實體

// 3. 解析interpolate形式標籤, 輸出變量

// 4. 解析evaluate形式標籤, 建立可執行的JavaScript代碼

// 5. 生成一個處理函數, 該函數在獲得數據後可直接填充到模板並返回填充後的字符串

// 6. 根據參數返回填充後的字符串或處理函數的句柄

// -------------------

// 在模板體內, 可經過argments獲取2個參數, 分別爲填充數據(名稱爲obj)和Underscore對象(名稱爲_)

_.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對象的方法鏈操做, 可參考 wrapper.prototype.chain

_.chain = function(obj) {

    return _(obj).chain();

};

// Underscore對象封裝相關方法

// ---------------
// 建立一個包裝器, 將一些原始數據進行包裝

// 全部的undersocre對象, 內部均經過wrapper函數進行構造和封裝

// Underscore與wrapper的內部關係:

// -內部定義變量_, 將Underscore相關的方法添加到_, 這樣就能夠支持函數式的調用, 如_.bind()

// -內部定義wrapper類, 將_的原型對象指向wrapper類的原型

// -將Underscore相關的方法添加到wrapper原型, 建立的_對象就具有了Underscore的方法

// -將Array.prototype相關方法添加到wrapper原型, 建立的_對象就具有了Array.prototype中的方法

// -new _()時實際建立並返回了一個wrapper()對象, 並將原始數組存儲到_wrapped變量, 並將原始值做爲第一個參數調用對應方法

var wrapper = function(obj) {

    // 原始數據存放在包裝對象的_wrapped屬性中

    this._wrapped = obj;

};

// 將Underscore的原型對象指向wrapper的原型, 所以經過像wrapper原型中添加方法, Underscore對象也會具有一樣的方法

_.prototype = wrapper.prototype;
// 返回一個對象, 若是當前Underscore調用了chain()方法(即_chain屬性爲true), 則返回一個被包裝的Underscore對象, 不然返回對象自己

// result函數用於在構造方法鏈時返回Underscore的包裝對象

var result = function(obj, chain) {

    return chain ? _(obj).chain() : obj;

};

// 將一個自定義方法添加到Underscore對象中(實際是添加到wrapper的原型中, 而Underscore對象的原型指向了wrapper的原型)

var addToWrapper = function(name, func) {

    // 向wrapper原型中添加一個name函數, 該函數調用func函數, 並支持了方法鏈的處理

    wrapper.prototype[name] = function() {

        // 獲取func函數的參數, 並將當前的原始數據添加到第一個參數

        var args = slice.call(arguments);

        unshift.call(args, this._wrapped);

        // 執行函數並返回結果, 並經過result函數對方法鏈進行封裝, 若是當前調用了chain()方法, 則返回封裝後的Underscore對象, 不然返回對象自己

        return result(func.apply(_, args), this._chain);

    };

};

// 將內部定義的_(下劃線, 即Underscore方法集合對象)中的方法複製到wrapper的原型鏈中(即Underscore的原型鏈中)

// 這是爲了在構造對象式調用的Underscore對象時, 這些對象也會具備內部定義的Underscore方法

_.mixin(_);
// 將Array.prototype中的相關方法添加到Underscore對象中, 所以在封裝後的Underscore對象中也能夠直接調用Array.prototype中的方法

// 如: _([]).push()

each(['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift'], function(name) {

    // 獲取Array.prototype中對應方法的引用

    var method = ArrayProto[name];

    // 將該方法添加到Underscore對象中(實際是添加到wrapper的原型對象, 所以在建立Underscore對象時同時具有了該方法)

    wrapper.prototype[name] = function() {

        // _wrapped變量中存儲Underscore對象的原始值

        var wrapped = this._wrapped;

        // 調用Array對應的方法並返回結果

        method.apply(wrapped, arguments);

        var length = wrapped.length;

        if((name == 'shift' || name == 'splice') && length === 0)

            delete wrapped[0];

        // 即便是對於Array中的方法, Underscore一樣支持方法鏈操做

        return result(wrapped, this._chain);

    };

});

// 做用同於上一段代碼, 將數組中的一些方法添加到Underscore對象, 並支持了方法鏈操做

// 區別在於上一段代碼所添加的函數, 均返回Array對象自己(也多是封裝後的Array), concat, join, slice方法將返回一個新的Array對象(也多是封裝後的Array)

each(['concat', 'join', 'slice'], function(name) {

    var method = ArrayProto[name];

    wrapper.prototype[name] = function() {

        return result(method.apply(this._wrapped, arguments), this._chain);

    };

});

// 對Underscore對象進行鏈式操做的聲明方法

wrapper.prototype.chain = function() {

    // this._chain用來標示當前對象是否使用鏈式操做

    // 對於支持方法鏈操做的數據, 通常在具體方法中會返回一個Underscore對象, 並將原始值存放在_wrapped屬性中, 也能夠經過value()方法獲取原始值

    this._chain = true;

    return this;

};

// 返回被封裝的Underscore對象的原始值(存放在_wrapped屬性中)

wrapper.prototype.value = function() {

    return this._wrapped;

};

}).call(this);dom

相關文章
相關標籤/搜索