javascript技巧

关注公众号 jb51net

关闭
首页 > 网络编程 > JavaScript > javascript技巧 > JS之Array构造函数上的静态方法

JS之Array构造函数上的静态方法与实例解读

作者:Hello--_--World

本文详细介绍了JavaScript Array对象的实例方法和静态方法,包括Array.prototype.at()、concat()、entries()、every()等实例方法,以及from()、fromAsync()、isArray()、of()等静态方法,实例方法主要用于数组操作和处理,如查找、合并、转换等

在 JavaScript 中,Array 既是一个构造函数,也是一个全局对象。它身上的方法分为两类:静态方法(直接通过 Array.xxx 调用)和实例方法(通过数组实例 [].xxx 调用)。

一、 Array 实例方法

1.1 Array.prototype.at()

at() 方法接收一个整数值,并返回该索引对应的元素。

1.1.1 语法与参数

参数: index (整数)。

返回值: 对应索引的元素。如果找不到(索引超限),则返回 undefined。

const arr = ['Apple', 'Banana', 'Cherry'];

console.log(arr.at(0));  // "Apple"
console.log(arr.at(-1)); // "Cherry" (倒数第一个)
console.log(arr.at(-2)); // "Banana" (倒数第二个)
console.log(arr.at(10)); // undefined

1.1.2 注意点

1.1.3 类似实现方法

除了 at(),通常有以下几种替代方案:

方案示例优缺点
方括号索引arr[arr.length - 1]最传统,但取末尾元素很麻烦。
slice()arr.slice(-1)[0]支持负数,但它会创建一个新数组,性能略低,写起来也绕。
pop()arr.pop()能拿到最后一个,但它会改变(删除)原数组。

1.2 Array.prototype.concat()

Array.prototype.concat() 是 JavaScript 中最经典、最常用的合并方法。

它的核心作用是将 两个或多个数组(或值)连接成一个全新的数组

1.2.1 语法与参数

const newArray = oldArray.concat(value1, value2, ..., valueN);

1.2.2 使用场景

A. 合并多个数组

最直观的用途,把碎片化的数据拼成大列表。

const digital = [1, 2];
const analog = [3, 4];
const alpha = ['a', 'b'];

const combined = digital.concat(analog, alpha); 
// [1, 2, 3, 4, 'a', 'b']

B. 混合合并(值与数组)

你可以在合并数组的同时,顺手塞进几个散装的值。

const base = [1, 2];
const result = base.concat(3, [4, 5]); 
// [1, 2, 3, 4, 5]

C. 浅拷贝数组(传统做法)

const copy = arr.concat();

1.2.3 注意点

① 浅拷贝警告 (Shallow Copy)

concat 进行的是 浅拷贝。如果数组里存的是对象,合并后的新数组和原数组指向的是同一个对象地址。修改其中一个,另一个也会跟着变。

② 只能扁平化一层数组

concat 会自动拆解参数中的第一层数组,但不会递归拆解。

const arr = [1].concat([2, [3, 4]]);
// 结果是 [1, 2, [3, 4]],而不是 [1, 2, 3, 4]

如果需要完全拍平,请配合 flat() 方法使用。

1.2.4 类似实现方法

目前开发中,concat 最强力的竞争对手是 ES6 的 扩展运算符 (…)

方法示例优点缺点
concat()a.concat(b)语义明确;兼容性极好(ES3)。语法略显老旧。
扩展运算符“[…a, …b]”现代主流;更简洁;易于在中间插入值。需要编译(Babel)以支持旧环境。
push(…others)a.push(…b)性能极高(对于超大数组)。会改变原数组 a。

1.3 Array.prototype.constructor

在 JavaScript 中,每个对象都有一个 constructor 属性,指向创建该实例对象的构造函数

1.3.1 为什么要引入这个属性?(核心意义)

在面向对象编程(OOP)中,constructor 的存在是为了让实例能够溯源。

1.3.2 使用场景

A. 准确判断类型

虽然现在流行用 Array.isArray(),但在处理自定义类时,constructor 非常有用。

const arr = [1, 2, 3];
if (arr.constructor === Array) {
    console.log("这是一个数组");
}

B. 基于现有实例创建新实例

当你编写一个通用的函数,需要返回一个与输入对象类型相同的新对象时:

function cloneEmpty(obj) {
    // 无论传进来的是 Array 还是自定义的 MyArray,都能创建正确的类型
    return new obj.constructor(); 
}

const newArr = cloneEmpty([1, 2, 3]); // 返回 []

1.4 Array.prototype.copyWithin() - 破坏性

它的行为像是在数组内部进行了一次 “复制 + 粘贴”。

1.4.1 语法与参数

arr.copyWithin(target, start, end); // 左闭右开
const arr = [1, 2, 3, 4, 5];

// 把索引 3 到 4 的元素(也就是数字 4),复制到索引 0 的位置
const result = arr.copyWithin(0, 3, 4);

