ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

JavaScript 数组(Array)完全指南:有序集合、队列栈操作与性能优化

JavaScript 数组(Array)完全指南:有序集合、队列栈操作与性能优化 文档/教程前端【免费下载链接】en.javascript.infoModern JavaScript Tutorial项目地址https://gitcode.com/gh_mirrors/en/en.javascript.info点击查看免费下载数组Array是 JavaScript 中存储有序集合的核心数据结构本教程Modern JavaScript Tutorial在 04-array/article.md 中系统讲解了它的声明方式、元素访问、队列/栈操作、内部实现原理、性能特征与常见陷阱。读完本文你将掌握数组的完整声明语法[]与new Array()、at()负索引访问、push/pop/shift/unshift四件套、length的读写特性以及为什么不能用比较数组等实战要点。为什么需要数组从对象到有序集合对象Object适合存储键值对形式的集合。但当我们需要一个1st、2nd、3rd…依次排列的有序集合时对象就不够用了——对象不提供任何管理元素顺序的方法也无法在已有属性之间插入一个新属性。对象本来就不是为这种用法设计的。于是 JavaScript 提供了专门的数据结构Array用来存储有序集合。无论是用户列表、商品列表还是 HTML 元素集合数组都是最自然的载体。声明数组的两种语法创建空数组有两种写法let arr new Array(); let arr [];绝大多数情况下使用第二种方括号写法它更简短。也可以直接在方括号中提供初始元素let fruits [Apple, Orange, Plum];数组元素从零开始编号可以用方括号加数字下标获取元素let fruits [Apple, Orange, Plum]; alert( fruits[0] ); // Apple alert( fruits[1] ); // Orange alert( fruits[2] ); // Plum替换元素与追加元素同样简单fruits[2] Pear; // 现在为 [Apple, Orange, Pear] fruits[3] Lemon; // 现在为 [Apple, Orange, Pear, Lemon]元素总数就是数组的length属性let fruits [Apple, Orange, Plum]; alert( fruits.length ); // 3也可以用alert直接打印整个数组元素之间以逗号分隔let fruits [Apple, Orange, Plum]; alert( fruits ); // Apple,Orange,Plum数组可以存放任意类型的混合元素// 混合值 let arr [ Apple, { name: John }, true, function() { alert(hello); } ]; // 取下标 1 的对象并读取其 name 属性 alert( arr[1].name ); // John // 取下标 3 的函数并调用它 arr[3](); // hello尾随逗号Trailing comma和对象一样数组也允许以逗号结尾let fruits [ Apple, Orange, Plum, ];这种风格让插入/删除元素更方便因为每一行都长得一样。用 at() 获取最后一个元素不少语言支持负数下标如fruits[-1]。但在 JavaScript 中这样写不生效——方括号中的下标会被原样当作属性名处理结果得到undefined。因此访问最后一个元素需要显式计算下标let fruits [Apple, Orange, Plum]; alert( fruits[fruits.length-1] ); // Plum这种写法要把变量名写两遍略显笨拙。好在有更短的语法fruits.at(-1)let fruits [Apple, Orange, Plum]; // 等价于 fruits[fruits.length-1] alert( fruits.at(-1) ); // Plumarr.at(i)的规则该特性来自较新的浏览器注意兼容性当i 0时与arr[i]完全一致当i为负数时从数组末尾向前数。队列与栈push/pop、shift/unshift数组最经典的两大用途是队列queue和栈stack。队列FIFO先进先出支持两个操作push把元素追加到末尾shift从开头取出元素队列向前推进原来的第 2 个元素变成第 1 个。栈LIFO后进先出支持两个操作push把元素加到末尾pop从末尾取出元素。栈常被画成一摞牌新牌总是从顶端放入或取走。在计算机科学中既能在头部又能在尾部增删元素的这种结构被称为双端队列deque。JavaScript 数组恰好集两者于一身可以同时充当队列和栈。作用于末尾的方法pop 与 pushpop取出数组最后一个元素并返回它同时修改数组let fruits [Apple, Orange, Pear]; alert( fruits.pop() ); // 移除 Pear 并 alert 出来 alert( fruits ); // Apple, Orange注意区别fruits.pop()和fruits.at(-1)都能返回最后一个元素但只有pop()会把它从数组中移除。push把元素追加到末尾let fruits [Apple, Orange]; fruits.push(Pear); alert( fruits ); // Apple, Orange, Pearfruits.push(...)等价于fruits[fruits.length] ...。作用于开头的方法shift 与 unshiftshift取出数组第一个元素并返回它let fruits [Apple, Orange, Pear]; alert( fruits.shift() ); // 移除 Apple 并 alert 出来 alert( fruits ); // Orange, Pearunshift把元素加到开头let fruits [Orange, Pear]; fruits.unshift(Apple); alert( fruits ); // Apple, Orange, Pearpush和unshift都能一次添加多个元素let fruits [Apple]; fruits.push(Orange, Peach); fruits.unshift(Pineapple, Lemon); // [Pineapple, Lemon, Apple, Orange, Peach] alert( fruits );配套练习仓库在该章节下附带了三道配套任务可用于巩固上述 API1-item-value验证数组是按引用复制的——把fruits赋给shoppingCart后push原数组fruits.length同样变成 4参见 solution.md。2-create-array综合演练 5 个数组操作——创建、追加、替换中间值对奇数长度数组取Math.floor((styles.length - 1) / 2)、shift头部弹出、unshift头部批量插入参见 solution.md。3-call-array-this把函数push进数组后用arr[2]()调用此时this指向数组本身——因为arr[2]()本质上就是obj[method]()参见 solution.md。数组内部原理本质上是特殊对象数组是一种特殊的对象。arr[0]这种方括号访问本质上是对象属性访问语法obj[key]其中arr是对象、数字作为键。数组在对象之上扩展了处理有序集合的特殊方法并提供了length属性但核心依然是对象。JavaScript 只有八种基本数据类型详见 Data types 一章数组属于对象因此行为也像对象。例如它按引用复制let fruits [Banana] let arr fruits; // 按引用复制两个变量指向同一个数组 alert( arr fruits ); // true arr.push(Pear); // 通过引用修改数组 alert( fruits ); // Banana, Pear —— 现在有 2 个元素真正让数组特殊的是其内部表示JS 引擎会尽量把元素存放在连续的内存区域中一个接一个并配合其他优化手段让数组操作飞快。但如果把数组当成普通对象来用这些优化就会全部失效。例如技术上我们可以let fruits []; // 创建数组 fruits[99999] 5; // 给远超 length 的下标赋值 fruits.age 25; // 创建任意名字的属性这在语法上没问题数组底子是对象能加任意属性但引擎会判定你正在把数组当普通对象用针对数组的优化会被关闭性能优势随之消失。滥用数组的几种典型方式添加非数字属性如arr.test 5制造空洞比如先加arr[0]再加arr[1000]中间什么都不填逆序填充如先arr[1000]再arr[999]……请把数组当作专门处理有序数据的特殊结构引擎为连续有序数据做了精心调优请按这个方式使用它。如果确实需要任意键大概率你真正需要的是一个普通对象{}。性能为什么 push/pop 快shift/unshift 慢关键在于操作发生在末尾还是开头。执行fruits.shift()时仅仅取出下标 0 的元素是不够的其他元素必须跟着重新编号。shift需要做三件事移除下标 0 的元素把所有元素向左移动重新编号下标 1 变 0、2 变 1依此类推更新length属性。数组元素越多移动耗时越长内存操作也越多。unshift同理为了在开头插入元素必须先让现有元素向右移动、下标递增。而push/pop什么都不用移动pop只需清掉末尾下标并缩短length即可。pop无需移动任何元素因为其余元素的下标保持不变——所以它快得惊人。push也一样。这就是为什么在数组头部频繁增删元素时要警惕性能尾部的增删才是数组最擅长的操作。遍历数组的三种循环最传统的方式是基于下标的for循环let arr [Apple, Orange, Pear]; for (let i 0; i arr.length; i) { alert( arr[i] ); }现代语法for..of更简短直接迭代元素值let fruits [Apple, Orange, Plum]; // 遍历数组元素 for (let fruit of fruits) { alert( fruit ); }for..of拿不到当前元素的下标只能拿到值——但大多数场景下这就够了。技术上数组也是对象所以也能用for..inlet arr [Apple, Orange, Pear]; for (let key in arr) { alert( arr[key] ); // Apple, Orange, Pear }但这是一个坏主意有两个隐患for..in遍历的是所有属性不止数字下标。浏览器和其他环境中存在类数组对象有length和下标属性可能还带其他非数字属性与方法for..in会把那些多余属性也列出来处理类数组时容易出问题for..in是为普通对象优化的不是为数组设计的速度可能慢10~100 倍。虽然绝对速度仍然很快只在瓶颈处有意义但应当清楚这一差异。结论遍历数组不要用for..in。关于 length 的细节length属性会在修改数组时自动更新。准确地说它不是数组中值的个数而是最大的数字下标加 1。比如单个元素放在大下标处length会变得很大let fruits []; fruits[123] Apple; alert( fruits.length ); // 124注意通常我们不会这样用数组。length还有一个特性它是可写的。手动调大不会发生什么但调小会截断数组且过程不可逆let arr [1, 2, 3, 4, 5]; arr.length 2; // 截断为 2 个元素 alert( arr ); // [1, 2] arr.length 5; // 把 length 调回去 alert( arr[3] ); // undefined —— 被截掉的元素不会回来因此清空数组最简单的方式就是arr.length 0;。new Array() 的陷阱还有一种创建数组的语法let arr new Array(Apple, Pear, etc);它很少被使用因为方括号[]更短。而且它有一个容易踩坑的特性如果new Array只传一个数字参数它创建的不是含该数字的数组而是长度等于该数字、但没有任何元素的数组let arr new Array(2); // 它会创建一个 [2] 吗 alert( arr[0] ); // undefined! 里面没有元素 alert( arr.length ); // length 是 2为了避免这种意外通常我们都用方括号——除非你非常确定自己在做什么。多维数组数组的元素也可以是数组可以借此构建多维数组例如存储矩阵let matrix [ [1, 2, 3], [4, 5, 6], [7, 8, 9] ]; alert( matrix[0][1] ); // 2 —— 第一个内部数组的第二个值toString 与隐式转换数组实现了自己的toString方法返回逗号分隔的元素列表let arr [1, 2, 3]; alert( arr ); // 1,2,3 alert( String(arr) 1,2,3 ); // true再看这个经典示例alert( [] 1 ); // 1 alert( [1] 1 ); // 11 alert( [1,2] 1 ); // 1,21数组没有Symbol.toPrimitive也没有可用的valueOf只实现了toString转换所以这里[]变成空字符串、[1]变成1、[1,2]变成1,2。二元加号遇到字符串时会把它也转成字符串于是下一步变成了alert( 1 ); // 1 alert( 1 1 ); // 11 alert( 1,2 1 ); // 1,21不要用 比较数组与某些语言不同JavaScript 中不应用运算符比较数组。该运算符对数组没有任何特殊处理按普通对象规则运作。回顾一下规则两个对象只有引用同一个对象时才相等若一方是对象、另一方是原始值对象会按 Object to primitive conversion 一章的规则转成原始值例外是null和undefined它们互相相等且不与任何其他值相等。严格比较更简单因为不转换类型。所以两个不同数组用比较永远不相等除非两个变量引用同一个数组alert( [] [] ); // false alert( [0] [0] ); // false这两个数组在技术上是不同的对象不会做逐项比较。与原始值比较时还会出现看似诡异的结果alert( 0 [] ); // true alert(0 [] ); // false两种情况下都是原始值 vs 数组对象数组[]为了比较被转成原始值变成空字符串。接下来按 类型转换 一章的规则继续比较// [] 被转换成 之后 alert( 0 ); // true因为 被转成数字 0 alert(0 ); // false不发生类型转换两个字符串不同那数组该怎么比较很简单别用。改用循环或迭代方法逐项比较迭代方法详见下一章 Array methods。综合实战最大子数组和本章配套任务 10-maximal-subarray 是数组循环与性能思考的绝佳练习输入一个数字数组求和最大的连续子数组。朴素解法用嵌套循环穷举所有子段复杂度为 O(n²)参见 solution.md更优的解法只需一趟遍历——维护当前部分和partialSum一旦它为负就归零同时用Math.max记录历史最大值复杂度降到 O(n)function getMaxSubSum(arr) { let maxSum 0; let partialSum 0; for (let item of arr) { // 遍历 arr 的每一项 partialSum item; // 累加到部分和 maxSum Math.max(maxSum, partialSum); // 记住最大值 if (partialSum 0) partialSum 0; // 为负则归零 } return maxSum; } alert( getMaxSubSum([-1, 2, 3, -9]) ); // 5 alert( getMaxSubSum([-1, 2, 3, -9, 11]) ); // 11 alert( getMaxSubSum([-2, -1, 1, 2]) ); // 3 alert( getMaxSubSum([100, -9, 2, -3, 5]) ); // 100 alert( getMaxSubSum([-1, -2, -3]) ); // 0 —— 全部为负时取空子数组另一道练习 5-array-input-sum 则要求用prompt收集用户输入存入数组、在遇到非数字/空串/取消时停止并求和进一步巩固数组与循环的配合。本章小结数组是专门用来存储和管理有序数据项的特殊对象。声明// 方括号常规用法 let arr [item1, item2...]; // new Array极其罕见 let arr new Array(item1, item2...);new Array(number)创建的是有指定长度、但没有元素的数组。length 要点length即数组长度准确说是最大数字下标加 1由数组方法自动维护手动缩短length会截断数组。取元素用下标取如arr[0]用at(i)支持负下标i为负时从末尾倒数i 0时与arr[i]相同。作为双端队列的操作push(...items)把items加到末尾pop()从末尾移除并返回元素shift()从开头移除并返回元素unshift(...items)把items加到开头。遍历for (let i0; iarr.length; i)最快兼容旧浏览器for (let item of arr)现代语法只取元素值for (let i in arr)永远不要用。比较数组不要用以及、等其他比较运算符它们对数组没有特殊处理会按对象规则运作通常不是我们想要的结果。应改用for..of循环逐项比较。后续章节 Array methods 会继续深入讲解增删、提取、排序等更丰富的数组方法。赞分享文档/教程前端【免费下载链接】en.javascript.infoModern JavaScript Tutorial项目地址https://gitcode.com/gh_mirrors/en/en.javascript.info点击查看免费下载相关推荐Garnet有序集合操作范围查询性能优化Garnet有序集合操作范围查询性能优化 引言有序集合范围查询的性能瓶颈 在高性能键值存储系统中有序集合Sorted Set是支撑实时排行榜、时间序列缓存KV存储后端高级数据结构操作列表、集合与有序集合高级数据结构操作列表、集合与有序集合 本文深入探讨了Redis中三种高级数据结构列表、集合和有序集合在go redis客户端中的操作与应用。详细介绍了列表后端数据库客户端缓存Learn-Algorithms 数列交并集面试题精讲集合交集、有序数组合并与双序列和差最小化Learn Algorithms 数列交并集面试题精讲集合交集、有序数组合并与双序列和差最小化 本文源自本仓库面试题笔记 5.3 数列 交并集.md http教程上一篇终极FGO助手Chaldea从素材规划到战斗模拟的全方位指南下一篇三步完成微信朋友圈永久备份WechatMoments终极指南创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表