编程教育资源分享平台

网站首页 > 后端开发 正文

帮你精通JS:神奇的array.map的6个案例

luoriw 2024-01-31 12:32:22 后端开发 13 ℃ 0 评论

当我们开始学习FP函数式编程的时候,彷佛是从金庸武侠的山洞里,因缘巧合撞上武林秘籍。从此,再也不会陷入到种种 loop 以及 nested-loop的 dirty-details之中了。

前文我们介绍了 array.reduce 方法 帮你精通JS:神奇的array.reduce方法的10个案例 乃是 函数编程的第一式。第一式是基础,是我们思考的起点。后面的招数都从此演化出来,array.map就是从array.reduce演化出来的第二式。

一、array.map 方法概览

map() 方法创建一个新数组,其结果是该数组中的每个元素是调用一次提供的函数后的返回值。

const array1 = [1, 4, 9, 16];

// pass a function to map
const map1 = array1.map(x => x * 2);

console.log(map1);
// expected output: Array [2, 8, 18, 32]

二、array.map 的语法

基本语法为:

const array1 = [1, 4, 9, 16];

// pass a function to map
const map1 = array1.map(x => x * 2);

console.log(map1);
// expected output: Array [2, 8, 18, 32]

参数

  1. callback 生成新数组元素的函数,使用三个参数:
  2. - currentValue
  3. callback 数组中正在处理的当前元素。
  4. - index可选
  5. callback 数组中正在处理的当前元素的索引。
  6. - array可选 map 方法调用的数组。

  7. thisArg可选 执行 callback 函数时值被用作this。

与array.reduce相比较,少了accumulator一项,因为reduce是基础,从reduce里将map实现出来。

返回值

一个由原数组每个元素执行回调函数的结果组成的新数组。

三、描述

map 方法会给原数组中的每个元素都按顺序调用一次 callback 函数。callback 每次执行后的返回值(包括 undefined)组合起来形成一个新数组。 callback 函数只会在有值的索引上被调用;那些从来没被赋过值或者使用 delete 删除的索引则不会被调用。

因为map是pure-fuction,生成一个新数组,并且没有 side-effect。当你不打算使用返回的新数组却使用map是违背设计初衷的,请用forEach或者for-of替代。你不该使用map: A)你不打算使用返回的新数组,或/且 B) 你没有从回调函数中返回值。

callback 函数会被自动传入三个参数:数组元素,元素索引,原数组本身。

如果 thisArg 参数提供给map,则会被用作回调函数的this值。否则undefined会被用作回调函数的this值。this的值最终相对于callback函数的可观察性是依据the usual rules for determining the this seen by a function决定的。

map 不修改调用它的原数组本身(当然可以在 callback 执行时改变原数组),换言之乃是pure-function。

map 方法处理数组元素的范围是在 callback 方法第一次调用之前就已经确定了。调用map方法之后追加的数组元素不会被callback访问。如果存在的数组元素改变了,那么传给callback的值是map访问该元素时的值。在map函数调用后但在访问该元素前,该元素被删除的话,则无法被访问到。

根据规范中定义的算法,如果被map调用的数组是离散的,新数组将也是离散的保持相同的索引为空。

案例01.求数组中每个元素的平方根

下面的代码创建了一个新数组,值为原数组中对应数字的平方根。

var numbers = [1, 4, 9];
var roots = numbers.map(Math.sqrt);
// roots的值为[1, 2, 3], numbers的值仍为[1, 4, 9]

案例02.使用 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}]

案例03. 使用一个包含一个参数的函数来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]

案例04. 一般的map 方法

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

var map = Array.prototype.map
var a = map.call("Hello World", function(x) {
  return x.charCodeAt(0);
})
// a的值为[72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100]

案例05. 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]

案例06 压轴案例

通常情况下,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忽视了, but not the second one, 但不是第二个。因此可能出现混淆。下面是迭代步骤的简明示例:

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


以上。

Tags:

本文暂时没有评论,来添加一个吧(●'◡'●)

欢迎 发表评论:

最近发表
标签列表
最新留言