console.log(arr);    // 输出: [4, 2, 3, 4, 5]
console.log(result); // 输出: [4, 2, 3, 4, 5]
console.log(arr === result); // 输出: true (指向同一个内存地址)

1.4.2 行为特征(重点)

由于 copyWithin 始终维持原数组的 length,它就像是在一个固定长度的容器里挪动东西:如果挪过去的东西太长,放不下的部分就会被“挤出去”丢掉。

const arr = ['a', 'b', 'c', 'd', 'e'];

// 目标位置 (target): 3
// 开始读取 (start): 0
// 结束读取 (end): 默认到末尾 (索引 5)
// 复制的内容本应是: ['a', 'b', 'c', 'd', 'e'] (长度为 5)

arr.copyWithin(3, 0);

console.log(arr); 
// 输出: ["a", "b", "c", "a", "b"]
// 数组长度依然是: 5

1.5 Array.prototype.entries()

它主要用于将数组转换为一个可迭代的对象,方便我们在遍历时同时获取“索引”和“值”。

1.5.1 语法与参数

const iterator = arr.entries();

1.5.2 为什么要引入这个方法?

在 entries() 出现之前,JavaScript 遍历数组面临两难选择:

引入意义: entries() 让 for…of 能够以极其优雅的方式同时获取 索引 和 值

数组本身确实是可迭代的。当你直接使用 for…of 遍历数组时,它默认调用的是数组的 values() 方法。只能拿到值,拿不到索引。

1.5.3 使用场景

A. 在 for…of 中获取索引(最常用)

const fruits = ['Apple', 'Banana', 'Cherry'];

for (const [index, value] of fruits.entries()) {
    console.log(`第 ${index} 个水果是 ${value}`);
}

B. 处理稀疏数组

entries() 会遍历数组中的“空洞”(holes),并将其值视为 undefined。这比 forEach 更严谨,因为 forEach 会直接跳过空单元。

const sparse = [1, , 3];
for (const [i, v] of sparse.entries()) {
    console.log(i, v); // 输出: 0 1, 1 undefined, 2 3
}

C. 快速将数组转为 Map

const arr = ['a', 'b', 'c'];
const map = new Map(arr.entries()); 
// Map(3) { 0 => 'a', 1 => 'b', 2 => 'c' }

1.6 Array.prototype.every()

它的核心作用是:检查数组中的 所有 元素是否都符合某个条件。

1.6.1 语法与参数

const allPassed = arr.every(callbackFn(element, index, array), thisArg);

callbackFn (回调函数):对每个元素执行的函数,返回 true 表示通过,false 表示失败。

thisArg (可选):执行回调时用作 this 的值。

返回值:布尔值 (true 或 false)。

1.6.2 行为特征:短路机制 (Short-circuiting)

every() 非常聪明。它不需要遍历完整个数组:

1.6.3 避坑指南

① 空数组陷阱 (重点!)

对于空数组 [],every() 总是返回 true。

这被称为“空真理”(Vacuous truth)。逻辑依据是:在一个空集合中,没有任何元素违反条件。

[].every(x => x > 10); // true!

② 非破坏性:它不会修改原数组。

1.7 Array.prototype.fill() - 破坏性

它能用一个固定值填充数组的全部或部分。

1.7.1 语法与参数

arr.fill(value, start, end);

1.7.2 使用场景

A. 快速初始化数组

这是最常见的用法,配合 new Array() 使用:

const grades = new Array(5).fill(100); 
// [100, 100, 100, 100, 100]

B. 重置部分数组数据

将数组中索引 1 到 3 的元素清零:

const data = [10, 20, 30, 40, 50];
data.fill(0, 1, 4); 
// [10, 0, 0, 0, 50]

1.7.3 避坑指南

① 引用类型陷阱

这是 fill() 最容易翻车的地方。 如果填充的值是一个对象或数组,那么数组里的所有元素都会指向同一个内存地址(浅拷贝)。

const arr = new Array(3).fill({ name: 'Gemini' });
arr[0].name = 'AI';

console.log(arr[1].name); // 也是 'AI'!因为它们是同一个对象。

② 改变原数组

fill() 会直接修改调用它的数组,而不是返回副本。

1.8 Array.prototype.filter()

Array.prototype.filter() 是 JavaScript 中最常用的数据筛选工具。它的核心作用是:从原数组中挑出符合条件的元素,并组成一个新数组。

1.8.1 语法与参数

const newArray = arr.filter(callbackFn(element, index, array), thisArg);

callbackFn (回调函数):对每个元素执行的测试函数。

返回值:一个包含所有通过测试的元素的新数组。如果没有元素通过测试,则返回空数组 []。

const products = [
    { name: 'iPhone', price: 800 },
    { name: 'Case', price: 20 }
];
const expensive = products.filter(p => p.price > 100); 
// [{ name: 'iPhone', price: 800 }]

1.8.2 避坑指南

1.9 Array.prototype.find()

