JS進(jìn)階——underscore源碼(2)

_.each 和 _.forEach

在 ES5 中,只能對數(shù)組對象進(jìn)行迭代,而 underscore 提供的迭代方法,除了支持 array, 還支持 object 的迭代, 對 object 迭代的依據(jù)是對象的鍵序列 keys,我們可以查看 underscore 中的 _.each 方法:

_.each = _.forEach = function(obj, iteratee, context) {
        iteratee = optimizeCb(iteratee, context);
        // 先處理一下傳入的迭代函數(shù),回顧一下,這里如果沒有context,則直接使用iteratee作為函數(shù)遍歷,否則迭代函數(shù)將以當(dāng)前值、當(dāng)前索引、完整集合作為參數(shù)進(jìn)行調(diào)用
        let i, length;
        if (isArrayLike(obj)) {
            // 區(qū)分?jǐn)?shù)組和對象的迭代過程
            for (i = 0, length = obj.length; i < length; i++) {
                iteratee(obj[i], i, obj);
                // 數(shù)組的迭代回調(diào)傳入三個參數(shù)(迭代值, 迭代索引, 迭代對象)
            }
        } else {
            let keys = _.keys(obj);
            for (i = 0, length = keys.length; i < length; i++) {
                iteratee(obj[keys[i]], keys[i], obj);
                // 對象的迭代回調(diào)傳入三個參數(shù)(迭代值, 迭代的key, 迭代對象)
            }
        }
        // 返回對象自身, 以便進(jìn)行鏈?zhǔn)綐?gòu)造
        return obj;
    };

_.map 和 _.collect

map 的實(shí)現(xiàn)思路如下:
1、創(chuàng)建一個新列表或者元素
2、遍歷原列表或者原對象的值,用指定的函數(shù) func 作用于每個遍歷到的元素,輸出一個新的元素放入新列表或者對象中

    _.map = _.collect = function(obj, iteratee, context) {
        iteratee = cb(iteratee, context);
        // 這里將根據(jù)iteratee決定是返回等價(jià)、函數(shù)調(diào)用、屬性匹配或者屬性訪問
        let keys = !isArrayLike(obj) && _.keys(obj),
            // 類數(shù)組對象為false,否則則取對象全部鍵
            length = (keys || obj).length,
            // 類數(shù)組對象為length屬性,否則為對象鍵值對數(shù)量
            results = Array(length);
            // 要返回的新的集合
        for (let index = 0; index < length; index++) {
            let currentKey = keys ? keys[index] : index;
            // 類數(shù)組對象取索引,否則取鍵名
            results[index] = iteratee(obj[currentKey], currentKey, obj);
            // 放入對應(yīng)位置的值經(jīng)過iteratee處理后的值
        }
        return results;
    };

使用實(shí)例

對數(shù)組使用 _.map 函數(shù):

var array = [1,2,3,4,5];
var doubledArray = _.map(array, function(elem, index, array){
    return 2*elem;
}); // => doubledArray: [2,4,6,8,10]

對一般對象使用 _.map 函數(shù):

var obj = {
  name: 'wxj',
  age: 13,
  sex: 'male'
};
var wxjInfos = _.map(obj, function(value ,key, obj){
  return [key, value].join(':');
}); // => wxjInfors: ['name:wxj', 'age:13', 'sex:male']

