ES5对Array增强的9个API

时间:2023-06-28 17:27:10

在 JavaScript 中,对于数组的操作非常频繁,对应的 API 也很丰富 。ECMAScript 规范在每一版发布时,都会提供新的 API 来增强数组的操作能力,下面将详细介绍这些 API 的一些特性。

ES5 新增的 9 个API

1、forEach( callback[,thisArg] )

在 ES5 之前,我们可以通过 forfor in 两种方式来遍历数组,ES5 引入了一个新方法 forEach,使数组遍历更加简洁,forEach 需要传递两个参数,第一个参数是回调函数,是必选参数,第二个参数是一个对象,用来改变 callback 中的 this 指向,是可选参数。

var arr = ['a', 'b', 'c'];
arr.forEach(function(v, i, r) {
  console.log(v, i, r);
})
>
a 0 ['a', 'b', 'c']
b 1 ['a', 'b', 'c']
c 2 ['a', 'b', 'c']

callback 中传入了3个参数 v,i,r 分别表示当前元素、当前位置、数组对象。再看看使用 thisArg 的例子:

var obj = {
  print: function (a, b) {
    console.log(a, b);
  }
};
var arr = ['a', 'b', 'c'];
arr.forEach(function (v, i, a) {
  this.print(v, i);
}, obj);

不传 thisArgs 时,callback 中的 this 默认指向 window 对象,当传递 thisArg 时,callback 中的 this 就指向了 thisArg,因此这个参数的目的就是为了改变回调函数中的this指向。

对于不支持 ES5 的浏览器,我们可以对 forEach 进行简单的扩展来兼容老的浏览器:

if (!Array.prototype.forEach) {
  Array.prototype.forEach = function (callback, thisArg) {
    for (var i=0; i < this.length; i++) {
      // 当thisArg为undefined时,JS引擎会将window作为其调用者
      callback.call(thisArg, this[i], i, this.toString());
    }
  }
}

2、filter( callback [, thisArg] )

filter 是`过滤`的意思,所以这个方法的作用就是返回一个匹配过滤条件的新数组,其接收两个参数 callback thisArgcallback 也是回调函数,主要用于对元素进行条件匹配,thisArgforEach 中的 thisArg 作用一样,在这里就不重复了,看下面示例:

var arr = ["a", "b", "a", "c"];
var newArr = arr.filter(function (item) {
  return item === "a";
});
newArr > ["a", "a"]

没有filter的时候,要实现这个功能,我们事先要创建一个空的数组,把匹配到的元素再 push 进去,现在就不需要那么麻烦了,我们再看看对filter的扩展:

if (!Array.prototype.filter) {
  Array.prototype.filter = function (callback, thisArg) {
    var temp = [];
    for (var i = 0; i < this.length; i++) {
      if (callback.call(thisArg, this[i])){
        // 如果callback返回true,则该元素符合过滤条件,将元素压入temp中
        temp.push(this[i]);
      }
    }
    return temp;
  }
}

可以看出,filter 将过滤的结果作为一个新数组返回,即使符合条件的元素只有一个,返回的也是数组 。为了更方便的对单个元素进行查询,ES6 在数组原型上提供了 find 方法,用于从数组中查询单个符合条件的元素,和 filter 不同的是,它返回的是单个元素。

[2, 3, 5, 8, 9, 3].find(item => item == 3); // 3  

需要注意的是,find 只返回第一个匹配到的元素,如果没有匹配到,则会返回 undefined 。和 filter 一样,find 也可以传递第 2 个参数,用于设置回调函数的 this 指针 。

3、map( callback[,thisArg] )

map 的作用是对原数组进行加工处理后并将其作为一个新数组返回,该方法同样接收两个参数,callback 是回调函数用于对数组进行加工处理,thisArg 和上面的一样。先看一个简单的例子:

var arr = [
  {w: 10, h: 10}, //定义长和宽
  {w: 15, h: 20},
  {w: 12, h: 12}
];
var newArr = arr.map(function (item) {
  // 根据长宽计算出面积并赋值给新属性area
  item.area = item.w * item.h;
  return item;
});
newArr[0] > {w: 10, h: 10, area: 100}

可以看出,newArr 返回的是增加了 area 属性的对象数组。这个方法非常实用,一般情况下,当一个ajax请求返回时,我们都要对其结果集进行过滤和校验等操作,这时 map 就派上用场了。我们再看看如果对 map 进行兼容性扩展:

