Array.prototype.map()
map()
方法创建一个新数组,这个新数组由原数组中的每个元素都调用一次提供的函数后的返回值组成。(每个元素都是回调函数的返回值)
1、语法
map(callbackFn)
map(callbackFn,thisArg)
参数:
callbackFn: 为数组中的每个元素执行的函数。它的返回值作为一个元素被添加为新数组中。该函数被调用时将传入以下参数:(1)element:数组中当前正在处理的元素(2)index:正在处理的元素在数组中的索引(3)array:调用 map()的数组本身。
thisArg: 执行 callbackFn 时用作 this 的值
map()是一个迭代方法。为数组中每一个元素调用一次提供的 callbackFn 函数,并用结果构建一个新数组。
callbackFn
仅在已分配值的数组索引处被调用。它不会在稀疏数组中的空槽处被调用。
map()不会改变 this。作为 callbackFn 提供的函数可以更改数组。
由于 map 创建一个新数组,在没有使用返回的数组的情况下调用它是不恰当的;应当使用 forEach 或者 for of 作为代替。
示例 1:求数组中每个元素的平方根
1 2 3 4
| const numbers = [1, 4, 9] const roots = numbers.map((num) => Math.sqrt(num))
|
示例 2:map 格式化数组中的对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| const kvArray = [ { key: 1, value: 10 }, { key: 2, value: 20 }, { key: 3, value: 30 } ]
const reformattedArray = kvArray.map(({ key, value }) => ({ [key]: value }))
console.log(reformattedArray) console.log(kvArray)
|
示例 3:使用单个参数的函数来映射一个数字数组
1 2 3 4 5 6
| const numbers = [1, 4, 9] const doubles = numbers.map((num) => num * 2)
console.log(doubles) console.log(numbers)
|
示例 4:非数组对象上调用 map()
1 2 3 4 5 6 7 8 9
| const arrayLike = { length: 3, 0: 2, 1: 3, 2: 4 } console.log(Array.prototype.map.call(arrayLike, (x) => x ** 2))
|
示例 5:NodeList 使用 map()
如何去遍历通过 querySelectorAll
得到的对象集合。这是因为 querySelectorAll
返回的是一个对象集合 NodeList
。
1 2 3 4 5 6 7
| const elems = document.querySelectorAll('select option:checked') const values = Array.prototype.map.call(elems, ({ value }) => value)
const images = document.querySelectorAll('img') const sources = Array.from(images, (image) => image.src) const insecureSources = sources.filter((link) => link.startsWith('http://'))
|
示例 6:稀疏数组使用 map()
1 2 3 4 5 6 7 8 9 10
| console.log( [1, , 3].map((x, index) => { console.log(`Visit ${index}`) return x * 2 }) )
|
Array.prototype.forEach()
forEach()方法对数组的每个元素执行一次给定的函数。不会改变其调用的数组
语法同 map()
forEach()
方法是一个迭代方法。它按索引升序地为数组中的每个元素调用一次提供的 callbackFn
函数。与 map()
不同,forEach()
总是返回 undefined
,而且不能继续链式调用。其典型的用法是在链式调用的末尾执行某些操作。
除非抛出异常,否则没有办法停止或中断 forEach()
循环。如果有这样的需求,则不应该使用 forEach()
方法。
可以通过像 for
、for...of
和 for...in
这样的循环语句来实现提前终止。当不需要进一步迭代时,诸如 every()
、some()
、find()
和 findIndex()
等数组方法也会立即停止迭代。
注意:跳出循环和跳出本次循环是有区别的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| const array1 = [1, 2, 3, 4, 5]; let sum1 = 0;
array1.forEach((element) => { if (element === 3) { return; } console.log(element); sum1 += element; });
console.log(sum1);
const array = [1, 2, 3, 4, 5]; let sum = 0;
array.forEach((element) => { if (element === 3) { break; } sum += element; });
console.log(sum);
|
forEach()
期望的是一个同步函数,它不会等待 Promise 兑现。在使用 Promise(或异步函数)作为 forEach
回调时,请确保你意识到这一点可能带来的影响。
1 2 3 4 5 6 7 8 9 10 11 12
| const ratings = [5, 4, 5] let sum = 0
const sumFunction = async (a, b) => a + b
ratings.forEach(async (rating) => { sum = await sumFunction(sum, rating) })
console.log(sum)
|
如果希望按照顺序或者并发的执行一系列操作
1 2 3 4 5
| ;[func1, func2, func3] .reduce((p, f) => p.then(f), Promise.resolve()) .then((result3) => { })
|
使用 reduce 把一个异步函数数组变为一个 Promise 链。上面代码等于:
1 2 3 4 5 6 7 8 9 10 11 12 13
| Promise.resolve() .then(func1) .then(func2) .then(func3) .then((result3) => { })
const applyAsync = (acc, val) => acc.then(val) const composeAsync = (...funcs) => (x) => funcs.reduce(applyAsync, Promise.resolve(x))
|
composeAsync 函数将会接受任意数量的函数作为其参数,并返回一个新的函数,而该函数又接受一个初始值,该组合的参数传递管线如下所示:
1 2
| const transformData = composeAsync(func1, func2, func3) const result3 = transformData(data)
|
顺序组合还可以使用 async 和 await(考虑是否真的有必要——因为它们会阻塞彼此,除非一个 Promise 的执行依赖于另一个 Promise 的结果,否则最好并发运行 Promise。)
1 2 3 4 5
| let result for (const f of [func1, func2, func3]) { result = await f(result) }
|
示例 1:稀疏数组使用 forEach()
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| const arraySparse = [1, 3 , , 7] let numCallbackRuns = 0
arraySparse.forEach((element) => { console.log({ element }) numCallbackRuns++ })
console.log({ numCallbackRuns })
|
示例 2:打印数组内容
1 2 3 4 5 6 7 8 9 10
| const logArrayElements = (element, index /*, array */) => { console.log(`a[${index}] = ${element}`) }
;[2, 5, , 9].forEach(logArrayElements)
|
示例 3:对象复制函数
创建对象副本
1 2 3 4 5 6 7 8 9 10 11 12
| const copy = (obj) => { const copy = Object.create(Object.getPrototypeOf(obj)) const propNames = Object.getOwnPropertyNames(obj) propNames.forEach((name) => { const desc = Object.getOwnPropertyDescriptor(obj, name) Object.defineProperty(copy, name, desc) }) return copy }
const obj1 = { a: 1, b: 2 } const obj2 = copy(obj1)
|
示例 4:迭代期间修改数组
1 2 3 4 5 6 7 8 9
| const words = ['one', 'two', 'three', 'four'] words.forEach((word) => { console.log(word) if (word === 'two') { words.shift() } })
console.log(words)
|
示例 5:非数组对象上调用 forEach
1 2 3 4 5 6 7 8 9 10
| const arrayLike = { length: 3, 0: 2, 1: 3, 2: 4 } Array.prototype.forEach.call(arrayLike, (x) => console.log(x))
|
map 和 forEach 会不会改变原数组
(1)、基本数据类型
forEach
1 2 3 4 5
| const array = [1, 2, 3, 4] array.forEach((item) => { item = item + 1 }) console.log(array)
|
map
1 2 3 4 5
| const array = [1, 2, 3, 4] array.map((item) => { item = item + 1 }) console.log(array)
|
结论:数组中的元素为基本数据类型时,原数组不会改变
(2)、引用数据类型
forEach
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| const arr = [ { name: 'shaka', age: 23 }, { name: 'virgo', age: 18 } ] arr.forEach((item) => { if (item.name === 'shaka') { item.age = 100 } }) console.log(arr)
|
map
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| const arr = [ { name: 'shaka', age: 23 }, { name: 'virgo', age: 18 } ] arr.map((item) => { if (item.name === 'shaka') { item.age = 100 } }) console.log(arr)
|
结论:数组的元素为引用类型时,原数组会改变
数组的元素为引用类型时,原数组会改变的原因是什么?
这是因为在使用 forEach
和 map
方法时,对引用类型
元素的修改会直接反映在原始数组
中。这是因为引用类型的元素实际上存储的是引用(内存地址
),而非值本身
。因此,通过引用
可以访问和修改原始数组
中的元素。基本类型在栈内存中直接存储变量与值。
总结
forEach
和 map
的实现原理相似。它们都是通过遍历数组,对数组的每个元素执行特定的函数。区别主要在于它们处理函数返回值的方式不同。forEach
忽略函数的返回值,而 map
则将函数的返回值收集到一个新的数组中。