_.reduce的實(shí)現(xiàn)

    // 抽象遞歸過程
    let createReduce = function(dir) {
        // 包裝遞歸
        let reducer = function(obj, iteratee, memo, initial) {
            let keys = !isArrayLike(obj) && _.keys(obj),
                length = (keys || obj).length,
                index = dir > 0 ? 0 : length - 1;
                // dir為1從左往右,為-1從右往左
            if (!initial) {
                // 第一次的時候創(chuàng)建memo用來存儲
                memo = obj[keys ? keys[index] : index];
                index += dir;
            }
            // 根據(jù)方向遞歸遍歷
            for (; index >= 0 && index < length; index += dir) {
                let currentKey = keys ? keys[index] : index;
                memo = iteratee(memo, obj[currentKey], currentKey, obj);
            }
            return memo;
        };
        // 傳入要遍歷的對象、迭代器、記錄、上下文
        return function(obj, iteratee, memo, context) {
            // 確認(rèn)initial的初值
            let initial = arguments.length >= 3;
            // 返回迭代為累加器的迭代函數(shù)
            return reducer(obj, optimizeCb(iteratee, context, 4), memo, initial);
        };
    };

    // 從左往右遞歸
    _.reduce = _.foldl = _.inject = createReduce(1);
    // 從右往左遞歸
    _.reduceRight = _.foldr = createReduce(-1);

使用用例:
1、對數(shù)組使用 _.reduce:

var array = [1,2,3,4,5];
var sum = _.reduce(array, function(prev, current){
  return prev+current;
} ,0);
// => sum: 15

2、一般對象也可以進(jìn)行_.reduce

var scores = {
  english: 93,
  math: 88,
  chinese: 100
};
var total = _.reduce(scores, function(prev, value, key){
  return prev+value;
}, 0);
// => total: 281

查詢

對于元素位置查詢,underscore 提供了以下 API:

_.indexOf
_.lastIndexOf
_.findIndex
_.findLastIndex
_.sortedIndex

(_.indexOf 及 _.lastIndexOf 只支持對于數(shù)組元素的搜索。)

對于元素查詢,underscore 提供了以下 API:

_.find = _.detect
_.findWhere
_.where

如果集合是對象,即集合是鍵值對構(gòu)成的,則提供了以下 API:

_.findKey
_.pluck

對于判斷元素是否存在,underscore 提供了以下 API:

_.contains

_.indexOf和_.lastIndexOf

createIndexFinder(dir, predicateFind, sortedIndex) 接受 3 個參數(shù):
1、dir:查詢方向,_.indexOf 即是正向查詢, _.lastIndexOf 即是反向查詢。
2、predicateFind:真值檢測函數(shù),該函數(shù)只有在查詢元素不是數(shù)字(NaN)才會使用。
3、sortedIndex:有序數(shù)組的索引獲得函數(shù)。如果設(shè)置了該參數(shù),將假定數(shù)組已經(jīng)有序,從而更加高效的通過針對有序數(shù)組的查詢函數(shù)(比如二分查找等)來優(yōu)化查詢性能。

// 傳遞三個參數(shù),分別是方向、判斷函數(shù)、查找函數(shù)
var createIndexFinder = function(dir, predicateFind, sortedIndex) {

  // 返回的函數(shù)有三個參數(shù),分別是要查詢的數(shù)組、要查詢的內(nèi)容、以及查詢的起始位置或者是否排序
  return function(array, item, idx) {

    var i = 0, length = getLength(array);
    if (typeof idx == 'number') {  // 首先如果傳遞的索引是數(shù)字
      if (dir > 0) {  // 如果從前往后
        i = idx >= 0 ? idx : Math.max(idx + length, i);  // 處理索引起點(diǎn),當(dāng)輸入負(fù)數(shù)的時候表示從后往前,但轉(zhuǎn)換成從前往后的索引位置
      } else {
        length = idx >= 0 ? Math.min(idx + 1, length) : idx + length + 1;  // 處理查找的最后位置
      }

    } else if (sortedIndex && idx && length) { // 如果有有序查找函數(shù)、并且已知數(shù)組有序并且非空
      idx = sortedIndex(array, item); // 查找到相應(yīng)的位置
      return array[idx] === item ? idx : -1;  // 如果該位置就是要查找的內(nèi)容則返回該位置,否則返回-1
    }


    if (item !== item) {  // 若果item是NaN
      idx = predicateFind(slice.call(array, i, length), _.isNaN);  // 索引是第一個NaN的位置
      return idx >= 0 ? idx + i : -1;  // 如果存在則返回索引,否則返回-1
    }

    // 根據(jù)不同方向遍歷
    for (idx = dir > 0 ? i : length - 1; idx >= 0 && idx < length; idx += dir) {
      if (array[idx] === item) return idx;  // 如果找到則返回索引
    }
    return -1;  // 找不到返回-1
  };
};

createIndexFinder 將會返回一個索引查詢器,該索引查詢器支持三個參數(shù):
1、array:待搜索數(shù)組
2、item:待搜索對象
3、idx: 查詢起點(diǎn),從數(shù)組的哪個位置開始查找。如果以數(shù)字的方式設(shè)置了查詢起點(diǎn),或者未設(shè)置查詢起點(diǎn),則無法使用 sortedIndex 方法進(jìn)行查詢優(yōu)化。通常,我們可以設(shè)置該值為語義更加明顯的 true(代表啟用查詢優(yōu)化)來對有序數(shù)組進(jìn)行查詢優(yōu)化。

_.indexOf = createIndexFinder(1, _.findIndex, _.sortedIndex);
_.lastIndexOf = createIndexFinder(-1, _.findLastIndex);

用例:

// 創(chuàng)建一個有序的大容量數(shù)組
var array = [];
for(var i=0;i < 1000000;i++) {
  array[i] = i;
}
console.time("以數(shù)字方式設(shè)置了查詢起點(diǎn),搜索耗時");
_.indexOf(array,500000);
console.timeEnd("以數(shù)字方式設(shè)置了查詢起,搜索耗時");
// 以數(shù)字方式設(shè)置了查詢起,搜索耗時:1.561ms
console.time("以非數(shù)字方式設(shè)置了查詢起點(diǎn),搜索耗時");
_.indexOf(array,500000, true);
console.timeEnd("以非數(shù)字方式設(shè)置了查詢起點(diǎn),搜索耗時");
// 以非數(shù)字方式設(shè)置了查詢起點(diǎn),搜索耗時:0.308ms

_.sortedIndex

// 傳遞四個參數(shù),分別是要查詢的數(shù)組、想要判斷的對象、迭代器、上下文
_.sortedIndex = function(array, obj, iteratee, context) {
  iteratee = cb(iteratee, context, 1);  // 迭代函數(shù),用來處理迭代
  var value = iteratee(obj);
  var low = 0, high = getLength(array);  // 二分法的高低位設(shè)置
  while (low < high) {  // 循環(huán)
    var mid = Math.floor((low + high) / 2);
    if (iteratee(array[mid]) < value) low = mid + 1; else high = mid;
  }
  return low;
};

用例:

_.sortedIndex([10, 20, 30, 40, 50], 20); // => 1
// _.sortedIndex` 如果查找的元素不存在,將返回元素應(yīng)當(dāng)存在的位置
_.sortedIndex([10, 20, 30, 40, 50], 35); // => 3
// _.sortedIndex`也支持對對象集合的搜索。
_.sortedIndex([{name: 'wxj'}, {name: 'lx'}, {name: 'lcx'}, {name: 'wxj'}]);
// => 0

_.findInde和_.findLastIndex

尋找符合條件的元素第一次或者最后一次出現(xiàn)的地方。

createPredicateIndexFinder 接受 1 個參數(shù):dir:搜索方向

var createPredicateIndexFinder = function(dir) {
return function(array, predicate, context) {
    predicate = cb(predicate, context);
    var length = getLength(array);
    var index = dir > 0 ? 0 : length - 1;
    // 根據(jù)方向遍歷
    for (; index >= 0 && index < length; index += dir) {
if (predicate(array[index], index, array)) return index;
    }
return -1;
  };
};

它將返回一個索引查詢函數(shù),該查詢函數(shù)接受 3 個參數(shù):
1、array: 待搜索數(shù)組
2、predicate:真值檢測函數(shù)
3、context: 執(zhí)行上下文
如果傳入的 predicate 是一個立即數(shù),會被 cb 優(yōu)化為一個
_.property(predicate) 函數(shù),用來獲得對象的某個屬性。

