1625行,解開 underscore.js 的面紗 - 第四章

繼續前面的內容,前文咱們提到了不少方法的講解,其實到這裏就已經差很少了,由於大部分代碼其實都是套路,一些基礎函數再靈活變化就能夠組成不少實用的功能。html

_.sortBy = function(obj, iteratee, context) {
    var index = 0;
    iteratee = cb(iteratee, context);
    return _.pluck(_.map(obj, function(value, key, list) {
      return {
        value: value,
        index: index++,
        criteria: iteratee(value, key, list)
      };
    }).sort(function(left, right) {
      var a = left.criteria;
      var b = right.criteria;
      if (a !== b) {
        if (a > b || a === void 0) return 1;
        if (a < b || b === void 0) return -1;
      }
      return left.index - right.index;
    }), 'value');
  };

_.sortBy,顧名思義這是一個對數組進行排序處理的函數,在原生 JAVASCRIPT 中 sort() 的詳情可參考 Array.prototype.sort()TypedArray.prototype.sort()_.sortBy 接收三個參數分別爲 obj、iteratee 回調和 context,其中 iteratee 與 context 是可選參數。
當傳入值只有 obj 時,應該限定 obj 類型爲數組且值爲 Number,爲何呢,這裏涉及到 JAVASCRIPT 對數字字符串的比較的問題了,JAVASCRIPT 在進行字符串比較的時候遵循的是二進制與運算,也就是說並非數字 length 越長就會大於 length 小的。舉個栗子:linux

_.sortBy([1, 2, 3, 4, 5, 6, 8, 7, 11, 13]);
  [1, 2, 3, 4, 5, 6, 7, 8, 11, 13]
  _.sortBy(['1', '2', '3', '4', '5', '6', '8', '7', '11', '13']);
  ["1", "11", "13", "2", "3", "4", "5", "6", "7", "8"]

同窗們都很聰明,不用我在說了,言歸正傳,當只有 obj 一個值且值爲 Number,那麼默認從左到右從小到大排序,爲何呢,我看下代碼,在 _.pluck 中代碼只作了一件事,就是整理數據,當沒有 iteratee 的時候執行 cb 函數裏的 if (value == null) return _.identity; 也就是至關於默認 iteratee function 爲 _.identity 即 return obj,因此 _.map 中回調的 criteria 值即 value。有點繞口,代碼起開(假定只有 obj 一個參數):數組

_.sortBy = function(obj) {
     var index = 0;
     return _.pluck(_.map(obj, function(value, key, list) {
       return {
         value: value,
         index: index++,
         criteria: (function(value, key, list) {
             return value;
           })(value, key, list);
       };
     }).sort(function(left, right) {
       var a = left.criteria;
       var b = right.criteria;
       if (a !== b) {
         if (a > b || a === void 0) return 1;
         if (a < b || b === void 0) return -1;
       }
       return left.index - right.index;
     }), 'value');
   };

這樣看上去就直白好多。整理完數據以後就是 arr.sort([compareFunction]) 進行排序,這裏不說了。當傳入參數有 iteratee 回調的時候,依舊老套路優化回調,而後根據回調函數裏面的設定決定 criteria 參數值,criteria 參數是 arr.sort([compareFunction]) 進行排序的關鍵標識,so必定要是 Number才行。網絡

var group = function(behavior, partition) {
    return function(obj, iteratee, context) {
      var result = partition ? [[], []] : {};
      iteratee = cb(iteratee, context);
      _.each(obj, function(value, index) {
        var key = iteratee(value, index, obj);
        behavior(result, value, key);
      });
      return result;
    };
  };

group 是一個內部函數,我以爲它最特別在於將回調稱之爲一個 behavior,爲何呢,由於雖然 behavior function 只能被動接受 value, index, obj 三個參數進行數值運算,但做者巧妙的用它結合 group 包裝出 _.groupBy_.indexBy_.countBy_.partition 四個函數,在實際開發中咱們處理數據時可能須要各類適用場景的工具,那麼把如何函數寫好寫活呢,group 給了我很大的啓發,言歸正傳,group 的 behavior 回調是在外部定義,源碼到這裏並不知道 behavior 是什麼東西,因此先一帶而過。數據結構

