用一次就爱上的 Array.from —— 构建 m*n 数组的绝对优雅姿势

举报
watermelo37 发表于 2025/07/31 22:47:38 2025/07/31
【摘要】         作者:watermelo37        CSDN全栈领域优质创作者、万粉博主、华为云云享专家、阿里云专家博主、腾讯云“创作之星”特邀作者、支付宝合作作者,全平台博客昵称watermelo37。        一个假装是giser的coder,做不只专注于业务逻辑的前端工程师,Java、Docker、Python、LLM均有涉猎。----------------------...

        作者: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 属性,并且通过数值索引访问元素的对象。

        一个合法的类数组对象,一般满足这两个条件:

  1. 拥有一个非负整数的 length 属性;

  2. 属性名为 "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));

        是不是比嵌套循环看起来更舒服?

        只有锻炼思维才能可持续地解决问题,只有思维才是真正值得学习和分享的核心要素。如果这篇博客能给您带来一点帮助,麻烦您点个赞支持一下,还可以收藏起来以备不时之需,有疑问和错误欢迎在评论区指出~

        其他热门文章,请关注

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。