目录
用一次就爱上的 Array.from —— 构建 m*n 数组的绝对优雅姿势
作者:watermelo37
CSDN全栈领域优质创作者、万粉博主、华为云云享专家、阿里云专家博主、腾讯云“创作之星”特邀作者、支付宝合作作者,全平台博客昵称watermelo37。
一个假装是giser的coder,做不只专注于业务逻辑的前端工程师,Java、Docker、Python、LLM均有涉猎。
---------------------------------------------------------------------
温柔地对待温柔的人,包容的三观就是最大的温柔。
---------------------------------------------------------------------
用一次就爱上的 Array.from —— 构建 m*n 数组的绝对优雅姿势
一、应用背景
1、错误案例
最近在项目中碰到一个小需求:创建一个 m × n 的二维数组并初始化每个元素。常规方法就是两个循环并初始化填充,但这显然太麻烦了。
很多人第一次写这种二维数组初始化的优化写法,都会自然而然地写出这样的代码:
const matrix = new Array(m).fill(new Array(n).fill(0));
表面上看没毛病,打印出来也是一个 m×n 的全零数组。但你要是尝试修改某个元素,比如:
matrix[0][0] = 1;
然后你惊讶地发现:
console.log(matrix);
// [[1, 0, 0], [1, 0, 0], [1, 0, 0], ...]
全都变了!
这是因为 .fill() 传入的是同一个数组引用,m 个元素共享了同一个子数组,改了一个,全都跟着改。
.fill()只能值处理,而不能执行语句,也就是说.fill(data())是先执行data(),再将data()的返回值输入给.fill()方法,所以上面的写法会将 new Array(n).fill(0) 这个长度为n,值全为0的数组的引用作为.fill()的值,所以最终生成的数组每个元素都是相同的长度为n,初始值为0的数组。
2、用 Array.from 配合函数工厂生成多维数组
使用 Array.from,我们可以为每一项动态生成一个新数组实例:
const matrix = Array.from({ length: m }, () => Array(n).fill(0));
一行代码,优雅简单,并且无误。
这行代码的含义是:
-
创建一个长度为 m 的数组
-
对每一项调用函数 () => Array(n).fill(0),生成一个新的数组
-
从而生成一个结构上完全独立的二维数组
修改某一项时,只影响那一行,非常符合直觉。
拓展:
其实还有一种简单的写法:
let arr = new Array(m).fill(null).map(() => new Array(n).fill(0))
其中fill(null)不可省略,因为new Array得到的数组初始值是empty,map不会操作empty的元素,所以必须要先fill一个值,null,0,999都可以,然后再用map改变这些元素值。
这种写法没用到Array.from的性质,并且性能开销相对更高,在这里不多赘述。
二、彻底理解 Array.from 的用法
1、基本语法
Array.from(arrayLike[, mapFn[, thisArg]])
Array.from 可以从类数组对象或可迭代对象(如字符串、Set、Map 等)创建一个新的数组,还可以接受一个映射函数来处理每个元素。
映射函数可选,它的作用相当于在创建数组时就顺手对每个元素做 map(),不需要后续再调用 .map(),写法更简洁。
2、类数组对象与可迭代对象
类数组对象(array-like object) 指的是:具有 length 属性,并且通过数值索引访问元素的对象。
一个合法的类数组对象,一般满足这两个条件:
-
拥有一个非负整数的
length
属性; -
属性名为
"0"
,"1"
,"2"
... 等数字字符串的键,对应下标访问。
它不是数组,但长得很像数组,比如这样:
const obj = {
0: 'a',
1: 'b',
2: 'c',
length: 3
};
console.log(Array.from(obj)); // ['a', 'b', 'c']
虽然 obj 不是数组(没有数组原型方法),但它可以通过 Array.from 被转换成真正的数组,因为它满足“类数组”的两个条件。
{ length: 5 }虽然没有任何数值索引(如 0: xxx),但它有 length,所以Array.from 会根据 length 创建一个长度为 5 的新数组。
可迭代对象是指实现了 [Symbol.iterator] 方法的对象,它能被 for...of、展开运算符(...)、Array.from() 等迭代机制使用。
简而言之:
只要一个对象实现了 Symbol.iterator 方法,并且该方法返回一个迭代器对象,就被视为可迭代对象。
类数组与可迭代对象之间没有确定的关系:
常见类数组名称 | 示例 | 是否可迭代 |
---|---|---|
arguments 对象 | 函数内部的 arguments | ❌(ES6 前) |
DOM 的 NodeList | document.querySelectorAll('div') | ✅ |
HTMLCollection | document.forms | ✅ |
字符串 | "hello" | ✅(但它是可迭代对象,不仅是类数组) |
手动构造的对象 | {0: 'a', 1: 'b', length: 2} | ❌ |
常见的可迭代对象包括:Array、String、Set、Map、ES6之后的arguments、TypedArray、Generator、DOM NodeList(少数浏览器不支持)
类数组 vs 可迭代对象:
特性 | 类数组对象 | 可迭代对象 |
---|---|---|
是否有 length 属性 | ✅ 必须有 | ❌ 不需要 |
是否支持 [index] 访问 | ✅ 是 | ❌ 不一定 |
是否实现 [Symbol.iterator] | ❌ 不一定 | ✅ 必须有 |
例子 | {0:'a',1:'b',length:2} | Set , Map , String |
是否能用 for...of | ❌ 不行(旧类数组) | ✅ 可用 |
3、举例说明
①将类数组对象转为数组
获取 DOM 节点时常用:
const divs = document.querySelectorAll('div'); // NodeList,不是真数组
const divArray = Array.from(divs); // 变成数组
②将字符串拆分成字符数组
Array.from('hello'); // ['h', 'e', 'l', 'l', 'o']
③构建范围数组
这种写法比 for 循环优雅太多了,尤其用于 mock 数据、初始化结构时非常实用。
// 创建 [0, 1, 2, 3, 4]
Array.from({ length: 5 }, (_, i) => i);
// 创建 [1, 2, 3, ..., 100]
Array.from({ length: 100 }, (_, i) => i + 1);
里面的 _ 是箭头函数的形参,没有特别的语义,你完全可以换成别的任意变量名。但是用 _ 在ts里面不会报错(吃过ts苦的兄弟们都知道)。
④去重后的数组
搭配 Set 使用,可以快速数组去重:
const arr = [1, 2, 2, 3, 4, 4];
const unique = Array.from(new Set(arr)); // [1, 2, 3, 4]
三、结语
Array.from 是一个被很多人忽略的宝藏方法,它不仅能转换结构,还能配合生成函数用作数组工厂。无论是构造 m×n 数组、范围数组,还是结构转换,它都能帮你写出更直观、简洁、优雅的代码。
再回头看那一行:
const matrix = Array.from({ length: m }, () => Array(n).fill(0));
是不是比嵌套循环看起来更舒服?
只有锻炼思维才能可持续地解决问题,只有思维才是真正值得学习和分享的核心要素。如果这篇博客能给您带来一点帮助,麻烦您点个赞支持一下,还可以收藏起来以备不时之需,有疑问和错误欢迎在评论区指出~
其他热门文章,请关注:
极致的灵活度满足工程美学:用Vue Flow绘制一个完美流程图
你真的会使用Vue3的onMounted钩子函数吗?Vue3中onMounted的用法详解
通过array.filter()实现数组的数据筛选、数据清洗和链式调用
通过Array.sort() 实现多字段排序、排序稳定性、随机排序洗牌算法、优化排序性能
TreeSize:免费的磁盘清理与管理神器,解决C盘爆满的燃眉之急
测评:这B班上的值不值?在不同城市过上同等生活水平到底需要多少钱?
通过MongoDB Atlas 实现语义搜索与 RAG——迈向AI的搜索机制
深入理解 JavaScript 中的 Array.find() 方法:原理、性能优势与实用案例详解
前端实战:基于Vue3与免费满血版DeepSeek实现无限滚动+懒加载+瀑布流模块及优化策略
el-table实现动态数据的实时排序,一篇文章讲清楚elementui的表格排序功能
JavaScript双问号操作符(??)详解,解决使用 || 时因类型转换带来的问题
内存泄漏——海量数据背后隐藏的项目生产环境崩溃风险!如何避免内存泄漏
MutationObserver详解+案例——深入理解 JavaScript 中的 MutationObserver