_.groupBy = group(function(result, value, key) {
    if (_.has(result, key)) result[key].push(value); else result[key] = [value];
  });

_.groupBy 官網定義把一個集合分組爲多個集合,經過 iterator 返回的結果進行分組. 若是 iterator 是一個字符串而不是函數, 那麼將使用 iterator 做爲各元素的屬性名來對比進行分組.併發

———————— 頹廢的分割線 ————————ide

從昨天到今天狀態不佳,昏天黑地的看了兩天電影,看到最後都不知道本身在看什麼,我須要吐槽一下小米路由器,因爲我是 linux 系統,做爲 deiban 死忠黨來講一臺不到兩千元的臺式機想要連接無線網絡,折騰的時間和金錢都不如再填個路由器作中繼划算,因而我買了這貨 小米路由器,它在路由器模式下還算能夠,一但調整到中繼模式,這徹底就是一個入坑的神展開,啪啪啪的隨時無間歇性斷網沒商量,莫名其妙的就連不上網了,即便鏈接上網絡網速都不如無線的通常有木有,在過去的一段時間裏我有 N 次想把這款路由器摔在地上(額,或者摔在牆上),但願你們不要吐槽我兩千塊都不到的臺式主力機,價錢雖然 lower 了點,但性能絕對夠用,對於 mac 黨們我很但願你們轉粉,雖然我也有 mac 可是我平均開機數目大約在 1/(1~2個月)。函數

寫到這裏目測大約水了一百多個文字,繼續前天的講解 ╮(╯Д╰)╭ 。工具

———————— END ————————性能

官網的意思是什麼呢,假如我有一個 obj,那麼我可使用 _.groupBy 函數將這個 obj 經過其內部值的某個屬性進行分類,而這個屬性值的判斷也能夠經過回調進行擴展斷言。那麼當 iteratee 爲 null 時,_.groupBy 默認使用前面的 group 函數中的 cb 函數的 if (value == null) return _.identity; 處理 iteratee 爲空的狀況,我來簡化一下 _.groupBy

_.groupBy = function(obj) {
    var result = partition ? [[], []] : {};
    _.each(obj, function(value, index) {
         var key = value;
        if (_.has(result, key)) result[key].push(value); else result[key] = [value];
    })
    return result;
}

這樣理解是否是淺顯不少呢,設置 result 空數組,而後 _.each 遍歷 obj,滿滿的都是套路有木有,惟一亮點的地方就是 if 判斷是根據 _.has 函數肯定 result 中是否已經存在 key-value。可是這裏面還有一個更深的套路,那就是做者沒有對 obj 做進一步處理,因此 _.groupBy 函數只能適用於 Array,舉個栗子:

_.groupBy(['one', 'two', 'three']);
  {"one":["one"],"two":["two"],"three":["three"]}
  _.groupBy([{a:'one'}, {b:'two'}, {c:'three'}]);
  {"[object Object]":[{"a":"one"},{"b":"two"},{"c":"three"}]}

而後咱們再說一下 _.groupBy 參數有第二個參數的狀況,這裏能夠看出 cb 函數的重要性,它對 iteratee 的類型狀況作了細緻的判斷和處理,咱們前面能夠知道 cb 函數除了 Null、Function、Object 意外的類型都用 _.property 處理,即 生成獲取屬性值的函數,那麼咱們傳參爲數組呢,see ↓↓↓

_.groupBy(['one', 'two', 'three'],[1,2,3])
 {"false":["one","two","three"]}

