文档/教程前端【免费下载链接】en.javascript.infoModern JavaScript Tutorial项目地址https://gitcode.com/gh_mirrors/en/en.javascript.info点击查看免费下载导读本文围绕 Modern JavaScript Tutorialjavascript.info《Arrays》章节中的经典习题Is array copied数组被复制了吗展开从题目结论4出发完整剖析数组是对象、变量保存的是引用这一 JavaScript 核心语义。读完后你将掌握为何let shoppingCart fruits不会复制数组、push与length如何联动、数组与基础类型在赋值行为上的本质差异以及实战中如何真正克隆数组、正确比较数组避免引用共享带来的隐蔽 Bug。习题回顾答案确实是 4习题原题位于 1-js/05-data-types/04-array/1-item-value/task.md题目如下let fruits [Apples, Pear, Orange]; // push a new value into the copy let shoppingCart fruits; shoppingCart.push(Banana); // whats in fruits? alert( fruits.length ); // ?题目设问把fruits复制给shoppingCart后通过shoppingCart.push(Banana)向副本追加元素fruits.length会是多少官方解答solution.md给出的答案是4let fruits [Apples, Pear, Orange]; let shoppingCart fruits; shoppingCart.push(Banana); alert( fruits.length ); // 4答案的核心理由只有一句话因为数组是对象所以shoppingCart和fruits指向的是同一个数组的引用。shoppingCart.push(Banana)实际修改的是两者共同引用的那块数组fruits.length自然从 3 变为 4。下面我们把这个结论层层拆开。为什么是 4变量里装的是引用不是数组本身赋值不复制数组只复制引用关键误区在于把let shoppingCart fruits理解为复制了数组内容。实际上fruits变量中存储的不是数组元素本身而是该数组在内存中的地址引用let shoppingCart fruits复制的是这份引用数组对象本身并未被复制两个变量现在指向同一个数组对象通过任意一个变量修改数组另一个变量都能看到变化。这正是 1-js/04-object-basics/02-object-copy/article.md 中反复强调的对象语义A variable assigned to an object stores not the object itself, but its address in memory -- in other words a reference to it.赋值给对象的变量保存的不是对象本身而是它在内存中的地址即对它的引用。一次 push两个变量同时看到把执行过程逐步展开let fruits [Apples, Pear, Orange]; // fruits - 数组A3个元素 let shoppingCart fruits; // shoppingCart - 同一个数组A shoppingCart.push(Banana); // 在数组A末尾追加元素 alert( fruits.length ); // 4因为数组A现在有4个元素同章主文 article.md 的 Internals 一节给出了等价的最小演示并附上引用相等的验证let fruits [Banana] let arr fruits; // copy by reference (two variables reference the same array) alert( arr fruits ); // true arr.push(Pear); // modify the array by reference alert( fruits ); // Banana, Pear - 2 items nowarr fruits为true直接证明了两个变量引用的是同一个对象——这是理解整个习题的第一性原理。数组的本质一种特殊的对象为什么数组会像对象一样被复制在 JavaScript 中只有 8 种基础数据类型Array并不是其中之一。主文 article.md 明确指出An array is a special kind of object. The square brackets used to access a propertyarr[0]actually come from the object syntax. Thats essentially the same asobj[key], wherearris the object, while numbers are used as keys.也就是说arr[0]本质上就是对象属性访问obj[key]的语法糖只是键是数字。数组在对象的基础上额外扩展了管理有序集合的特殊方法push/pop/shift/unshift等自动维护的length属性。但内核仍然是对象——这正是let shoppingCart fruits只复制引用的根本原因。对象的赋值/传参语义按引用天然适用于数组。引擎优化与滥用数组的代价同章主文还提醒引擎会尽量把数组元素存储在连续内存区域中并做其他优化让数组操作非常快。但这些优化有一个前提——始终把数组当作有序集合使用。如果把它当普通对象用article.mdlet fruits []; // make an array fruits[99999] 5; // assign a property with the index far greater than its length fruits.age 25; // create a property with an arbitrary name技术上可行因为数组本质是对象但引擎会判定你在把它当普通对象用从而关闭数组专属优化。典型的错误用法有三种添加非数字属性如arr.test 5制造空洞如只填arr[0]和arr[1000]中间留空逆序填充如依次写arr[1000]、arr[999]……需要任意键名时应该改用普通对象{}而不是数组。值复制与引用复制的根本分野要彻底理解这道题还需要把它和基础类型primitive的赋值行为对照。同属本仓库的 Object references and copying 章节给出了对照基础类型string、number、boolean 等按值复制let message Hello!; let phrase message; // 两个独立变量各存一份 Hello!对象数组同属对象按引用复制变量存的是内存地址复制变量 复制地址对象只有一个。由此可以推广出两个实战判断标准let b aa 为数组/对象后b.push(...)、b[0] ...等修改a一定看得见若想得到一份真正独立的数组必须显式执行克隆操作见下文。这也解释了为什么const声明的数组依然可以被修改object-copy 章节的说明const锁住的是变量必须始终引用同一个对象而不是对象内容不可变。push 与 length同一数组上的联动习题中的length从 3 变 4背后是两条语言规则规则一push等价于在末尾下标处赋值。主文 article.md 明确fruits.push(...)等于fruits[fruits.length] ...。因为shoppingCart和fruits是同一数组这次赋值就发生在两个变量共享的那个数组上。规则二length不是元素计数而是最大数字索引 1且由数组方法自动维护。例如let fruits []; fruits[123] Apple; alert( fruits.length ); // 124所以初始 3 个元素索引 0、1、2length 为 3的数组push(Banana)后末尾索引变为 3length自动更新为 4。此外length还是可写的手动调小会截断数组且不可逆arr.length 0是清空数组最简洁的方式article.md。实战影响如何真正复制数组、如何比较数组理解了引用复制就必须知道如何规避它。真正的克隆该怎么做如果确实需要一份独立副本不能直接赋值可以选用Object.assign浅拷贝把原数组元素逐个复制到新对象同章对象章节 object-copy 有完整介绍Object.assign({}, user)对对象适用对数组也可借助其对象本质实现浅拷贝。展开语法对象章节提到clone {...user}object-copy同一思路作用于数组即为[...arr]同样是浅拷贝。structuredClone深拷贝对于嵌套数组/对象Object.assign和展开语法只会复制第一层内层仍是共享引用需要深拷贝时应使用structuredClone。官方章节明确说明它可以克隆对象、数组和基础值等大多数数据类型object-copy唯一的限制是不支持函数属性。注意浅拷贝后一维数组元素基础类型彼此独立若元素本身是对象/数组内层仍共享修改内层依然会互相影响。不要用比较数组引用语义的另一大实战陷阱是数组比较。主文 article.md 专门用一节强调Dont compare arrays with 两个对象仅在引用同一对象时才相等不同数组即使内容完全一致也不相等alert( [] [] ); // false alert( [0] [0] ); // false数组与基础类型比较时数组会先被toString转换为字符串结果常出人意料alert( 0 [] ); // true[] 转为 再转为 0 alert(0 [] ); // false0 与 不相等正确做法是逐元素比较用循环或下一章 array-methods 介绍的迭代方法。同章姊妹习题数组对象本质的进一步印证同目录下的 3-call-array-this/task.md 习题与本题互为印证——它同样利用了数组是对象这一事实let arr [a, b]; arr.push(function() { alert( this ); }); arr[2](); // ?因为数组可以存放任意类型的元素包括函数且可以通过数字下标访问属性所以arr.push(function(){...})把一个函数放进了数组arr[2]()再把它作为方法调用。这与本题共享同一个认知前提数组的下标访问、元素追加本质都是对象层面的操作区别只在于数组被专门优化用于有序数据。小结回到最初的习题alert(fruits.length)输出4因为数组是对象let shoppingCart fruits只复制了引用没有复制数组solution.mdshoppingCart.push(Banana)修改的是两个变量共同引用的同一个数组push等价于fruits[fruits.length] ...length自动更新为 4article.md。由这道题可以提炼出三条可迁移的实战准则赋值不等于拷贝数组以及一切对象的变量赋值、函数传参都是传引用共享同一份数据需要独立副本时显式克隆浅拷贝用Object.assign/[...arr]深拷贝用structuredClone比较用逐元素而非引用相等不代表内容相等。把数组当作经过优化的有序对象来理解引用复制、length联动、陷阱等一系列行为都会变得顺理成章。赞分享文档/教程前端【免费下载链接】en.javascript.infoModern JavaScript Tutorial项目地址https://gitcode.com/gh_mirrors/en/en.javascript.info点击查看免费下载相关推荐freeCodeCamp 每日编程挑战第 248 题「Sorted Array Swap」排序后按倍数索引交换的数组变换全解析freeCodeCamp 每日编程挑战第 248 题「Sorted Array Swap」排序后按倍数索引交换的数组变换全解析 本题出自 freeCodeCa前端后端教育DLSS Swapper完整指南如何在游戏不更新的情况下切换 DLSS 版本DLSS Swapper完整指南如何在游戏不更新的情况下切换 DLSS 版本 DLSS Swapper 是一款面向 PC 玩家的免费开源 Windows 工具桌面应用pretrained-models.pytorch学术引用分析被300顶会论文引用的背后pretrained models.pytorch学术引用分析被300顶会论文引用的背后 在计算机视觉领域研究者常面临模型训练耗时、资源消耗大的问题。pr人工智能深度学习计算机视觉上一篇多语言国际化React JSON Schema Form全球化表单解决方案下一篇解密WVP-GB28181-Pro构建企业级分布式视频监控平台的终极指南创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考