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

目录

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

一、应用背景

1、错误案例        

2、用 Array.from 配合函数工厂生成多维数组

二、彻底理解 Array.from 的用法

1、基本语法

2、类数组对象与可迭代对象

3、举例说明

①将类数组对象转为数组

②将字符串拆分成字符数组

③构建范围数组

④去重后的数组

三、结语


        作者: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 的 NodeListdocument.querySelectorAll('div')
HTMLCollectiondocument.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的用法详解

        DeepSeek:全栈开发者视角下的AI革命者

        通过array.filter()实现数组的数据筛选、数据清洗和链式调用

        通过Array.sort() 实现多字段排序、排序稳定性、随机排序洗牌算法、优化排序性能

        TreeSize:免费的磁盘清理与管理神器,解决C盘爆满的燃眉之急

        Web Worker:让前端飞起来的隐形引擎

        测评:这B班上的值不值?在不同城市过上同等生活水平到底需要多少钱?

        通过MongoDB Atlas 实现语义搜索与 RAG——迈向AI的搜索机制

        深入理解 JavaScript 中的 Array.find() 方法:原理、性能优势与实用案例详解

        前端实战:基于Vue3与免费满血版DeepSeek实现无限滚动+懒加载+瀑布流模块及优化策略

        el-table实现动态数据的实时排序,一篇文章讲清楚elementui的表格排序功能

        JavaScript双问号操作符(??)详解,解决使用 || 时因类型转换带来的问题

        内存泄漏——海量数据背后隐藏的项目生产环境崩溃风险!如何避免内存泄漏

        MutationObserver详解+案例——深入理解 JavaScript 中的 MutationObserver

        JavaScript中通过array.map()实现数据转换、创建派生数组、异步数据流处理、DOM操作等

      【前端实战】如何让用户回到上次阅读的位置?

        高效工作流:用Mermaid绘制你的专属流程图;如何在Vue3中导入mermaid绘制流程图

评论 82
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值