数组遍历
全量遍历
forEach:无返回值,仅执行逻辑
遍历数组,对每一项执行回调函数,无返回值,除非抛出错误否则无法中断遍历。
arr.forEach(callback(currentValue, index, array), thisArg)
参数列表
| 参数 |
说明 |
| currentValue |
当前遍历的元素 |
| index |
当前元素索引(可选) |
| array |
原数组本身(可选) |
| thisArg |
指定回调内的this指向(可选) |
调用示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| const fruits = ["苹果", "香蕉", "橙子"];
const context = { prefix: "我喜欢的水果:", printFruit: function (name) { console.log(this.prefix + name); }, };
fruits.forEach(function (currentValue, index, arr) { console.log("当前元素:", currentValue); console.log("当前索引:", index); console.log("原数组:", arr); this.printFruit(currentValue); }, context);
|
注意事项
- 函数参数是按位置匹配的,如果要拿到第三个参数
array,前面的两个参数必须占位置,不能直接跳过第二个写第三个,不需要的参数可以用 _ 占位。
- 如果要让
thisArg 生效,回调函数就不能用箭头函数;如果回调函数内部是一个复用的代码块,那么可以用 thisArg 参数设置 this,避免在复用代码块内部硬编码。
map:返回新数组
对每一项执行回调,把回调的返回值收集为新数组返回,原数组保持不变。
const newArr = arr.map(callback(currentValue, index, array), thisArg)
语法和参数与 forEach 方法完全一致。
调用示例
1 2
| const nums = [1, 2, 3]; const strs = nums.map((num) => String(num));
|
1 2 3 4 5
| const users = [ { name: "张三", age: 18 }, { name: "李四", age: 20 }, ]; const names = users.map((user) => user.name);
|
注意事项
- map 必须有返回值,如果回调里没有写
return,新数组的对应项会是 undefined 。
- 如果不需要生成新数组,只是想遍历执行操作,直接用
forEach 即可,map 会额外创建新数组浪费内存。
条件遍历
以下所有方法都是 原型方法,且都 不会修改原数组。
通用参数规则
- 回调第一个参数:当前遍历的元素 currentValue
- 回调第二个参数:当前元素的索引 index(可选)
- 回调第三个参数:调用方法的原数组 array(可选)
条件遍历方法
- filter:提取所有满足条件的元素,返回新数组。
1 2 3 4 5 6 7
| const goods = [ { name: "耳机", price: 99 }, { name: "键盘", price: 299 }, { name: "鼠标", price: 159 }, ];
const highPriceGoods = goods.filter((item) => item.price > 100);
|
- find:返回第一个满足条件的元素,找到后立即停止遍历,找不到则返回
undefined。
1 2 3 4
| const users = [{ name: "李四" }, { name: "张三" }, { name: "张三" }];
const targetUser = users.find((item) => item.name === "张三");
|
- findIndex:和
find 逻辑一致,返回第一个符合条件元素的索引,找不到则返回 -1。
1 2 3 4
| const nums = [10, 20, 30, 40];
const index = nums.findIndex((num) => num > 25);
|
- some:判断数组中是否存在至少一个满足条件的元素,找到就停止遍历并返回
true,否则返回 false。
1 2 3 4
| const scores = [59, 60, 85];
const hasFail = scores.some((score) => score < 60);
|
- every:判断数组中是否所有元素都满足条件,有一个不符合就停止遍历并返回
false,否则返回 true。
1 2 3 4
| const ages = [18, 22, 25];
const isAllAdult = ages.every((age) => age >= 18);
|
- indexOf:查找指定元素在数组中第一次出现的索引,本质是按
=== 条件匹配,找不到则返回 -1。
1 2 3 4
| const arr = ["a", "b", "c", "b"];
const index = arr.indexOf("b");
|
- includes:判断数组中是否包含指定元素,也是按
=== 匹配,返回布尔值。
1 2 3 4
| const arr = [1, 2, 3, NaN];
const hasNaN = arr.includes(NaN);
|
- reduce:对数组中的每个元素执行一个归约函数(reducer),将其结果汇总为单个任意类型的返回值。
参数列表
- callback (必填):每个元素执行的函数,包含四个参数:
| 参数 |
说明 |
| accumulator (acc) |
累计器 - 上一次调用 callback 后的返回值,或 initialValue |
| currentValue (cur) |
当前正在处理的元素 |
| index (可选) |
当前元素的索引 |
| array (可选) |
调用 reduce 方法的数组本身 |
- initialValue (可选):第一次调用
callback 时 accumulator 的初始值。
调用示例
1 2 3 4
| const nums = [1, 2, 3, 4];
const product = nums.reduce((acc, cur) => acc * cur, 1); console.log(product);
|
1 2 3 4 5 6 7 8 9
| const arr = ["a", "b", "a", "c", "b"];
const unique = arr.reduce((acc, cur) => { if (!acc.includes(cur)) { acc.push(cur); } return acc; }, []); console.log(unique);
|
1 2 3 4 5 6 7 8 9 10 11 12 13
| const numbers = [1, 2, 3, 4, 5, 6];
const result1 = numbers.filter((n) => n % 2 === 0).map((n) => n * 2);
const result2 = numbers.reduce((acc, cur) => { if (cur % 2 === 0) { acc.push(cur * 2); } return acc; }, []); console.log(result2);
|
注意事项
- 如果提供了
initialValue:accumulator 第一次等于 initialValue ,callback 从数组的第一个元素开始执行。
- 如果没有提供
initialValue:accumulator 第一次等于数组的第一个元素,callback 从数组的第二个元素开始执行。
reduce 与 map、forEach 的区别
map 强制返回和原数组长度相等的新数组;forEach 强制返回 undefined;而 reduce 的返回值完全自定义,如果返回数组,长度可以任意控制。
对象遍历
静态方法
以下方法只处理对象自身的属性,不会遍历到原型链:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| const obj = { name: "Alice", age: 25 };
Object.keys(obj).forEach((key) => { console.log(key); });
Object.values(obj).forEach((value) => { console.log(value); });
Object.entries(obj).forEach(([key, value]) => { console.log(key, value); });
|
静态方法 和 原型方法 的区别
静态方法的 this 指向类本身(即构造函数),只能访问类的静态属性和其他静态方法,拿不到实例的属性。
- 不需要依赖具体实例数据、属于整个类的通用工具能力,就定义为 静态方法;
- 需要依赖具体实例的数据、属于实例自身的行为能力,就定义为 原型方法。
如果 push 是静态方法,每次调用都要手动把数组实例传进去:Array.push(arr, 1),远不如 arr.push(1) 简洁。
for…in
遍历对象 自身以及原型链上 的所有可枚举属性。
1 2 3 4 5 6 7 8 9
| for (const 键名 in 遍历目标) { }
const obj = { name: "张三", age: 18 }; for (const key in obj) { console.log(key); }
|
设置属性不可枚举的标准方法:Object.defineProperty()
1 2 3 4 5 6 7
| const obj = { name: "Alice" };
Object.defineProperty(obj, "age", { value: 25, enumerable: false, });
|
如果只需要遍历对象 自身的属性,可以用 hasOwnProperty() 过滤。
1 2 3 4 5
| for (let key in p) { if (p.hasOwnProperty(key)) { console.log(key); } }
|
for...in 可以遍历数组:数组本质是一个特殊的对象。
1 2 3 4 5 6
| const arr = ["a", "b", "c"];
for (let key in arr) { console.log(key); console.log(arr[key]); }
|
for…of
遍历 可迭代对象 的元素值。
for...of 的设计初衷是统一各种集合类型的遍历方式,替代之前的 forEach 等方法,是目前最通用的遍历语法。
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 26 27 28 29 30 31 32 33 34 35 36 37
| for (const 元素值 of 可迭代对象) { }
const arr = ["a", "b", "c"]; for (const item of arr) { console.log(item); }
const str = "abc"; for (const char of str) { console.log(char); }
const set = new Set([1, 2, 3]); for (const num of set) { console.log(num); }
const map = new Map([ ["name", "张三"], ["age", 18], ]); for (const [key, value] of map) { console.log(key, value); }
for (let [key, value] of Object.entries(obj)) { console.log(key, value); }
|
for...of 可以通过 entries() 方法获取索引。
1 2 3 4 5
| const arr = ["a", "b", "c"];
for (const [index, value] of arr.entries()) { console.log(`索引${index}:值${value}`); }
|
entries() 是所有 可迭代对象 都支持的原型方法,作用是返回一个 迭代器对象,迭代器每次产生的值都是 [键/索引, 值] 形式的数组。
entries() 返回的对象同时满足 迭代器 和 可迭代对象 的定义,既实现了 next 方法,又实现了 Symbol.iterator 方法(返回它自身),因此它可以被 for...of 遍历。
实例方法 和 原型方法 是两个概念,因为实例本身通常不存方法,所以两者高度重合。
注意事项
for...of 遍历的是 元素值,不是键。
- 仅遍历可迭代对象自身的属性,不会遍历原型属性。
- 支持 中断遍历,可以用
break 中断、continue 跳过、return 退出外层函数(for...of 是是一个 语句 而非 方法,因此它的 return 不是对自身起作用的)。
迭代器和生成器
迭代器 和 可迭代对象 是ES6引入的一套统一的遍历机制,让不同类型的数据结构可以用通用的方式去遍历。
可迭代对象
任何实现了 Symbol.iterator 方法的对象都是 可迭代对象。
为什么用 Symbol 类型作为键名?
Symbol.iterator 本质是 Symbol 构造函数挂载的一个 静态属性,它的值是一个预定义好的全局唯一的 Symbol 类型值,避免和用户定义的 iterator 命名冲突。
内置对象 是指 ES 规范预先定义好的、可以直接使用的对象,有三种类型:
- 内置构造函数:类型是
function,可以用 new 生成实例,例如 Array Object String Number;
- 内置非构造函数:类型是
function,不能 new,只能直接调用,例如 Symbol BigInt;
- 普通内置对象:类型是
object,不是函数,不能 new,例如 Math JSON。
原生可迭代对象:数组、字符串、Map、Set 等。
临时包装对象
字符串、数字、布尔值都是基本数据类型,本质不是对象,是不能调用方法、访问属性的。JS 为了让基本类型也能方便地使用配套能力,设计了 临时包装对象机制:
对基本数据类型调用方法、访问属性时(比如 str.length),JS 会自动做3件事:
- 临时创建一个和字符串值对应的 引用类型实例(也就是包装对象);
- 在这个 临时对象 上执行对应的操作(比如调用方法、访问属性);
- 操作完成后立刻销毁这个临时对象,不留下任何痕迹。
Symbol.iterator 是一个特殊的内置标识,它要求返回一个 迭代器对象。
迭代器
任何实现了 next 方法的对象,都是 迭代器对象。
next 方法要求返回一个固定格式的对象:{ value: 本次遍历的值, done: 布尔值 },其中:
value:当前遍历到的元素值;
done:false 表示还有后续元素,true 表示遍历已经结束。
可迭代对象是 被遍历的目标,迭代器对象是 执行遍历的工具。
遍历一个 可迭代对象 时(比如用 for...of),JS 会自动调用它的 Symbol.iterator 方法,得到一个迭代器;然后不断调用迭代器的 next() 方法,每次拿到 { value, done } ,直到 done 为 true 就停止遍历。
模拟数组的迭代过程
1 2 3 4 5 6 7 8
| const arr = [10, 20, 30];
const iterator = arrSymbol.iterator;
console.log(iterator.next()); console.log(iterator.next()); console.log(iterator.next()); console.log(iterator.next());
|
迭代器遍历到 最后一个元素 时,done 仍然是 false,只有当所有元素都已经被返回、再无元素可遍历时,下一次 next() 才会返回 { value: undefined, done: true}。
让一个普通对象可迭代
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 26
| const myObj = { name: '小明', age: 18, gender: '男', Symbol.iterator { const keys = Object.keys(this); let index = 0; return { next: () => { if (index < keys.length) { const key = keys[index]; index++; return { value: this[key], done: false }; } else { return { value: undefined, done: true }; } } } } }
|
生成器
生成器 是 ES6 新增的一种可以暂停执行、恢复执行的特殊 函数。
两个核心标识:
- 定义时在
function 后面加:function* gen() {} 或 function *gen() {};
- 函数内部用
yield 关键字标记暂停点。
生成器是创建迭代器的 语法糖,生成器执行后返回的对象本身就是迭代器:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| function* myGenerator() { console.log("第一步执行"); yield "暂停点1"; console.log("第二步执行"); yield "暂停点2"; console.log("第三步执行"); return "结束"; }
const gen = myGenerator();
const res1 = gen.next(); console.log(res1); const res2 = gen.next(); console.log(res2); const res3 = gen.next(); console.log(res3);
|
next 方法还可以接收一个参数,这个参数会作为上一次 yield 表达式的返回值:
1 2 3 4 5 6 7 8 9 10 11 12
| function* calcGenerator() { const num1 = yield "请输入第一个数"; const num2 = yield "请输入第二个数"; return num1 + num2; }
const gen = calcGenerator(); console.log(gen.next());
console.log(gen.next(10));
console.log(gen.next(20));
|
async/await 本质是 生成器 + Promise + 自动执行器 的语法糖。
- 事件循环
- 垃圾回收
- 异步编程
- 遍历
- 闭包