也就是說做者雖然大才,可是並無對超出範圍的值類型作進一步的處理,也就是說 iteratee 的可選值類型只能爲 Function 和 String。固然這並非錯,從工具的角度來說咱們應用函數應該遵照函數創造者設定的規則,超出規則後出現錯誤並非說做者的函數必定有問題,也多是咱們太過於調皮了(好比番茄西紅柿須要用平底鍋來炒,但廚師非要用電飯煲,這是廚師的錯仍是平底鍋生產商的錯 ─=≡Σ((( つ•̀ω•́)つ)。

言歸正傳當傳入合理的 iteratee 值時,其實整個函數的重點仍是 group 函數內部的 cb 函數,由於咱們能夠看源碼 _.groupBy 上的回調最終是落實到 cb 上,將一個函數比做一個公共房間,衆多人就是傳入傳出的參數,那麼 cb 就是門禁卡識別每一個人的身份併發身份牌。若是 iteratee 是 String 則用 _.property 處理恰到好處(生成獲取屬性值的函數),若是是 Function 也只是在 if (_.has(result, key)) result[key].push(value); else result[key] = [value]; 以前經過回調生成相應的 key 值。

_.indexBy = group(function(result, value, key) {
    result[key] = value;
  });

官網釋義 給定一個list,和 一個用來返回一個在列表中的每一個元素鍵 的iterator 函數(或屬性名),返回一個每一項索引的對象。關鍵代碼參考 _.groupBy,兩者的二區別也之有一行代碼,理解起來並不難,我就再也不水文字了。

_.countBy = group(function(result, value, key) {
    if (_.has(result, key)) result[key]++; else result[key] = 1;
  });

官網釋義 排序一個列表組成一個組,而且返回各組中的對象的數量的計數。相似groupBy,可是不是返回列表的值,而是返回在該組中值的數目。其實就是對匹配成功的元素計數。

var reStrSymbol = /[^\ud800-\udfff]|[\ud800-\udbff][\udc00-\udfff]|[\ud800-\udfff]/g;

reStrSymbol 用於正則函數,這一塊我也不是很熟悉,可是我找到了兩篇文章作了參考,Unicode Regular Expressions, Surrogate Points and UTF-8
Re: Java char and Unicode 3.0+ (was:Canonical equivalence in rendering: mandatory or recommended?)unicode。另外知乎上也有人對這句話作了判斷:

[^\ud800-\udfff] 普通的 BMP 字符,表示不包含代理對代碼點的全部字符
 [\ud800-\udbff][\udc00-\udfff] 成對的代理項對,表示合法的代理對的全部字符
 [\ud800-\udfff] 未成對的代理項字,表示代理對的代碼點(自己不是合法的Unicode字符)

以上僅供參考,我也不是很清楚,等我作好這方面功課的時候再從新說這個話題。

_.toArray = function(obj) {
    if (!obj) return [];
    if (_.isArray(obj)) return slice.call(obj);
    if (_.isString(obj)) {
      return obj.match(reStrSymbol);
    }
    if (isArrayLike(obj)) return _.map(obj, _.identity);
    return _.values(obj);
  };

官網說 把list(任何能夠迭代的對象)轉換成一個數組,在轉換 arguments 對象時很是有用,並給出一個 (function(){ return _.toArray(arguments).slice(1); })(1, 2, 3, 4);,說內心話每當看到 arguments 的時候我第一個印象是 Array.prototype.slice.call(arguments, indexes);,這裏做者對待 Array 的原理一樣是這個。_.toArray 函數自己沒有重點,無非就是根據字符串、數組、對象進行數組轉換,須要注意的是當轉換 Object 的時候會忽略 key-value 的 key,只單獨把 value 放到數組中,另外就是 if (_.isArray(obj))if (isArrayLike(obj)),顧名思義第一個是判斷數組,第二個難道是考慮到 {'length':[1,2,3,4]} 這種數據結構的狀況?

_.size = function(obj) {
    if (obj == null) return 0;
    return isArrayLike(obj) ? obj.length : _.keys(obj).length;
  };

_.size 用於返回傳入參數的長度,包括但不限於 Object、Array 、 String 和 Function,Function 返回的是 Function 中傳入參數的個數(arguments)。另外 Map 這裏有個坑,Map返回值是12,衆所周知 Map是一個大的對象,因此返回值是它的12個基本屬性的個數。

_.partition = group(function(result, value, pass) {
    result[pass ? 0 : 1].push(value);
  }, true);

_.partition 是第四個用 group 函數包裝的函數,用來對傳入 obj 作判斷時返回符合回調斷言的結果集以及不符合的結果集,從 result[pass ? 0 : 1].push(value) 這裏就可見一斑了,也就是說 group 的第三個傳參 partition 也就是爲了 _.partition 而存在。partition 使 result 的設定爲固定的 [[][]],這種寫法我以爲並非看上去最優雅地,理想狀況是最好不存在第三個參數纔對,但這必定是相對節約性能的,面對可節約的性能怎麼取捨已經很清楚了。

相關文章
相關標籤/搜索