// Underscore.js 1.3.3 // (c) 2009-2012 Jeremy Ashkenas, DocumentCloud Inc. // Underscore is freely distributable under the MIT license. // 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() { // 創建一個全局對象, 在浏覽器中表示為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);