if (!Array.prototype.map) {
  Array.prototype.map = function (callback, thisArg) {
    var temp = [];
    for (var i = 0; i < this.length; i++) {
      var newItem = callback.call(thisArg, this[i]);
      temp.push(newItem); // 将callback返回的新元素压入temp中
    }
    return temp;
  }
}

4、reduce ( callback[,initialValue] )

reduce 在这里有`减少`的意思,其作用是对数组进行归并操作,换句话说就是对数组每一个元素进行累加,最终返回所有元素之和。 回调函数 callback 接收4个参数:

previousValue - 存放的是上一次callback返回的结果,其初始值默认为数组的第一个元素。

currentValue - 是当前元素 。默认从数组的第二个元素开始。

currentIndex - 是当前元素位置 。

array - 是当前数组。

var arr = [1, 2, 3, 4];
var newArr = arr.reduce(function (previousValue, currentValue, currentIndex, array) {
  console.log(previousValue, currentValue, currentIndex);
  return previousValue + currentValue;
});
1 2 1
3 3 2
6 4 3
newArr > 10

reduce 除过可以传递 callback 之外,还可以传递一个参数 initialValue ,作为数组累加的基数。当传了这个参数以后,callback 中的 previousValue 初始值就被置为 initialValuereduce 也改为从数组的第一个元素开始遍历。

var arr = [1, 2, 3, 4];
var newArr = arr.reduce(function (previousValue, currentValue, currentIndex, array){
  console.log(previousValue, currentValue, currentIndex);
  return previousValue + currentValue;
}, 100);
100 1 0
101 2 1
103 3 2
106 4 3
newArr > 110

从结果可以看出,reduce 最终返回的是: previousValue + 数组本身归并计算的结果。对 reduce 的 polyfill  实现如下:

if (!Array.prototype.reduce) {
  Array.prototype.reduce = function (callback, initialValue) {
    var previousValue = initialValue || this[0];// 如果不指定intialValue,则默认为数组的第一个元素
    // 如果不指定initialValue,i从1开始遍历,否则就从0开始遍历
    for (var i = initialValue ? 0 : 1; i < this.length; i++) {
      // previousValue 累加每一次返回的结果
      previousValue = callback(previousValue, this[i], i, this.toString());
    }
    return previousValue;
  }
}

5、reduceRight ( callback[,initialValue] )

和 reduce 的作用完全相同,唯一的不同是,reduceRight 是从右至左遍历数组的元素。

6、some ( callback[,thisArg] )

some 是`某些、一些`的意思,其作用是对数组中的每一项执行回调函数,如果该函数对任一项返回 true,则停止遍历,并返回 true 。

var arr = [ 1, 2, 3, 4];
var result = arr.some(function(item, index, array ){
  console.log(item, index, array);
  return item > 2;
});
>
1 0 [1, 2, 3, 4]
2 1 [1, 2, 3, 4]
3 2 [1, 2, 3, 4]
restule > true

some 检测整个数组,只要当arr中有一个元素符合条件 item>2 就停止检测和遍历,并返回 true,以表示检测到目标。这和我们在 for 循环中使用 break 语言的作用有点类似。 对于 some 的兼容性扩展如下:

if(!Array.prototype.some) {
  Array.prototype.some = function (callback, thisArg) {
    for (var i = 0; i < this.length; i++) {
      if(callback.call(thisArg, this[i], i, this.toString())){
        return true; // 检测到callback返回true,跳出循环,并返回true
      }
    }
    return false; // 一个符合条件的都没有检测到,返回false
  }
}

7、every (callback[,thisArg])

every 是`每一个`的意思,其作用是对数组中的每一项执行回调函数,如果该函数对每一项都返回 true,则返回 true 。

var arr = [ 1, 2, 3, 4];
var result = arr.every(function(item, index, array ){
  console.log(item, index, array);
  return item < 3;
});
1 0 [1, 2, 3, 4]
2 1 [1, 2, 3, 4]
3 2 [1, 2, 3, 4]
result > false

当检测第3个元素时,item<3 为 false,停止检测,并返回 false,这说明every在检测元素时,要求每一个元素都要符合条件 item<3,如果有一个不符合就停止检测,并返回false。(你可以测试 item<5 时的运行结果,返回值一定是 true ) 。

那 every 到底有什么作用呢? 当一个 for 循环使用了 break 语句后,我们想知道 for 循环是否正常的执行完时, 我们一般会通过检测for中的索引 i==arr.length 来判断,因此every 的作用就体现在这里。

