Better

Ethan的博客,欢迎访问交流

JavaScript数组最值、扁平化和元素查找

很多基本的操作我们其实都懂,但是我们是不是该思考一下有没有更加合适的方法呢?比如提高效率,简化代码!

最大值和最小值

Math.max

JavaScript 提供了 Math.max 函数返回一组数中的最大值,用法是:

Math.max([value1[,value2, ...]])

需要注意的是:

  • 如果有任一参数不能被转换为数值,则结果为 NaN。
  • max 是 Math 的静态方法,所以应该像这样使用:Math.max(),而不是作为 Math 实例的方法 (简单的来说,就是不使用 new )
  • 如果没有参数,则结果为 -Infinity (注意是负无穷大)

基本方法

原始方法-循环遍历

var result = arr[0];
for (var i = 1; i < arr.length; i++) {
    result =  Math.max(result, arr[i]);
}

reduce简化代码

function max(prev, next) {
    return Math.max(prev, next);
}
console.log(arr.reduce(max));

排序

如果我们先对数组进行一次排序,那么最值就是两端的值。

arr.sort(function(a,b){return a - b;});
console.log(arr[arr.length - 1])

eval、apply、...

Math.max 支持传多个参数来进行比较,那么我们如何将一个数组转换成参数传进 Math.max 函数呢?eval 便是一种。

var max = eval("Math.max(" + arr + ")");

使用 apply 是另一种。

console.log(Math.max.apply(null, arr))

使用 ES6 的扩展运算符。

console.log(Math.max(...arr))

数组扁平化

数组的扁平化,就是将一个嵌套多层的数组 array (嵌套可以是任何层数)转换为只有一层的数组。

那么如何书写一个扁平化函数呢,常用办法如下:

  • 递归
  • 如果数组的元素都是数字,那么我们可以考虑使用 toString 方法

    function flatten(arr) {
      return arr.toString().split(',').map(function(item){
          return +item
      })
    }
    
  • reduce简化代码

    function flatten(arr) {
      return arr.reduce(function(prev, next){
          return prev.concat(Array.isArray(next) ? flatten(next) : next)
      }, [])
      // []表示初始值
    }
    
  • ...操作符

    var arr = [1, [2, [3, 4]]];
    console.log([].concat(...arr)); // [1, 2, [3, 4]]
    // 只能扁平化一层,没关系。修改一下
    function flatten(arr) {
    
      while (arr.some(item => Array.isArray(item))) {
          arr = [].concat(...arr);
      }
    
      return arr;
    }
    
  • 抽象扁平化函数

    /**
    * 数组扁平化
    * @param  {Array} input   要处理的数组
    * @param  {boolean} shallow 是否只扁平一层
    * @param  {boolean} strict  是否严格处理元素,下面有解释
    * @param  {Array} output  这是为了方便递归而传递的参数
    * 源码地址:https://github.com/jashkenas/underscore/blob/master/underscore.js#L528
    */
    function flatten(input, shallow, strict, output) {
      // 递归使用的时候会用到output
      output = output || [];
      var idx = output.length;
      for (var i = 0, len = input.length; i < len; i++) {
          var value = input[i];
          // 如果是数组,就进行处理
          if (Array.isArray(value)) {
              // 如果是只扁平一层,遍历该数组,依此填入 output
              if (shallow) {
                  var j = 0, len = value.length;
                  while (j < len) output[idx++] = value[j++];
              }
              // 如果是全部扁平就递归,传入已经处理的 output,递归中接着处理 output
              else {
                  flatten(value, shallow, strict, output);
                  idx = output.length;
              }
          }
          // 不是数组,根据 strict 的值判断是跳过不处理还是放入 output
          else if (!strict){
              output[idx++] = value;
          }
      }
      return output;
    }
    

元素查找

在这里我想让自己的理解的就是可以传递函数形参作为迭代器使用

实现ES6的findIndex方法,功能如下:

function isBigEnough(element) {
  return element >= 15;
}

[12, 5, 8, 130, 44].findIndex(isBigEnough);  // 3

实现findIndex

function findIndex(array, predicate, context) {
    for (var i = 0; i < array.length; i++) {
        if (predicate.call(context, array[i], i, array)) return i;
    }
    return -1;
}

实现findLastIndex

function findLastIndex(array, predicate, context) {
    var length = array.length;
    for (var i = length; i >= 0; i--) {
        if (predicate.call(context, array[i], i, array)) return i;
    }
    return -1;
}

细心的话上面的代码其实重复,有没有办法可以精简一些呢,在这里我们需要一个工厂函数,这里很关键

function createIndexFinder(dir) {
    return function(array, predicate, context) {

        var length = array.length;
        var index = dir > 0 ? 0 : length - 1;

        for (; index >= 0 && index < length; index += dir) {
            if (predicate.call(context, array[index], index, array)) return index;
        }

        return -1;
    }
}

var findIndex = createIndexFinder(1);
var findLastIndex = createIndexFinder(-1);

实现sortedIndex 在一个排好序的数组中找到 value 对应的位置,保证插入数组后,依然保持有序的状态。

问题的关键就是给需要插入的元素找一个满足要求的下标即可,为提交效率,针对排序的数组,采用二分法。先来一版简单代码:

function sortedIndex(array, obj) {

    var low = 0, high = array.length;

    while (low < high) {
        var mid = Math.floor((low + high) / 2);
        if (array[mid] < obj) low = mid + 1;
        else high = mid;
    }

    return high;
};

我希望自己养成爱思考的习惯,上面的代码有什么不足呢?他只能针对原始类型,如果我的数组内元素是对象,且判断依据是对象的某个属性呢?可以就需要传入迭代器函数、

function cb(fn, context) {
    return function(obj) {
        // 如果有迭代函数,则执行,否则返回本身
        return fn ? fn.call(context, obj) : obj;
    }
}

function sortedIndex(array, obj, iteratee, context) {

    iteratee = cb(iteratee, context)

    var low = 0, high = array.length;
    while (low < high) {
        var mid = Math.floor((low + high) / 2);
        if (iteratee(array[mid]) < iteratee(obj)) low = mid + 1;
        else high = mid;
    }
    return high;
};

来源



留言