Array.prototype.find() 是 JavaScript 中用于定位单个元素的高阶方法。它的核心作用是:返回数组中满足条件的第一个元素的值。

1.9.1 语法与参数

const foundElement = arr.find(callbackFn(element, index, array), thisArg);

callbackFn (回调函数):对每个元素执行的测试函数。

返回值:数组中第一个通过测试的元素值。如果没有任何元素符合条件,则返回 undefined。

const users = [
    { id: 1, name: 'Alice' },
    { id: 2, name: 'Bob' },
    { id: 3, name: 'Charlie' }
];

const user = users.find(u => u.id === 2);
// { id: 2, name: 'Bob' }

1.9.2 避坑指南

1.10 Array.prototype.findIndex()

Array.prototype.findIndex() 是 find() 的亲兄弟。区别在于:find() 返回元素本身,而 findIndex() 返回该元素在数组中的位置(索引)。

1.10.1 语法与参数

const index = arr.findIndex(callbackFn(element, index, array), thisArg);

callbackFn (回调函数):测试函数。

返回值:返回第一个符合条件的元素的索引(从 0 开始)。如果没有任何元素符合条件,则返回 -1

const users = [{id: 1, name: 'A'}, {id: 2, name: 'B'}, {id: 3, name: 'C'}];

const index = users.findIndex(u => u.id === 2);

if (index !== -1) {
    users.splice(index, 1); // 成功删除 'B'
}

1.10.2 避坑指南

1.10.2 类似实现方法

方法返回值适用场景
indexOf()索引仅限于查找简单的原始类型(如数字、字符串)。
find()元素本身当你只需要获取数据,而不需要它的索引位置时。
lastIndexOf()索引从后往前找第一个符合条件的索引(仅限简单值)。
findLastIndex()索引ES2023 引入,支持从后往前通过回调函数找索引。

1.11 Array.prototype.findLast()

它的功能与 find() 完全一样,唯一的区别在于它的查找方向:它从数组的最后一个元素开始反向遍历。

1.11.1 语法与参数

const found = arr.findLast(callbackFn(element, index, array), thisArg);
const timeline = [
    { id: 1, action: 'login', time: '10:00' },
    { id: 2, action: 'post',  time: '10:05' },
    { id: 3, action: 'login', time: '10:10' }
];

// 查找最后一次登录的信息
const lastLogin = timeline.findLast(item => item.action === 'login');
// { id: 3, action: 'login', time: '10:10' }

1.12 Array.prototype.findLastIndex()

它是 findIndex() 的反向版本:从数组末尾开始向前搜索,返回第一个符合条件的元素的索引。

1.12.1 语法与参数

const index = arr.findLastIndex(callbackFn(element, index, array), thisArg);

返回值:

const logs = [
    { type: 'save', id: 101 },
    { type: 'edit', id: 102 },
    { type: 'save', id: 103 },
    { type: 'edit', id: 104 }
];

// 找到最后一次 'save' 类型操作的索引
const lastSaveIndex = logs.findLastIndex(item => item.type === 'save');
console.log(lastSaveIndex); // 2

1.12.2 避坑指南

1.13 Array.prototype.flat()

它的核心作用是:按照指定的深度递归遍历数组,将子数组中的元素合并到新数组中,俗称 “数组拍平”

1.13.1 语法与参数

const newArray = arr.flat(depth);

depth (可选):指定要提取嵌套数组的结构深度。

返回值:一个包含所有子数组元素组成的新数组

const nested = [1, [2, [3, 4]]];

console.log(nested.flat());    // [1, 2, [3, 4]] (默认深度为1)
console.log(nested.flat(2));   // [1, 2, 3, 4]

彻底拍平(不管嵌套多深)当你处理来源不明、嵌套层级混乱的数据时:

const crazy = [1, [2, [3, [4, [5]]]]];
const flatResult = crazy.flat(Infinity); 
// [1, 2, 3, 4, 5]

1.13.2 避坑指南

1.14 Array.prototype.flatMap()

它就是 map() 和 flat(1) 的组合体,但它比手动链式调用这两者更高效。

1.14.1 语法与参数

const newArray = arr.flatMap(callbackFn(element, index, array), thisArg);
const orders = [
  { id: 1, items: ["苹果", "香蕉"] },
  { id: 2, items: ["橙子"] },
  { id: 3, items: ["西瓜", "葡萄"] }
];

const res = orders.flatMap(order => order.items);

console.log(res);
// 结果是:["苹果", "香蕉", "橙子", "西瓜", "葡萄"]
// 直接拿到了干净的商品清单!

1.14.2 避坑指南

1.15 Array.prototype.forEach()

Array.prototype.forEach()是JavaScript 中最基础、最常用的遍历方法。它的核心作用是:对数组的每个元素执行一次给定的函数。

1.15.1 语法与参数

arr.forEach(callbackFn(element, index, array), thisArg);

callbackFn(回调函数):为每个元素执行的函数。

