当我们开始学习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); con(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); con(map1); // expected output: Array [2, 8, 18, 32]
参数
- callback 生成新数组元素的函数,使用三个参数:
- - currentValue
- callback 数组中正在处理的当前元素。
- - index可选
- callback 数组中正在处理的当前元素的索引。
- - array可选 map 方法调用的数组。
- 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.ma); // 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. 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. 传递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); con(xs); // 输出结果为 (3) [10, NaN, 2] // Actual result of 10,NaN,2 may be unexpected based on the above description.
以上。