概述:
map()方法返回一个由原数组中的每个元素调用一个指定方法后的返回值组成的新数组,它不会改变原来的数组。
语法:
let newArr = oldArr.map(callback[, thisArg])
参数:
callback
原数组中的元素调用该方法后返回一个新数组。它接收三个参数,分别为 currentValue、index、array。
currentValue
callback的第一个参数,数组中当前被传递的元素。
index(可选)
callback的第二个参数,数组中当前被传递的元素的索引。
array(可选)
callback的第三个参数,调用map()方法的数组,即原数组。
thisArg(可选)
执行callback函数时this指向的对象。
描述
map()方法会给原数组中的每个元素都按顺序调用一次callback函数。callback每次执行后的返回值组合起来形成一个新的数组。callback函数只会在有值的索引上被调用,那些从来没被赋过值或者使用delete删除的索引则不会被调用。
callback函数会被自动传入三个参数:数组元素、数组元素索引、原数组本身。
如果thisArg参数有值,则每次调用callback函数时,this都会指向thisArg参数上的这个对象。如果省略了thisArg参数,或者赋值为null或undefined,则this指向全局对象。
使用map()方法处理数组时,数组元素的范围是在callback函数第一次被调用之前就确定了。在map()方法执行的过程中:原数组中新增加的元素将不会被callback访问到;若已经存在的元素被改变或删除了,则它们传递到callback的值是map()方法遍历到它们那一时刻的值;而被删除的元素将不会被访问到。
示例
1.求数组每个元素的平方根
var numbers = [1, 4, 9];var roots = numbers.map(Math.sqrt);console.log(numbers) // [1, 4, 9]console.log(roots) // [1, 2, 3]
代码封装:
var numbers = [1, 4, 9];const arrBat = (arr, func) => arr.map(func)var roots = arrBat(numbers, Math.sqrt)console.log(numbers) // [1, 4, 9]console.log(roots) // [1, 2, 3]
只需要传入对应的处理方法,即可对数组所有元素做批处理。
当然也可对此方法进行二次封装:
var numbers = [1, 4, 9];const arrBat = (arr, func) => arr.map(func)const arrToSqrt = (arr) => arrBat(arr, Math.sqrt) // 开平方根const arrToSquare = (arr) => arrBat(arr, e => Math.pow(e, 2)) // 平方const arrToRound = (arr) => arrBat(arr, Math.round) // 四舍五入const arrToCeil = (arr) => arrBat(arr, Math.ceil) // 求上整const arrToFloor = (arr) => arrBat(arr, Math.floor) // 求下整const arrToDouble = (arr) => arrBat(arr, e => 2 * e) // 求倍数arrToSquare(numbers) // [1, 16, 81]arrToSqrt(numbers) // [1, 2, 3]
2.将数组中的单词转换成复数形式。
function fuzzyPlural(single){ var result = single.replace(/o/g,"e"); if(single === "kangaroo"){ result += "se"; } return result; }var words = ["foot","goose","moose","kangaroo"];console.log(words.map(fuzzyPlural)); //每个元素都按顺序调用一次fuzzyPlural函数//返回["feet","geese","meese","kangareese"];
3.使用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;});// reformattedArry数组为: [{1: 10}, {2: 20}, {3: 30}]// kvArray 数组未被修改: // [{key: 1, value: 10}, // {key: 2, value: 20}, // {key: 3, value: 30}]
代码封装:
var kvArray = [{key: 1, value: 10}, {key: 2, value: 20}, {key: 3, value: 30}]kvArrayToObjArray = (obj) => obj.map(e => { var rArr = []; rArr.push(e.key, e.value); return rArr;})var reformattedArray = kvArrayToObjArray(kvArray)// [[1, 10], [2, 20], [3, 30]]
4.反转字符串
var str = 'Hello';Array.prototype.map.call(str, function(x) { return x;}).reverse().join(''); // 'olleH'
代码封装①:
const reverseStr = str => Array.prototype.map.call(str, e => e).reverse().join('')c = reverseStr('Hello') // 'olleH'
代码封装②:使用ES6解析
const reverseString = str => [...str].reverse().join('');console.log(reverseString('foobar')) // 'raboof'
5.将字符串转为ASIIC码组成的数组
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]
代码封装:
const strToAscii = str => Array.prototype.map.call(str, e => e.charCodeAt(0))console.log(strToAscii("Hello World")) // [72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100]
6.Dom操作
遍历用 querySelectorAll 得到的动态对象集合。在这里,我们获得了文档里所有选中的选项,并将其打印
var elems = document.querySelectorAll('select option:checked');var values = Array.prototype.map.call(elems, function(obj) { return obj.value;});
7. 多参数函数批量转化的误区
相比看完上面的解说觉得自己已经深入理解了吧,那么在看下面一个例子
["1", "2", "3"].map(parseInt);
第一反应,这里应该返回的是 [1, 2, 3],然而,实际上返回的却是 [1, NaN, NaN]。
这是为什么呢?
通常情况下,map 方法中的 callback 函数只需要接受一个参数,就是正在被遍历的数组元素本身。但这并不意味着 map 只给 callback 传了一个参数。这个思维惯性可能会让我们犯一个很容易犯的错误。
// 下面的语句返回什么呢:["1", "2", "3"].map(parseInt);// 你可能觉的会是[1, 2, 3]// 但实际的结果是 [1, NaN, NaN]// 通常使用parseInt时,只需要传递一个参数.// 但实际上,parseInt可以有两个参数.第二个参数是进制数.// 可以通过语句"alert(parseInt.length)===2"来验证.// map方法在调用callback函数时,会给它传递三个参数:当前正在遍历的元素, // 元素索引, 原数组本身.// 第三个参数parseInt会忽视, 但第二个参数不会,也就是说,// parseInt把传过来的索引值当成进制数来使用.从而返回了NaN.function returnInt(element) { return parseInt(element, 10);}['1', '2', '3'].map(returnInt); // [1, 2, 3]// 意料之中的结果// 也可以使用简单的箭头函数,结果同上['1', '2', '3'].map( str => parseInt(str) );// 一个更简单的方式:['1', '2', '3'].map(Number); // [1, 2, 3]// 与`parseInt` 不同,下面的结果会返回浮点数或指数:['1.1', '2.2e2', '3e300'].map(Number); // [1.1, 220, 3e+300]
8.封装Array.prototype.map()方法
下面是实现map方法的函数封装。 暂时只是实现了基础功能,具体还有很多优化可以做。
function myMap(data, fn) { var arg = []; for (var i = 0; i < data.length; i++) { (function (ele, fn) { //每一个元素处理后放入新数组 arg.push(fn(ele)); })(data[i], fn); } return arg; } var data = [10, 20, 30, 40]; var roots= myMap(data, function (ele) { return ele / 10; }); console.log(roots);//[1,2,3,4]
兼容旧环境
map
是在最近的 ECMA-262 标准中新添加的方法;所以一些旧版本的浏览器可能没有实现该方法。在那些没有原生支持 map
方法的浏览器中,你可以使用下面的 Javascript 代码来实现它。
所使用的算法正是 ECMA-262,第 5 版规定的。假定Object
, TypeError
, 和 Array
有他们的原始值。而且 callback.call
的原始值也是
1 // 实现 ECMA-262, Edition 5, 15.4.4.19 2 // 参考: http://es5.github.com/#x15.4.4.19 3 if (!Array.prototype.map) { 4 Array.prototype.map = function(callback, thisArg) { 5 6 var T, A, k; 7 8 if (this == null) { 9 throw new TypeError(" this is null or not defined");10 }11 12 // 1. 将O赋值为调用map方法的数组.13 var O = Object(this);14 15 // 2.将len赋值为数组O的长度.16 var len = O.length >>> 0;17 18 // 3.如果callback不是函数,则抛出TypeError异常.19 if (Object.prototype.toString.call(callback) != "[object Function]") {20 throw new TypeError(callback + " is not a function");21 }22 23 // 4. 如果参数thisArg有值,则将T赋值为thisArg;否则T为undefined.24 if (thisArg) {25 T = thisArg;26 }27 28 // 5. 创建新数组A,长度为原数组O长度len29 A = new Array(len);30 31 // 6. 将k赋值为032 k = 0;33 34 // 7. 当 k < len 时,执行循环.35 while(k < len) {36 37 var kValue, mappedValue;38 39 //遍历O,k为原数组索引40 if (k in O) {41 42 //kValue为索引k对应的值.43 kValue = O[ k ];44 45 // 执行callback,this指向T,参数有三个.分别是kValue:值,k:索引,O:原数组.46 mappedValue = callback.call(T, kValue, k, O);47 48 // 返回值添加到新数组A中.49 A[ k ] = mappedValue;50 }51 // k自增152 k++;53 }54 55 // 8. 返回新数组A56 return A;57 }; 58 }
参考资料: