今天繼續上次的內容,以前咱們講到了 reduce
的用法,其實我以爲用法卻是其次的關鍵是做者實現 reduce
過程當中所靈活用到的函數處理方法,咱們只要有心稍加總覺徹底能夠拿來主義,豐富本身的代碼└(^o^)┘。數組
_.find = _.detect = function(obj, predicate, context) { var keyFinder = isArrayLike(obj) ? _.findIndex : _.findKey; var key = keyFinder(obj, predicate, context); if (key !== void 0 && key !== -1) return obj[key]; };
_.find
,討論這個函數首先要弄懂 _.findIndex
和 _.findKey
,這裏咱們先簡單知道一個是針對數組一個是針對對象,具體的後面讀到源碼再說。傳入值 obj 進行 isArrayLike 判斷以此決定 keyFinder 函數,將三個參數包括回調傳入 keyFinder 中其中 predicate 回調函數充當迭代器進行真值檢測,最後 return obj[key]。閉包
var createPredicateIndexFinder = function(dir) { return function(array, predicate, context) { predicate = cb(predicate, context); var length = getLength(array); var index = dir > 0 ? 0 : length - 1; for (; index >= 0 && index < length; index += dir) { if (predicate(array[index], index, array)) return index; } return -1; }; };
以 _.findIndex
爲例簡單介紹一下,_.findIndex
是由 createPredicateIndexFinder 包裝而成,意義在於返回 predicate 函數內部 return true。app
_.filter = _.select = function(obj, predicate, context) { var results = []; predicate = cb(predicate, context); _.each(obj, function(value, index, list) { if (predicate(value, index, list)) results.push(value); }); return results; };
_.filter
函數與 _.find
相似,內部實現較之 _.find
更簡單些,_.find
意爲匹配 predicate 回調 return true 惟一就近值,_.filter
則是匹配全部值的集合。那麼有人說爲何不用 _.filter()[0]
取代 _.find
,理論上兩者確實是相同值,可是 _.filter
會遍歷傳參 obj 直至結束,而 _.find
則是遍歷過程當中匹配成功結束遍歷,因此某些狀況下 _.find
優於 _.filter
。dom
_.reject = function(obj, predicate, context) { return _.filter(obj, _.negate(cb(predicate)), context); };
_.reject
,經過 _.negate
和 cb
函數包裝 predicate 回調,實際上就是用 optimizeCb
優化 predicate function,而後用 _.negate
返回與 predicate 相反的 Boolean 類型值,以此得到與 _.filter
做用相反的結果集合。函數
_.every = _.all = function(obj, predicate, context) { predicate = cb(predicate, context); var keys = !isArrayLike(obj) && _.keys(obj), length = (keys || obj).length; for (var index = 0; index < length; index++) { var currentKey = keys ? keys[index] : index; if (!predicate(obj[currentKey], currentKey, obj)) return false; } return true; };
_.every
,咱們看源碼中的返回值類型爲 Boolean 知道這是一個用於真值檢測的函數,內部的處理步驟已經很程序化了,首先優化回調函數 predicate,處理傳參 obj(根據 Object 或者 Array),回調中接收 obj[currentKey], currentKey, obj
三個參數進行 Boolean 判斷,當判斷失敗的時候則 if (!false) return false;
結束 for 循環。這個方法看上去很雞肋,但實際上結合 predicate 回調應用於某些判斷處理很給力。優化
_.some = _.any = function(obj, predicate, context) { predicate = cb(predicate, context); var keys = !isArrayLike(obj) && _.keys(obj), length = (keys || obj).length; for (var index = 0; index < length; index++) { var currentKey = keys ? keys[index] : index; if (predicate(obj[currentKey], currentKey, obj)) return true; } return false; };
_.some
,看源碼咱們能夠知道它基本上與 _.every
相似,區別在於 _.some
遍歷 obj 過程當中只要任何一個元素經過 predicate 回調的真值檢測就直接當即中斷遍歷並返回 true。我主觀意識上更偏向於 _.every
和 _.some
用一個相同的基礎函數包裝再經過判斷值構建它們,就像 createReduce
函數構成 _.reduce
、_.reduceRight
同樣,可是不知道做者爲何沒有這樣作,可能有其餘的考慮吧,這裏再也不揣測。this
_.contains = _.includes = _.include = function(obj, item, fromIndex, guard) { if (!isArrayLike(obj)) obj = _.values(obj); if (typeof fromIndex != 'number' || guard) fromIndex = 0; return _.indexOf(obj, item, fromIndex) >= 0; };
_.contains
用於檢查 obj 中是否包含 item 值,我更傾向於這是一個簡化版的 _.some
,若是是我寫基礎函數可能真的就只有 _.some
不用 _.contains
,可是 Undescore.js 做爲一個知名函數庫,在代碼優化的執行速度上確定要比咱們作的更細。
這裏順便說一下 _.indexOf
和 guard
,_.indexOf
是由 createIndexFinder 包裝而來,能夠理解爲數組版的 indexOf,indexOf 概念可參考 String.prototype.indexOf() 和 Array.prototype.indexOf()。關於 array.indexOf(searchElement[, fromIndex = 0])
,我這裏再說幾句,這個 JAVASCRIPT 函數傳入1或2個參數,第一個參數爲將要進行匹配的內容,可爲 Number 可爲 String,第二個可選參數爲(須要定向匹配數組中某一值的數組下標值 - array.length)*n,且 n!= 0
,array.indexOf
根據這個下標進行定向匹配驗證,若是匹配成功則返回值爲被匹配值的數組下標,匹配失敗則返回 -1。prototype
var array = [2, 9, 9,9,9,3,4]; undefined array.indexOf(9,2); 2 array.indexOf(9,3); 3 array.indexOf(9,4); 4 array.indexOf(9,5); -1 array.indexOf(3,5); 5 array.indexOf(5); -1 array.indexOf(2, -7); 0
_.indexOf
雖然與 array.indexOf(searchElement[, fromIndex = 0])
有所區別,但也有不少相通之處。rest
_.invoke = restArgs(function(obj, method, args) { var isFunc = _.isFunction(method); return _.map(obj, function(value) { var func = isFunc ? method : value[method]; return func == null ? func : func.apply(value, args); }); });
_.invoke
用於批量執行方法,前面咱們講了 restArgs 方法,雖然代碼很複雜,但目前實際上只應用了以下簡化的結構:code
var restArgs = function(func) { return function() { return func.apply(this, arguments); }; };
也就是說 _.invoke
拋開閉包的概念以後等同於:
function(obj, method, args) { var isFunc = _.isFunction(method); return _.map(obj, function(value) { var func = isFunc ? method : value[method]; return func == null ? func : func.apply(value, args); }); }
其中 _.isFunction
是判斷是否爲 function,接下來 _.map
回調,實際上我很納悶萬一傳入的 method 是 obj[i] 對象上沒有的方法怎麼辦,按照 return 的結果若是沒有則返回 func 也就是 null
,總以爲這樣返回缺乏點什麼。
_.pluck = function(obj, key) { return _.map(obj, _.property(key)); };
_.pluck
返回傳入 obj 的 key 的集合,或者說 key 的集合有點武斷,更具體點說是 obj 下第二層所包含 key 的值的集合,而第一層也就是 obj 可爲 Object 或 Array,但 obj 中第二層必須是 Object。這是爲何呢?
_.map(obj, function(key) { return (function(obj) { return obj == null ? void 0 : obj[key]; })(key); })
在上述簡化的代碼中咱們能夠看出 return obj == null ? void 0 : obj[key];
的值是 obj[key],因此第二層只能是 Object。
_.where = function(obj, attrs) { return _.filter(obj, _.matcher(attrs)); };
_.where
頗有趣,代碼簡化以後是:
_.where = function(obj, attrs) { return _.filter(obj, (function(attrs) { attrs = _.extendOwn({}, attrs); return function(obj) { return _.isMatch(obj, attrs); })(attrs); }); };
_.filter
咱們講過是獲取全部匹配值的集合,而回調中的 _.extendOwn
將 attrs 放入空對象 {}
中並 return,_.isMatch
是個斷言用於判斷 obj 中是否存在 key-value。那麼 _.where
就是 _.isMatch
和 _.filter
的增強版,它用於判斷一個大的對象數組中存在與傳入 attrs 相同的鍵值對,若是存在則返回匹配目標鍵值對所在的 Object,而且返回值是一個集合。
var list = [{author:"Shakespeare",title:"china"}, {author:"Shakespeare",year:1611,title:"china"}, {author:"Shakespeare",year:1611,title:"English"}, {year:1611,title:"china"}]; _.where(list, {author: "Shakespeare", year: 1611}); [{"author":"Shakespeare","year":1611,"title":"china"},{"author":"Shakespeare","year":1611,"title":"English"}]
這個方法在處理數據的時候特別有用。
_.findWhere = function(obj, attrs) { return _.find(obj, _.matcher(attrs)); };
_.findWhere
,至關於 _.where()[0]
,即返回結果集合的第一個值,這麼設定的目的和 _.find
與 _.filter
同樣,運算更快,遍歷到目標立刻中止遍歷。
_.max = function(obj, iteratee, context) { var result = -Infinity, lastComputed = -Infinity, value, computed; if (iteratee == null || (typeof iteratee == 'number' && typeof obj[0] != 'object') && obj != null) { obj = isArrayLike(obj) ? obj : _.values(obj); for (var i = 0, length = obj.length; i < length; i++) { value = obj[i]; if (value != null && value > result) { result = value; } } } else { iteratee = cb(iteratee, context); _.each(obj, function(v, index, list) { computed = iteratee(v, index, list); if (computed > lastComputed || computed === -Infinity && result === -Infinity) { result = v; lastComputed = computed; } }); } return result; };
_.max
用來查找 obj 對象數組中某一 key 的最大值的 Object,限定是 key-value 的 value 必須是 Number 類型。-Infinity
我更喜歡叫它負無窮,這裏的 if true 第一個判斷能夠忽略了,爲何不講了呢,由於做者要放棄 typeof iteratee == 'number' && typeof obj[0] != 'object'
這種狀況,可見其餘版本的 Underscore.js。若是忽略 typeof iteratee == 'number' && typeof obj[0] != 'object'
的狀況則 _.max
傳參爲一個數組,return 爲數組中最大值。if false 則進行常規的 _.each
代碼很簡單這裏再也不講解。
_.min = function(obj, iteratee, context) { var result = Infinity, lastComputed = Infinity, value, computed; if (iteratee == null || (typeof iteratee == 'number' && typeof obj[0] != 'object') && obj != null) { obj = isArrayLike(obj) ? obj : _.values(obj); for (var i = 0, length = obj.length; i < length; i++) { value = obj[i]; if (value != null && value < result) { result = value; } } } else { iteratee = cb(iteratee, context); _.each(obj, function(v, index, list) { computed = iteratee(v, index, list); if (computed < lastComputed || computed === Infinity && result === Infinity) { result = v; lastComputed = computed; } }); } return result; };
_.min
真心不用講了,參考 _.max
。
_.shuffle = function(obj) { return _.sample(obj, Infinity); };
_.shuffle
官網釋義是返回一個隨機亂序的 list 副本, 使用 Fisher-Yates shuffle 來進行隨機亂序.
,Fisher-Yates shuffle
是什麼鬼,咱們這裏看到 _.shuffle
這個函數用到了 _.sample
,因此咱們先講 _.sample
。
_.sample = function(obj, n, guard) { if (n == null || guard) { if (!isArrayLike(obj)) obj = _.values(obj); return obj[_.random(obj.length - 1)]; } var sample = isArrayLike(obj) ? _.clone(obj) : _.values(obj); var length = getLength(sample); n = Math.max(Math.min(n, length), 0); var last = length - 1; for (var index = 0; index < n; index++) { var rand = _.random(index, last); var temp = sample[index]; sample[index] = sample[rand]; sample[rand] = temp; } return sample.slice(0, n); };
_.sample
是從一個 obj 中隨機返回值,而且返回值受限於 n 這個參數,若是沒有傳入 n 或者傳入了 guard = true 則執行 if 語句,目的是將 obj 判斷處理以後返回單一值。這裏以爲特雞肋有木有,也就是說 _.sample(obj,n,true)
和_.sample(obj)
是一回事。若是按照 _.sample(obj,n)
的邏輯執行,依賴是老套路,處理 obj (Object 和 Array),而後 n = Math.max(Math.min(n, length), 0);
得到合理的 n 值,前面咱們講到了 Infinity
正無窮和 -Infinity
負無窮,這段代碼利用了 Infinity 的特性包裝了 _.shuffle
函數,關鍵就是 Infinity 大於全部 Number 數字,即 Math.min(Infinity, Number)
等於 Number,好處就是讓人眼前一亮,哇,原來代碼還能夠這樣寫,壞處就是當單獨使用 _.sample
函數的 n 大於處理以後的 obj 的長度時並不會報錯,而是默認執行 n=sample.length
,仁者見仁,智者見智吧。後面就是很套路的根據數組下標替換數組內容,固然數組下標是經過 _.random
隨機的,而後 slice 一刀切數組。