下面是对于 every 的兼容性扩展:

if (!Array.prototype.every) {
  Array.prototype.every = function (callback, thisArg) {
    for (var i = 0; i < this.length; i++) {
      if(!callback.call(thisArg,this[i], i, this.toString())){
        return false; // 检测到不符合条件的元素,跳出循环,并返回false
      }
    }
    return true; // 所有元素都符合条件,返回true
  }
}

8、indexOf[searchElement[, fromIndex]]

indexOf() 用于查询数组元素对应的索引位置,可以传递两个参数,第一个参数是要匹配的元素,必须是简单数据类型。第二个参数是指定查询的起始位置。

// 默认从索引0的位置开始
[1, 2, 3, 5, 2].indexOf(2); // 1
// 指定从索引3的位置开始
[1, 2, 3, 5, 2].indexOf(2, 3); // 4

indexOf() 返回的是元素在数组中的位置 。如果只想知道数组中是否存在某个元素,而不关心元素的位置,也可以使用 ES6 提供的 includes() 方法来判断。

let a = [1, 2, 3];
a.includes(1); // true
a.includes(1, 1);// false

includes() 也是数组原型上的方法, 和 indexOf() 的传参是一样的。

需要注意的是,indexOf() 适用于数组元素是简单类型的情况,而无法检索对象数组的元素位置。

let arr = [{c: 1}, {c: 2}]; // 对象数组
arr.indexOf({c: 1}); // -1

对于这个问题,可以使用 forEach() 来遍历数组,当找到符合条件的元素时,就可以获取到对应的数组下标,而在 ES6 中,可以使用 findIndex() 达到同样的目的。

findIndex() 也是用于查询数组元素的位置,和 indexOf() 不同的是,它可以检索对象数组的元素位置,但需要通过回调函数来指定匹配的元素。

//简单数组
[1, 2, 3, 5].findIndex(item => item == 3); // 2
//对象数组
[{id: 1}, {id: 3}, {id: 5}].findIndex(item => item.id == 3); // 1

9、lastIndexOf[searchElement[, fromIndex]]

和 indexOf() 的作用完全相同,唯一的不同是,lastIndexOf() 是从右至左检索数组元素。

其他常用 API

1、sort( [compareFunction] )

对数组做原地排序,并返回这个数组,默认按照字符串 UNICODE 的码位点排序,如下所示:

var fruit = ['cherries', 'apples', 'bananas'];
fruit.sort(); // ['apples', 'bananas', 'cherries']
var scores = [1, 10, 2, 21];
scores.sort(); // [1, 10, 2, 21]

这显然不是我们想要的结果。为了实现真正意义上的排序,可以给 sort 传递一个回调函数 compareFunction, 其接收两个参数ab,分别代表数组中待比较的两个元素,a 元素 排在 b 的前面 。

回调函数需要返回一个表达式,用以标明 升序 或 降序 操作:

  • return a - b :如果表达式 a - b 为真,触发交换操作。也就是说, 当 a > b 时,进行元素交换,让较小的元素 b 排在较大的元素 a 前面,即 升序操作。
  • return b - a :如果表达式 b - a 为真,触发交换操作。也就是说, 当 a < b 时,进行元素交换,让较大的元素 b 排在较小的元素 a 前面,即 降序操作。  
var numbers = [2, 4, 1, 10, 3];
// 回调参数 a,b 是数组要比较的两个元素,a 排在 b 的前面。
numbers.sort(function(a, b){
  // 当 a > b 时触发交换操作,把较小的排在前面,即升序。
  return a - b;
});
> [1,2,3,4,10] numbers.sort(function(a,b){
  // 当 a < b 时触发交换操作,把较大的排在前面,即降序。
  return b - a;
});
> [10,4,3,2,1]

2、join( [separator] )

将数组中的所有元素连接成一个字符串。separtor 用于指定连接每个数组元素的分隔符。分隔符会被转成字符串类型;如果省略的话,默认为一个逗号。如果separtor 是一个空字符串,那么数组中的所有元素将被直接连接。

var data = ['Wind', 'Rain', 'Fire'];
data.join(); // Wind,Rain,Fire
data.join(', '); // Wind, Rain, Fire
data.join(' + '); // Wind + Rain + Fire
data.join(''); // WindRainFire

3、concat( value1,...,valueN )