返回值:undefined。它不返回任何有意义的结果,本质上是一个“过程控制”工具。

const items = ['apple', 'banana'];
const copy = [];

items.forEach(item => {
    copy.push(item.toUpperCase()); // 操作外部变量
});

1.15.2 避坑指南

① 无法中断(不可break/continue)

这是它最大的痛点。 forEach一旦开始,除非抛出异常,否则它会遍历完数组中的所有元素。

② 异步陷阱

forEach不会等待异步函数(如Promise)完成。它会像机关枪一样瞬间打完所有回调,而不理会内部的await。

// 错误示例
arr.forEach(async (item) => {
    await someAsyncCall(item); // 这里不会按顺序等待
});

1.16 Array.prototype.includes()

它的核心作用是:判断一个数组是否包含一个指定的值,根据情况返回true或false。

1.16.1 语法与参数

const hasValue = arr.includes(searchElement, fromIndex);

searchElement(必填):需要查找的元素值。

fromIndex(可选):从哪个索引处开始查找。

返回值:布尔值( true/ false)。

const fruits = ['apple', 'banana', 'mango'];

// 1. 基本用法
console.log(fruits.includes('banana')); // true
console.log(fruits.includes('grape'));  // false

// 2. 第二个参数:起始查找位置
console.log(fruits.includes('apple', 1)); // false (从索引1开始找,找不到apple)

// 3. 完美处理 NaN
const arr = [1, 2, NaN];
console.log(arr.indexOf(NaN));    // -1 (旧方法的坑)
console.log(arr.includes(NaN));   // true (ES7 正确识别)

1.16.2 避坑指南

1.17 Array.prototype.indexOf()

Array.prototype.indexOf()是JavaScript 中最经典的索引查找方法。它的核心作用是:从数组中寻找指定元素,并返回它第一次出现的位置索引。

1.17.1 语法与参数

const index = arr.indexOf(searchElement, fromIndex);

如果是负数,则表示从倒数第几个开始找(但查找方向依然是从左往右)。

返回值:

const beasts = ['ant', 'bison', 'camel', 'duck', 'bison'];

console.log(beasts.indexOf('bison'));     // 1
console.log(beasts.indexOf('bison', 2));  // 4 (从索引2开始找)
if (arr.indexOf('something') !== -1) {
    // 元素存在
}
const unique = arr.filter((item, index) => arr.indexOf(item) === index);
// 只有当元素的“第一次出现索引”等于“当前遍历索引”时,才保留它

1.17.2 避坑指南

严格相等与NaN:indexOf使用的是严格相等( ===)。这导致它有两个局限:

只能找“第一个”:如果数组中有重复元素,它只会返回最左边的那一个。如果你想找最后一个,请使用lastIndexOf()。

1.18 Array.prototype.join()

用于将数组转换为字符串的核心方法。它的作用是将数组的所有元素连接起来,并在元素之间插入指定的连接符。

1.18.1 语法与参数

const str = arr.join(separator);

separator(可选):指定连接每个元素的字符串。

返回值:一个包含所有数组元素连接而成的新字符串。如果数组长度为0,则返回空字符串。

const elements = ['Fire', 'Air', 'Water'];

console.log(elements.join());      // "Fire,Air,Water"
console.log(elements.join(' - ')); // "Fire - Air - Water"

1.18.2 避坑指南

① 自动类型转换

[1, null, undefined, 4].join('-'); // "1---4"

1.19 Array.prototype.keys()

Array.prototype.keys()是ES6 引入的方法,它与entries()和values()属于同一个迭代器家族。它的核心作用是:返回一个包含数组中所有 索引(键) 的可迭代对象。

1.19.1 语法与参数

const iterator = arr.keys();
const arr = ['a', 'b', 'c'];

for (const key of arr.keys()) {
    console.log(key); // 输出: 0, 1, 2
}
const range = [...Array(5).keys()]; 
// [0, 1, 2, 3, 4]

1.19.2 避坑指南

1.20 Array.prototype.lastIndexOf()

是indexOf()的“倒序版本”。它的核心作用是:从数组的末尾开始向前搜索,返回指定元素最后一次出现的位置索引。

1.20.1 语法与参数

const index = arr.lastIndexOf(searchElement, fromIndex);

searchElement(必填):要查找的元素。

fromIndex(可选):开始反向查找的位置。

返回值:

const numbers = [2, 5, 9, 2];

console.log(numbers.lastIndexOf(2));    // 3
console.log(numbers.lastIndexOf(7));    // -1
console.log(numbers.lastIndexOf(2, 2)); // 0 (从索引2开始往前找)

通过对比indexOf和lastIndexOf的结果,可以快速判断一个元素是否在数组中出现了多次

const isDuplicate = (arr, val) => arr.indexOf(val) !== arr.lastIndexOf(val);

isDuplicate([1, 2, 3, 2], 2); // true

1.20.2 避坑指南

① 查找方向

② 严格相等(===)

1.21 Array.prototype.map()

