JS进阶——underscore源码(2)

_.each 和 _.forEach

在 ES5 中,只能对数组对象进行迭代,而 underscore 提供的迭代方法,除了支持 array, 还支持 object 的迭代, 对 object 迭代的依据是对象的键序列 keys,我们可以查看 underscore 中的 _.each 方法:

_.each = _.forEach = function(obj, iteratee, context) {
        iteratee = optimizeCb(iteratee, context);
        // 先处理一下传入的迭代函数,回顾一下,这里如果没有context,则直接使用iteratee作为函数遍历,否则迭代函数将以当前值、当前索引、完整集合作为参数进行调用
        let i, length;
        if (isArrayLike(obj)) {
            // 区分数组和对象的迭代过程
            for (i = 0, length = obj.length; i < length; i++) {
                iteratee(obj[i], i, obj);
                // 数组的迭代回调传入三个参数(迭代值, 迭代索引, 迭代对象)
            }
        } else {
            let keys = _.keys(obj);
            for (i = 0, length = keys.length; i < length; i++) {
                iteratee(obj[keys[i]], keys[i], obj);
                // 对象的迭代回调传入三个参数(迭代值, 迭代的key, 迭代对象)
            }
        }
        // 返回对象自身, 以便进行链式构造
        return obj;
    };

_.map 和 _.collect

map 的实现思路如下:
1、创建一个新列表或者元素
2、遍历原列表或者原对象的值,用指定的函数 func 作用于每个遍历到的元素,输出一个新的元素放入新列表或者对象中

    _.map = _.collect = function(obj, iteratee, context) {
        iteratee = cb(iteratee, context);
        // 这里将根据iteratee决定是返回等价、函数调用、属性匹配或者属性访问
        let keys = !isArrayLike(obj) && _.keys(obj),
            // 类数组对象为false,否则则取对象全部键
            length = (keys || obj).length,
            // 类数组对象为length属性,否则为对象键值对数量
            results = Array(length);
            // 要返回的新的集合
        for (let index = 0; index < length; index++) {
            let currentKey = keys ? keys[index] : index;
            // 类数组对象取索引,否则取键名
            results[index] = iteratee(obj[currentKey], currentKey, obj);
            // 放入对应位置的值经过iteratee处理后的值
        }
        return results;
    };

使用实例

对数组使用 _.map 函数:

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 函数:

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的实现

    // 抽象递归过程
    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) {
                // 第一次的时候创建memo用来存储
                memo = obj[keys ? keys[index] : index];
                index += dir;
            }
            // 根据方向递归遍历
            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) {
            // 确认initial的初值
            let initial = arguments.length >= 3;
            // 返回迭代为累加器的迭代函数
            return reducer(obj, optimizeCb(iteratee, context, 4), memo, initial);
        };
    };

    // 从左往右递归
    _.reduce = _.foldl = _.inject = createReduce(1);
    // 从右往左递归
    _.reduceRight = _.foldr = createReduce(-1);

使用用例:
1、对数组使用 _.reduce:

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

2、一般对象也可以进行_.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 只支持对于数组元素的搜索。)

对于元素查询,underscore 提供了以下 API:

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

如果集合是对象,即集合是键值对构成的,则提供了以下 API:

_.findKey
_.pluck

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

_.contains

_.indexOf和_.lastIndexOf

createIndexFinder(dir, predicateFind, sortedIndex) 接受 3 个参数:
1、dir:查询方向,_.indexOf 即是正向查询, _.lastIndexOf 即是反向查询。
2、predicateFind:真值检测函数,该函数只有在查询元素不是数字(NaN)才会使用。
3、sortedIndex:有序数组的索引获得函数。如果设置了该参数,将假定数组已经有序,从而更加高效的通过针对有序数组的查询函数(比如二分查找等)来优化查询性能。

