JavaScript数组是最常用的数据类型之一,对于数组的操作,JavaScript也提供了一些非常方便的函数和方法,对这些函数与方法的熟练掌握和运用,能让程序编写更方便,也使程序结构更清楚、更容易理解,本文代码均来自modilla MDN开发者官网。

1. map()方法

在JavaScript中,数组的map方法原型为Array.prototype.map()。

map()方法调用一个函数,将函数应用在数组中每个元素上,然后创建并返回一个新数组(不会修改原数组)


const array1=[1,2,3,4];
const map1=array1.map(x=>x*2);
//map1=[2,4,6,8]


官方文档中定义的map方法用法如下,其中,callback函数包含一个currentValue(数组中当前要处理的元素)参数与两个可选的参数index(当前正在处理的元素索引)以及array(map方法调用的数组),以及一个可选的thisArg用来指定this的作用域。


var new_array = arr.map(function callback(currentValue[, index[, array]]) {
 // Return element for new_array 
}[, thisArg])


MDN官方文档提供了几个关于map方法的示例:

1.1 使用 map 重新格式化数组中的对象

以下代码使用一个包含对象的数组来重新创建一个格式化后的数组。


var kvArray = [{key: 1, value: 10}, 
               {key: 2, value: 20}, 
               {key: 3, value: 30}];
var reformattedArray = kvArray.map(function(obj) { 
   var rObj = {};
   rObj[obj.key] = obj.value;
   return rObj;
});
// reformattedArray 数组为: [{1: 10}, {2: 20}, {3: 30}], 
// kvArray 数组未被修改: 
// [{key: 1, value: 10}, 
//  {key: 2, value: 20}, 
//  {key: 3, value: 30}]
/*也可以用ES6开始支持的箭头函数写成以下样式
var rfArray=kvArray.map((obj)=>{
  let rObj={};
  rObj[obj.key]=obj.value;
  return rObj});
*/


1.2 使用一个包含一个参数的函数来mapping(构建)一个数字数组

下面的代码表示了当函数需要一个参数时map的工作方式。当map循环遍历原始数组时,这个参数会自动被分配成数组中对应的每个元素。


var numbers = [1, 4, 9];
var doubles = numbers.map(function(num) {
  return num * 2;
});
// doubles数组的值为: [2, 8, 18]
// numbers数组未被修改: [1, 4, 9]


1.3 一般的map 方法

下面的例子演示如何在一个 String 上使用 map 方法获取字符串中每个字符所对应的 ASCII 码组成的数组:


var map = Array.prototype.map
var a = map.call("Hello World", function(x) { 
  return x.charCodeAt(0); 
})
//也可以用箭头函数改写
//var a = map.call("Hello World", x=> x.charCodeAt(0))
// a的值为[72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100]


1.4 quenrySelectorAll应用

下面代码展示了如何去遍历用 querySelectorAll 得到的动态对象集合。在这里,我们获得了文档里所有选中的选项,并将其打印:


var elems = document.querySelectorAll('select option:checked');
var values = Array.prototype.map.call(elems, function(obj) {
  return obj.value;
});


1.5 使用技巧案例

通常情况下,map 方法中的 callback 函数只需要接受一个参数,就是正在被遍历的数组元素本身。但这并不意味着 map 只给 callback 传了一个参数。这个思维惯性可能会让我们犯一个很容易犯的错误。

考虑下例:


["1", "2", "3"].map(parseInt);


我们期望输出 [1, 2, 3], 而实际结果是 [1, NaN, NaN].

parseInt 经常被带着一个参数使用, 但是这里接受两个。第一个参数是一个表达式而第二个是callback function的基, Array.prototype.map 传递3个参数:

  • the element
  • the index
  • the array

第三个参数被parseInt忽视了,, 但不是第二个。因此可能出现混淆。下面是迭代步骤的简明示例:


// parseInt(string, radix) -> map(parseInt(value, index))
/*  first iteration (index is 0): */ parseInt("1", 0); // 1
/* second iteration (index is 1): */ parseInt("2", 1); // NaN
/*  third iteration (index is 2): */ parseInt("3", 2); // NaN


下面让我们来讨论解决方案:


function returnInt(element) {
  return parseInt(element, 10);
}
['1', '2', '3'].map(returnInt); // [1, 2, 3]
// Actual result is an array of numbers (as expected)
// Same as above, but using the concise arrow function syntax
['1', '2', '3'].map( str => parseInt(str) );
// A simpler way to achieve the above, while avoiding the "gotcha":
['1', '2', '3'].map(Number); // [1, 2, 3]
// But unlike parseInt(), Number() will also return a float or (resolved) exponential notation:
['1.1', '2.2e2', '3e300'].map(Number); // [1.1, 220, 3e+300]
// For comparison, if we use parseInt() on the array above:
['1.1', '2.2e2', '3e300'].map( str => parseInt(str) ); // [1, 2, 3]


一个map方法调用 parseInt 作为一个参数的等效输出运行如下:


var xs = ['10', '10', '10'];
xs = xs.map(parseInt);
console.log(xs);  // 输出结果为(3) [10, NaN, 2]
// Actual result of 10,NaN,2 may be unexpected based on the above description.


1.6 Mapping 含 undefined的数组

当返回undefined 或没有返回任何内容时:


var numbers = [1, 2, 3, 4];
var filteredNumbers = numbers.map(function(num, index) {
  if(index < 3) {
     return num;
  }
});
//index goes from 0,so the filterNumbers are 1,2,3 and undefined.
// filteredNumbers is [1, 2, 3, undefined]
// numbers is still [1, 2, 3, 4]


2.filter()方法

在JavaScript中,数组的filter方法原型为Array.prototype.filter()。

与map()方法类似,filter()也提供一个函数并返回一个新的数组(不修改原数组),filter()返回的数组包含了满足函数条件的所有元素。

官方文档中提供的filter()方法如下,其中,callback函数包含一个element(数组中当前要处理的元素)参数与两个可选的参数index(当前正在处理的元素索引)以及array(filter方法调用的数组),以及一个可选的thisArg用来指定this的作用域。如果未提供thisArg参数,在非严格模式下this代表全局对象,严格模式下为undefined.


var


MDN官方文档提供了几个关于filter方法的示例:

2.1 筛选排除所有较小的值

下例使用 filter 创建了一个新数组,该数组的元素由原数组中值大于 10 的元素组成。


function


2.2 过滤 JSON 中的无效条目

以下示例使用 filter() 创建具有非零 id 的元素的 json。


var


2.3 在数组中搜索

下例使用 filter() 根据搜索条件来过滤数组内容。


var fruits = ['apple', 'banana', 'grapes', 'mango', 'orange'];
/**
 * Array filters items based on search criteria (query)
 */
function filterItems(query) {
  return fruits.filter(function(el) {
      return el.toLowerCase().indexOf(query.toLowerCase()) > -1;
  })
}
console.log(filterItems('ap')); // ['apple', 'grapes']
console.log(filterItems('an')); // ['banana', 'mango', 'orange']


2.4 ES2015 实现


const


3. reduce()方法(与reduceRight())

在JavaScript中,数组的reduce方法原型为Array.prototype.reduce()。与reduce方法类似,数组还包含了一个reduceRight()方法,唯一区别是后者逆序遍历每个数组元组。

reduce()方法对数组中每个元素依次执行一个给定的reducer函数,并将结果汇总为 单个值并返回。

官方文档中提供的reduce()方法如下,其中,callback函数包含一个accumulator(累加器中当前的结果)参数,一个currentValue(当前处理的数组元素)与两个可选的参数index(当前正在处理的元素索引)以及array(filter方法调用的数组),以及一个可选的initialValue用来指定函数初值。如果未提供initialValue参数,reduce函数的执行结果可能会出现意外情况,这是在使用reduce方法时尤其要注意的。


arr


MDN官方文档提供了几个关于reduce方法的示例:

3.1数组里所有值的和


var


你也可以写成箭头函数的形式:


var


3.2 累加对象数组里的值

要累加对象数组中包含的值,必须提供初始值,以便各个item正确通过你的函数。


var


你也可以写成箭头函数的形式:


var


3.3 将二维数组转化为一维


var


你也可以写成箭头函数的形式:


var


3.4 计算数组中每个元素出现的次数


var


3.5 按属性对object分类


var


3.6 使用扩展运算符和initialValue绑定包含在对象数组中的数组


// friends - 对象数组


3.7 数组去重

注意: 如果你正在使用一个可以兼容Set 和 Array.from() 的环境, 你可以使用let orderedArray = Array.from(new Set(myArray)); 来获得一个相同元素被移除的数组。


var


3.8 按顺序运行Promise


/**


3.9 功能型函数管道


// Building-blocks to use for composition


3.10 使用 reduce实现map


if


4. 函数的apply()方法

和前面三个方法不同,apply()方法虽然通常被用在数组上,但在JavaScript中,apply()是Function中的方法,其原型为Function.prototype.apply()。apply()方法调用一个给定this值的函数,以及一个作为数组提供的参数。


apply方法和call方法非常类似,不同的地方在于apply接受一个参数数组,而call接受一个参数列表。


官方文档中提供的apply()方法如下,其中,thisArg参数代表函数运行时使用的this值,在非严格模式下,指定为null或undefined可以自动替换为指向全局对象,argArray参数是可选的,其中的数组元素将作为参数传递给函数,如果该参数值为null或者undefined,则表示不需要传入任何参数。

4.1 用 apply 将数组添加到另一个数组

我们可以使用push将元素追加到数组中。并且,因为push接受可变数量的参数,我们也可以一次推送多个元素。但是,如果我们传递一个数组来推送,它实际上会将该数组作为单个元素添加,而不是单独添加元素,因此我们最终得到一个数组内的数组。如果那不是我们想要的怎么办?在这种情况下,concat确实具有我们想要的行为,但它实际上并不附加到现有数组,而是创建并返回一个新数组。 但是我们想要附加到我们现有的阵列......那么现在呢? 写一个循环?当然不是吗?

apply来帮你!


var


4.2 使用apply和内置函数

聪明的apply用法允许你在某些本来需要写成遍历数组变量的任务中使用内建的函数。在接下里的例子中我们会使用Math.max/Math.min来找出一个数组中的最大/最小值。


/* 找出数组中最大/小的数字 */