它的核心作用是:创建一个新数组,其结果是原数组中的每个元素都调用一次提供的函数后的返回值。

1.21.1 语法与参数

const newArray = arr.map(callbackFn(element, index, array), thisArg);

callbackFn(回调函数) :生成新数组元素的函数。

返回值:一个由每次callbackFn返回的结果组成的新数组。

const users = [
    { id: 1, name: 'Alice', email: 'a@test.com' },
    { id: 2, name: 'Bob', email: 'b@test.com' }
];

const names = users.map(user => user.name);
// ['Alice', 'Bob']
const prices = [10, 20, 30];
const discounted = prices.map(p => p * 0.8);
// [8, 16, 24]

1.21.2 避坑指南

1.22 Array.prototype.pop() - 破坏性

它的核心作用是:从数组中删除最后一个元素,并返回该元素的值。

1.22.1 语法与参数

const lastElement = arr.pop();

参数:无。

返回值:

副作用:此方法会改变原数组(修改数组的length属性)。

const history = ['home', 'settings', 'profile'];
const lastPage = history.pop(); 

console.log(lastPage); // 'profile'
console.log(history);  // ['home', 'settings']

1.22.2 避坑指南

1.23 Array.prototype.push() - 破坏性

它的核心作用是:将一个或多个元素添加到数组的末尾

1.23.1 语法与参数

const newLength = arr.push(element1, element2, ..., elementN);
const list1 = ['a', 'b'];
const list2 = ['c', 'd'];

list1.push(...list2); 
// list1 变为 ['a', 'b', 'c', 'd']

1.23.2 避坑指南

1.24 Array.prototype.reduce()

的核心作用是:将数组中的所有元素通过一个累加器(accumulator)函数,最终计算为一个单一的值。

1.24.1 语法与参数

const result = arr.reduce(callbackFn(accumulator, currentValue, currentIndex, array), initialValue);

callbackFn (累加器函数):

initialValue (初始值 - 建议必填):

const nums = [1, 2, 3, 4];
const sum = nums.reduce((acc, curr) => acc + curr, 0);
// 结果: 10

将数组转换为对象 (数据聚合)

这是 reduce 的杀手锏,比如统计单词出现次数:

const fruits = ['apple', 'banana', 'apple', 'orange', 'banana', 'apple'];

const count = fruits.reduce((acc, fruit) => {
    acc[fruit] = (acc[fruit] || 0) + 1;
    return acc;
}, {});
// 结果: { apple: 3, banana: 2, orange: 1 }

数组去重

虽然 Set 更快,但 reduce 展现了逻辑的灵活性:

const arr = [1, 2, 1, 3, 2, 4];
const unique = arr.reduce((acc, curr) => {
    if (!acc.includes(curr)) acc.push(curr);
    return acc;
}, []);

1.24.2 避坑指南

1.25 Array.prototype.reduceRight()

它的核心逻辑与 reduce() 完全一致,唯一的区别在于遍历方向:它从数组的 最后一个元素开始,向前(向左) 执行累加操作。

1.25.1 语法与参数

const result = arr.reduceRight(callbackFn(accumulator, currentValue, currentIndex, array), initialValue);
const words = ["World", "Hello"];
const sentence = words.reduceRight((acc, curr) => acc + " " + curr);
// 结果: "Hello World" (从右侧 "Hello" 开始,加上 "World")

1.26 Array.prototype.reverse() - 破坏性

Array.prototype.reverse() 是一个非常直观的方法,用于将数组中元素的顺序颠倒。它是处理排序逻辑和视图转换时的常用工具。

1.26.1 语法与参数

const reversedArray = arr.reverse();

切换排序顺序

配合 sort() 使用,可以实现降序排列(虽然 sort 自身也能实现,但 reverse 更语义化):

const numbers = [1, 2, 3, 4, 5];
numbers.reverse(); 
// [5, 4, 3, 2, 1]

1.26.2 避坑指南

① 原地修改 (In-place)

const a = [1, 2, 3];
const b = a.reverse();

console.log(a); // [3, 2, 1] —— 原数组被改了!
console.log(b === a); // true —— 它们指向同一个内存地址

如果你需要保留原数组,请先使用展开运算符进行拷贝:

const safeReversed = [...originalArray].reverse();

1.27 Array.prototype.shift() - 破坏性

Array.prototype.shift() 是 pop() 的“对手”。它的核心作用是:从数组中删除第一个元素(索引为 0 的元素),并返回该元素的值。

1.27.1 语法与参数

const firstElement = arr.shift();

参数:无。

返回值:

副作用:此方法会改变原数组,并自动更新剩余元素的索引(所有元素向前移动一位)。

处理一组按顺序到达的任务,处理完一个就踢出一个:

const taskQueue = ['download_file', 'extract_zip', 'cleanup'];

while (taskQueue.length > 0) {
    const currentTask = taskQueue.shift();
    console.log(`正在处理: ${currentTask}`);
}