// 传递三个参数,分别是方向、判断函数、查找函数
var createIndexFinder = function(dir, predicateFind, sortedIndex) {

  // 返回的函数有三个参数,分别是要查询的数组、要查询的内容、以及查询的起始位置或者是否排序
  return function(array, item, idx) {

    var i = 0, length = getLength(array);
    if (typeof idx == 'number') {  // 首先如果传递的索引是数字
      if (dir > 0) {  // 如果从前往后
        i = idx >= 0 ? idx : Math.max(idx + length, i);  // 处理索引起点,当输入负数的时候表示从后往前,但转换成从前往后的索引位置
      } else {
        length = idx >= 0 ? Math.min(idx + 1, length) : idx + length + 1;  // 处理查找的最后位置
      }

    } else if (sortedIndex && idx && length) { // 如果有有序查找函数、并且已知数组有序并且非空
      idx = sortedIndex(array, item); // 查找到相应的位置
      return array[idx] === item ? idx : -1;  // 如果该位置就是要查找的内容则返回该位置,否则返回-1
    }


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

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

createIndexFinder 将会返回一个索引查询器,该索引查询器支持三个参数:
1、array:待搜索数组
2、item:待搜索对象
3、idx: 查询起点,从数组的哪个位置开始查找。如果以数字的方式设置了查询起点,或者未设置查询起点,则无法使用 sortedIndex 方法进行查询优化。通常,我们可以设置该值为语义更加明显的 true(代表启用查询优化)来对有序数组进行查询优化。

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

用例:

// 创建一个有序的大容量数组
var array = [];
for(var i=0;i < 1000000;i++) {
  array[i] = i;
}
console.time("以数字方式设置了查询起点,搜索耗时");
_.indexOf(array,500000);
console.timeEnd("以数字方式设置了查询起,搜索耗时");
// 以数字方式设置了查询起,搜索耗时:1.561ms
console.time("以非数字方式设置了查询起点,搜索耗时");
_.indexOf(array,500000, true);
console.timeEnd("以非数字方式设置了查询起点,搜索耗时");
// 以非数字方式设置了查询起点,搜索耗时:0.308ms

_.sortedIndex

// 传递四个参数,分别是要查询的数组、想要判断的对象、迭代器、上下文
_.sortedIndex = function(array, obj, iteratee, context) {
  iteratee = cb(iteratee, context, 1);  // 迭代函数,用来处理迭代
  var value = iteratee(obj);
  var low = 0, high = getLength(array);  // 二分法的高低位设置
  while (low < high) {  // 循环
    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` 如果查找的元素不存在,将返回元素应当存在的位置
_.sortedIndex([10, 20, 30, 40, 50], 35); // => 3
// _.sortedIndex`也支持对对象集合的搜索。
_.sortedIndex([{name: 'wxj'}, {name: 'lx'}, {name: 'lcx'}, {name: 'wxj'}]);
// => 0

_.findInde和_.findLastIndex

寻找符合条件的元素第一次或者最后一次出现的地方。

createPredicateIndexFinder 接受 1 个参数: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;
    // 根据方向遍历
    for (; index >= 0 && index < length; index += dir) {
if (predicate(array[index], index, array)) return index;
    }
return -1;
  };
};

它将返回一个索引查询函数,该查询函数接受 3 个参数:
1、array: 待搜索数组
2、predicate:真值检测函数
3、context: 执行上下文
如果传入的 predicate 是一个立即数,会被 cb 优化为一个
_.property(predicate) 函数,用来获得对象的某个属性。

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

用例:

// 下面的调用将不会返回3,因为`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;  
// 判断函数传递三个参数,分别是属性值、属性名和整个对象
  }
};

用例:

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

_.pluck

_.pluck(obj, key):取出 obj 中 key 对应的值。

_.pluck = function (obj, key) {
    // 迭代集合, 每个迭代元素返回其对应属性的对应值
    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) {
        // 传入三个参数,分别是要查找的对象、判断条件、上下文
        let keyFinder = isArrayLike(obj) ? _.findIndex : _.findKey;
        // 数组则查找索引,对象查找键
        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

创建判断对象是否符合给定的条件的函数。

_.matcher = _.matches = function(attrs) {
  attrs = _.extendOwn({}, attrs);  // 使用传参创建一个对象
  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,最终返回一个校验过程,通过 _.isMatch 来校验 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,可以设置查询起点 fromIndex。

_.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([1, 2, 3, 4, 5], 2);
// => true
_.contains([1, 2, 3, 4, 5], 2, 4);
// => false
_.contains({name: 'wxj', age: 13, sex: 'male'}, 'male');
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,390评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,821评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,632评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,170评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,033评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,098评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,511评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,204评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,479评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,572评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,341评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,213评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,576评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,893评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,171评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,486评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,676评论 2 335

推荐阅读更多精彩内容

  • 工厂模式类似于现实生活中的工厂可以产生大量相似的商品,去做同样的事情,实现同样的效果;这时候需要使用工厂模式。简单...
    舟渔行舟阅读 7,697评论 2 17
  • 单例模式 适用场景:可能会在场景中使用到对象,但只有一个实例,加载时并不主动创建,需要时才创建 最常见的单例模式,...
    Obeing阅读 2,045评论 1 10
  • 最近在开发 App 的时候有两件事情让我印象深刻 第一件事情 有一天我在测试 WebViewController ...
    azhunchen阅读 400评论 0 5
  • QT默认源码编码为不带BOM的UTF-8 vc编译器支持带BOM的UTF-8编码的源码,如果编码为不带BOM的UT...
    厝弧阅读 1,521评论 0 0
  • 昨夜繁星月下 我嗅到了都快已淡忘的芳香 依然是那么迷人与醉人心魂 回头望去空无一物 突然觉得还是自己太敏感 望着空...
    窮奇阅读 180评论 0 0