1 (function() { 2 3 // 建立一個全局對象, 在瀏覽器中表示爲window對象, 在Node.js中表示global對象 4 var root = this; 5 6 // 保存"_"(下劃線變量)被覆蓋以前的值 7 // 若是出現命名衝突或考慮到規範, 可經過_.noConflict()方法恢復"_"被Underscore佔用以前的值, 並返回Underscore對象以便從新命名 8 var previousUnderscore = root._; 9 10 // 建立一個空的對象常量, 便於內部共享使用 11 var breaker = {}; 12 13 // 將內置對象的原型鏈緩存在局部變量, 方便快速調用 14 var ArrayProto = Array.prototype, // 15 ObjProto = Object.prototype, // 16 FuncProto = Function.prototype; 17 18 // 將內置對象原型中的經常使用方法緩存在局部變量, 方便快速調用 19 var slice = ArrayProto.slice, // 20 unshift = ArrayProto.unshift, // 21 toString = ObjProto.toString, // 22 hasOwnProperty = ObjProto.hasOwnProperty; 23 24 // 這裏定義了一些JavaScript 1.6提供的新方法 25 // 若是宿主環境中支持這些方法則優先調用, 若是宿主環境中沒有提供, 則會由Underscore實現 26 var nativeForEach = ArrayProto.forEach, // 27 nativeMap = ArrayProto.map, // 28 nativeReduce = ArrayProto.reduce, // 29 nativeReduceRight = ArrayProto.reduceRight, // 30 nativeFilter = ArrayProto.filter, // 31 nativeEvery = ArrayProto.every, // 32 nativeSome = ArrayProto.some, // 33 nativeIndexOf = ArrayProto.indexOf, // 34 nativeLastIndexOf = ArrayProto.lastIndexOf, // 35 nativeIsArray = Array.isArray, // 36 nativeKeys = Object.keys, // 37 nativeBind = FuncProto.bind; 38 39 // 建立對象式的調用方式, 將返回一個Underscore包裝器, 包裝器對象的原型中包含Underscore全部方法(相似與將DOM對象包裝爲一個jQuery對象) 40 var _ = function(obj) { 41 // 全部Underscore對象在內部均經過wrapper對象進行構造 42 return new wrapper(obj); 43 }; 44 // 針對不一樣的宿主環境, 將Undersocre的命名變量存放到不一樣的對象中 45 if( typeof exports !== 'undefined') {// Node.js環境 46 if( typeof module !== 'undefined' && module.exports) { 47 exports = module.exports = _; 48 } 49 exports._ = _; 50 } else {// 瀏覽器環境中Underscore的命名變量被掛在window對象中 51 root['_'] = _; 52 } 53 54 // 版本聲明 55 _.VERSION = '1.3.3'; 56 57 // 集合相關的方法(數據和對象的通用處理方法) 58 // -------------------- 59 60 // 迭代處理器, 對集合中每個元素執行處理器方法 61 var each = _.each = _.forEach = function(obj, iterator, context) { 62 // 不處理空值 63 if(obj == null) 64 return; 65 if(nativeForEach && obj.forEach === nativeForEach) { 66 // 若是宿主環境支持, 則優先調用JavaScript 1.6提供的forEach方法 67 obj.forEach(iterator, context); 68 } else if(obj.length === +obj.length) { 69 // 對<數組>中每個元素執行處理器方法 70 for(var i = 0, l = obj.length; i < l; i++) { 71 if( i in obj && iterator.call(context, obj[i], i, obj) === breaker) 72 return; 73 } 74 } else { 75 // 對<對象>中每個元素執行處理器方法 76 for(var key in obj) { 77 if(_.has(obj, key)) { 78 if(iterator.call(context, obj[key], key, obj) === breaker) 79 return; 80 } 81 } 82 } 83 }; 84 // 迭代處理器, 與each方法的差別在於map會存儲每次迭代的返回值, 並做爲一個新的數組返回 85 _.map = _.collect = function(obj, iterator, context) { 86 // 用於存放返回值的數組 87 var results = []; 88 if(obj == null) 89 return results; 90 // 優先調用宿主環境提供的map方法 91 if(nativeMap && obj.map === nativeMap) 92 return obj.map(iterator, context); 93 // 迭代處理集合中的元素 94 each(obj, function(value, index, list) { 95 // 將每次迭代處理的返回值存儲到results數組 96 results[results.length] = iterator.call(context, value, index, list); 97 }); 98 // 返回處理結果 99 if(obj.length === +obj.length) 100 results.length = obj.length; 101 return results; 102 }; 103 // 將集合中每一個元素放入迭代處理器, 並將本次迭代的返回值做爲"memo"傳遞到下一次迭代, 通常用於累計結果或鏈接數據 104 _.reduce = _.foldl = _.inject = function(obj, iterator, memo, context) { 105 // 經過參數數量檢查是否存在初始值 106 var initial = arguments.length > 2; 107 if(obj == null) 108 obj = []; 109 // 優先調用宿主環境提供的reduce方法 110 if(nativeReduce && obj.reduce === nativeReduce && false) { 111 if(context) 112 iterator = _.bind(iterator, context); 113 return initial ? obj.reduce(iterator, memo) : obj.reduce(iterator); 114 } 115 // 迭代處理集合中的元素 116 each(obj, function(value, index, list) { 117 if(!initial) { 118 // 若是沒有初始值, 則將第一個元素做爲初始值; 若是被處理的是對象集合, 則默認值爲第一個屬性的值 119 memo = value; 120 initial = true; 121 } else { 122 // 記錄處理結果, 並將結果傳遞給下一次迭代 123 memo = iterator.call(context, memo, value, index, list); 124 } 125 }); 126 if(!initial) 127 throw new TypeError('Reduce of empty array with no initial value'); 128 return memo; 129 }; 130 // 與reduce做用類似, 將逆向迭代集合中的元素(即從最後一個元素開始直到第一個元素) 131 _.reduceRight = _.foldr = function(obj, iterator, memo, context) { 132 var initial = arguments.length > 2; 133 if(obj == null) 134 obj = []; 135 // 優先調用宿主環境提供的reduceRight方法 136 if(nativeReduceRight && obj.reduceRight === nativeReduceRight) { 137 if(context) 138 iterator = _.bind(iterator, context); 139 return initial ? obj.reduceRight(iterator, memo) : obj.reduceRight(iterator); 140 } 141 // 逆轉集合中的元素順序 142 var reversed = _.toArray(obj).reverse(); 143 if(context && !initial) 144 iterator = _.bind(iterator, context); 145 // 經過reduce方法處理數據 146 return initial ? _.reduce(reversed, iterator, memo, context) : _.reduce(reversed, iterator); 147 }; 148 // 遍歷集合中的元素, 返回第一個可以經過處理器驗證的元素 149 _.find = _.detect = function(obj, iterator, context) { 150 // result存放第一個可以經過驗證的元素 151 var result; 152 // 經過any方法遍歷數據, 並記錄經過驗證的元素 153 // (若是是在迭代中檢查處理器返回狀態, 這裏使用each方法會更合適) 154 any(obj, function(value, index, list) { 155 // 若是處理器返回的結果被轉換爲Boolean類型後值爲true, 則當前記錄並返回當前元素 156 if(iterator.call(context, value, index, list)) { 157 result = value; 158 return true; 159 } 160 }); 161 return result; 162 }; 163 // 與find方法做用相似, 但filter方法會記錄下集合中全部經過驗證的元素 164 _.filter = _.select = function(obj, iterator, context) { 165 // 用於存儲經過驗證的元素數組 166 var results = []; 167 if(obj == null) 168 return results; 169 // 優先調用宿主環境提供的filter方法 170 if(nativeFilter && obj.filter === nativeFilter) 171 return obj.filter(iterator, context); 172 // 迭代集合中的元素, 並將經過處理器驗證的元素放到數組中並返回 173 each(obj, function(value, index, list) { 174 if(iterator.call(context, value, index, list)) 175 results[results.length] = value; 176 }); 177 return results; 178 }; 179 // 與filter方法做用相反, 即返回沒有經過處理器驗證的元素列表 180 _.reject = function(obj, iterator, context) { 181 var results = []; 182 if(obj == null) 183 return results; 184 each(obj, function(value, index, list) { 185 if(!iterator.call(context, value, index, list)) 186 results[results.length] = value; 187 }); 188 return results; 189 }; 190 // 若是集合中全部元素均能經過處理器驗證, 則返回true 191 _.every = _.all = function(obj, iterator, context) { 192 var result = true; 193 if(obj == null) 194 return result; 195 // 優先調用宿主環境提供的every方法 196 if(nativeEvery && obj.every === nativeEvery) 197 return obj.every(iterator, context); 198 // 迭代集合中的元素 199 each(obj, function(value, index, list) { 200 // 這裏理解爲 result = (result && iterator.call(context, value, index, list)) 201 // 驗證處理器的結果被轉換爲Boolean類型後是否爲true值 202 if(!( result = result && iterator.call(context, value, index, list))) 203 return breaker; 204 }); 205 return !!result; 206 }; 207 // 檢查集合中任何一個元素在被轉換爲Boolean類型時, 是否爲true值?或者經過處理器處理後, 是否值爲true? 208 var any = _.some = _.any = function(obj, iterator, context) { 209 // 若是沒有指定處理器參數, 則默認的處理器函數會返回元素自己, 並在迭代時經過將元素轉換爲Boolean類型來判斷是否爲true值 210 iterator || ( iterator = _.identity); 211 var result = false; 212 if(obj == null) 213 return result; 214 // 優先調用宿主環境提供的some方法 215 if(nativeSome && obj.some === nativeSome) 216 return obj.some(iterator, context); 217 // 迭代集合中的元素 218 each(obj, function(value, index, list) { 219 if(result || ( result = iterator.call(context, value, index, list))) 220 return breaker; 221 }); 222 return !!result; 223 }; 224 // 檢查集合中是否有值與目標參數徹底匹配(同時將匹配數據類型) 225 _.include = _.contains = function(obj, target) { 226 var found = false; 227 if(obj == null) 228 return found; 229 // 優先調用宿主環境提供的Array.prototype.indexOf方法 230 if(nativeIndexOf && obj.indexOf === nativeIndexOf) 231 return obj.indexOf(target) != -1; 232 // 經過any方法迭代集合中的元素, 驗證元素的值和類型與目標是否徹底匹配 233 found = any(obj, function(value) { 234 return value === target; 235 }); 236 return found; 237 }; 238 // 依次調用集合中全部元素的同名方法, 從第3個參數開始, 將被以此傳入到元素的調用方法中 239 // 返回一個數組, 存儲了全部方法的處理結果 240 _.invoke = function(obj, method) { 241 // 調用同名方法時傳遞的參數(從第3個參數開始) 242 var args = slice.call(arguments, 2); 243 // 依次調用每一個元素的方法, 並將結果放入數組中返回 244 return _.map(obj, function(value) { 245 return (_.isFunction(method) ? method || value : value[method]).apply(value, args); 246 }); 247 }; 248 // 遍歷一個由對象列表組成的數組, 並返回每一個對象中的指定屬性的值列表 249 _.pluck = function(obj, key) { 250 // 若是某一個對象中不存在該屬性, 則返回undefined 251 return _.map(obj, function(value) { 252 return value[key]; 253 }); 254 }; 255 // 返回集合中的最大值, 若是不存在可比較的值, 則返回undefined 256 _.max = function(obj, iterator, context) { 257 // 若是集合是一個數組, 且沒有使用處理器, 則使用Math.max獲取最大值 258 // 通常會是在一個數組存儲了一系列Number類型的數據 259 if(!iterator && _.isArray(obj) && obj[0] === +obj[0]) 260 return Math.max.apply(Math, obj); 261 // 對於空值, 直接返回負無窮大 262 if(!iterator && _.isEmpty(obj)) 263 return -Infinity; 264 // 一個臨時的對象, computed用於在比較過程當中存儲最大值(臨時的) 265 var result = { 266 computed : -Infinity 267 }; 268 // 迭代集合中的元素 269 each(obj, function(value, index, list) { 270 // 若是指定了處理器參數, 則比較的數據爲處理器返回的值, 不然直接使用each遍歷時的默認值 271 var computed = iterator ? iterator.call(context, value, index, list) : value; 272 // 若是比較值相比上一個值要大, 則將當前值放入result.value 273 computed >= result.computed && ( result = { 274 value : value, 275 computed : computed 276 }); 277 }); 278 // 返回最大值 279 return result.value; 280 }; 281 // 返回集合中的最小值, 處理過程與max方法一致 282 _.min = function(obj, iterator, context) { 283 if(!iterator && _.isArray(obj) && obj[0] === +obj[0]) 284 return Math.min.apply(Math, obj); 285 if(!iterator && _.isEmpty(obj)) 286 return Infinity; 287 var result = { 288 computed : Infinity 289 }; 290 each(obj, function(value, index, list) { 291 var computed = iterator ? iterator.call(context, value, index, list) : value; 292 computed < result.computed && ( result = { 293 value : value, 294 computed : computed 295 }); 296 }); 297 return result.value; 298 }; 299 // 經過隨機數, 讓數組無須排列 300 _.shuffle = function(obj) { 301 // shuffled變量存儲處理過程及最終的結果數據 302 var shuffled = [], rand; 303 // 迭代集合中的元素 304 each(obj, function(value, index, list) { 305 // 生成一個隨機數, 隨機數在<0-當前已處理的數量>之間 306 rand = Math.floor(Math.random() * (index + 1)); 307 // 將已經隨機獲得的元素放到shuffled數組末尾 308 shuffled[index] = shuffled[rand]; 309 // 在前面獲得的隨機數的位置插入最新值 310 shuffled[rand] = value; 311 }); 312 // 返回一個數組, 該數組中存儲了通過隨機混排的集合元素 313 return shuffled; 314 }; 315 // 對集合中元素, 按照特定的字段或值進行排列 316 // 相比Array.prototype.sort方法, sortBy方法支持對對象排序 317 _.sortBy = function(obj, val, context) { 318 // val應該是對象的一個屬性, 或一個處理器函數, 若是是一個處理器, 則應該返回須要進行比較的數據 319 var iterator = _.isFunction(val) ? val : function(obj) { 320 return obj[val]; 321 }; 322 // 調用順序: _.pluck(_.map().sort()); 323 // 調用_.map()方法遍歷集合, 並將集合中的元素放到value節點, 將元素中須要進行比較的數據放到criteria屬性中 324 // 調用sort()方法將集合中的元素按照criteria屬性中的數據進行順序排序 325 // 調用pluck獲取排序後的對象集合並返回 326 return _.pluck(_.map(obj, function(value, index, list) { 327 return { 328 value : value, 329 criteria : iterator.call(context, value, index, list) 330 }; 331 }).sort(function(left, right) { 332 var a = left.criteria, b = right.criteria; 333 if(a === 334 void 0) 335 return 1; 336 if(b === 337 void 0) 338 return -1; 339 return a < b ? -1 : a > b ? 1 : 0; 340 }), 'value'); 341 }; 342 // 將集合中的元素, 按處理器返回的key分爲多個數組 343 _.groupBy = function(obj, val) { 344 var result = {}; 345 // val將被轉換爲進行分組的處理器函數, 若是val不是一個Function類型的數據, 則將被做爲篩選元素時的key值 346 var iterator = _.isFunction(val) ? val : function(obj) { 347 return obj[val]; 348 }; 349 // 迭代集合中的元素 350 each(obj, function(value, index) { 351 // 將處理器的返回值做爲key, 並將相同的key元素放到一個新的數組 352 var key = iterator(value, index); 353 (result[key] || (result[key] = [])).push(value); 354 }); 355 // 返回已分組的數據 356 return result; 357 }; 358 _.sortedIndex = function(array, obj, iterator) { 359 iterator || ( iterator = _.identity); 360 var low = 0, high = array.length; 361 while(low < high) { 362 var mid = (low + high) >> 1; 363 iterator(array[mid]) < iterator(obj) ? low = mid + 1 : high = mid; 364 } 365 return low; 366 }; 367 // 將一個集合轉換一個數組並返回 368 // 通常用於將arguments轉換爲數組, 或將對象無序集合轉換爲數據形式的有序集合 369 _.toArray = function(obj) { 370 if(!obj) 371 return []; 372 if(_.isArray(obj)) 373 return slice.call(obj); 374 // 將arguments轉換爲數組 375 if(_.isArguments(obj)) 376 return slice.call(obj); 377 if(obj.toArray && _.isFunction(obj.toArray)) 378 return obj.toArray(); 379 // 將對象轉換爲數組, 數組中包含對象中全部屬性的值列表(不包含對象原型鏈中的屬性) 380 return _.values(obj); 381 }; 382 // 計算集合中元素的數量 383 _.size = function(obj) { 384 // 若是集合是一個數組, 則計算數組元素數量 385 // 若是集合是一個對象, 則計算對象中的屬性數量(不包含對象原型鏈中的屬性) 386 return _.isArray(obj) ? obj.length : _.keys(obj).length; 387 }; 388 // 數組相關的方法 389 // --------------- 390 391 // 返回一個數組的第一個或順序指定的n個元素 392 _.first = _.head = _.take = function(array, n, guard) { 393 // 若是沒有指定參數n, 則返回第一個元素 394 // 若是指定了n, 則返回一個新的數組, 包含順序指定數量n個元素 395 // guard參數用於肯定只返回第一個元素, 當guard爲true時, 指定數量n無效 396 return (n != null) && !guard ? slice.call(array, 0, n) : array[0]; 397 }; 398 // 返回一個新數組, 包含除第一個元素外的其它元素, 或排除從最後一個元素開始向前指定n個元素 399 // 與first方法不一樣在於, first肯定須要的元素在數組以前的位置, initial肯定能排除的元素在數組最後的位置 400 _.initial = function(array, n, guard) { 401 // 若是沒有傳遞參數n, 則默認返回除最後一個元素外的其它元素 402 // 若是傳遞參數n, 則返回從最後一個元素開始向前的n個元素外的其它元素 403 // guard用於肯定只返回一個元素, 當guard爲true時, 指定數量n無效 404 return slice.call(array, 0, array.length - ((n == null) || guard ? 1 : n)); 405 }; 406 // 返回數組的最後一個或倒序指定的n個元素 407 _.last = function(array, n, guard) { 408 if((n != null) && !guard) { 409 // 計算並指定獲取的元素位置n, 直到數組末尾, 做爲一個新的數組返回 410 return slice.call(array, Math.max(array.length - n, 0)); 411 } else { 412 // 若是沒有指定數量, 或guard爲true時, 只返回最後一個元素 413 return array[array.length - 1]; 414 } 415 }; 416 // 獲取除了第一個或指定前n個元素外的其它元素 417 _.rest = _.tail = function(array, index, guard) { 418 // 計算slice的第二個位置參數, 直到數組末尾 419 // 若是沒有指定index, 或guard值爲true, 則返回除第一個元素外的其它元素 420 // (index == null)值爲true時, 做爲參數傳遞給slice函數將被自動轉換爲1 421 return slice.call(array, (index == null) || guard ? 1 : index); 422 }; 423 // 返回數組中全部值能被轉換爲true的元素, 返回一個新的數組 424 // 不能被轉換的值包括 false, 0, '', null, undefined, NaN, 這些值將被轉換爲false 425 _.compact = function(array) { 426 return _.filter(array, function(value) { 427 return !!value; 428 }); 429 }; 430 // 將一個多維數組合成爲一維數組, 支持深層合併 431 // shallow參數用於控制合併深度, 當shallow爲true時, 只合並第一層, 默認進行深層合併 432 _.flatten = function(array, shallow) { 433 // 迭代數組中的每個元素, 並將返回值做爲demo傳遞給下一次迭代 434 return _.reduce(array, function(memo, value) { 435 // 若是元素依然是一個數組, 進行如下判斷: 436 // - 若是不進行深層合併, 則使用Array.prototype.concat將當前數組和以前的數據進行鏈接 437 // - 若是支持深層合併, 則迭代調用flatten方法, 直到底層元素再也不是數組類型 438 if(_.isArray(value)) 439 return memo.concat( shallow ? value : _.flatten(value)); 440 // 數據(value)已經處於底層, 再也不是數組類型, 則將數據合併到memo中並返回 441 memo[memo.length] = value; 442 return memo; 443 }, []); 444 }; 445 // 篩選並返回當前數組中與指定數據不相等的差別數據(可參考difference方法註釋) 446 _.without = function(array) { 447 return _.difference(array, slice.call(arguments, 1)); 448 }; 449 // 對數組中的數據進行去重(使用===進行比較) 450 // 當isSorted參數不爲false時, 將依次對數組中的元素調用include方法, 檢查相同元素是否已經被添加到返回值(數組)中 451 // 若是調用以前確保數組中數據按順序排列, 則能夠將isSorted設爲true, 它將經過與最後一個元素進行對比來排除相同值, 使用isSorted效率會高於默認的include方式 452 // uniq方法默認將以數組中的數據進行對比, 若是聲明iterator處理器, 則會根據處理器建立一個對比數組, 比較時以該數組中的數據爲準, 但最終返回的惟一數據仍然是原始數組 453 _.uniq = _.unique = function(array, isSorted, iterator) { 454 // 若是使用了iterator處理器, 則先將當前數組中的數據會先通過按迭代器處理, 並返回一個處理後的新數組 455 // 新數組用於做爲比較的基準 456 var initial = iterator ? _.map(array, iterator) : array; 457 // 用於記錄處理結果的臨時數組 458 var results = []; 459 // 若是數組中只有2個值, 則不須要使用include方法進行比較, 將isSorted設置爲true能提升運行效率 460 if(array.length < 3) 461 isSorted = true; 462 // 使用reduce方法迭代並累加處理結果 463 // initial變量是須要進行比較的基準數據, 它多是原始數組, 也多是處理器的結果集合(若是設置過iterator) 464 _.reduce(initial, function(memo, value, index) { 465 // 若是isSorted參數爲true, 則直接使用===比較記錄中的最後一個數據 466 // 若是isSorted參數爲false, 則使用include方法與集合中的每個數據進行對比 467 if( isSorted ? _.last(memo) !== value || !memo.length : !_.include(memo, value)) { 468 // memo記錄了已經比較過的無重複數據 469 // 根據iterator參數的狀態, memo中記錄的數據多是原始數據, 也多是處理器處理後的數據 470 memo.push(value); 471 // 處理結果數組中保存的始終爲原始數組中的數據 472 results.push(array[index]); 473 } 474 return memo; 475 }, []); 476 // 返回處理結果, 它只包含數組中無重複的數據 477 return results; 478 }; 479 // union方法與uniq方法做用一致, 不一樣之處在於union容許在參數中傳入多個數組 480 _.union = function() { 481 // union對參數中的多個數組進行淺層合併爲一個數組對象傳遞給uniq方法進行處理 482 return _.uniq(_.flatten(arguments, true)); 483 }; 484 // 獲取當前數組與其它一個或多個數組的交集元素 485 // 從第二個參數開始爲須要進行比較的一個或多個數組 486 _.intersection = _.intersect = function(array) { 487 // rest變量記錄須要進行比較的其它數組對象 488 var rest = slice.call(arguments, 1); 489 // 使用uniq方法去除當前數組中的重複數據, 避免重複計算 490 // 對當前數組的數據經過處理器進行過濾, 並返回符合條件(比較相同元素)的數據 491 return _.filter(_.uniq(array), function(item) { 492 // 使用every方法驗證每個數組中都包含了須要對比的數據 493 // 若是全部數組中均包含對比數據, 則所有返回true, 若是任意一個數組沒有包含該元素, 則返回false 494 return _.every(rest, function(other) { 495 // other參數存儲了每個須要進行對比的數組 496 // item存儲了當前數組中須要進行對比的數據 497 // 使用indexOf方法搜索數組中是否存在該元素(可參考indexOf方法註釋) 498 return _.indexOf(other, item) >= 0; 499 }); 500 }); 501 }; 502 // 篩選並返回當前數組中與指定數據不相等的差別數據 503 // 該函數通常用於刪除數組中指定的數據, 並獲得刪除後的新數組 504 // 該方法的做用與without相等, without方法參數形式上不容許數據被包含在數組中, 而difference方法參數形式上建議是數組(也能夠和without使用相同形式的參數) 505 _.difference = function(array) { 506 // 對第2個參數開始的全部參數, 做爲一個數組進行合併(僅合併第一層, 而並不是深層合併) 507 // rest變量存儲驗證數據, 在本方法中用於與原數據對比 508 var rest = _.flatten(slice.call(arguments, 1), true); 509 // 對合並後的數組數據進行過濾, 過濾條件是當前數組中不包含參數指定的驗證數據的內容 510 // 將符合過濾條件的數據組合爲一個新的數組並返回 511 return _.filter(array, function(value) { 512 return !_.include(rest, value); 513 }); 514 }; 515 // 將每一個數組的相同位置的數據做爲一個新的二維數組返回, 返回的數組長度以傳入參數中最大的數組長度爲準, 其它數組的空白位置使用undefined填充 516 // zip方法應該包含多個參數, 且每一個參數應該均爲數組 517 _.zip = function() { 518 // 將參數轉換爲數組, 此時args是一個二維數組 519 var args = slice.call(arguments); 520 // 計算每個數組的長度, 並返回其中最大長度值 521 var length = _.max(_.pluck(args, 'length')); 522 // 依照最大長度值建立一個新的空數組, 該數組用於存儲處理結果 523 var results = new Array(length); 524 // 循環最大長度, 在每次循環將調用pluck方法獲取每一個數組中相同位置的數據(依次從0到最後位置) 525 // 將獲取到的數據存儲在一個新的數組, 放入results並返回 526 for(var i = 0; i < length; i++) 527 results[i] = _.pluck(args, "" + i); 528 // 返回的結果是一個二維數組 529 return results; 530 }; 531 // 搜索一個元素在數組中首次出現的位置, 若是元素不存在則返回 -1 532 // 搜索時使用 === 對元素進行匹配 533 _.indexOf = function(array, item, isSorted) { 534 if(array == null) 535 return -1; 536 var i, l; 537 if(isSorted) { 538 i = _.sortedIndex(array, item); 539 return array[i] === item ? i : -1; 540 } 541 // 優先調用宿主環境提供的indexOf方法 542 if(nativeIndexOf && array.indexOf === nativeIndexOf) 543 return array.indexOf(item); 544 // 循環並返回元素首次出現的位置 545 for( i = 0, l = array.length; i < l; i++) 546 if( i in array && array[i] === item) 547 return i; 548 // 沒有找到元素, 返回-1 549 return -1; 550 }; 551 // 返回一個元素在數組中最後一次出現的位置, 若是元素不存在則返回 -1 552 // 搜索時使用 === 對元素進行匹配 553 _.lastIndexOf = function(array, item) { 554 if(array == null) 555 return -1; 556 // 優先調用宿主環境提供的lastIndexOf方法 557 if(nativeLastIndexOf && array.lastIndexOf === nativeLastIndexOf) 558 return array.lastIndexOf(item); 559 var i = array.length; 560 // 循環並返回元素最後出現的位置 561 while(i--) 562 if( i in array && array[i] === item) 563 return i; 564 // 沒有找到元素, 返回-1 565 return -1; 566 }; 567 // 根據區間和步長, 生成一系列整數, 並做爲數組返回 568 // start參數表示最小數 569 // stop參數表示最大數 570 // step參數表示生成多個數值之間的步長值 571 _.range = function(start, stop, step) { 572 // 參數控制 573 if(arguments.length <= 1) { 574 // 若是沒有參數, 則start = 0, stop = 0, 在循環中不會生成任何數據, 將返回一個空數組 575 // 若是有1個參數, 則參數指定給stop, start = 0 576 stop = start || 0; 577 start = 0; 578 } 579 // 生成整數的步長值, 默認爲1 580 step = arguments[2] || 1; 581 582 // 根據區間和步長計算將生成的最大值 583 var len = Math.max(Math.ceil((stop - start) / step), 0); 584 var idx = 0; 585 var range = new Array(len); 586 587 // 生成整數列表, 並存儲到range數組 588 while(idx < len) { 589 range[idx++] = start; 590 start += step; 591 } 592 593 // 返回列表結果 594 return range; 595 }; 596 // 函數相關方法 597 // ------------------ 598 599 // 建立一個用於設置prototype的公共函數對象 600 var ctor = function() { 601 }; 602 // 爲一個函數綁定執行上下文, 任何狀況下調用該函數, 函數中的this均指向context對象 603 // 綁定函數時, 能夠同時給函數傳遞調用形參 604 _.bind = function bind(func, context) { 605 var bound, args; 606 // 優先調用宿主環境提供的bind方法 607 if(func.bind === nativeBind && nativeBind) 608 return nativeBind.apply(func, slice.call(arguments, 1)); 609 // func參數必須是一個函數(Function)類型 610 if(!_.isFunction(func)) 611 throw new TypeError; 612 // args變量存儲了bind方法第三個開始的參數列表, 每次調用時都將傳遞給func函數 613 args = slice.call(arguments, 2); 614 return bound = function() { 615 if(!(this instanceof bound)) 616 return func.apply(context, sargs.concat(slice.call(arguments))); 617 ctor.prototype = func.prototype; 618 var self = new ctor; 619 var result = func.apply(self, args.concat(slice.call(arguments))); 620 if(Object(result) === result) 621 return result; 622 return self; 623 }; 624 }; 625 // 將指定的函數, 或對象自己的全部函數上下本綁定到對象自己, 被綁定的函數在被調用時, 上下文對象始終指向對象自己 626 // 該方法通常在處理對象事件時使用, 例如: 627 // _(obj).bindAll(); // 或_(obj).bindAll('handlerClick'); 628 // document.addEventListener('click', obj.handlerClick); 629 // 在handlerClick方法中, 上下文依然是obj對象 630 _.bindAll = function(obj) { 631 // 第二個參數開始表示須要綁定的函數名稱 632 var funcs = slice.call(arguments, 1); 633 // 若是沒有指定特定的函數名稱, 則默認綁定對象自己全部類型爲Function的屬性 634 if(funcs.length == 0) 635 funcs = _.functions(obj); 636 // 循環並將全部的函數上下本設置爲obj對象自己 637 // each方法自己不會遍歷對象原型鏈中的方法, 但此處的funcs列表是經過_.functions方法獲取的, 它已經包含了原型鏈中的方法 638 each(funcs, function(f) { 639 obj[f] = _.bind(obj[f], obj); 640 }); 641 return obj; 642 }; 643 // memoize方法將返回一個函數, 該函數集成了緩存功能, 將通過計算的值緩存到局部變量並在下次調用時直接返回 644 // 若是計算結果是一個龐大的對象或數據, 使用時應該考慮內存佔用狀況 645 _.memoize = function(func, hasher) { 646 // 用於存儲緩存結果的memo對象 647 var memo = {}; 648 // hasher參數應該是一個function, 它用於返回一個key, 該key做爲讀取緩存的標識 649 // 若是沒有指定key, 則默認使用函數的第一個參數做爲key, 若是函數的第一個參數是複合數據類型, 可能會返回相似[Object object]的key, 這個key可能會形成後續計算的數據不正確 650 hasher || ( hasher = _.identity); 651 // 返回一個函數, 該函數首先經過檢查緩存, 再對沒有緩存過的數據進行調用 652 return function() { 653 var key = hasher.apply(this, arguments); 654 return _.has(memo, key) ? memo[key] : (memo[key] = func.apply(this, arguments)); 655 }; 656 }; 657 // 延時執行一個函數 658 // wait單位爲ms, 第3個參數開始將被依次傳遞給執行函數 659 _.delay = function(func, wait) { 660 var args = slice.call(arguments, 2); 661 return setTimeout(function() { 662 return func.apply(null, args); 663 }, wait); 664 }; 665 // 延遲執行函數 666 // JavaScript中的setTimeout會被放到一個單獨的函數堆棧中執行, 執行時間是在當前堆棧中調用的函數都被執行完畢以後 667 // defer設置函數在1ms後執行, 目的是將func函數放到單獨的堆棧中, 等待當前函數執行完成後再執行 668 // defer方法通常用於處理DOM操做的優先級, 實現正確的邏輯流程和更流暢的交互體驗 669 _.defer = function(func) { 670 return _.delay.apply(_, [func, 1].concat(slice.call(arguments, 1))); 671 }; 672 // 函數節流方法, throttle方法主要用於控制函數的執行頻率, 在被控制的時間間隔內, 頻繁調用函數不會被屢次執行 673 // 在時間間隔內若是屢次調用了函數, 時間隔截止時會自動調用一次, 不須要等到時間截止後再手動調用(自動調用時不會有返回值) 674 // throttle函數通常用於處理複雜和調用頻繁的函數, 經過節流控制函數的調用頻率, 節省處理資源 675 // 例如window.onresize綁定的事件函數, 或element.onmousemove綁定的事件函數, 能夠用throttle進行包裝 676 // throttle方法返回一個函數, 該函數會自動調用func並進行節流控制 677 _.throttle = function(func, wait) { 678 var context, args, timeout, throttling, more, result; 679 // whenDone變量調用了debounce方法, 所以在屢次連續調用函數時, 最後一次調用會覆蓋以前調用的定時器, 清除狀態函數也僅會被執行一次 680 // whenDone函數在最後一次函數執行的時間間隔截止時調用, 清除節流和調用過程當中記錄的一些狀態 681 var whenDone = _.debounce(function() { 682 more = throttling = false; 683 }, wait); 684 // 返回一個函數, 並在函數內進行節流控制 685 return function() { 686 // 保存函數的執行上下文和參數 687 context = this; 688 args = arguments; 689 // later函數在上一次函數調用時間間隔截止時執行 690 var later = function() { 691 // 清除timeout句柄, 方便下一次函數調用 692 timeout = null; 693 // more記錄了在上一次調用至時間間隔截止之間, 是否重複調用了函數 694 // 若是重複調用了函數, 在時間間隔截止時將自動再次調用函數 695 if(more) 696 func.apply(context, args); 697 // 調用whenDone, 用於在時間間隔後清除節流狀態 698 whenDone(); 699 }; 700 // timeout記錄了上一次函數執行的時間間隔句柄 701 // timeout時間間隔截止時調用later函數, later中將清除timeout, 並檢查是否須要再次調用函數 702 if(!timeout) 703 timeout = setTimeout(later, wait); 704 // throttling變量記錄上次調用的時間間隔是否已經結束, 便是否處於節流過程當中 705 // throttling在每次函數調用時設爲true, 表示須要進行節流, 在時間間隔截止時設置爲false(在whenDone函數中實現) 706 if(throttling) { 707 // 節流過程當中進行了屢次調用, 在more中記錄一個狀態, 表示在時間間隔截止時須要再次自動調用函數 708 more = true; 709 } else { 710 // 沒有處於節流過程, 多是第一次調用函數, 或已經超過上一次調用的間隔, 能夠直接調用函數 711 result = func.apply(context, args); 712 } 713 // 調用whenDone, 用於在時間間隔後清除節流狀態 714 whenDone(); 715 // throttling變量記錄函數調用時的節流狀態 716 throttling = true; 717 // 返回調用結果 718 return result; 719 }; 720 }; 721 // debounce與throttle方法相似, 用於函數節流, 它們的不一樣之處在於: 722 // -- throttle關注函數的執行頻率, 在指定頻率內函數只會被執行一次; 723 // -- debounce函數更關注函數執行的間隔, 即函數兩次的調用時間不能小於指定時間; 724 // 若是兩次函數的執行間隔小於wait, 定時器會被清除並從新建立, 這意味着連續頻繁地調用函數, 函數一直不會被執行, 直到某一次調用與上一次調用的時間不小於wait毫秒 725 // debounce函數通常用於控制須要一段時間以後才能執行的操做, 例如在用戶輸入完畢200ms後提示用戶, 可使用debounce包裝一個函數, 綁定到onkeyup事件 726 // ---------------------------------------------------------------- 727 // @param {Function} func 表示被執行的函數 728 // @param {Number} wait 表示容許的時間間隔, 在該時間範圍內重複調用會被從新推遲wait毫秒 729 // @param {Boolean} immediate 表示函數調用後是否當即執行, true爲當即調用, false爲在時間截止時調用 730 // debounce方法返回一個函數, 該函數會自動調用func並進行節流控制 731 _.debounce = function(func, wait, immediate) { 732 // timeout用於記錄函數上一次調用的執行狀態(定時器句柄) 733 // 當timeout爲null時, 表示上一次調用已經結束 734 var timeout; 735 // 返回一個函數, 並在函數內進行節流控制 736 return function() { 737 // 保持函數的上下文對象和參數 738 var context = this, args = arguments; 739 var later = function() { 740 // 設置timeout爲null 741 // later函數會在容許的時間截止時被調用 742 // 調用該函數時, 代表上一次函數執行時間已經超過了約定的時間間隔, 此時以後再進行調用都是被容許的 743 timeout = null; 744 if(!immediate) 745 func.apply(context, args); 746 }; 747 // 若是函數被設定爲當即執行, 且上一次調用的時間間隔已通過去, 則當即調用函數 748 if(immediate && !timeout) 749 func.apply(context, args); 750 // 建立一個定時器用於檢查和設置函數的調用狀態 751 // 建立定時器以前先清空上一次setTimeout句柄, 不管上一次綁定的函數是否已經被執行 752 // 若是本次函數在調用時, 上一次函數執行尚未開始(通常是immediate設置爲false時), 則函數的執行時間會被推遲, 所以timeout句柄會被從新建立 753 clearTimeout(timeout); 754 // 在容許的時間截止時調用later函數 755 timeout = setTimeout(later, wait); 756 }; 757 }; 758 // 建立一個只會被執行一次的函數, 若是該函數被重複調用, 將返回第一次執行的結果 759 // 該函數用於獲取和計算固定數據的邏輯, 如獲取用戶所用的瀏覽器類型 760 _.once = function(func) { 761 // ran記錄函數是否被執行過 762 // memo記錄函數最後一次執行的結果 763 var ran = false, memo; 764 return function() { 765 // 若是函數已被執行過, 則直接返回第一次執行的結果 766 if(ran) 767 return memo; 768 ran = true; 769 return memo = func.apply(this, arguments); 770 }; 771 }; 772 // 返回一個函數, 該函數會將當前函數做爲參數傳遞給一個包裹函數 773 // 在包裹函數中能夠經過第一個參數調用當前函數, 並返回結果 774 // 通常用於多個流程處理函數的低耦合組合調用 775 _.wrap = function(func, wrapper) { 776 return function() { 777 // 將當前函數做爲第一個參數, 傳遞給wrapper函數 778 var args = [func].concat(slice.call(arguments, 0)); 779 // 返回wrapper函數的處理結果 780 return wrapper.apply(this, args); 781 }; 782 }; 783 // 將多個函數組合到一塊兒, 按照參數傳遞的順序, 後一個函數的返回值會被一次做爲參數傳遞給前一個函數做爲參數繼續處理 784 // _.compose(A, B, C); 等同於 A(B(C())); 785 // 該方法的缺點在於被關聯的函數處理的參數數量只能有一個, 若是須要傳遞多個參數, 能夠經過Array或Object複合數據類型進行組裝 786 _.compose = function() { 787 // 獲取函數列表, 全部參數需均爲Function類型 788 var funcs = arguments; 789 // 返回一個供調用的函數句柄 790 return function() { 791 // 從後向前依次執行函數, 並將記錄的返回值做爲參數傳遞給前一個函數繼續處理 792 var args = arguments; 793 for(var i = funcs.length - 1; i >= 0; i--) { 794 args = [funcs[i].apply(this, args)]; 795 } 796 // 返回最後一次調用函數的返回值 797 return args[0]; 798 }; 799 }; 800 // 返回一個函數, 該函數做爲調用計數器, 當該函數被調用times次(或超過times次)後, func函數將被執行 801 // after方法通常用做異步的計數器, 例如在多個AJAX請求所有完成後須要執行一個函數, 則可使用after在每一個AJAX請求完成後調用 802 _.after = function(times, func) { 803 // 若是沒有指定或指定無效次數, 則func被直接調用 804 if(times <= 0) 805 return func(); 806 // 返回一個計數器函數 807 return function() { 808 // 每次調用計數器函數times減1, 調用times次以後執行func函數並返回func函數的返回值 809 if(--times < 1) { 810 return func.apply(this, arguments); 811 } 812 }; 813 }; 814 // 對象相關方法 815 // ---------------- 816 817 // 獲取一個對象的屬性名列表(不包含原型鏈中的屬性) 818 _.keys = nativeKeys || 819 function(obj) { 820 if(obj !== Object(obj)) 821 throw new TypeError('Invalid object'); 822 var keys = []; 823 // 記錄並返回對象的全部屬性名 824 for(var key in obj) 825 if(_.has(obj, key)) 826 keys[keys.length] = key; 827 return keys; 828 }; 829 830 // 返回一個對象中全部屬性的值列表(不包含原型鏈中的屬性) 831 _.values = function(obj) { 832 return _.map(obj, _.identity); 833 }; 834 // 獲取一個對象中全部屬性值爲Function類型的key列表, 並按key名進行排序(包含原型鏈中的屬性) 835 _.functions = _.methods = function(obj) { 836 var names = []; 837 for(var key in obj) { 838 if(_.isFunction(obj[key])) 839 names.push(key); 840 } 841 return names.sort(); 842 }; 843 // 將一個或多個對象的屬性(包含原型鏈中的屬性), 複製到obj對象, 若是存在同名屬性則覆蓋 844 _.extend = function(obj) { 845 // each循環參數中的一個或多個對象 846 each(slice.call(arguments, 1), function(source) { 847 // 將對象中的所有屬性複製或覆蓋到obj對象 848 for(var prop in source) { 849 obj[prop] = source[prop]; 850 } 851 }); 852 return obj; 853 }; 854 // 返回一個新對象, 並從obj中複製指定的屬性到新對象中 855 // 第2個參數開始爲指定的須要複製的屬性名(支持多個參數和深層數組) 856 _.pick = function(obj) { 857 // 建立一個對象, 存放複製的指定屬性 858 var result = {}; 859 // 從第二個參數開始合併爲一個存放屬性名列表的數組 860 each(_.flatten(slice.call(arguments, 1)), function(key) { 861 // 循環屬性名列表, 若是obj中存在該屬性, 則將其複製到result對象 862 if( key in obj) 863 result[key] = obj[key]; 864 }); 865 // 返回複製結果 866 return result; 867 }; 868 // 將obj中不存在或轉換爲Boolean類型後值爲false的屬性, 從參數中指定的一個或多個對象中複製到obj 869 // 通常用於給對象指定默認值 870 _.defaults = function(obj) { 871 // 從第二個參數開始可指定多個對象, 這些對象中的屬性將被依次複製到obj對象中(若是obj對象中不存在該屬性的話) 872 each(slice.call(arguments, 1), function(source) { 873 // 遍歷每一個對象中的全部屬性 874 for(var prop in source) { 875 // 若是obj中不存在或屬性值轉換爲Boolean類型後值爲false, 則將屬性複製到obj中 876 if(obj[prop] == null) 877 obj[prop] = source[prop]; 878 } 879 }); 880 return obj; 881 }; 882 // 建立一個obj的副本, 返回一個新的對象, 該對象包含obj中的全部屬性和值的狀態 883 // clone函數不支持深層複製, 例如obj中的某個屬性存放着一個對象, 則該對象不會被複制 884 // 若是obj是一個數組, 則會建立一個相同的數組對象 885 _.clone = function(obj) { 886 // 不支持非數組和對象類型的數據 887 if(!_.isObject(obj)) 888 return obj; 889 // 複製並返回數組或對象 890 return _.isArray(obj) ? obj.slice() : _.extend({}, obj); 891 }; 892 // 執行一個函數, 並將obj做爲參數傳遞給該函數, 函數執行完畢後最終返回obj對象 893 // 通常在建立一個方法鏈的時候會使用tap方法, 例如: 894 // _(obj).chain().tap(click).tap(mouseover).tap(mouseout); 895 _.tap = function(obj, interceptor) { 896 interceptor(obj); 897 return obj; 898 }; 899 // eq函數只在isEqual方法中調用, 用於比較兩個數據的值是否相等 900 // 與 === 不一樣在於, eq更關注數據的值 901 // 若是進行比較的是兩個複合數據類型, 不只僅比較是否來自同一個引用, 且會進行深層比較(對兩個對象的結構和數據進行比較) 902 function eq(a, b, stack) { 903 // 檢查兩個簡單數據類型的值是否相等 904 // 對於複合數據類型, 若是它們來自同一個引用, 則認爲其相等 905 // 若是被比較的值其中包含0, 則檢查另外一個值是否爲-0, 由於 0 === -0 是成立的 906 // 而 1 / 0 == 1 / -0 是不成立的(1 / 0值爲Infinity, 1 / -0值爲-Infinity, 而Infinity不等於-Infinity) 907 if(a === b) 908 return a !== 0 || 1 / a == 1 / b; 909 // 將數據轉換爲布爾類型後若是值爲false, 將判斷兩個值的數據類型是否相等(由於null與undefined, false, 0, 空字符串, 在非嚴格比較下值是相等的) 910 if(a == null || b == null) 911 return a === b; 912 // 若是進行比較的數據是一個Underscore封裝的對象(具備_chain屬性的對象被認爲是Underscore對象) 913 // 則將對象解封后獲取自己的數據(經過_wrapped訪問), 而後再對自己的數據進行比較 914 // 它們的關係相似與一個jQuery封裝的DOM對象, 和瀏覽器自己建立的DOM對象 915 if(a._chain) 916 a = a._wrapped; 917 if(b._chain) 918 b = b._wrapped; 919 // 若是對象提供了自定義的isEqual方法(此處的isEqual方法並不是Undersocre對象的isEqual方法, 由於在上一步已經對Undersocre對象進行了解封) 920 // 則使用對象自定義的isEqual方法與另外一個對象進行比較 921 if(a.isEqual && _.isFunction(a.isEqual)) 922 return a.isEqual(b); 923 if(b.isEqual && _.isFunction(b.isEqual)) 924 return b.isEqual(a); 925 // 對兩個數據的數據類型進行驗證 926 // 獲取對象a的數據類型(經過Object.prototype.toString方法) 927 var className = toString.call(a); 928 // 若是對象a的數據類型與對象b不匹配, 則認爲兩個數據值也不匹配 929 if(className != toString.call(b)) 930 return false; 931 // 執行到此處, 能夠確保須要比較的兩個數據均爲複合數據類型, 且數據類型相等 932 // 經過switch檢查數據的數據類型, 針對不一樣數據類型進行不一樣的比較 933 // (此處不包括對數組和對象類型, 由於它們可能包含更深層次的數據, 將在後面進行深層比較) 934 switch (className) { 935 case '[object String]': 936 // 若是被比較的是字符串類型(其中a的是經過new String()建立的字符串) 937 // 則將B轉換爲String對象後進行匹配(這裏匹配並不是進行嚴格的數據類型檢查, 由於它們並不是來自同一個對象的引用) 938 // 在調用 == 進行比較時, 會自動調用對象的toString()方法, 返回兩個簡單數據類型的字符串 939 return a == String(b); 940 case '[object Number]': 941 // 經過+a將a轉成一個Number, 若是a被轉換以前與轉換以後不相等, 則認爲a是一個NaN類型 942 // 由於NaN與NaN是不相等的, 所以當a值爲NaN時, 沒法簡單地使用a == b進行匹配, 而是用相同的方法檢查b是否爲NaN(即 b != +b) 943 // 當a值是一個非NaN的數據時, 則檢查a是否爲0, 由於當b爲-0時, 0 === -0是成立的(實際上它們在邏輯上屬於兩個不一樣的數據) 944 return a != +a ? b != +b : (a == 0 ? 1 / a == 1 / b : a == +b); 945 case '[object Date]': 946 // 對日期類型沒有使用return或break, 所以會繼續執行到下一步(不管數據類型是否爲Boolean類型, 由於下一步將對Boolean類型進行檢查) 947 case '[object Boolean]': 948 // 將日期或布爾類型轉換爲數字 949 // 日期類型將轉換爲數值類型的時間戳(無效的日期格式將被換轉爲NaN) 950 // 布爾類型中, true被轉換爲1, false被轉換爲0 951 // 比較兩個日期或布爾類型被轉換爲數字後是否相等 952 return +a == +b; 953 case '[object RegExp]': 954 // 正則表達式類型, 經過source訪問表達式的字符串形式 955 // 檢查兩個表達式的字符串形式是否相等 956 // 檢查兩個表達式的全局屬性是否相同(包括g, i, m) 957 // 若是徹底相等, 則認爲兩個數據相等 958 return a.source == b.source && a.global == b.global && a.multiline == b.multiline && a.ignoreCase == b.ignoreCase; 959 } 960 // 當執行到此時, ab兩個數據應該爲類型相同的對象或數組類型 961 if( typeof a != 'object' || typeof b != 'object') 962 return false; 963 // stack(堆)是在isEqual調用eq函數時內部傳遞的空數組, 在後面比較對象和數據的內部迭代中調用eq方法也會傳遞 964 // length記錄堆的長度 965 var length = stack.length; 966 while(length--) { 967 // 若是堆中的某個對象與數據a匹配, 則認爲相等 968 if(stack[length] == a) 969 return true; 970 } 971 // 將數據a添加到堆中 972 stack.push(a); 973 // 定義一些局部變量 974 var size = 0, result = true; 975 // 經過遞歸深層比較對象和數組 976 if(className == '[object Array]') { 977 // 被比較的數據爲數組類型 978 // size記錄數組的長度 979 // result比較兩個數組的長度是否一致, 若是長度不一致, 則方法的最後將返回result(即false) 980 size = a.length; 981 result = size == b.length; 982 // 若是兩個數組的長度一致 983 if(result) { 984 // 調用eq方法對數組中的元素進行迭代比較(若是數組中包含二維數組或對象, eq方法會進行深層比較) 985 while(size--) { 986 // 在確保兩個數組都存在當前索引的元素時, 調用eq方法深層比較(將堆數據傳遞給eq方法) 987 // 將比較的結果存儲到result變量, 若是result爲false(即在比較中獲得某個元素的數據不一致), 則中止迭代 988 if(!( result = size in a == size in b && eq(a[size], b[size], stack))) 989 break; 990 } 991 } 992 } else { 993 // 被比較的數據爲對象類型 994 // 若是兩個對象不是同一個類的實例(經過constructor屬性比較), 則認爲兩個對象不相等 995 if('constructor' in a != 'constructor' in b || a.constructor != b.constructor) 996 return false; 997 // 深層比較兩個對象中的數據 998 for(var key in a) { 999 if(_.has(a, key)) { 1000 // size用於記錄比較過的屬性數量, 由於這裏遍歷的是a對象的屬性, 並比較b對象中該屬性的數據 1001 // 當b對象中的屬性數量多餘a對象時, 此處的邏輯成立, 但兩個對象並不相等 1002 size++; 1003 // 迭代調用eq方法, 深層比較兩個對象中的屬性值 1004 // 將比較的結果記錄到result變量, 當比較到不相等的數據時中止迭代 1005 if(!( result = _.has(b, key) && eq(a[key], b[key], stack))) 1006 break; 1007 } 1008 } 1009 // 深層比較完畢, 這裏已經能夠確保在對象a中的全部數據, 對象b中也存在相同的數據 1010 // 根據size(對象屬性長度)檢查對象b中的屬性數量是否與對象a相等 1011 if(result) { 1012 // 遍歷對象b中的全部屬性 1013 for(key in b) { 1014 // 當size已經到0時(即對象a中的屬性數量已經遍歷完畢), 而對象b中還存在有屬性, 則對象b中的屬性多於對象a 1015 if(_.has(b, key) && !(size--)) 1016 break; 1017 } 1018 // 當對象b中的屬性多於對象a, 則認爲兩個對象不相等 1019 result = !size; 1020 } 1021 } 1022 // 函數執行完畢時, 從堆中移除第一個數據(在比較對象或數組時, 會迭代eq方法, 堆中可能存在多個數據) 1023 stack.pop(); 1024 // 返回的result記錄了最終的比較結果 1025 return result; 1026 } 1027 1028 // 對兩個數據的值進行比較(支持複合數據類型), 內部函數eq的外部方法 1029 _.isEqual = function(a, b) { 1030 return eq(a, b, []); 1031 }; 1032 // 檢查數據是否爲空值, 包含'', false, 0, null, undefined, NaN, 空數組(數組長度爲0)和空對象(對象自己沒有任何屬性) 1033 _.isEmpty = function(obj) { 1034 // obj被轉換爲Boolean類型後值爲false 1035 if(obj == null) 1036 return true; 1037 // 檢查對象或字符串長度是否爲0 1038 if(_.isArray(obj) || _.isString(obj)) 1039 return obj.length === 0; 1040 // 檢查對象(使用for in循環時將首先循環對象自己的屬性, 其次是原型鏈中的屬性), 所以若是第一個屬性是屬於對象自己的, 那麼該對象不是一個空對象 1041 for(var key in obj) 1042 if(_.has(obj, key)) 1043 return false; 1044 // 全部數據類型均沒有經過驗證, 是一個空數據 1045 return true; 1046 }; 1047 // 驗證對象是不是一個DOM對象 1048 _.isElement = function(obj) { 1049 return !!(obj && obj.nodeType == 1); 1050 }; 1051 // 驗證對象是不是一個數組類型, 優先調用宿主環境提供的isArray方法 1052 _.isArray = nativeIsArray || 1053 function(obj) { 1054 return toString.call(obj) == '[object Array]'; 1055 }; 1056 1057 // 驗證對象是不是一個複合數據類型的對象(即非基本數據類型String, Boolean, Number, null, undefined) 1058 // 若是基本數據類型經過new進行建立, 則也屬於對象類型 1059 _.isObject = function(obj) { 1060 return obj === Object(obj); 1061 }; 1062 // 檢查一個數據是不是一個arguments參數對象 1063 _.isArguments = function(obj) { 1064 return toString.call(obj) == '[object Arguments]'; 1065 }; 1066 // 驗證isArguments函數, 若是運行環境沒法正常驗證arguments類型的數據, 則從新定義isArguments方法 1067 if(!_.isArguments(arguments)) { 1068 // 對於環境沒法經過toString驗證arguments類型的, 則經過調用arguments獨有的callee方法來進行驗證 1069 _.isArguments = function(obj) { 1070 // callee是arguments的一個屬性, 指向對arguments所屬函數自身的引用 1071 return !!(obj && _.has(obj, 'callee')); 1072 }; 1073 } 1074 1075 // 驗證對象是不是一個函數類型 1076 _.isFunction = function(obj) { 1077 return toString.call(obj) == '[object Function]'; 1078 }; 1079 // 驗證對象是不是一個字符串類型 1080 _.isString = function(obj) { 1081 return toString.call(obj) == '[object String]'; 1082 }; 1083 // 驗證對象是不是一個數字類型 1084 _.isNumber = function(obj) { 1085 return toString.call(obj) == '[object Number]'; 1086 }; 1087 // 檢查一個數字是否爲有效數字且有效範圍(Number類型, 值在負無窮大 - 正無窮大之間) 1088 _.isFinite = function(obj) { 1089 return _.isNumber(obj) && isFinite(obj); 1090 }; 1091 // 檢查數據是否爲NaN類型(全部數據中只有NaN與NaN不相等) 1092 _.isNaN = function(obj) { 1093 return obj !== obj; 1094 }; 1095 // 檢查數據是否時Boolean類型 1096 _.isBoolean = function(obj) { 1097 // 支持字面量和對象形式的Boolean數據 1098 return obj === true || obj === false || toString.call(obj) == '[object Boolean]'; 1099 }; 1100 // 檢查數據是不是一個Date類型 1101 _.isDate = function(obj) { 1102 return toString.call(obj) == '[object Date]'; 1103 }; 1104 // 檢查數據是不是一個正則表達式類型 1105 _.isRegExp = function(obj) { 1106 return toString.call(obj) == '[object RegExp]'; 1107 }; 1108 // 檢查數據是不是Null值 1109 _.isNull = function(obj) { 1110 return obj === null; 1111 }; 1112 // 檢查數據是不是Undefined(未定義的)值 1113 _.isUndefined = function(obj) { 1114 return obj === 1115 void 0; 1116 }; 1117 // 檢查一個屬性是否屬於對象自己, 而非原型鏈中 1118 _.has = function(obj, key) { 1119 return hasOwnProperty.call(obj, key); 1120 }; 1121 // 工具函數 1122 // ----------------- 1123 1124 // 放棄_(下劃線)命名的Underscore對象, 並返回Underscore對象, 通常用於避免命名衝突或規範命名方式 1125 // 例如: 1126 // var us = _.noConflict(); // 取消_(下劃線)命名, 並將Underscore對象存放於us變量中 1127 // console.log(_); // _(下劃線)已經沒法再訪問Underscore對象, 而恢復爲Underscore定義前的值 1128 _.noConflict = function() { 1129 // previousUnderscore變量記錄了Underscore定義前_(下劃線)的值 1130 root._ = previousUnderscore; 1131 return this; 1132 }; 1133 // 返回與參數相同的值, 通常用於將一個數據的獲取方式轉換爲函數獲取方式(內部用於構建方法時做爲默認處理器函數) 1134 _.identity = function(value) { 1135 return value; 1136 }; 1137 // 使指定的函數迭代執行n次(無參數) 1138 _.times = function(n, iterator, context) { 1139 for(var i = 0; i < n; i++) 1140 iterator.call(context, i); 1141 }; 1142 // 將HTML字符串中的特殊字符轉換爲HTML實體, 包含 & < > " ' \ 1143 _.escape = function(string) { 1144 return ('' + string).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"').replace(/'/g, ''').replace(/\//g, '/'); 1145 }; 1146 // 指定一個對象的屬性, 返回該屬性對應的值, 若是該屬性對應的是一個函數, 則會執行該函數並返回結果 1147 _.result = function(object, property) { 1148 if(object == null) 1149 return null; 1150 // 獲取對象的值 1151 var value = object[property]; 1152 // 若是值是一個函數, 則執行並返回, 不然將直接返回 1153 return _.isFunction(value) ? value.call(object) : value; 1154 }; 1155 // 添加一系列自定義方法到Underscore對象中, 用於擴展Underscore插件 1156 _.mixin = function(obj) { 1157 // obj是一個集合一系列自定義方法的對象, 此處經過each遍歷對象的方法 1158 each(_.functions(obj), function(name) { 1159 // 經過addToWrapper函數將自定義方法添加到Underscore構建的對象中, 用於支持對象式調用 1160 // 同時將方法添加到 _ 自己, 用於支持函數式調用 1161 addToWrapper(name, _[name] = obj[name]); 1162 }); 1163 }; 1164 // 獲取一個全局惟一標識, 標識從0開始累加 1165 var idCounter = 0; 1166 // prefix表示標識的前綴, 若是沒有指定前綴則直接返回標識, 通常用於給對象或DOM建立惟一ID 1167 _.uniqueId = function(prefix) { 1168 var id = idCounter++; 1169 return prefix ? prefix + id : id; 1170 }; 1171 // 定義模板的界定符號, 在template方法中使用 1172 _.templateSettings = { 1173 // JavaScript可執行代碼的界定符 1174 evaluate : /<%([\s\S]+?)%>/g, 1175 // 直接輸出變量的界定符 1176 interpolate : /<%=([\s\S]+?)%>/g, 1177 // 須要將HTML輸出爲字符串(將特殊符號轉換爲字符串形式)的界定符 1178 escape : /<%-([\s\S]+?)%>/g 1179 }; 1180 1181 var noMatch = /.^/; 1182 1183 // escapes對象記錄了須要進行相互換轉的特殊符號與字符串形式的對應關係, 在二者進行相互轉換時做爲索引使用 1184 // 首先根據字符串形式定義特殊字符 1185 var escapes = { 1186 '\\' : '\\', 1187 "'" : "'", 1188 'r' : '\r', 1189 'n' : '\n', 1190 't' : '\t', 1191 'u2028' : '\u2028', 1192 'u2029' : '\u2029' 1193 }; 1194 // 遍歷全部特殊字符字符串, 並以特殊字符做爲key記錄字符串形式 1195 for(var p in escapes) 1196 escapes[escapes[p]] = p; 1197 // 定義模板中須要替換的特殊符號, 包含反斜槓, 單引號, 回車符, 換行符, 製表符, 行分隔符, 段落分隔符 1198 // 在將字符串中的特殊符號轉換爲字符串形式時使用 1199 var escaper = /\\|'|\r|\n|\t|\u2028|\u2029/g; 1200 // 在將字符串形式的特殊符號進行反轉(替換)時使用 1201 var unescaper = /\\(\\|'|r|n|t|u2028|u2029)/g; 1202 1203 // 反轉字符串中的特殊符號 1204 // 在模板中涉及到須要執行的JavaScript源碼, 須要進行特殊符號反轉, 不然若是以HTML實體或字符串形式出現, 會拋出語法錯誤 1205 var unescape = function(code) { 1206 return code.replace(unescaper, function(match, escape) { 1207 return escapes[escape]; 1208 }); 1209 }; 1210 // Underscore模板解析方法, 用於將數據填充到一個模板字符串中 1211 // 模板解析流程: 1212 // 1. 將模板中的特殊符號轉換爲字符串 1213 // 2. 解析escape形式標籤, 將內容解析爲HTML實體 1214 // 3. 解析interpolate形式標籤, 輸出變量 1215 // 4. 解析evaluate形式標籤, 建立可執行的JavaScript代碼 1216 // 5. 生成一個處理函數, 該函數在獲得數據後可直接填充到模板並返回填充後的字符串 1217 // 6. 根據參數返回填充後的字符串或處理函數的句柄 1218 // ------------------- 1219 // 在模板體內, 可經過argments獲取2個參數, 分別爲填充數據(名稱爲obj)和Underscore對象(名稱爲_) 1220 _.template = function(text, data, settings) { 1221 // 模板配置, 若是沒有指定配置項, 則使用templateSettings中指定的配置項 1222 settings = _.defaults(settings || {}, _.templateSettings); 1223 1224 // 開始將模板解析爲可執行源碼 1225 var source = "__p+='" + text.replace(escaper, function(match) { 1226 // 將特殊符號轉移爲字符串形式 1227 return '\\' + escapes[match]; 1228 }).replace(settings.escape || noMatch, function(match, code) { 1229 // 解析escape形式標籤 <%- %>, 將變量中包含的HTML經過_.escape函數轉換爲HTML實體 1230 return "'+\n_.escape(" + unescape(code) + ")+\n'"; 1231 }).replace(settings.interpolate || noMatch, function(match, code) { 1232 // 解析interpolate形式標籤 <%= %>, 將模板內容做爲一個變量與其它字符串鏈接起來, 則會做爲一個變量輸出 1233 return "'+\n(" + unescape(code) + ")+\n'"; 1234 }).replace(settings.evaluate || noMatch, function(match, code) { 1235 // 解析evaluate形式標籤 <% %>, evaluate標籤中存儲了須要執行的JavaScript代碼, 這裏結束當前的字符串拼接, 並在新的一行做爲JavaScript語法執行, 並將後面的內容再次做爲字符串的開始, 所以evaluate標籤內的JavaScript代碼就能被正常執行 1236 return "';\n" + unescape(code) + "\n;__p+='"; 1237 }) + "';\n"; 1238 if(!settings.variable) 1239 source = 'with(obj||{}){\n' + source + '}\n'; 1240 source = "var __p='';" + "var print=function(){__p+=Array.prototype.join.call(arguments, '')};\n" + source + "return __p;\n"; 1241 1242 // 建立一個函數, 將源碼做爲函數執行體, 將obj和Underscore做爲參數傳遞給該函數 1243 var render = new Function(settings.variable || 'obj', '_', source); 1244 // 若是指定了模板的填充數據, 則替換模板內容, 並返回替換後的結果 1245 if(data) 1246 return render(data, _); 1247 // 若是沒有指定填充數據, 則返回一個函數, 該函數用於將接收到的數據替換到模板 1248 // 若是在程序中會屢次填充相同模板, 那麼在第一次調用時建議不指定填充數據, 在得到處理函數的引用後, 再直接調用會提升運行效率 1249 var template = function(data) { 1250 return render.call(this, data, _); 1251 }; 1252 // 將建立的源碼字符串添加到函數對象中, 通常用於調試和測試 1253 template.source = 'function(' + (settings.variable || 'obj') + '){\n' + source + '}'; 1254 // 沒有指定填充數據的狀況下, 返回處理函數句柄 1255 return template; 1256 }; 1257 // 支持Underscore對象的方法鏈操做, 可參考 wrapper.prototype.chain 1258 _.chain = function(obj) { 1259 return _(obj).chain(); 1260 }; 1261 // Underscore對象封裝相關方法 1262 // --------------- 1263 1264 // 建立一個包裝器, 將一些原始數據進行包裝 1265 // 全部的undersocre對象, 內部均經過wrapper函數進行構造和封裝 1266 // Underscore與wrapper的內部關係: 1267 // -內部定義變量_, 將Underscore相關的方法添加到_, 這樣就能夠支持函數式的調用, 如_.bind() 1268 // -內部定義wrapper類, 將_的原型對象指向wrapper類的原型 1269 // -將Underscore相關的方法添加到wrapper原型, 建立的_對象就具有了Underscore的方法 1270 // -將Array.prototype相關方法添加到wrapper原型, 建立的_對象就具有了Array.prototype中的方法 1271 // -new _()時實際建立並返回了一個wrapper()對象, 並將原始數組存儲到_wrapped變量, 並將原始值做爲第一個參數調用對應方法 1272 var wrapper = function(obj) { 1273 // 原始數據存放在包裝對象的_wrapped屬性中 1274 this._wrapped = obj; 1275 }; 1276 // 將Underscore的原型對象指向wrapper的原型, 所以經過像wrapper原型中添加方法, Underscore對象也會具有一樣的方法 1277 _.prototype = wrapper.prototype; 1278 1279 // 返回一個對象, 若是當前Underscore調用了chain()方法(即_chain屬性爲true), 則返回一個被包裝的Underscore對象, 不然返回對象自己 1280 // result函數用於在構造方法鏈時返回Underscore的包裝對象 1281 var result = function(obj, chain) { 1282 return chain ? _(obj).chain() : obj; 1283 }; 1284 // 將一個自定義方法添加到Underscore對象中(實際是添加到wrapper的原型中, 而Underscore對象的原型指向了wrapper的原型) 1285 var addToWrapper = function(name, func) { 1286 // 向wrapper原型中添加一個name函數, 該函數調用func函數, 並支持了方法鏈的處理 1287 wrapper.prototype[name] = function() { 1288 // 獲取func函數的參數, 並將當前的原始數據添加到第一個參數 1289 var args = slice.call(arguments); 1290 unshift.call(args, this._wrapped); 1291 // 執行函數並返回結果, 並經過result函數對方法鏈進行封裝, 若是當前調用了chain()方法, 則返回封裝後的Underscore對象, 不然返回對象自己 1292 return result(func.apply(_, args), this._chain); 1293 }; 1294 }; 1295 // 將內部定義的_(下劃線, 即Underscore方法集合對象)中的方法複製到wrapper的原型鏈中(即Underscore的原型鏈中) 1296 // 這是爲了在構造對象式調用的Underscore對象時, 這些對象也會具備內部定義的Underscore方法 1297 _.mixin(_); 1298 1299 // 將Array.prototype中的相關方法添加到Underscore對象中, 所以在封裝後的Underscore對象中也能夠直接調用Array.prototype中的方法 1300 // 如: _([]).push() 1301 each(['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift'], function(name) { 1302 // 獲取Array.prototype中對應方法的引用 1303 var method = ArrayProto[name]; 1304 // 將該方法添加到Underscore對象中(實際是添加到wrapper的原型對象, 所以在建立Underscore對象時同時具有了該方法) 1305 wrapper.prototype[name] = function() { 1306 // _wrapped變量中存儲Underscore對象的原始值 1307 var wrapped = this._wrapped; 1308 // 調用Array對應的方法並返回結果 1309 method.apply(wrapped, arguments); 1310 var length = wrapped.length; 1311 if((name == 'shift' || name == 'splice') && length === 0) 1312 delete wrapped[0]; 1313 // 即便是對於Array中的方法, Underscore一樣支持方法鏈操做 1314 return result(wrapped, this._chain); 1315 }; 1316 }); 1317 // 做用同於上一段代碼, 將數組中的一些方法添加到Underscore對象, 並支持了方法鏈操做 1318 // 區別在於上一段代碼所添加的函數, 均返回Array對象自己(也多是封裝後的Array), concat, join, slice方法將返回一個新的Array對象(也多是封裝後的Array) 1319 each(['concat', 'join', 'slice'], function(name) { 1320 var method = ArrayProto[name]; 1321 wrapper.prototype[name] = function() { 1322 return result(method.apply(this._wrapped, arguments), this._chain); 1323 }; 1324 }); 1325 // 對Underscore對象進行鏈式操做的聲明方法 1326 wrapper.prototype.chain = function() { 1327 // this._chain用來標示當前對象是否使用鏈式操做 1328 // 對於支持方法鏈操做的數據, 通常在具體方法中會返回一個Underscore對象, 並將原始值存放在_wrapped屬性中, 也能夠經過value()方法獲取原始值 1329 this._chain = true; 1330 return this; 1331 }; 1332 // 返回被封裝的Underscore對象的原始值(存放在_wrapped屬性中) 1333 wrapper.prototype.value = function() { 1334 return this._wrapped; 1335 }; 1336 }).call(this);