1.27.2 避坑指南

① 性能警告 (O(n))

这是 shift() 和 pop() 最大的区别。

建议:在处理包含数万个元素的超大型数组时,频繁使用 shift() 会导致明显的性能下降。

② 破坏性

1.28 Array.prototype.slice()

它的核心作用是:提取原数组的一部分,并将其作为一个新数组返回。

最关键的一点是:slice() 不会修改原数组(非破坏性)

1.28.1 语法与参数

const newArray = arr.slice(begin, end);

begin (可选):从该索引开始提取(包含该索引)。

end (可选):在该索引处结束提取(不包含该索引)(左闭右开)。

返回值:包含提取元素的一个新数组

const animals = ['ant', 'bison', 'camel', 'duck', 'elephant'];

console.log(animals.slice(2));      // ["camel", "duck", "elephant"]
console.log(animals.slice(2, 4));   // ["camel", "duck"] (不包含索引4)
console.log(animals.slice(-2));     // ["duck", "elephant"] (取最后两个)

浅拷贝整个数组

这是在 ES6 展开运算符 ([…]) 流行之前,克隆数组最标准的方法:

const original = [1, 2, 3];
const clone = original.slice(); 
// clone 是一个新数组,修改它不会影响 original

1.28.2 避坑指南

① 浅拷贝陷阱

② 参数的**“左闭右开”**

1.29 Array.prototype.some()

它的核心作用是:测试数组中是否至少有一个元素通过了由提供的函数实现的测试。

它返回一个 布尔值,且具有 “短路”特性

1.29.1 语法与参数

const hasMatch = arr.some(callbackFn(element, index, array), thisArg);

检查用户组里是否有管理员:

const users = [
    { name: 'Tom', role: 'user' },
    { name: 'Jerry', role: 'admin' },
    { name: 'Spike', role: 'user' }
];

const hasAdmin = users.some(user => user.role === 'admin'); 
// true (在遍历到 Jerry 时就停止了)

替代 includes 查找复杂对象

includes() 只能找简单值或引用地址,而 some() 可以根据属性查找:

const fruits = [{ id: 1, name: 'apple' }, { id: 2, name: 'banana' }];

const hasApple = fruits.some(f => f.name === 'apple'); // true

1.29.2 避坑指南

① 空数组陷阱

② 短路机制 (Short-circuiting)

③ 非破坏性

1.30 Array.prototype.sort() - 破坏性

对数组元素进行原地(In-place)排序

1.30.1 语法与参数

arr.sort(compareFn);

核心痛点:为什么 [10, 2].sort() 会变成 [10, 2]?

这是新手最常遇到的坑。默认情况下,sort() 会把元素当成字符串处理:

比较函数 compareFn(a, b) 的逻辑

该函数应返回一个数字,告诉引擎如何交换位置:

const numbers = [10, 5, 8, 1, 7];

// 升序排列
numbers.sort((a, b) => a - b); // [1, 5, 7, 8, 10]

// 降序排列
numbers.sort((a, b) => b - a); // [10, 8, 7, 5, 1]

1.30.2 避坑指南

① 破坏性

const sorted = [...originalArray].sort();

② 稳定性 (Stability)

1.31 Array.prototype.splice() - 破坏性

它的核心作用是:在数组的 任意位置 进行 删除、替换或添加元素

1.31.1 语法与参数

const deletedItems = arr.splice(start, deleteCount, item1, item2, ...);

A. 删除元素

从索引 2 开始删除 1 个元素:

const fruits = ['apple', 'banana', 'orange', 'mango'];
fruits.splice(2, 1); 
// fruits 变为: ['apple', 'banana', 'mango']

B. 插入元素

在索引 2 的位置插入 ‘lemon’,不删除任何东西:

const fruits = ['apple', 'banana', 'mango'];
fruits.splice(2, 0, 'lemon'); 
// fruits 变为: ['apple', 'banana', 'lemon', 'mango']

C. 替换元素

删除 1 个并原地填补 1 个:

const fruits = ['apple', 'banana', 'mango'];
fruits.splice(1, 1, 'strawberry'); 
// fruits 变为: ['apple', 'strawberry', 'mango']

1.31.2 避坑指南

分不清 splice 和 slice?

性能开销:由于 splice 涉及到删除和插入,在数组中间操作会导致后续所有元素重排索引,在处理超大型数组时,性能开销比 pop 或 push 大。

负数索引:如果你写 arr.splice(-1, 1),它会非常方便地删掉数组的最后一个元素。

1.32 Array.prototype.toLocaleString()

它的核心作用是:将数组中的每个元素根据特定的语言环境(如时区、货币格式、数字分隔符)转换成字符串,并用逗号连接。

1.32.1 语法与参数

const str = arr.toLocaleString(locales, options);

为什么要使用它?

普通的 toString() 或 join() 只是机械地把值转成字符串。但在处理金钱、日期或大数字时,不同国家有不同的写法。

货币转换

