經過underscore系列的一和二,咱們掌握了一些underscore框架設計的思想以及基礎的數據類型診斷,從這一節開始,咱們將慢慢進入underscore的難點和核心。這個系列的三和四,咱們會全方位的瞭解underscore中對迭代器的實現。javascript
javascript的迭代器咱們並不陌生。咱們能夠這樣理解,迭代器提供了一種方法按順序訪問集合中的每一項,而這種集合能夠是數組,也能夠是對象。咱們先回憶一下,在ES6以前,javascript有7種數組的迭代器,分別是java
var arr = [1,2,4]
for(var i=0;i<arr.length;i++) {
console.log(i)
}
複製代碼
function add(num) {console.log(num + 1)}
var arr = [1,2,3]
arr.forEach(add)
複製代碼
function judge(a) {return a > 1}
var arr = [1,23,4];
console.log(arr.every(judge)); // false
複製代碼
function judge(a) {return a > 1}
var arr = [1,23,4];
console.log(arr.some(judge)); // true
複製代碼
function filterNum(a) {return a> 2}
var arr = [1,45,56,2,5]
console.log(arr.filter(filterNum)) // [45, 56, 5]
複製代碼
function add(a, b) {return a +b }
var arr = [1,3,4,5,6]
console.log(arr.reduce(add, 0)) //19
複製代碼
function mapNum(a) {return a + 1 }
var arr = [2,4,56]
console.log(arr.map(mapNum)) // [3,5,57]
複製代碼
針對對象 ,咱們常常使用for in進行迭代, 也可使用 Object.keys等方式進行迭代算法
var a = {b: 1, c: 2}
for(var i in a) {
console.log(i) //b, c
}
Object.keys(a) // b, c
複製代碼
首先從最複雜的reduce入手,reduce的基本功能前面在數組的迭代器方法中已經介紹,咱們只重點關注實現的細節。其中iteratee 爲迭代器函數,該函數有四個參數memo,value 和 迭代的index(或者 key)和最後一個引用的整個 list。數組
reduce 和 reduceRight 惟一的區別在於遍歷順序,一個從左往右, 一個從右往左,所以能夠用同一個函數來設計reduce。其中 context 改變this的指向咱們稍後分析。瀏覽器
_.reduce = createReduce(1);
_.reduceRight = createReduce(-1);
var createReduce = function(dir) {
// dir 來區分
return function (obj, iteratee, meno, context) {
// 兩種類型,對象和類數組須要區別處理方便for循環遍歷, 對象咱們須要拿到全部的屬性集合,數組,類數組咱們關注的是下標。
// 巧妙點:當爲數組,類數組時 keys = false, 當爲對象時 keys = 屬性數組
var keys = !isArrayLike(obj) && _.keys(obj)
var lengths = (keys || obj).length;
// 處理遍歷方向,即參數dir的值
var index = dir > 0 ? 0 : length-1;
for (; index >= 0 && index < lengths; index += dir) {
// 若是是數組,類數組則取下標,若是是對象則取屬性值
var currentKey = keys ? keys[index] : index;
// 執行迭代器函數,並把返回值賦值給meno,繼續循環迭代
meno = iteratee(meno, obj[currentKey], currentKey, obj);
}
return meno
}
}
複製代碼
reduce 函數在使用的時候,meno是可選項,若是沒有傳遞meno, 則自動會把list 中的第一個元素賦值給meno。所以咱們能夠將處理的核心代碼抽離爲一個獨立的函數,並將是否有meno的初始值作獨立判斷。bash
_.reduce = createReduce(1);
_.reduceRight = createReduce(-1);
var createReduce = function(dir) {
// dir 來區分
var reducer = function (obj, iteratee, meno, context, initial) {
···
// 增長meno 的初始值判斷賦值
if (!initial) {
memo = obj[keys ? keys[index] : index];
index += dir;
}
···
}
return function (obj, iteratee, meno, context) {
// 記錄是否有meno傳值
var initial = arguments.length >=3;
return reducer(obj, iteratee, meno, context, initial)
}
}
複製代碼
reduce的實現已經基本完成,然而依然留着一個懸念,那就是context能夠改變this的指向。underscore源碼中單獨將context改變this指向的方法抽離成一個獨立的函數optimizeCb,該方法能夠兼容underscore中 全部須要改變this指向的過程, reduce 爲其中一種情形,函數調用call方法改變this指向所傳遞的參數分別爲 meno, value, index, list框架
var createReduce = function(dir) {
···
return function(obj, iteratee, meno, context) {
···
return reducer(obj, optimizeCb(iteratee, context, 4), initial) //optimizeCb優化
}
}
var optimizeCb = function(func, context, argCount) {
// 當沒有特定的this指向時, 返回原函數
if(context == void 0) return func;
switch(argCount) {
// 針對reduce函數的context指向
case 4: return function(context, meno, value, index, list) {
return func.call(context, meno, value, index, list);
}
}
}
複製代碼
underscore 中map 方法原理上會生成一個新的數組,該數組與目標源數組,類數組的length屬性相同,或者與對象的自身可枚舉屬性個數相同,所以有了reduce 的基礎,咱們能夠簡單的實現map 方法ide
_.map = function (obj, iteratee, context) {
var keys = !isArrayLike(obj) && _.keys(obj)
var lengths = (keys || obj).length;
// 生成一個個數和目標源數組個數相同,或者目標源對象自身可枚舉屬性個數相同的數組
var result = new Array(lengths);
for(var i=0; i<lengths;i++) {
var currentKey = keys ? keys[i] : i;
results = iteratee(obj[currentKey], currentKey, obj)
}
}
複製代碼
對於map的使用,咱們能夠不傳遞iteratee迭代器,能夠傳遞一個函數類型的迭代器,也能夠傳遞一個對象類型做爲迭代器,所以須要在進入迭代過程以前作一層過濾,根據不一樣的迭代器類型作不一樣的操做。函數
_.map = function(obj, iteratee, context) {
// 迭代器類型分類
iteratee = cb(iteratee, context);
···
}
複製代碼
var cb = function(iteratee, context) {
if(iteratee == null) return _.identity
}
_.identity = function(value) {
return value
}
複製代碼
var cb = function(iteratee, context) {
if( _.isFunction(iteratee) ) return optimizeCb(iteratee, context, 3) // 此時類型爲3
}
// 完善optimizeCb 函數
var optimizeCb = function(func, context, argCount) {
// 當沒有特定的this指向時, 返回原函數
if(context == void 0) return func;
switch(argCount) {
case 3: return function(context, value, index, list) {
return func.call(context, value, index, list);
}
case 4: return function(context, meno, value, index, list) {
return func.call(context, meno, value, index, list);
}
}
}
複製代碼
var cb = function(iteratee, context) {
if (_.isObject(value) && !_.isArray(value)) return _.matcher(value); // 返回斷言函數
}
複製代碼
_.times也是underscore提供的迭代器,它會調用迭代器n次,每次調用時傳遞index做爲參數,最終結果返回一個執行結果的數組。例如:post
console.log(_.times(n, function(i) {return i * 2 })) // [0, 1, 4]
複製代碼
簡單的實現以下,關鍵點在註釋中說明:
_.times = function(n, iteratee, context) {
// 必須保證n的值是比0 大的數字
var n = Math.max(0, n);
// 建立一個個數爲n的新數組
var arr = new Array(n);
for(var i = 0; i<n;i++) {
arr[i] = iteratee(i)
}
return arr
}
複製代碼
一樣,涉及context 改變this指向,咱們一樣能夠經過optimizeCb進行優化,此時完善optimizeCb函數
_.times = function(n, iteratee, context) {
···
iteratee = optimizeCb(iteratee, context, 1)
}
// 完善optimizeCb 函數
var optimizeCb = function(func, context, argCount) {
// 當沒有特定的this指向時, 返回原函數
if(context == void 0) return func;
switch(argCount) {
case 1: return function(context, value) {
return func.call(context, value)
}
case 3: return function(context, value, index, list) {
return func.call(context, value, index, list);
}
case 4: return function(context, meno, value, index, list) {
return func.call(context, meno, value, index, list);
}
}
}
複製代碼
經過列舉三種迭代器的設計,咱們不但掌握了underscore迭代器設計的基本思想,也對中間核心optimizeCb函數設計的可能進行了枚舉,underscore中optimizeCb函數優化的類型只有三種,對應的數值分別爲1,3,4(注意:並無2的類別)。咱們也對迭代器的三種類型進行了枚舉,在cb函數中分別區分了不傳遞迭代器,迭代器自身爲數組,對象時的處理。掌握了optimizeCb和cb兩個函數的設計思想,在設計剩餘的迭代器時難度便小了不少。 因爲篇幅過長,underscore中其餘迭代器的實現咱們放到下一節闡述。