美文网首页
JS进阶——underscore源码(2)

JS进阶——underscore源码(2)

作者: 金华每文 | 来源:发表于2017-07-04 19:58 被阅读0次

    _.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');
    

    相关文章

      网友评论

          本文标题:JS进阶——underscore源码(2)

          本文链接:https://www.haomeiwen.com/subject/iuvthxtx.html