这是它最强大的地方之一,可以配合 options 自动添加货币符号:

const prices = [100.5, 200, 3000];
const formatted = prices.toLocaleString('zh-CN', {
  style: 'currency',
  currency: 'CNY'
});
// "¥100.50,¥200.00,¥3,000.00"

日期数组本地化

const dates = [new Date(), new Date('2026-01-01')];
console.log(dates.toLocaleString('zh-CN'));
// "2026/2/27 13:56:43, 2026/1/1 08:00:00"

1.33 Array.prototype.toReversed()

Array.prototype.toReversed() 是 ES2023 (ES14) 引入的新特性。它的核心作用是:返回一个元素顺序颠倒的新数组,而 保持原数组不变

1.33.1 语法与参数

const reversedArray = arr.toReversed();
const original = [1, 2, 3];
const reversed = original.toReversed();

console.log(original); // [1, 2, 3] <- 稳如泰山
console.log(reversed); // [3, 2, 1] <- 得到想要的结果

1.33.2 避坑指南

1.34 Array.prototype.toSorted()

它是经典 sort() 方法的“纯函数”版本,其核心作用是:返回一个排序后的新数组,而完全不修改原始数组。

1.34.1 语法与参数

const sortedArray = arr.toSorted(compareFn);
const numbers = [10, 5, 8];
// 错误写法:const s = numbers.sort(); 这会改掉 numbers
const sorted = [...numbers].sort((a, b) => a - b);
const numbers = [10, 5, 8];
const sorted = numbers.toSorted((a, b) => a - b);

console.log(numbers); // [10, 5, 8] (原封不动)
console.log(sorted);  // [5, 8, 10] (得到新数组)

1.34.2 避坑指南

① 依然需要比较函数

② 处理稀疏数组

1.35 Array.prototype.toSpliced()

它是经典 splice() 的“克隆版”,核心作用是:返回一个执行了删除、替换或插入操作后的新数组,而保持原数组完全不变。

1.35.1 语法与参数

const newArray = arr.toSpliced(start, deleteCount, item1, item2, ...);

参数:与 splice() 完全一致。

const months = ["Jan", "Mar", "Apr"];
const fullMonths = months.toSpliced(1, 0, "Feb");

console.log(months);     // ["Jan", "Mar", "Apr"] (原封不动)
console.log(fullMonths); // ["Jan", "Feb", "Mar", "Apr"]
const numbers = [10, 20, 30, 40];
const result = numbers
  .toSpliced(1, 1, 25) // 把 20 换成 25
  .map(n => n * 2);    // [20, 50, 60, 80]

1.35.2 避坑指南

1.36 Array.prototype.toString()

Array.prototype.toString() 是 JavaScript 数组最基础的类型转换方法。它的核心作用是:将数组中的所有元素转换成字符串,并用 逗号 连接成一个长字符串。

1.36.1 语法与参数

const resultString = arr.toString();
const arr = [1, 2, 'a', 'b'];
console.log(arr.toString()); // "1,2,a,b"

const dateArr = [new Date(2026, 0, 1)];
console.log(dateArr.toString()); // "Thu Jan 01 2026 00:00:00 GMT..."

1.36.2 避坑指南

A. 嵌套数组会被“拍平”

const nested = [1, [2, 3], [4, [5]]];
console.log(nested.toString()); // "1,2,3,4,5"

B. null 和 undefined 的特殊处理

const arr = [1, null, 3, undefined];
console.log(arr.toString()); // "1,,3,"

自动类型转换

const arr = [1, 2, 3];
console.log("Result: " + arr); // "Result: 1,2,3"
// 实际上发生了: "Result: " + arr.toString()

1.37 Array.prototype.unshift() - 破坏性

Array.prototype.unshift() 是 push() 的“镜像”方法。它的核心作用是:将一个或多个元素添加到数组的开头。

1.37.1 语法与参数

const newLength = arr.unshift(element1, element2, ..., elementN);
const numbers = [3, 4];
numbers.unshift(1, 2); 
// [1, 2, 3, 4]

1.37.2 避坑指南

① 返回值是长度

const result = [2, 3].unshift(1);
console.log(result); // 3 (数组长度)

② 破坏性

const newArr = [newItem, ...oldArr];

1.38 Array.prototype.values()

它的核心作用是:返回一个新的数组迭代器(Array Iterator)对象,该对象包含了数组中每个索引对应的值。

1.38.1 语法与参数

const iterator = arr.values();

A. 配合 for…of 循环

这是最自然的使用方式(尽管直接遍历数组更常见,但 values() 让语义更显式):

const fruits = ['apple', 'banana', 'cherry'];
const iterator = fruits.values();

for (const value of iterator) {
  console.log(value); 
}
// 输出: apple, banana, cherry

B. 手动控制遍历进度

你可以通过调用 .next() 逐步获取值,这在实现复杂的异步逻辑或生成器时非常有用:

const arr = ['a', 'b'];
const iter = arr.values();

console.log(iter.next()); // { value: 'a', done: false }
console.log(iter.next()); // { value: 'b', done: false }
console.log(iter.next()); // { value: undefined, done: true }

1.39 Array.prototype.with()

它的核心作用是:修改数组中指定索引的值,并返回一个新数组,而不改变原数组。

1.39.1 语法与参数

const newArray = arr.with(index, value);
const arr = [1, 2, 3];
// 旧方案 1:展开运算符
const newArr = [...arr];
newArr[1] = 99;

// 旧方案 2:slice
const newArr2 = arr.slice(0, 1).concat(99, arr.slice(2));
const temperatures = [22, 25, 21, 28];
const corrected = temperatures.with(2, 23); 

console.log(temperatures); // [22, 25, 21, 28] (原封不动)
console.log(corrected);    // [22, 25, 23, 28] (索引 2 已更新)
const players = ['Alice', 'Bob', 'Charlie'];
const updated = players.with(-1, 'Zoe');
// ['Alice', 'Bob', 'Zoe']

1.39.2 避坑指南

二、 Array 静态方法 (Static Methods)

2.1 Array.from()

类数组对象(Array-like)或 可迭代对象(Iterable)转换成一个真正的数组。

它是一个静态方法,必须通过 Array.from() 调用,而不是在数组实例上调用。

2.1.1 语法与参数

Array.from(arrayLike, mapFn, thisArg);

arrayLike (必填):想要转换的对象。

mapFn (可选):新数组中的每个元素都会执行的回调函数,类似于 .map()。

返回值:一个新的数组实例。

A. 将 Set 或 Map 转为数组

常用于数组去重:

const set = new Set([1, 2, 2, 3]);
const uniqueArr = Array.from(set); 
// [1, 2, 3]

B. 操作 DOM NodeList

让 querySelectorAll 获取的节点可以使用数组方法:

const divs = document.querySelectorAll('div');
const divIds = Array.from(divs, div => div.id);

C. 快速生成序列

这是 Array.from() 最精妙的用法之一,可以替代繁琐的 for 循环:

// 生成 0 到 4 的数组
const range = Array.from({ length: 5 }, (v, i) => i);
// [0, 1, 2, 3, 4]

2.1.2 避坑指南

① 浅拷贝

② 与展开运算符 ([…]) 的区别

// 只有 Array.from 能处理这个:
const obj = { length: 2, 0: 'a', 1: 'b' };
console.log(Array.from(obj)); // ['a', 'b']
console.log([...obj]);        // TypeError: obj is not iterable

2.2 Array.fromAsync()

它是 Array.from() 的异步孪生兄弟,专门用于处理 异步可迭代对象(Async Iterables),例如从网络流、数据库游标或异步生成器中获取数据。

2.2.1 语法与参数

const newArray = await Array.fromAsync(asyncIterable, mapFn, thisArg);

为什么要使用它?

在它出现之前,如果你想把一个异步迭代器转换为数组,你需要使用 for await…of 循环:

const arr = [];
for await (const item of asyncGen()) {
  arr.push(item);
}

A. 转换异步生成器 (Async Generator)

async function* asyncGen() {
  yield Promise.resolve(1);
  yield 2;
  yield 3;
}

const arr = await Array.fromAsync(asyncGen());
console.log(arr); // [1, 2, 3]

2.3 Array.isArray()

Array.isArray() 是判断一个变量是否为数组的终极权威方法。它的核心作用是:可靠地检测传入的值是否是一个真正的 Array 实例。

2.3.1 语法与参数

const result = Array.isArray(value);

为什么不用 typeof?

在 JavaScript 的历史中,检测数组一直是个麻烦事:

引入意义: Array.isArray() 能够跨越 iframe 或不同的执行上下文,始终准确地识别数组。

// 全部为 true
Array.isArray([]);
Array.isArray([1, 2]);
Array.isArray(new Array());
Array.isArray(Array.prototype); // 没错,原型也是数组

// 全部为 false
Array.isArray();
Array.isArray({});
Array.isArray(null);
Array.isArray(undefined);
Array.isArray(17);
Array.isArray('Array');
Array.isArray(new Uint8Array(10)); // 类型化数组不是普通数组

2.4 Array.of()

创建一个具有可变数量参数的新数组实例,而不考虑参数的数量或类型。

2.4.1 语法与参数

Array.of(element0, element1, ..., elementN);

为什么要使用它?(解决 Array 构造函数的陷阱)

这是 Array.of() 存在的唯一且最重要的原因。传统的 Array() 构造函数在处理单个数字参数时存在一个非常迷惑的行为:

A. 修正构造函数歧义

当你需要动态创建一个包含单个数字的数组时,Array.of() 是最安全的选择:

const size = 7;

const a = new Array(size); // [empty × 7]
const b = Array.of(size);  // [7]

2.4.2 避坑指南

与 Array.from() 的区别:

总结

以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。

您可能感兴趣的文章:
阅读全文