但是当心:如果用上面的方式调用apply,会有超出JavaScript引擎的参数长度限制的风险。当你对一个方法传入非常多的参数(比如一万个)时,就非常有可能会导致越界问题, 这个临界值是根据不同的 JavaScript 引擎而定的(JavaScript 核心中已经做了硬编码 参数个数限制在65536),因为这个限制(实际上也是任何用到超大栈空间的行为的自然表现)是未指定的. 有些引擎会抛出异常。更糟糕的是其他引擎会直接限制传入到方法的参数个数,导致参数丢失。举个例子:如果某个引擎限制了方法参数最多为4个(实际真正的参数个数限制当然要高得多了, 这里只是打个比方), 上面的代码中, 真正通过 apply传到目标方法中的参数为 5, 6, 2, 3 而不是完整的数组。

如果你的参数数组可能非常大,那么推荐使用下面这种策略来处理:将参数数组切块后循环传入目标方法:


function


4.3 使用apply来链接构造器

你可以使用apply来链接一个对象构造器,类似于Java。在接下来的例子中我们会创建一个全局Function 对象的construct方法 ,来使你能够在构造器中使用一个类数组对象而非参数列表。


Function


注意:

Using Object.__proto__:


Function.prototype.construct = function (aArgs) {
  var oNew = {};
  oNew.__proto__ = this.prototype;
  this.apply(oNew, aArgs);
  return oNew;
};


使用闭包:


Function.prototype.construct = function(aArgs) {
  var fConstructor = this, fNewConstr = function() {
    fConstructor.apply(this, aArgs);
  };
  fNewConstr.prototype = fConstructor.prototype;
  return new fNewConstr();
};


使用 Function 构造器:


Function


5. 数组的其他常用方法

5.1 indexOf(),lastIndexOf(),find()与findIndex()

返回数组中某个元素出现的位置,如果没有找到指定元素则返回-1,lastIndexOf()返回指定元素在数组中出现的最后位置,如果不存在则返回-1.可以看出,indexOf和lastIndexOf实际上分别是正向和反向查找数组元素。

除了indexOf()和lastIndexOf()之外,在JavaScript中用来查找元素的常用方法还有find()和findIndex(),需要注意的是, find和findIndex的查找条件是函数而不是元素内容,这和indexOf以及lastIndexOf是不同的。


var


5.2 slice()方法和splice()方法

slice()方法与splice()方法在使用上有一个重要区别,前者会创建一个新的数组,而后者会改变原有数组。这在编程时需要特别注意。事实上,数组的大部分方法,在使用时都要注意是否会改变原有数组。在函数式编程的理念中,splice()一类会改变原始数组的方法应该尽量避免,因为这会给程序调试、测试带来很多不便。

slice()方法用来读取数组中一部分并返回新的数组。


var


splice()方法可以用来删除、添加或者替换元素,这取决于对其赋予的参数。splice方法是这样定义的array.splice(index,howmany,item1,item2...itemX)。其中,index表示开始删除/添加元素的位置,howmany表示要删除的元素数量,为0则不删除原有元素,从item1(可选参数)开始表示要插入的元素,不赋值则代表不添加新的元素。可以看出,当

howmany参数为0而存在item参数时,表示从index开始向数组插入元素,当howmany参数不为0而不存在item参数时,表示从index开始删除部分元素,当howmany参数不为0且存在item参数时 ,表示从index开始删除部分元素,再插入item元素。


var


5.3 every()方法与some()方法

every()方法测试是不是数组中所有元素都满足某一条件,如果其中有元素不满足则返回false。some()方法测试是不是数组中存在满足某一条件的元素,如果有则返回true,如果所有元素均不满足则返回false()。例如:


const


5.4 toString()方法和join()方法

数组的toString()方法将数组元素转换成一个用逗号连接的字符串并返回,join()方法同样返回一个字符串,但可以指定连接符。例如:


const


5.5 push(),pop(),shift()与unshift()

push(),pop(),shift()与unshift()方法时向数组中添加和删除元素的几个常用方法,在使用时,需要特别注意每个方法返回的对象,以避免误用。

push()方法向数组末尾添加一个或多个元素,并 返回新的长度

pop()方法删除数组的最后一个元素,并 返回删除的元素

shift()方法删除数组的第一个元素,并返回 删除的元素

unshift()方法向数组的开头添加一个或多个元素,并返回 新的长度