_.findIndex = createPredicateIndexFinder(1);  // 正向查找
_.findLastIndex = createPredicateIndexFinder(-1);  // 反向查找

用例:

// 下面的調(diào)用將不會返回3,因?yàn)閌12`會被修正為`_.property(12)`:
_.findIndex([4, 6, 8, 12],12);
// => -1
_.findIndex([4, 6, 8, 12], function(value){
    return value===0;
}); // => 3
_.findIndex([{name: 'wxj'}, {name: 'zxy'}], {
    name: 'zxy'
}); // => 1
_.findLastIndex([4, 6, 8, 12, 5, 12], function(value){
    return value===12;
}); // => 5
_.findLastIndex([{name: 'wxj'}, {name: 'zxy'}, {name:'zxy'}], {
    name: 'zxy'
}); // => 2

_.findKey

返回對象上第一個符合條件的屬性名。

_.findKey = function(obj, predicate, context) {
  predicate = cb(predicate, context);
  var keys = _.keys(obj), key;
  for (var i = 0, length = keys.length; i < length; i++) {
    key = keys[i];
    if (predicate(obj[key], key, obj)) return key;  
// 判斷函數(shù)傳遞三個參數(shù),分別是屬性值、屬性名和整個對象
  }
};

用例:

var student = {
  name: 'wxj',
  age: 18
};
_.findKey(student, function(value, key, obj) {
  return value === 18;
});
// => "age"

_.pluck

_.pluck(obj, key):取出 obj 中 key 對應(yīng)的值。

_.pluck = function (obj, key) {
    // 迭代集合, 每個迭代元素返回其對應(yīng)屬性的對應(yīng)值
    return _.map(obj, _.property(key));
};

用例:

var students = [
  {name: 'wxj', age: 18},
  {name: 'john', age: 14},
  {name: 'bob', age: 23}
];
_.pluck(students, 'name');
// ["wxj", "john", "bob"]

_.find和_.detect

_.find = _.detect = function(obj, predicate, context) {
        // 傳入三個參數(shù),分別是要查找的對象、判斷條件、上下文
        let keyFinder = isArrayLike(obj) ? _.findIndex : _.findKey;
        // 數(shù)組則查找索引,對象查找鍵
        let key = keyFinder(obj, predicate, context);
        if (key !== void 0 && key !== -1) return obj[key];
    };

用例

var obj = {
  name: 'wxj',
  age: 18,
  height: 163
};
var arr = [
  { name:'wxj', age: 18},
  { name: 'zxy', age: 44}
];
_.find(obj, function(value, key, obj){
  return value%2 === 0;
});
// => 18
_.find(arr, function(elem) {
  return elem.name === 'wxj';
});
// => { name: 'wxj', age: 18}

_.where

_.where = function (obj, attrs) {
    return _.filter(obj, _.matcher(attrs));
};

用例:

var users = [
  {name: 'wxj', age: 18, sex: 'male'},
  {name: 'zxy', age: 18, sex: 'male'},
  {name: 'zhangsan', age: 14, sex: 'famale'}
];
var ret = _.where(users, {age: 18, sex: 'male'});
// => [
//  {name: 'wxj', age: 18, sex: 'male'},
//  {name: 'zxy', age: 18, sex: 'male'},
//]

_.matcher和_.matches

創(chuàng)建判斷對象是否符合給定的條件的函數(shù)。

_.matcher = _.matches = function(attrs) {
  attrs = _.extendOwn({}, attrs);  // 使用傳參創(chuàng)建一個對象
  return function(obj) {
return _.isMatch(obj, attrs);  // 使用isMatch來判斷
  };
};

用例:

