先看一下兩個比較重要的內部函式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
63 var optimizeCb = function(func, context, argCount) { if (context === void 0) return func; switch (argCount == null ? 3 : argCount) { case 1: return function(value) { return func.call(context, value); }; case 2: return function(value, other) { return func.call(context, value, other); }; case 3: return function(value, index, collection) { return func.call(context, value, index, collection); }; case 4: return function(accumulator, value, index, collection) { return func.call(context, accumulator, value, index, collection); }; } return function() { return func.apply(context, arguments); }; }; |
這個函式是underscore內部很重要的函式,主要用來執行函式並改變所執行函式的作用域,最後加了一個argCount引數來指定引數個數,對引數個數小於等於4的情況進行分類處理。對不同引數的解釋大概是:
1的情況一般是用在接受單值的情況,比如times,sortedIndex之類的函式。
2的情況據說是給比如jQuery,zepto事件繫結,代理什麼的,但是在原始碼中沒有看到被呼叫。
3的情況用於迭代器函式,比如foreach,map,pick等。
4的情況用reduce和reduceRight函式。
1 2 3 4 5 6 |
87 var cb = function(value, context, argCount) { if (value == null) return _.identity; if (_.isFunction(value)) return optimizeCb(value, context, argCount); if (_.isObject(value)) return _.matcher(value); return _.property(value); }; |
這也是一個比較常用的內部函式,只是對引數進行了判斷:如果是函式則返回上面說到的回撥函式;如果是物件則返回一個能判斷物件是否相等的函式;預設返回一個獲取物件屬性的函式。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
140 _.each = _.forEach = function(obj, iteratee, context) { iteratee = optimizeCb(iteratee, context); var i, length; if (isArrayLike(obj)) { for (i = 0, length = obj.length; i < length; i++) { iteratee(obj[i], i, obj); } } else { var keys = _.keys(obj); for (i = 0, length = keys.length; i < length; i++) { iteratee(obj[keys[i]], keys[i], obj); } } return obj; }; // Return the results of applying the iteratee to each element. _.map = _.collect = function(obj, iteratee, context) { iteratee = cb(iteratee, context); var keys = !isArrayLike(obj) && _.keys(obj), length = (keys || obj).length, results = Array(length); for (var index = 0; index < length; index++) { var currentKey = keys ? keys[index] : index; results[index] = iteratee(obj[currentKey], currentKey, obj); } return results; }; |
從程式碼上看,each函式是包括map函式的,map只能處理物件,each可以處理物件和陣列。至於forEach和collect在API文件中看不到,應該是為了相容以前老版本做的別名處理。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
function createReduce(dir) { // Optimized iterator function as using arguments.length // in the main function will deoptimize the, see #1991. function iterator(obj, iteratee, memo, keys, index, length) { for (; index >= 0 && index < length; index += dir) { var currentKey = keys ? keys[index] : index; memo = iteratee(memo, obj[currentKey], currentKey, obj); } return memo; } return function(obj, iteratee, memo, context) { iteratee = optimizeCb(iteratee, context, 4); var keys = !isArrayLike(obj) && _.keys(obj), length = (keys || obj).length, index = dir > 0 ? 0 : length - 1; // Determine the initial value if none is provided. if (arguments.length < 3) { memo = obj[keys ? keys[index] : index]; index += dir; } return iterator(obj, iteratee, memo, keys, index, length); }; } |
這個是reduce和reduceRight呼叫的內部函式,將memo這個變數作為入參傳遞給iterator函式,呼叫自定義的iteratee函式進行迴圈處理,每次處理完的結果都賦值給memo變數,最後返回memo變數的結果。這裡有兩個問題
- 為什麼這裡不按照常理邏輯來寫程式碼而要用閉包呢?閉包大致有這麼幾個作用:避免命名衝突;私有化變數;變數持久化。這裡的作用主要就是變數(函式)持久化,好處就是重複呼叫的時候不需要再重新建立函式,從而提升執行速度。
- 為什麼要用兩層閉包呢?第一層閉包持久化iterator函式,呼叫reduce和reduceRight函式避免重複新建函式。第二層閉包儲存keys,index,length這些變數。
12345678_.invoke = function(obj, method) {var args = slice.call(arguments, 2);var isFunc = _.isFunction(method);return _.map(obj, function(value) {var func = isFunc ? method : value[method];return func == null ? func : func.apply(value, args);});};
這裡用slice.call(arguments, 2)
來獲取後面的不定引數,然後用func.apply(value, args)
來傳入該引數比較有意思。
打賞支援我寫出更多好文章,謝謝!
打賞作者
打賞支援我寫出更多好文章,謝謝!
任選一種支付方式