concat 方法将创建一个新的数组,然后将调用它的对象(this 指向的对象)中的元素以及所有参数中的数组类型的参数中的元素以及非数组类型的参数本身按照顺序放入这个新数组,并返回该数组, valueN 允许是数组或非数组值。在没有给 concat 传递参数的情况下,它只是复制当前数组并返回副本。

var alpha = ['a', 'b', 'c'];
alpha.concat(1, [2, 3]); //["a", "b", "c", 1, 2, 3]
alpha.concat(); // ['a', 'b', 'c']

4、push( element1,...,elementN )  和  pop( )

push 添加一个或多个元素到数组的末尾,并返回数组新的长度;pop删除一个数组中的最后的一个元素,并且返回这个元素。

var data = [1, 2, 3];
data.push(4, 5, 6); // 6 > 数组的长度 data > [1,2,3,4,5,6]
data.pop(); //6 > 出栈的元素 data > [1,2,3,4,5]

注意:push 和 pop 并不会改变原来的元素位置。

5、unshift( element1, ..., elementN )  和  shift( )

unshift 添加一个或多个元素到数组的开头,并返回数组新的长度;shift 删除一个数组中的第一个元素,并且返回这个元素。

var data = [1, 2, 3];
data.unshift(-1, -2, -3); // 6 > 新数组的长度 data > [-1,-2,-3,1,2,3]
data.shift(); // -1 > 被移除的元素 data > [-2,-3,1,2,3]

注意:unshift 和 shift 都会改变原来的元素位置。

如果把数组看成一个栈,push 和 pop 、unshift 和 shift 对数组的操作行为就符合 后进先出 (LIFO) 规律 ;如果把数组看成一个队列,pushshiftunshiftpop 对数组的操作行为就符合 先进先出 (FIFO) 规律。

因此,可以使用数组来实现对应的数据结构: 和 队列

6、slice( begin [, end] )

slice 方法从begin 的索引位置开始提取数组元素,到 end 位置结束,但不包括 end 位置的元素,如果 end 被省略,则默认提取到数组的结尾,如果结束位置小于起始位置,则返回空数组。

var data = [1, 2, 3];
data.slice(0); // [1,2,3] 提取的元素
data.slice(1, 2); // [2] 提取的元素
data.slice(2, 1); // []

如果参数中有一个负数,则用数组长度加上该数来确定相应的位置。例如,在一个包含 5 项的数组上调用 slice(-2, -1) 与调用  slice(3, 4) 得到的结果相同。

var t = [1, 2, 3, 4, 5];
t.slice(-2, -1); // [4]
t.slice(3, 4); // [4]

slice方法会返回一个新的数组,由于数组是引用类型, 通常会通过 arr.slice(0) 来实现数组的 浅拷贝,但这只对 数组元素是基本类型 的情况有效。

// 简单数组拷贝
var arr = [1, 2, 3, 4];
var cyarr = arr.slice(0); // 浅拷贝
arr.splice(3, 1); // 对原数组操作
console.log(arr, cyarr); //[1,2,3] , [1,2,3,4] > 拷贝成功

如果是对象数组,数组元素作为指针指向对象内存,slice(0) 仅仅是拷贝了一份指针作为副本,而副本中的指针,指向的还是原来的对象,因此,对一个数组中的对象做操作,另一个数组中的对象也会同步变化。

//对象数组拷贝
var list = [{name: 'zhangsan'}];
var cylist = list.slice(0); // 浅拷贝
list[0].name = 'lisi'; // 对原数组操作
console.log(list, cylist); // [{name:'lisi'}] , [{name:'lisi'}] -> 拷贝失败  

要实现数组的深拷贝,需要通过 JSON 的序列化和反序列化来实现,即: JSON.parse( JSON.stringify(arr) )

//对象数组拷贝
var list = [{name: 'zhangsan'}];
var cylist = JSON.parse(JSON.stringify(list)); // 深拷贝
list[0].name = 'lisi'; // 对原数组操作
console.log(list, cylist); // [{name: 'lisi'}] , [{name: 'zhangsan'}] > 拷贝成功  

7、splice( start, deleteCount[,value1,...,valueN] )

splice方法从一个数组中移除一个或多个元素,如果必要,在所移除元素的位置上插入新元素,返回所移除的元素。

data.splice(2, 1); // [3] > 被删除的元素 data > [1,2]
data.splice(2, 2, 4, 5); // [3] > 被删除的元素 data > [1,2,4,5]
data.splice(2, 2, 4, 5, 6); // [3] > 被删除的元素 data > [1,2,4,5,6]

原创发布 @一像素 2016.01