underscore源碼解析

   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);
相關文章
相關標籤/搜索