var users = [
  {name: 'wxj', age: 18, sex: 'male'},
  {name: 'zxy', age: 18, sex: 'male'},
  {name: 'zhangsan', age: 14, sex: 'famale'}
];
var matcher = _.matcher({age: 18, sex: 'male'});
var ret = _.filter(users, matcher);
// => [
//  {name: 'wxj', age: 18, sex: 'male'},
//  {name: 'zxy', age: 18, sex: 'male'},
//]

可以看到,_.matcher 接受傳入的屬性列表 attrs,最終返回一個校驗(yàn)過程,通過 _.isMatch 來校驗(yàn) obj 中屬性的是否與 attrs 的屬性相匹配。

_.isMatch

_.isMatch(obj, attrs):判斷 obj 是否滿足 attrs。

_.isMatch = function(object, attrs) {
var keys = _.keys(attrs), length = keys.length;
  if (object == null) return !length;
  var obj = Object(object);
  for (var i = 0; i < length; i++) {
var key = keys[i];
    if (attrs[key] !== obj[key] || !(key in obj)) return false;  // 有一個不符合就返回false
  }
return true;
};

用例:

var users = [
  {name: 'wxj', age: 18, sex: 'male'},
  {name: 'zxy', age: 18, sex: 'male'},
  {name: 'zhangsan', age: 14, sex: 'famale'}
];
_.isMatch(users[1], {age: 18, sex: 'male'}); // => true
_.isMatch(users[2], {age: 18, sex: 'male'}); // => false

_.findWhere

_.findWhere(obj, attrs):與 where 類似,但只返回第一條查詢到的記錄。

_.findWhere = function (obj, attrs) {
    return _.find(obj, _.matcher(attrs));
};

用例:

var users = [
  {name: 'wxj', age: 18, sex: 'male'},
  {name: 'zxy', age: 18, sex: 'male'},
  {name: 'zhangsan', age: 14, sex: 'famale'}
];
var ret = _.findWhere(users, {age: 18, sex: 'male'});
// => { name: 'wxj', age: 18, sex: 'male' }

_.contains 和_.includes 和_.include

_.contains(obj, item, fromIndex):判斷 obj 是否包含 item,可以設(shè)置查詢起點(diǎn) fromIndex。

_.contains = _.includes = _.include = function (obj, item, fromIndex, guard) {
    // 如果不是數(shù)組, 則根據(jù)值查找
    if (!isArrayLike(obj)) obj = _.values(obj);
    if (typeof fromIndex != 'number' || guard) fromIndex = 0;
    return _.indexOf(obj, item, fromIndex) >= 0;
};

用例:

_.contains([1, 2, 3, 4, 5], 2);
// => true
_.contains([1, 2, 3, 4, 5], 2, 4);
// => false
_.contains({name: 'wxj', age: 13, sex: 'male'}, 'male');
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 工廠模式類似于現(xiàn)實(shí)生活中的工廠可以產(chǎn)生大量相似的商品,去做同樣的事情,實(shí)現(xiàn)同樣的效果;這時候需要使用工廠模式。簡單...
    舟漁行舟閱讀 8,131評論 2 17
  • 單例模式 適用場景:可能會在場景中使用到對象,但只有一個實(shí)例,加載時并不主動創(chuàng)建,需要時才創(chuàng)建 最常見的單例模式,...
    Obeing閱讀 2,315評論 1 10
  • 最近在開發(fā) App 的時候有兩件事情讓我印象深刻 第一件事情 有一天我在測試 WebViewController ...
    azhunchen閱讀 469評論 0 5
  • QT默認(rèn)源碼編碼為不帶BOM的UTF-8 vc編譯器支持帶BOM的UTF-8編碼的源碼,如果編碼為不帶BOM的UT...
    厝弧閱讀 1,623評論 0 0
  • 昨夜繁星月下 我嗅到了都快已淡忘的芳香 依然是那么迷人與醉人心魂 回頭望去空無一物 突然覺得還是自己太敏感 望著空...
    窮奇閱讀 254評論 0 0

友情鏈接更多精彩內(nèi)容