查询

这里提到的查询主要是指完成如下意图的查询: 如果集合是数组:

  1. 根据给定元素或者查询条件,查询元素在数组中的位置
  2. 根据查询条件,获得元素或者元素集合
  3. 仅判断元素是否存在

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

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

_.indexOf_.lastIndexOf 只支持对于数组元素的搜索。

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

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

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

  • _.findKey
  • _.pluck

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

  • _.contains

createdIndexFinder

undersocre 中通过内置的工厂函数 createIndexFinder 来创建一个索引查询器,_.indexOf_.lastIndexOf 正是由该函数所创建的。

createIndexFinder(dir, predicateFind, sortedIndex) 接受 3 个参数:

  • dir:查询方向,_.indexOf 即是正向查询, _.lastIndexOf 即是反向查询。
  • predicateFind:真值检测函数,该函数只有在查询元素不是数字(NaN)才会使用。
  • 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) {
            // 如果传递sortedIndex函数, 则先假设array为排序好的, 获得item在array中的位置
            idx = sortedIndex(array, item);
            // 验证这个假设是否正确
            return array[idx] === item ? idx : -1;
        }
        // 如果待查找item不是数字,是NaN(JS中,NaN===NaN 为false), 需要通过predicateFind来查找
        if (item !== item) {
            idx = predicateFind(slice.call(array, i, length), _.isNaN);
            return idx >= 0 ? idx + i : -1;
        }
        // 否则直接通过 === 进行查找
        for (idx = dir > 0 ? i : length - 1; idx >= 0 && idx < length; idx += dir) {
            if (array[idx] === item) return idx;
        }
        return -1;
    };
};

createIndexFinder 将会返回一个索引查询器,该索引查询器支持三个参数:

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

_.indexOf

_.indexOf(array, item, sorted): 查询 arraycoll 中第一次出现的位置。

源码

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

值得一提的是,_.indexOf 方法的创建过程中被传递了 _.findIndex 作为元素的真值预测函数,以及 _.sortedIndex 作为当数组有序时获得索引的方式。这两个函数将在之后介绍。

用例

// 创建一个有序的大容量数组
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 优化后的 _.indexOf 搜索性能更高。

_.lastIndexOf

_.lastIndexOf(array, item, sorted):查询 itemarray 中最后一次出现的位置。

源码

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

用例

_.lastIndexOf([1,2,3,1,2,3], 2);
// => 4

注意,这些查找的索引的函数的都是 非贪婪 的,一旦查找到,立即返回索引并停止查找。

_.sortedIndex

_.sortedIndex(array, obj, iteratee):根据比较条件 iteratee,查询 objarray 中的位置,如果查询失败,则返回 obj 应当出现的位置。

源码

_.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 使用了二分查找作为查找算法。

用例

_.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

createPredicateIndexFinder

除了 createIndexFinder 以外,underscore 还内置了一个 createPredicateIndexFinder 的工厂函数,该函数甚至可以看做是 createIndexFinder 的增强版,因为其不仅能查询直接量在集合中的位置,也支持通过一个真值检测函数查找位置。createPredicateIndexFinder 接受 1 个参数:

  • dir:搜索方向
var createPredicateIndexFinder = function (dir) {
    /**
     * 返回的位置查询函数
     * @param array 待搜索数组
     * @param predicate 真值检测函数
     * @param context 执行上下文
     */
    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 个参数:

  • array: 待搜索数组
  • predicate:真值检测函数
  • context: 执行上下文

从源码中可以看到,现在,索引的查询是依赖于传入的真值检测函数 predicate。 并且,在迭代!迭代!迭代!中,我们知道,如果传入的 predicate 是一个立即数,会被 cb 优化为一个 _.property(predicate) 函数,用来获得对象的某个属性。

_.findIndex

_.findIndex(array, predicate):根据条件 predicate,查询元素在 array 中出现的位置。

源码

 _.findIndex = 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

_.findLastIndex(array, predicate):根据条件 predicate,查询元素在 array 中出现的位置。

源码

_.findLastIndex = createPredicateIndexFinder(-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(obj, predicate): 返回 obj 中第一个满足条件的 predicate 的 key。

源码

_.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):取出 objkey 对应的值。

源码

_.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(obj, predicate)obj 中满足条件 predicate 的元素。

源码

_.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 先确定 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

在 SQL 中,我们通常会利用到 where 关键字:

select * from users where password="123456" and username="wxj"

那么在 JavaScript 中,我们想要模拟一个 where 函数,就需要为其传递两个参数:

  • table: 集合 users
  • attrs: 属性匹配列表 { password: '123456', username='wxj'}

并且注意到,where 的核心过程仍然是集合过滤,所以 Array.prototype.filter 将是 where 的核心:

function where(table, attrs) {
    // 遍历table
    return table.filter(function(elem){
      // 观察当前遍历到的对象是否满足where条件
      return Object.keys(attrs).every(function(attr){
        return elem[attr] === attrs[attr];
      });
    });
}

// 测试
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'},
//]

_.where

_.where(obj, attrs):类似于 SQL 中的 where 限定符,获得满足 attrs 的元素。

源码

_.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'},
//]

_.where 的实现十分简洁,利用到了 _.filter 进行迭代,真值检测函数用到了 _.matcher。因此,有必要看一下 _.matcher 函数是干什么的。

_.matcher

_.matcher(obj, attrs):返回一个校验过程,用以校验对象的属性是否匹配给定的属性列表。

源码

_.matcher = _.matches = function (attrs) {
    attrs = _.extendOwn({}, attrs);
    return function (obj) {
        return _.isMatch(obj, attrs);
    };
};

用例

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];
        // 一旦遇到value不等, 或者attrs中的key不在obj中,立即返回false
        if (attrs[key] !== obj[key] || !(key in obj)) return 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');

results matching ""

    No results matching ""