作者按: 本文以 ECMAScript 官方规范(MDN Web Docs)、V8 官方博客(v8.dev)为基础,融合掘金、腾讯云开发者社区等技术社区的精华内容,对 JavaScript 中最核心的两大结构------数组(Array) 与 函数(Function)------进行系统、深度的梳理。内容覆盖:基础概念 → 内存模型 → V8 引擎底层优化 → 函数提升与执行上下文 → arguments 对象 → 函数式编程入门,并附大量完整可运行示例与 Mermaid 图解。力求让每一位读者从"会用"进化到"真正理解"。
目录
- 前置:流程控制语句回顾
- 数组(Array)
- 什么是数组
- 数组的内存模型
- 创建数组的三种方式
- [① 字面量方式(推荐)](#① 字面量方式(推荐))
- [② Array 函数方式](#② Array 函数方式)
- [③ new Array 构造函数方式](#③ new Array 构造函数方式)
- 三种方式的核心区别总结
- 完整可运行示例
- 数组元素的读写
- 稀疏数组
- 名词解释
- 产生稀疏数组的三种途径
- [空槽 vs undefined 的微妙差别](#空槽 vs undefined 的微妙差别)
- 工程实践警告
- 稀疏数组完整示例
- 数组的遍历(迭代)
- 数组元素的添加与删除
- 多维数组
- 字符串的数组特性与类数组
- 数组方法全景速查
- [改变原数组的方法(Mutating Methods)](#改变原数组的方法(Mutating Methods))
- [不改变原数组的方法(Non-Mutating Methods)](#不改变原数组的方法(Non-Mutating Methods))
- [深入原理:V8 引擎如何优化数组](#深入原理:V8 引擎如何优化数组)
- 名词解释(底层术语)
- [V8 Elements Kind 层次结构](#V8 Elements Kind 层次结构)
- 降级演示
- 性能影响:空槽的代价
- 高性能数组实践指南
- [V8 优化总结一览表](#V8 优化总结一览表)
- 函数(Function)
- 什么是函数
- 函数的组成
- 创建函数的四种方式
- 函数调用与返回值
- 深入原理:函数提升与执行上下文
- 名词解释(执行机制术语)
- [JavaScript 代码执行的两个阶段](#JavaScript 代码执行的两个阶段)
- [函数声明提升 vs 函数表达式](#函数声明提升 vs 函数表达式)
- 调用栈可视化
- 作用域链:变量查找的路径
- 完整可运行示例:提升与作用域链
- [深入原理:形参、实参与 arguments 对象](#深入原理:形参、实参与 arguments 对象)
- 名词解释
- 形实参数量不匹配的行为
- [arguments 对象详解](#arguments 对象详解)
- [arguments vs 剩余参数(ES6)](#arguments vs 剩余参数(ES6))
- [ES5 默认参数设置方式](#ES5 默认参数设置方式)
- [ES6 默认参数语法](#ES6 默认参数语法)
- 完整可运行示例
- 深入原理:函数是一等公民与函数式编程基础
- 名词解释(函数式编程术语)
- 函数作为一等公民的四种体现
- 高阶函数:map、filter、reduce
- [纯函数 vs 非纯函数](#纯函数 vs 非纯函数)
- 完整可运行示例:函数式编程实践
- 经典练习题解析
- 题目来源:课堂题目与强化练习
- 练习1:循环输出结果分析
- [练习2:do...while 死循环分析](#练习2:do…while 死循环分析)
- [练习3:for 循环步进分析](#练习3:for 循环步进分析)
- 练习4:水仙花数(完整解析)
- [练习5:等腰三角形(⭐ 经典双重循环)](#练习5:等腰三角形(⭐ 经典双重循环))
- 题目来源:课堂题目与强化练习
- 综合应用案例
- 知识总结与脑图
- [Day05 完整知识脉络](#Day05 完整知识脉络)
- 重点难点总结
- [函数vs数组 对比速记](#函数vs数组 对比速记)
- 附:练习题完整答案
- 理论深化:数组与函数的本质认知
- 数组的理论基础
- 数组在计算机科学中的地位
- 数组与其他数据结构的关系
- [length 属性的理论本质](#length 属性的理论本质)
- 函数的理论基础
- 数组的理论基础
- 经典业务场景全解析
- [场景一:电商购物车(数组 + 函数协作)](#场景一:电商购物车(数组 + 函数协作))
- [场景二:数据处理流水线(ETL 模式)](#场景二:数据处理流水线(ETL 模式))
- 场景三:防抖与节流(函数的工程级应用)
- 场景四:数组在状态管理中的不可变性原则
- 业务价值:为什么这两者是前端开发的基石
前置:流程控制语句回顾
在正式进入数组与函数之前,先对已学过的流程控制做一次系统梳理------它们是操作数组时最常用的工具。
名词解释
| 术语 | 英文 | 定义 |
|---|---|---|
| 循环语句 | Loop Statement | 在满足条件的情况下,重复执行某段代码块的语句 |
| 跳转语句 | Jump Statement | 改变程序正常顺序执行流程的语句 |
| 迭代 | Iteration | 每一次对过程的重复执行,常用于遍历集合 |
| break | --- | 立即终止最近的一层循环或 switch 语句 |
| continue | --- | 跳过当前迭代,继续下一次循环 |
三种循环的对比
循环结构
while 循环
do...while 循环
for 循环
先判断条件
条件为真才执行
可能一次都不执行
先执行一次
再判断条件
至少执行一次
初始化 + 条件 + 步进
紧凑写法
适合已知次数的遍历
循环执行流程图
true
false
break
continue
开始
初始化
条件判断
执行循环体
步进更新
结束
break 与 continue 的核心区别
continue效果
进入循环
遇到continue
跳过本次剩余代码
回到循环条件判断
继续下一次循环
break效果
进入循环
遇到break
立即退出循环
执行循环之后的代码
经典案例:while 实现九九乘法表
此案例来自课堂练习,展示循环语句在实际中的应用。
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>while 实现九九乘法表</title>
<style>
body { font-family: 'Courier New', monospace; background: #1a1a2e; color: #eee; padding: 30px; }
h1 { color: #e94560; text-align: center; }
.table-wrap { display: flex; flex-direction: column; gap: 4px; }
.row { display: flex; gap: 8px; }
.cell {
background: #16213e;
border: 1px solid #0f3460;
border-radius: 4px;
padding: 4px 10px;
color: #a8dadc;
font-size: 13px;
white-space: nowrap;
}
</style>
</head>
<body>
<h1>九九乘法表</h1>
<div class="table-wrap" id="output"></div>
<script>
var output = document.getElementById('output');
var i = 1;
while (i <= 9) {
var rowEl = document.createElement('div');
rowEl.className = 'row';
var j = 1;
while (j <= i) {
var cell = document.createElement('span');
cell.className = 'cell';
cell.textContent = j + '×' + i + '=' + (i * j);
rowEl.appendChild(cell);
j++;
}
output.appendChild(rowEl);
i++;
}
</script>
</body>
</html>

代码解析
行 / 片段 说明 var i = 1; while (i <= 9)外层循环控制行数 , i表示当前行(第几排),从 1 到 9var j = 1; while (j <= i)内层循环控制列数 , j最大等于i,保证每行只打印i列j + '×' + i + '=' + (i * j)字符串拼接生成"j×i=积",注意乘积 i*j需加括号防止字符串拼接优先级问题document.createElement('div')用 DOM 动态创建行容器,避免直接拼接 innerHTML 的 XSS 风险 rowEl.appendChild(cell)将每个格子追加到当前行容器,最后 output.appendChild(rowEl)把整行追加到页面i++/j++手动步进, while循环不自动递增,必须在循环体内更新计数器,否则死循环规律总结: 第
i行共有i列,第j列的内容是j×i。整体时间复杂度 O(n²/2),即约执行 45 次内层循环体。
数组(Array)
什么是数组
MDN 官方定义: Array 对象允许在单个变量名下存储多个元素,并具有执行常见数组操作的成员。
名词解释
| 术语 | 英文 | 定义 |
|---|---|---|
| 数组 | Array | 值的有序集合,每个值称为元素 |
| 元素 | Element | 数组中的每一个成员(值),可以是任意类型 |
| 索引 | Index | 元素在数组中的位置编号,从 0 开始 |
| 下标 | Subscript | 索引的另一种说法,与索引同义 |
| length | --- | 数组的长度属性,值为最大索引 + 1 |
| 稀疏数组 | Sparse Array | 含有"空槽"(empty slot)的数组,部分位置没有实际值 |
| 密集数组 | Dense Array | 每个索引位置都有值的数组 |
| 类数组 | Array-like / Like-Array | 具有 length 属性和数字索引属性,但不是真正数组的对象 |
数组的本质:对象
一个常被忽视的事实是------JavaScript 中的数组本质上是一个对象:
javascript
typeof []; // "object"
[] instanceof Array; // true
Array.isArray([]); // true ← 推荐的判断方式
数组与普通对象的差异在于:
- 数组的属性名(键)是非负整数(索引)
- 数组有自动维护的
length属性 - 数组继承了
Array.prototype上大量便利方法
数组的物理结构
渲染错误: Mermaid 渲染失败: Parse error on line 8: ...E["null"] F["[..]"] ----------------------^ Expecting 'end', got 'EOF'
内存示意
引用
arr 变量
(栈内存)
堆内存中的数组对象
0\]=100, \[1\]=45.1, \[2\]='hi', \[3\]=false, \[4\]=null, \[5\]=\[...
length=6
核心特性速记:
- 索引从 0 开始,最大为 2³²- 2(约 42 亿)
length始终等于最大索引 + 1(不是元素数量!稀疏数组时有区别)- 元素可以是任意类型,同一个数组里可以混放数字、字符串、布尔、对象、函数
- 数组是引用类型,赋值操作传递的是引用而非值的副本
数组的内存模型
理解数组在内存中的存储方式,有助于避免常见的引用陷阱。
数组的物理结构
元素值(可混用任意类型)
索引(Index)--- 从 0 连续递增
0
1
2
3
4
5
100
45.1
'hi'
false
null
...
说明: 上图用标准
flowchart表达「下标 → 槽位里的值」的一一对应关系。若你使用的工具较旧,可将 Mermaid 升级到 v10+ ;block-beta等实验语法在不少渲染器中仍会失败,故此处已改为通用写法。
javascript
var a = [10, 20, 30];
var b = a; // b 和 a 指向同一个数组对象
b[0] = 999;
console.log(a[0]); // 999 ------ a 也变了!
工程实践提示: 这种引用共享行为在 React/Vue 等框架的状态管理中极为重要。要避免直接修改,需要创建副本:
var b = [...a]或var b = a.slice()。
创建数组的三种方式
创建数组
① 字面量方式
推荐
② Array 函数方式
③ new Array 构造函数方式
⚠️ 单数字参数陷阱
Array(3) → [,,,]
而非 [3]
✅ 无陷阱
直观明了
性能最佳
① 字面量方式(推荐)
这是最简洁、最无歧义的创建方式,也是官方推荐的写法。
javascript
// 空数组
var arr1 = [];
// 混合类型的数组(JS 允许不同类型共存)
var arr2 = [100, 45.101, 'hello', false, null, undefined, [100, 200, 300]];
// 直接用字面量访问元素(无需赋值给变量)
[100, 200, 300, 400, 500][3]; // 400
② Array 函数方式
javascript
Array(); // [] 空数组
Array(200, 300, 'Hello', [10, 20]); // 4个元素的数组
Array(17); // [空×17] ← 陷阱!单数字参数=指定长度
Array('hello'); // ['hello'] ← 字符串参数=正常元素
③ new Array 构造函数方式
与 Array() 函数行为完全一致,new 关键字可省略但通常保留以表语义清晰。
javascript
new Array(); // []
new Array(200, 300, 'Hello'); // [200, 300, 'Hello']
new Array(17); // [空×17]
三种方式的核心区别总结
| 写法 | 空数组 | 多元素 | 单数字参数 | 单字符串参数 |
|---|---|---|---|---|
[] / [n] |
[] |
正常 | [n](元素) |
[s](元素) |
Array(n) |
[] |
正常 | [空×n](长度) |
[s](元素) |
new Array(n) |
[] |
正常 | [空×n](长度) |
[s](元素) |
ES6 解决方案:
Array.of()彻底解决了单数字参数的二义性问题:
javascriptArray.of(7); // [7] ------ 永远是包含该元素的数组,无歧义 Array(7); // [空×7] ------ 长度为7的空数组
完整可运行示例
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>创建数组的方式</title>
<style>
body { font-family: Arial, sans-serif; background: #f0f2f5; padding: 20px; }
h2 { color: #333; border-left: 4px solid #4f46e5; padding-left: 10px; }
.demo { background: #fff; border-radius: 8px; padding: 16px; margin: 10px 0;
box-shadow: 0 2px 8px rgba(0,0,0,.08); }
.label { font-weight: bold; color: #4f46e5; margin-bottom: 6px; }
.result { background: #1e1e1e; color: #9cdcfe; border-radius: 4px;
padding: 10px; font-family: monospace; white-space: pre-wrap; }
.warn { color: #f59e0b; font-size: 13px; margin-top: 8px; }
</style>
</head>
<body>
<h2>创建数组的三种方式</h2>
<div class="demo">
<div class="label">① 字面量方式</div>
<div class="result" id="r1"></div>
</div>
<div class="demo">
<div class="label">② Array() 函数方式</div>
<div class="result" id="r2"></div>
<div class="warn">⚠️ Array(3) 创建长度为3的空数组,而非 [3]</div>
</div>
<div class="demo">
<div class="label">③ new Array() 构造函数方式(与②行为相同)</div>
<div class="result" id="r3"></div>
</div>
<div class="demo">
<div class="label">ES6 Array.of() --- 无歧义方案</div>
<div class="result" id="r4"></div>
</div>
<script>
function show(id, lines) {
document.getElementById(id).textContent = lines.join('\n');
}
var arr1 = [];
var arr2 = [100, 45.101, 'hello', false, null, undefined];
show('r1', [
'[] → ' + JSON.stringify(arr1),
'[100, 45.101, "hello", false, null, undefined] → ' + JSON.stringify(arr2)
]);
var a1 = Array();
var a2 = Array(200, 300, 'Hello');
var a3 = Array(3);
var a4 = Array('hello');
show('r2', [
'Array() → ' + JSON.stringify(a1),
'Array(200,300,"Hello") → ' + JSON.stringify(a2),
'Array(3) → length=' + a3.length + ' 内容:' + JSON.stringify(a3),
'Array("hello") → ' + JSON.stringify(a4)
]);
var n1 = new Array();
var n2 = new Array(200, 300, 'Hello');
var n3 = new Array(3);
show('r3', [
'new Array() → ' + JSON.stringify(n1),
'new Array(200,300,"Hello") → ' + JSON.stringify(n2),
'new Array(3) → length=' + n3.length
]);
show('r4', [
'Array.of(7) → ' + JSON.stringify(Array.of(7)) + ' (一个元素 7)',
'Array(7) → length=7 的空数组(无元素)',
'Array.of(1, 2, 3) → ' + JSON.stringify(Array.of(1, 2, 3))
]);
</script>
</body>
</html>

代码解析
行 / 片段 说明 function show(id, lines)封装了"把字符串数组用换行拼接后写入指定元素"的公共逻辑,减少重复代码 var arr1 = []字面量空数组,是最简洁的创建方式,V8 会将其识别为 PACKED_SMI_ELEMENTS(最优状态)Array(3)→length=3单数字参数时 Array()将其视为长度而非元素,产生稀疏数组,这是最常见的陷阱JSON.stringify(a3)→"[]"JSON.stringify序列化稀疏数组时会把空槽输出为null,这里显示空是因为空槽无法序列化为字面值Array.of(7)→[7]ES6 新增方法,永远将参数作为元素处理,彻底消除单数字参数歧义 lines.join('\n')将数组转为多行文本,统一输出格式 核心要点: 日常开发中优先用
[]字面量;需要动态创建定长数组时,用Array.from({length: n}, fn)而非Array(n),可同时完成创建和填充。
数组元素的读写
语法
javascript
// 读取
数组[索引] // 返回该位置的值,不存在则返回 undefined
// 写入/修改
数组[索引] = 新值; // 修改已有元素,或在新位置添加元素
索引访问的底层原理
是
有
空槽
否
arr[i] 求值过程
索引 i 是否
在 0 ~ length-1 内?
该位置
是否有值?
返回实际值
返回 undefined
多维数组访问
javascript
var matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]];
matrix[1][2]; // 6 ------ 第二行,第三列
完整可运行示例
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>数组元素的读写</title>
<style>
body { font-family: Arial, sans-serif; background: #f8fafc; padding: 24px; }
h2 { color: #1e293b; }
.card { background: #fff; border-radius: 10px; padding: 20px; margin: 12px 0;
box-shadow: 0 2px 12px rgba(0,0,0,.07); }
.title { font-weight: bold; color: #6366f1; margin-bottom: 10px; font-size: 15px; }
pre { background: #0f172a; color: #7dd3fc; border-radius: 6px;
padding: 14px; margin: 0; overflow-x: auto; font-size: 13px; }
.highlight { color: #fbbf24; }
button {
background: #6366f1; color: #fff; border: none; border-radius: 6px;
padding: 8px 18px; cursor: pointer; margin-top: 10px; font-size: 14px;
}
button:hover { background: #4f46e5; }
</style>
</head>
<body>
<h2>数组元素的读写操作</h2>
<div class="card">
<div class="title">读取操作演示</div>
<pre id="readDemo"></pre>
</div>
<div class="card">
<div class="title">写入操作演示</div>
<pre id="writeDemo"></pre>
<button onclick="runWrite()">点击执行写入</button>
</div>
<div class="card">
<div class="title">嵌套数组访问</div>
<pre id="nestedDemo"></pre>
</div>
<script>
// ── 读取演示 ──
var arr = [100, 45.101, 'hello', false, null, undefined, [100, 200, 300]];
var readLines = [
'arr = ' + JSON.stringify(arr),
'',
'arr[0] → ' + arr[0],
'arr[2] → ' + arr[2],
'arr[6] → ' + JSON.stringify(arr[6]),
'arr[6][1] → ' + arr[6][1], // 嵌套访问
'arr[99] → ' + arr[99], // 不存在 → undefined
'arr[0] + arr[1]→ ' + (arr[0] + arr[1])
];
document.getElementById('readDemo').textContent = readLines.join('\n');
// ── 写入演示 ──
function runWrite() {
var a = [100, 45.101, 'hello', false, null, undefined, [100, 200, 300]];
a[2] = '修改了第3个元素';
a[6][1] = 250;
a[0] *= 2;
a[7] = '新增的第8个元素(索引7)';
var lines = [
'修改后的数组:',
JSON.stringify(a, null, 2)
];
document.getElementById('writeDemo').textContent = lines.join('\n');
}
// ── 嵌套访问 ──
var matrix = [
['苹果', '香蕉', '橙子'],
['红色', '蓝色', '绿色'],
[10, 20, 30 ]
];
var nestedLines = [
'matrix[0][1] → ' + matrix[0][1], // 香蕉
'matrix[1][0] → ' + matrix[1][0], // 红色
'matrix[2][2] → ' + matrix[2][2], // 30
];
document.getElementById('nestedDemo').textContent = nestedLines.join('\n');
</script>
</body>
</html>

代码解析
行 / 片段 说明 arr[99]→undefined访问不存在的索引不会报错,JavaScript 静默返回 undefined,这是弱类型语言的特征arr[6][1]链式索引访问嵌套数组:先取 arr[6](一个子数组),再取其[1]位置的元素arr[0] + arr[1]数值元素可直接参与运算: 100 + 45.101 = 145.101arr[2] = '修改了第3个元素'直接通过索引赋值修改,原值被覆盖,数组 length 不变 arr[7] = '新增的第8个元素'给超出范围的索引赋值会自动扩展 数组, length变为 8JSON.stringify(a, null, 2)第二参数 null表示不替换任何值,第三参数2表示用 2 个空格缩进,使输出更易读onclick="runWrite()"将函数绑定到按钮点击事件,实现交互式演示 注意事项:
arr[6][1] = 250修改的是嵌套子数组中的元素。由于数组是引用类型,如果多个变量指向同一个嵌套数组,修改会影响所有引用。
稀疏数组
名词解释
稀疏数组(Sparse Array) :数组中存在"空槽"(empty slot)的数组------即某些索引位置没有被赋值过的元素。空槽不同于
undefined,尽管读取空槽会得到undefined。
产生稀疏数组的三种途径
稀疏数组
Sparse Array
① 跳跃赋值
arr[0]=1; arr[5]=6
中间索引1-4为空槽
② Array(n) 单数字参数
Array(5) → [空×5]
只有length,无实际元素
③ 增大 length
arr.length = 100
新增位置全为空槽
空槽 vs undefined 的微妙差别
javascript
var sparse = [1, , , 4]; // 稀疏数组,索引1和2是空槽
var dense = [1, undefined, undefined, 4]; // 密集数组,明确的 undefined
// 读取时都返回 undefined
console.log(sparse[1]); // undefined
console.log(dense[1]); // undefined
// 但 forEach/map 对空槽的处理不同!
sparse.forEach((v, i) => console.log(i, v));
// 输出:0 1 和 3 4 (跳过了索引1和2)
dense.forEach((v, i) => console.log(i, v));
// 输出:0 1 1 undefined 2 undefined 3 4
工程实践警告
安全替代
Array.from({length: 3}, (_, i) => i * 2)
结果:[0, 2, 4]
✅ 正确执行
危险操作
new Array(3).map(x => x * 2)
结果:[空×3]
map 跳过空槽,根本不执行!
稀疏数组完整示例
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>稀疏数组</title>
<style>
body { font-family: Arial, sans-serif; background: #fff7ed; padding: 24px; }
h2 { color: #92400e; }
.card { background: #fff; border-radius: 10px; padding: 20px; margin: 12px 0;
border-left: 4px solid #f59e0b; box-shadow: 0 2px 8px rgba(0,0,0,.06); }
.title { font-weight: bold; color: #d97706; margin-bottom: 8px; }
pre { background: #1c1917; color: #fcd34d; border-radius: 6px; padding: 12px;
margin: 0; font-size: 13px; white-space: pre-wrap; }
.warning { background: #fef3c7; border: 1px solid #fbbf24; border-radius: 6px;
padding: 10px; margin-top: 10px; font-size: 13px; color: #78350f; }
</style>
</head>
<body>
<h2>稀疏数组(Sparse Array)深度解析</h2>
<div class="card">
<div class="title">① 跳跃赋值产生稀疏数组</div>
<pre id="demo1"></pre>
</div>
<div class="card">
<div class="title">② Array(n) 产生稀疏数组</div>
<pre id="demo2"></pre>
</div>
<div class="card">
<div class="title">③ 增大 length 产生稀疏数组</div>
<pre id="demo3"></pre>
</div>
<div class="card">
<div class="title">⚠️ 空槽 vs undefined 的遍历差异</div>
<pre id="demo4"></pre>
<div class="warning">
空槽(empty slot)在 forEach/map/filter 中会被跳过,这是常见的 Bug 来源!
推荐用 Array.from({length: n}) 创建密集数组。
</div>
</div>
<script>
// ① 跳跃赋值
var arr1 = [100, 45.101, 'hello', false, null, undefined, [100, 200, 300]];
arr1[12] = 3000;
document.getElementById('demo1').textContent =
'初始 arr length=7,给 arr[12] 赋值后:\n' +
'arr.length = ' + arr1.length + '\n' +
'arr[8] = ' + arr1[8] + ' (空槽读取得 undefined)\n' +
'arr[12] = ' + arr1[12];
// ② Array(n)
var arr2 = Array(7);
document.getElementById('demo2').textContent =
'Array(7) → length=' + arr2.length + '\n' +
'arr2[0] = ' + arr2[0] + ' (空槽)\n' +
'0 in arr2 = ' + (0 in arr2) + ' ← false 说明索引0不存在,是真正空槽';
// ③ 增大 length
var arr3 = [100, 200, 300, 400, 500, 600];
arr3.length = 10;
document.getElementById('demo3').textContent =
'原数组 length=6,设置 arr.length=10 后:\n' +
'arr3 length=' + arr3.length + '\n' +
'arr3[7] = ' + arr3[7] + ' (新增的空槽)';
// ④ 空槽 vs undefined
var sparse = [1, , , 4];
var dense = [1, undefined, undefined, 4];
var sparseLog = [];
var denseLog = [];
sparse.forEach((v, i) => sparseLog.push('索引' + i + ': ' + v));
dense.forEach((v, i) => denseLog.push('索引' + i + ': ' + v));
document.getElementById('demo4').textContent =
'稀疏 [1, 空, 空, 4].forEach 输出:\n' + sparseLog.join('\n') +
'\n\n密集 [1, undefined, undefined, 4].forEach 输出:\n' + denseLog.join('\n');
</script>
</body>
</html>

代码解析
行 / 片段 说明 arr1[12] = 3000跳跃赋值:索引 7~11 变为空槽, length自动变为 130 in arr2in运算符检查属性是否存在于对象 (不检查值),对于真正的空槽返回false,是区分空槽与undefined的可靠方法arr3.length = 10直接赋值扩大 length,新增位置 6~9 全变为空槽;反之,缩小length会截断并删除末尾元素(不可恢复)var sparse = [1, , , 4]连续逗号语法创建稀疏数组,索引 1 和 2 是真正的空槽 sparse.forEach(...)forEach对空槽的回调不会执行,因此输出只有索引 0 和 3dense.forEach(...)密集数组中的 undefined是真实值,forEach正常遍历,输出 4 次工程实践: 生产代码中应完全避免稀疏数组。检测空槽用
index in arr;创建有初始值的数组用Array.from({length: n}, () => defaultValue)。
数组的遍历(迭代)
名词解释
遍历(Traversal)/ 迭代(Iteration):按照一定的顺序依次访问数据结构中的每一个元素的过程。
遍历方式全景图
数组遍历方式
经典循环
for...in
⚠️ 不推荐用于数组
for...of(ES6)
✅ 推荐
数组方法遍历
while / do-while
for 循环 ✅ 最常用
forEach()
map()
filter()
reduce()
各遍历方式对比
| 方式 | 语法 | 特点 | 适用场景 |
|---|---|---|---|
for 循环 |
for(var i=0;i<arr.length;i++) |
性能最好,可控制索引 | 通用,尤其是需要索引时 |
for...in |
for(var i in arr) |
遍历所有可枚举属性,会包含自定义属性 | ❌ 不推荐用于数组 |
for...of |
for(var v of arr) |
只遍历元素值,不包含额外属性 | 只需要值时 |
forEach |
arr.forEach(fn) |
无法 break,无返回值 | 副作用操作 |
map |
arr.map(fn) |
返回新数组 | 数据转换 |
filter |
arr.filter(fn) |
返回满足条件的元素 | 数据过滤 |
for...in 的危险陷阱
javascript
var arr = [10, 20, 30];
arr.myProp = '自定义属性'; // 给数组对象加属性
// for...in 会遍历到 myProp!
for (var key in arr) {
console.log(key, arr[key]);
}
// 输出:0 10 | 1 20 | 2 30 | myProp 自定义属性 ← 多出了属性!
遍历数组完整示例
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>数组遍历方式对比</title>
<style>
* { box-sizing: border-box; }
body { font-family: Arial, sans-serif; background: #f1f5f9; margin: 0; padding: 24px; }
h2 { color: #1e293b; margin-bottom: 20px; }
.grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); gap: 16px; }
.card { background: #fff; border-radius: 10px; padding: 18px;
box-shadow: 0 2px 10px rgba(0,0,0,.08); }
.card-title { font-weight: bold; color: #3b82f6; margin-bottom: 10px; font-size: 14px; }
.output { background: #0f172a; color: #86efac; border-radius: 6px; padding: 12px;
font-family: monospace; font-size: 12px; min-height: 80px; white-space: pre-wrap; }
.tag { display: inline-block; background: #dbeafe; color: #1d4ed8; border-radius: 4px;
padding: 2px 8px; font-size: 11px; margin-bottom: 8px; }
.warn-tag { background: #fef3c7; color: #92400e; }
</style>
</head>
<body>
<h2>数组遍历方式全面对比</h2>
<div class="grid">
<div class="card">
<div class="tag">经典</div>
<div class="card-title">① for 循环(推荐)</div>
<div class="output" id="o1"></div>
</div>
<div class="card">
<div class="tag warn-tag">⚠️ 慎用</div>
<div class="card-title">② for...in(含自定义属性)</div>
<div class="output" id="o2"></div>
</div>
<div class="card">
<div class="tag">ES6</div>
<div class="card-title">③ for...of(只要值)</div>
<div class="output" id="o3"></div>
</div>
<div class="card">
<div class="tag">方法</div>
<div class="card-title">④ forEach</div>
<div class="output" id="o4"></div>
</div>
<div class="card">
<div class="tag">方法</div>
<div class="card-title">⑤ map(返回新数组)</div>
<div class="output" id="o5"></div>
</div>
<div class="card">
<div class="tag">方法</div>
<div class="card-title">⑥ filter(过滤)</div>
<div class="output" id="o6"></div>
</div>
</div>
<script>
var fruits = ['苹果', '香蕉', '橙子', '葡萄', '芒果'];
fruits.customProp = '我是自定义属性';
// ① for 循环
var lines1 = [];
for (var i = 0; i < fruits.length; i++) {
lines1.push('fruits[' + i + '] = ' + fruits[i]);
}
document.getElementById('o1').textContent = lines1.join('\n');
// ② for...in(会遍历到 customProp!)
var lines2 = [];
for (var key in fruits) {
lines2.push('key=' + key + ' → ' + fruits[key]);
}
document.getElementById('o2').textContent =
lines2.join('\n') + '\n← 注意最后一行是自定义属性!';
// ③ for...of(ES6)
var lines3 = [];
for (var val of fruits) {
lines3.push('值: ' + val);
}
document.getElementById('o3').textContent = lines3.join('\n');
// ④ forEach
var lines4 = [];
fruits.forEach(function(v, i) {
lines4.push('[' + i + '] ' + v);
});
document.getElementById('o4').textContent = lines4.join('\n');
// ⑤ map(转换)
var nums = [1, 2, 3, 4, 5];
var doubled = nums.map(function(n) { return n * 2; });
document.getElementById('o5').textContent =
'原数组: ' + JSON.stringify(nums) + '\n' +
'×2后: ' + JSON.stringify(doubled);
// ⑥ filter(过滤)
var numbers = [12, 45, 7, 89, 33, 56, 2, 78];
var big = numbers.filter(function(n) { return n > 30; });
document.getElementById('o6').textContent =
'原数组: ' + JSON.stringify(numbers) + '\n' +
'>30的: ' + JSON.stringify(big);
</script>
</body>
</html>

代码解析
行 / 片段 说明 fruits.customProp = '...'给数组对象添加自定义属性,用于演示 for...in的隐患for (var key in fruits)遍历所有可枚举属性 ,包括数字索引和 customProp,因此会多出一行,这是for...in不适合遍历数组的原因for (var val of fruits)ES6 迭代协议,只遍历数组的值 ,完全跳过自定义属性,是替代 for...in的安全方案fruits.forEach(function(v, i) {...})回调参数顺序固定为 (当前值, 当前索引, 原数组),forEach无法用break中断nums.map(function(n) { return n * 2; })map对每个元素执行回调并收集返回值 组成新数组,原数组nums不被修改numbers.filter(function(n) { return n > 30; })filter将回调返回true的元素收入新数组,常用于数据筛选display: grid; grid-template-columns: repeat(auto-fit, ...)CSS Grid 自适应布局,自动根据容器宽度调整列数 遍历选型原则: 需要索引 →
for;需要返回新数组 →map;筛选元素 →filter;不需要返回值只做副作用 →forEach;绝大多数场景避免 用for...in。
数组元素的添加与删除
这是数组操作中最核心的一组 API,工程中使用频率极高。
方法分类总览
数组增删操作
添加元素
删除元素
增删并用
数组[length] = val
末尾追加
push(...items)
末尾追加一个或多个
unshift(...items)
头部插入一个或多个
splice(idx, 0, ...items)
指定位置插入
length -= n
删除后n个
pop()
删除末尾,返回被删元素
shift()
删除头部,返回被删元素
splice(idx, n)
删除指定位置n个元素
splice(idx, n, ...items)
先删后插,实现替换
各方法性能特点
低性能 On
高性能 O1
push() / pop()
末尾操作,无需移动其他元素
unshift() / shift()
头部操作,所有元素需整体移位
splice() 中间插入删除
需要移动后续所有元素
✅ 大量数据时优先用 push/pop
⚠️ 性能敏感场景避免频繁使用
⚠️ 操作中间位置代价较高
返回值说明
| 方法 | 返回值 |
|---|---|
push() |
新数组的 length |
pop() |
被删除的那个元素 |
unshift() |
新数组的 length |
shift() |
被删除的那个元素 |
splice() |
被删除元素组成的新数组 |
完整可运行示例
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>数组增删操作</title>
<style>
* { box-sizing: border-box; }
body { font-family: Arial, sans-serif; background: #f8fafc; margin: 0; padding: 24px; }
h2 { color: #0f172a; }
.section { background: #fff; border-radius: 10px; padding: 20px; margin: 14px 0;
box-shadow: 0 2px 10px rgba(0,0,0,.07); }
.section h3 { color: #6366f1; margin: 0 0 12px; font-size: 15px; }
.btn-row { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 12px; }
button { padding: 8px 16px; border: none; border-radius: 6px; cursor: pointer;
font-size: 13px; font-weight: bold; transition: all .2s; }
.btn-add { background: #22c55e; color: #fff; }
.btn-del { background: #ef4444; color: #fff; }
.btn-add:hover { background: #16a34a; }
.btn-del:hover { background: #dc2626; }
.arr-display { display: flex; flex-wrap: wrap; gap: 6px; min-height: 40px;
padding: 10px; background: #f1f5f9; border-radius: 6px; }
.arr-item { background: #6366f1; color: #fff; border-radius: 4px;
padding: 4px 10px; font-size: 13px; font-family: monospace; }
.log { font-size: 12px; color: #64748b; margin-top: 6px; font-family: monospace; }
</style>
</head>
<body>
<h2>数组增删操作演示</h2>
<div class="section">
<h3>互动演示:任务列表</h3>
<div class="btn-row">
<button class="btn-add" onclick="pushItem()">push() 末尾添加</button>
<button class="btn-add" onclick="unshiftItem()">unshift() 头部添加</button>
<button class="btn-add" onclick="spliceInsert()">splice() 中间插入(索引2)</button>
<button class="btn-del" onclick="popItem()">pop() 删末尾</button>
<button class="btn-del" onclick="shiftItem()">shift() 删头部</button>
<button class="btn-del" onclick="spliceDelete()">splice() 删索引2起2个</button>
</div>
<div class="arr-display" id="arrDisplay"></div>
<div class="log" id="logLine">当前数组长度: 0</div>
</div>
<div class="section">
<h3>splice() 的三种用法代码示例</h3>
<pre id="spliceDemo" style="background:#0f172a;color:#a5f3fc;border-radius:6px;padding:14px;font-size:13px;margin:0;"></pre>
</div>
<script>
var taskArr = ['任务A', '任务B', '任务C', '任务D'];
var counter = 5;
function render() {
var display = document.getElementById('arrDisplay');
display.innerHTML = '';
taskArr.forEach(function(v, i) {
var span = document.createElement('span');
span.className = 'arr-item';
span.textContent = '[' + i + '] ' + v;
display.appendChild(span);
});
document.getElementById('logLine').textContent =
'当前数组长度: ' + taskArr.length;
}
function pushItem() {
var ret = taskArr.push('任务' + String.fromCharCode(64 + counter++));
render();
}
function unshiftItem() {
taskArr.unshift('紧急任务' + counter++);
render();
}
function spliceInsert() {
if (taskArr.length < 2) return;
taskArr.splice(2, 0, '插入任务' + counter++);
render();
}
function popItem() {
if (taskArr.length === 0) return;
var removed = taskArr.pop();
render();
}
function shiftItem() {
if (taskArr.length === 0) return;
taskArr.shift();
render();
}
function spliceDelete() {
if (taskArr.length < 3) return;
var removed = taskArr.splice(2, 2);
render();
}
// splice 代码示例
var demos = [
'// 1. 纯插入(删除0个,在索引2处插入)',
'arr.splice(2, 0, "新元素");',
'',
'// 2. 纯删除(删除索引3起的2个元素,不插入)',
'var removed = arr.splice(3, 2);',
'// removed = 被删除的元素数组',
'',
'// 3. 替换(删1个,插入新元素)',
'arr.splice(1, 1, "替换元素A", "替换元素B");',
'',
'// splice 返回值 = 被删除元素的数组',
'var arr = ["a","b","c","d","e"];',
'var out = arr.splice(1, 2);',
'// out → ["b","c"]',
'// arr → ["a","d","e"]'
];
document.getElementById('spliceDemo').textContent = demos.join('\n');
render();
</script>
</body>
</html>

代码解析
行 / 片段 说明 taskArr.push('任务' + String.fromCharCode(64 + counter++))String.fromCharCode(65)是'A',递增counter生成 B、C、D 等字母标签taskArr.unshift('紧急任务' + counter++)unshift在头部 插入,所有现有元素索引后移一位,返回新lengthtaskArr.splice(2, 0, '插入任务' + counter++)第二参数 0表示删除 0 个元素(纯插入),在索引 2 处插入新元素taskArr.pop()删除并返回最后一个 元素, length自动减 1;空数组调用返回undefinedtaskArr.shift()删除并返回第一个 元素,后续所有元素向前移位,性能比 pop低(O(n))taskArr.splice(2, 2)从索引 2 开始删除 2 个元素,返回被删除元素组成的数组 display.innerHTML = ''清空容器内所有子节点,等同于 while(el.firstChild) el.removeChild(el.firstChild),但更简洁splice(idx, 0, item)纯插入写法,记忆技巧:第二参数=0 时只插不删 push/pop vs unshift/shift 性能:
push/pop操作末尾,时间复杂度 O(1);unshift/shift操作头部,需移动所有元素,时间复杂度 O(n)。大量数据时优先选push/pop。
多维数组
名词解释
多维数组(Multidimensional Array):数组的元素本身也是数组时,形成嵌套层级结构。最常见的是二维数组(矩阵)。
矩阵(Matrix):二维数组,通常用于表示表格、棋盘、图像像素等具有行列结构的数据。
多维数组内存结构
外层数组 matrix
matrix[0] → ['苹果','香蕉','橙子']
matrix[1] → ['红色','蓝色','绿色']
matrix[2] → [10, 20, 30]
0\]\[0\]='苹果' \[0\]\[1\]='香蕉' \[0\]\[2\]='橙子' ##### 工程实战:渲染数据表格 多维数组在前端开发中极为常见------电商的商品分类、日历的日期网格、数据仪表盘的报表,本质上都是二维数据的渲染。 ```html
多维数组工程应用
学生成绩表(二维数组 → HTML 表格)
乘法矩阵(嵌套循环生成)
可用 for 循环遍历
常见的类数组对象
| 类数组对象 | 来源 | 说明 |
|---|---|---|
String |
字符串 | 字符的类数组 |
arguments |
函数内部 | 函数所有实参的集合 |
NodeList |
DOM 查询 | querySelectorAll() 返回值 |
HTMLCollection |
DOM 查询 | getElementsByTagName() 返回值 |
{ 0:'a', 1:'b', length:2 } |
手动构造 | 符合规范的类数组 |
类数组转真数组
javascript
// 方式一:Array.from()(ES6,推荐)
var realArr = Array.from('hello'); // ['h','e','l','l','o']
var realArr2 = Array.from(arguments);
// 方式二:扩展运算符(ES6)
var realArr3 = [...'hello']; // ['h','e','l','l','o']
// 方式三:Array.prototype.slice(ES5 兼容)
var realArr4 = Array.prototype.slice.call(arguments);
完整可运行示例
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>字符串的数组特性</title>
<style>
body { font-family: Arial, sans-serif; background: #fafaf9; padding: 24px; }
h2 { color: #292524; }
.card { background: #fff; border-radius: 10px; padding: 20px; margin: 12px 0;
box-shadow: 0 2px 10px rgba(0,0,0,.07); border-left: 4px solid #a78bfa; }
.card h3 { color: #7c3aed; margin: 0 0 10px; font-size: 15px; }
pre { background: #1c1917; color: #d4d4d8; border-radius: 6px; padding: 12px;
margin: 0; font-size: 13px; white-space: pre-wrap; }
.chars { display: flex; gap: 4px; flex-wrap: wrap; margin-top: 10px; }
.char-box { width: 32px; height: 32px; background: #7c3aed; color: #fff;
border-radius: 4px; display: flex; align-items: center;
justify-content: center; font-size: 14px; font-weight: bold; }
.index-box { width: 32px; height: 20px; background: #ede9fe; color: #6d28d9;
border-radius: 4px; display: flex; align-items: center;
justify-content: center; font-size: 11px; }
.char-col { display: flex; flex-direction: column; align-items: center; gap: 2px; }
</style>
</head>
<body>
<h2>字符串的数组特性与类数组</h2>
<div class="card">
<h3>字符串索引访问</h3>
<div class="chars" id="charViz"></div>
<pre id="strDemo"></pre>
</div>
<div class="card">
<h3>字符串"只读"特性(无法修改单个字符)</h3>
<pre id="readonlyDemo"></pre>
</div>
<div class="card">
<h3>类数组对象 → 真数组的转换</h3>
<pre id="convertDemo"></pre>
</div>
<script>
var msg = 'Hello JS!';
// 字符可视化
var charViz = document.getElementById('charViz');
for (var i = 0; i < msg.length; i++) {
var col = document.createElement('div');
col.className = 'char-col';
col.innerHTML = '<div class="char-box">' + msg[i] + '</div>' +
'<div class="index-box">' + i + '</div>';
charViz.appendChild(col);
}
// 属性读取演示
document.getElementById('strDemo').textContent = [
'msg = "' + msg + '"',
'msg.length → ' + msg.length,
'msg[0] → ' + msg[0],
'msg[6] → ' + msg[6],
'msg[msg.length-1] → ' + msg[msg.length - 1],
].join('\n');
// 只读演示
var s = 'Hello';
s[0] = 'h'; // 静默失败
s.length = 2; // 无效
document.getElementById('readonlyDemo').textContent = [
'原字符串: "Hello"',
'执行 s[0] = "h" → 静默失败,s 仍为: "' + s + '"',
'执行 s.length = 2 → 无效,length 仍为: ' + s.length,
'',
'⚠️ 在严格模式(use strict)下,以上操作会抛出 TypeError'
].join('\n');
// 转换演示
var str = 'hello';
var arr1 = Array.from(str);
var arr2 = str.split('');
document.getElementById('convertDemo').textContent = [
'字符串: "' + str + '"',
'',
'Array.from("hello") → ' + JSON.stringify(arr1),
'[..."hello"] → ' + JSON.stringify([...str]),
'"hello".split("") → ' + JSON.stringify(arr2),
'',
'转为数组后可使用数组方法:',
'Array.from("hello").reverse().join("") → "' + Array.from(str).reverse().join('') + '"'
].join('\n');
</script>
</body>
</html>

代码解析
行 / 片段 说明 for (var i = 0; i < msg.length; i++)用 length控制循环次数,遍历每个字符并动态创建 DOM 节点,形成可视化展示msg[0]字符串支持方括号索引语法,但返回的是只读字符,不能 通过 msg[0] = 'h'修改s[0] = 'h'赋值静默失败(非严格模式),字符串是不可变值类型,修改不生效 s.length = 2同样静默失败,字符串的 length属性是只读的,无法通过赋值截断字符串Array.from(str)将字符串转换为真正的字符数组,每个字符成为一个元素,之后可使用数组的全部方法 [...str]扩展运算符语法,效果与 Array.from(str)等价,但更简洁,正确处理 Unicode 代理对.reverse().join('')reverse()就地反转数组(修改原数组),join('')将字符数组拼回字符串,两者链式调用实现字符串翻转注意 Unicode:
Array.from('👋hello')能正确处理 emoji 等多字节字符(返回['👋','h','e','l','l','o']),而'👋hello'.split('')会把 emoji 拆成两个乱码字符。
数组方法全景速查
改变原数组的方法(Mutating Methods)
在 React/Vue 等框架的状态管理中,这些方法需要谨慎使用,因为它们会修改原数组,可能导致视图更新不触发。
改变原数组
末尾操作
push 添加一或多个
pop 删除并返回
头部操作
unshift 添加一或多个
shift 删除并返回
排序
sort 原地排序
reverse 原地反转
万能
splice 增删改
填充
fill ES6 填充
copyWithin ES6 内部复制
不改变原数组的方法(Non-Mutating Methods)
不改变原数组
查找
indexOf 正向查找索引
lastIndexOf 反向查找索引
find ES6 找元素
findIndex ES6 找索引
includes ES7 是否包含
转换
map 映射新数组
filter 过滤新数组
flat ES2019 展平
flatMap 映射+展平
聚合
reduce 左归约
reduceRight 右归约
判断
some 任一满足
every 全部满足
其他
slice 切片
concat 合并
join 转字符串
forEach 仅遍历
深入原理:V8 引擎如何优化数组
本节内容来自 V8 官方博客 Elements kinds in V8,帮助理解 JavaScript 数组在底层的运作机制,从而写出更高性能的代码。
名词解释(底层术语)
| 术语 | 英文 | 定义 |
|---|---|---|
| 元素种类 | Elements Kind | V8 内部跟踪每个数组存储的数据类型,据此选择最优的内部表示方式 |
| 密集数组 | Packed Array | 所有索引位都有实际值的数组,可被 V8 高度优化 |
| 多孔数组 | Holey Array | 含有空槽(holes)的数组,需要额外检查,性能较低 |
| SMI 元素 | Small Integer | 31/32 位的小整数,V8 可直接以机器整数存储,无需装箱 |
| 装箱/拆箱 | Boxing/Unboxing | 将基本类型包装成对象(装箱),或从对象取出基本值(拆箱) |
| 字典模式 | Dictionary Elements | 极稀疏数组的降级存储方式,用哈希表存储,速度最慢 |
V8 Elements Kind 层次结构
V8 内部维护了 21 种 Elements Kind,以下是最常见的层级,从快到慢依次降级,且降级不可逆:
加入浮点数
加入字符串/对象
产生空槽
产生空槽
产生空槽
超级稀疏
PACKED_SMI_ELEMENTS
⚡ 最快
连续小整数,直接机器整数存储
PACKED_DOUBLE_ELEMENTS
⚡ 快
连续浮点数,64位非装箱存储
PACKED_ELEMENTS
🟡 中等
连续混合类型,需类型检查
HOLEY_SMI_ELEMENTS
🟠 较慢
含空槽的小整数数组
HOLEY_DOUBLE_ELEMENTS
🟠 较慢
含空槽的浮点数组
HOLEY_ELEMENTS
🔴 慢
含空槽的混合类型数组
DICTIONARY_ELEMENTS
🔴🔴 最慢
极稀疏,哈希表存储
关键结论:降级是单向的,不可逆! 一旦数组从
PACKED_SMI_ELEMENTS降级到HOLEY_ELEMENTS,即使你填满所有空槽,它在 V8 内部永远是 HOLEY,无法恢复到 PACKED。
降级演示
javascript
// ① 初始:PACKED_SMI_ELEMENTS(最优状态)
var arr = [1, 2, 3];
// ② 加入浮点数 → 降级到 PACKED_DOUBLE_ELEMENTS
arr.push(4.5);
// ③ 加入字符串 → 降级到 PACKED_ELEMENTS
arr.push('hello');
// ④ 产生空槽 → 降级到 HOLEY_ELEMENTS(且不可逆)
arr[10] = 99;
// ⑤ 填满空槽也无法恢复 PACKED
for (var i = 5; i < 10; i++) arr[i] = 0;
// arr 内部仍然是 HOLEY_ELEMENTS!
性能影响:空槽的代价
当 V8 访问 HOLEY 数组中的某个索引时,必须按以下流程检查:
否
是
有
空槽
未找到
找到
arr[i] 访问
索引在 length 范围内?
返回 undefined
该位置
有实际值?
直接返回值 ⚡
沿原型链查找
arr.proto[i]?
返回 undefined
返回原型链上的值
PACKED 数组在第一步找到值就直接返回,HOLEY 数组每次访问都需要额外的原型链查找,带来不可忽视的性能开销。
高性能数组实践指南
❌ 应当避免
Array(n) 创建空数组
再逐个赋值
用 delete 删除元素
留下空槽
跨索引赋值
arr[100] = x
混合存放大量不同类型
(破坏类型一致性)
✅ 正确做法
用字面量创建密集数组
1, 2, 3
用 push() 追加元素
用 splice() 删除元素
保持连续性
用 Array.from({length:n}, fn)
创建填充数组
保持数组元素类型一致
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>V8 数组性能演示</title>
<style>
body { font-family: Arial, sans-serif; background: #0f172a; color: #e2e8f0; padding: 24px; }
h2 { color: #38bdf8; }
.grid { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; }
.card { border-radius: 10px; padding: 18px; }
.good { background: #052e16; border: 1px solid #22c55e; }
.bad { background: #2d0a0a; border: 1px solid #ef4444; }
.card h3 { margin: 0 0 10px; font-size: 14px; }
.good h3 { color: #4ade80; }
.bad h3 { color: #f87171; }
pre { margin: 0; font-size: 12px; color: #a5f3fc; white-space: pre-wrap; }
.result { margin-top: 10px; padding: 8px; background: rgba(255,255,255,.05);
border-radius: 6px; font-family: monospace; font-size: 12px; color: #fbbf24; }
</style>
</head>
<body>
<h2>V8 数组优化:正确 vs 错误创建方式</h2>
<div class="grid">
<div class="card good">
<h3>✅ 密集数组(PACKED)--- 高性能</h3>
<pre>// 方式一:字面量(最优)
var arr1 = [1, 2, 3, 4, 5];
// 方式二:push 逐步添加
var arr2 = [];
for (var i = 1; i <= 5; i++) arr2.push(i);
// 方式三:Array.from 填充(ES6)
var arr3 = Array.from({length: 5}, function(_, i) {
return i + 1;
});</pre>
<div class="result" id="goodResult"></div>
</div>
<div class="card bad">
<h3>❌ 稀疏数组(HOLEY)--- 性能损耗</h3>
<pre>// 陷阱一:Array(n) 构造 → HOLEY
var bad1 = Array(5);
// bad1 = [空×5],永久 HOLEY
// 陷阱二:delete 操作 → 留空槽
var bad2 = [1, 2, 3, 4, 5];
delete bad2[2];
// bad2 = [1, 2, 空, 4, 5],变为 HOLEY
// 陷阱三:跳跃赋值
var bad3 = [];
bad3[0] = 1;
bad3[4] = 5; // 索引1-3为空槽</pre>
<div class="result" id="badResult"></div>
</div>
</div>
<script>
// 正确做法演示
var arr1 = [1, 2, 3, 4, 5];
var arr2 = [];
for (var i = 1; i <= 5; i++) arr2.push(i);
var arr3 = Array.from({length: 5}, function(_, i) { return i + 1; });
document.getElementById('goodResult').textContent =
'字面量: ' + JSON.stringify(arr1) + '\n' +
'push方式: ' + JSON.stringify(arr2) + '\n' +
'Array.from: ' + JSON.stringify(arr3) + '\n' +
'✅ 以上均为 PACKED 密集数组,V8 可高度优化';
// 错误做法演示
var bad1 = Array(5);
var bad2 = [1, 2, 3, 4, 5];
delete bad2[2];
var bad3 = [];
bad3[0] = 1;
bad3[4] = 5;
document.getElementById('badResult').textContent =
'Array(5): length=' + bad1.length + ' 内容:' + JSON.stringify(bad1) + '\n' +
'delete后: ' + JSON.stringify(bad2) + '\n' +
'0 in bad2[2]? ' + (2 in bad2) + ' ← false=空槽\n' +
'跳跃赋值: ' + JSON.stringify(bad3) + '\n' +
'⚠️ 以上均为 HOLEY 数组,性能低于 PACKED';
</script>
</body>
</html>

代码解析
行 / 片段 说明 Array.from({length: 5}, function(_, i) { return i + 1; })第一参数 {length:5}是类数组对象;第二参数是映射函数,_表示忽略第一个参数(当前值),i是索引,结果为[1,2,3,4,5]Array(5)只传一个数字时创建长度为 5 的空数组 ,V8 内部标记为 HOLEY_SMI_ELEMENTS,此后永远是 HOLEY 状态delete bad2[2]delete运算符从对象中删除属性,应用于数组时留下空槽而非移动后续元素,是常见性能陷阱2 in bad2→falsedelete之后该索引真正不存在,in运算符返回false,而访问bad2[2]仍返回undefinedbad3[4] = 5(跳跃赋值)跨越中间索引赋值,索引 1~3 成为空槽, length自动变为 5JSON.stringify(bad2)中nullJSON.stringify将空槽序列化为null,这是 JSON 规范对缺失值的表示方式黄金法则: 一旦数组降级为 HOLEY,就算之后填满所有空槽,V8 内部依然保持 HOLEY 状态,降级不可逆。应从创建时就保证密集。
V8 优化总结一览表
| 操作 | 数组状态 | Elements Kind | 性能 |
|---|---|---|---|
[1, 2, 3] |
PACKED | SMI | ⚡⚡⚡ 最快 |
[1, 2, 3.5] |
PACKED | DOUBLE | ⚡⚡ 快 |
[1, 'a', true] |
PACKED | ELEMENTS | ⚡ 中 |
Array(5) |
HOLEY | SMI | 🟠 较慢 |
delete arr[i] |
HOLEY | ELEMENTS | 🔴 慢 |
arr[1000] = x (稀疏) |
DICTIONARY | --- | 🔴🔴 最慢 |
函数(Function)
什么是函数
MDN 官方定义: 函数是 JavaScript 的基本构建块之一。函数是一段 JavaScript 过程,执行特定任务或计算值。要使用某个函数,你必须在某个作用域中定义它。
名词解释
| 术语 | 英文 | 定义 |
|---|---|---|
| 函数 | Function | 封装了一段可重复调用代码的特殊对象 |
| 函数名 | Function Name | 标识函数的变量名,本质是一个持有函数引用的变量 |
| 函数体 | Function Body | {} 内的代码块,函数被调用时执行 |
| 形参 | Parameter | 定义函数时声明的参数变量,是占位符 |
| 实参 | Argument | 调用函数时传入的实际值 |
| 返回值 | Return Value | 函数执行结束后输出的值,由 return 指定 |
| 调用 | Call / Invoke | 执行函数,使用 函数名() 语法 |
| 作用域 | Scope | 变量可被访问的代码区域 |
函数的本质
函数(Function)
数据类型的一种
typeof function → 'function'
对象类型(Object)的子类型
具有属性和方法
可被赋值给变量
可作为参数传递
可作为返回值
具有自己的作用域
创建闭包的基础
函数是一等公民
First-class Function
一等公民(First-class Function):在编程语言中,函数可以像其他数据类型一样被赋值、传递和返回,就称函数是"一等公民"。这是 JavaScript 函数式编程的基础。
函数的组成
function add(a, b) {
return a + b;
}
函数名: add
本质是变量
形参: a, b
占位符变量
函数体: { return a + b; }
实际执行的代码
返回值: a + b
函数调用表达式的值
创建函数的四种方式
创建函数的方式
① function 关键字
函数声明(声明提升)
② 函数表达式
赋值给变量(不提升)
③ Function() 函数
动态创建(性能差)
④ new Function()
构造函数方式(同③)
⑤ 箭头函数 ES6
=> 语法(无this绑定)
推荐用于命名函数
可在定义前调用
推荐用于函数表达式
赋值后才能调用
了解即可
每次执行都重新解析字符串
了解即可
与③行为相同
ES6推荐
简洁,无独立this
① function 关键字方式(函数声明)
这是最经典的创建函数的方式,具有**函数提升(Hoisting)**特性。
javascript
// 即使在声明之前调用也可以(因为提升)
sayHello('Alice'); // 可以正常执行!
function sayHello(name) {
console.log('Hello, ' + name + '!');
}
// 有参数和返回值的函数
function multiply(a, b) {
return a * b;
}
console.log(multiply(6, 7)); // 42
② 函数表达式方式
函数被赋值给一个变量,不具备提升特性,必须先定义再调用。
javascript
// 调用必须在定义之后!
var greet = function(name) {
console.log('Hi, ' + name + '!');
};
greet('Bob');
// 有返回值
var square = function(n) {
return n * n;
};
console.log(square(9)); // 81
③ Function() 函数方式(了解)
通过字符串参数动态构建函数,性能较差,实际开发极少使用。
javascript
var add = Function('a', 'b', 'return a + b;');
console.log(add(3, 4)); // 7
// 等价写法(只有一个函数体参数时)
var sayHi = Function('console.log("Hi!")');
sayHi(); // Hi!
④ new Function() 构造函数方式(了解)
与方式③完全等价,new 关键字可加可不加。
javascript
var multiply = new Function('x', 'y', 'return x * y;');
console.log(multiply(5, 6)); // 30
⑤ 箭头函数(ES6,推荐)
javascript
// 单参数,单返回值 ------ 最简洁
var double = n => n * 2;
// 多参数
var add = (a, b) => a + b;
// 多行函数体
var greet = (name, age) => {
var msg = 'Hello ' + name + ', you are ' + age;
return msg;
};
四种方式核心差异
| 特性 | function 声明 | 函数表达式 | Function() | 箭头函数 |
|---|---|---|---|---|
| 函数提升 | ✅ | ❌ | ❌ | ❌ |
this 绑定 |
动态 | 动态 | 动态 | 词法(继承外层) |
arguments 对象 |
✅ | ✅ | ✅ | ❌ |
| 可用作构造函数 | ✅ | ✅ | ✅ | ❌ |
| 语法简洁度 | 中 | 中 | 低 | 高 |
| 推荐程度 | ✅✅ | ✅✅ | ⚠️ | ✅✅✅ |
完整可运行示例
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>创建函数的四种方式</title>
<style>
* { box-sizing: border-box; }
body { font-family: Arial, sans-serif; background: #f0fdf4; padding: 24px; }
h2 { color: #14532d; }
.grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); gap: 16px; }
.card { background: #fff; border-radius: 10px; padding: 20px;
box-shadow: 0 2px 10px rgba(0,0,0,.07); border-top: 4px solid #22c55e; }
.card h3 { color: #166534; margin: 0 0 12px; font-size: 14px; }
pre { background: #052e16; color: #bbf7d0; border-radius: 6px; padding: 12px;
margin: 0; font-size: 12px; white-space: pre-wrap; }
.tag { display: inline-block; background: #dcfce7; color: #166534; border-radius: 4px;
padding: 2px 8px; font-size: 11px; margin-bottom: 8px; }
.result { margin-top: 10px; padding: 8px 12px; background: #f0fdf4;
border-radius: 6px; font-size: 13px; color: #15803d; font-family: monospace; }
</style>
</head>
<body>
<h2>创建函数的四种方式</h2>
<div class="grid">
<div class="card">
<div class="tag">✅ 推荐</div>
<h3>① function 关键字(函数声明)</h3>
<pre>// 提升:可在声明前调用
function add(a, b) {
return a + b;
}
function greet(name) {
return 'Hello, ' + name + '!';
}</pre>
<div class="result" id="r1"></div>
</div>
<div class="card">
<div class="tag">✅ 推荐</div>
<h3>② 函数表达式</h3>
<pre>// 无提升:必须先定义后调用
var multiply = function(a, b) {
return a * b;
};
var describe = function(name, age) {
return name + ' 今年 ' + age + ' 岁';
};</pre>
<div class="result" id="r2"></div>
</div>
<div class="card">
<div class="tag">⚠️ 了解</div>
<h3>③ Function() 函数</h3>
<pre>// 参数为字符串,动态构建函数
var sub = Function('a', 'b',
'return a - b;'
);
var sayHi = Function(
'name',
'return "Hi " + name + "!"'
);</pre>
<div class="result" id="r3"></div>
</div>
<div class="card">
<div class="tag">⚠️ 了解</div>
<h3>④ new Function() 构造函数</h3>
<pre>// 与③完全等价
var power = new Function(
'base', 'exp',
'var r=1; for(var i=0;i<exp;i++)' +
'r*=base; return r;'
);</pre>
<div class="result" id="r4"></div>
</div>
</div>
<script>
// ① function 声明
function add(a, b) { return a + b; }
function greet(name) { return 'Hello, ' + name + '!'; }
document.getElementById('r1').textContent =
'add(10, 25) = ' + add(10, 25) + '\n' +
greet('Alice');
// ② 函数表达式
var multiply = function(a, b) { return a * b; };
var describe = function(name, age) { return name + ' 今年 ' + age + ' 岁'; };
document.getElementById('r2').textContent =
'multiply(7, 8) = ' + multiply(7, 8) + '\n' +
describe('Bob', 25);
// ③ Function()
var sub = Function('a', 'b', 'return a - b;');
var sayHi = Function('name', 'return "Hi " + name + "!"');
document.getElementById('r3').textContent =
'sub(100, 37) = ' + sub(100, 37) + '\n' +
sayHi('Carol');
// ④ new Function()
var power = new Function(
'base', 'exp',
'var r=1; for(var i=0;i<exp;i++) r*=base; return r;'
);
document.getElementById('r4').textContent =
'power(2, 10) = ' + power(2, 10) + '\n' +
'power(3, 4) = ' + power(3, 4);
</script>
</body>
</html>

代码解析
行 / 片段 说明 function add(a, b) { return a + b; }函数声明 语法, function关键字开头,存在提升,可以在文件任何位置定义var multiply = function(a, b) {...}函数表达式语法,将匿名函数赋给变量,不提升,必须先定义后调用 Function('a', 'b', 'return a - b;')Function构造器接收字符串参数,最后一个字符串为函数体,其余为参数名。每次调用都会重新解析字符串,性能差new Function('base', 'exp', '...')与 Function(...)完全等价,new可省略;函数体字符串中可拼接任意代码,存在代码注入风险'var r=1; for(var i=0;i<exp;i++) r*=base; return r;'函数体以字符串形式传入,注意 <符号不需要转义(字符串中直接写即可),但在 HTML 属性或 innerHTML 中需转义power(2, 10)调用动态创建的函数,结果为 2¹⁰ = 1024 安全提示:
Function()构造器与eval()类似,能执行任意字符串代码,在接受用户输入的场景下极其危险,会导致代码注入(Code Injection)攻击。生产代码中绝对禁止将用户输入传给Function()。
函数调用与返回值
调用流程
调用栈 函数 调用方 调用栈 函数 调用方 形参接收实参值 执行函数体代码 函数调用表达式的值 = 返回值 压入栈帧(保存当前上下文) 调用 func(实参) 遇到 return 或执行完毕 弹出栈帧(恢复上下文)
return 语句的行为
是
否
函数开始执行
遇到 return?
立即终止执行
将 return 后的值
作为函数返回值
继续执行函数体
执行到函数末尾
返回 undefined
(隐式)
函数调用表达式得到该值
关键点:
return不仅返回值,还会立即终止函数后续代码的执行。这可用于提前退出(Early Return 模式)。
完整可运行示例
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>函数调用与返回值</title>
<style>
body { font-family: Arial, sans-serif; background: #fef9c3; padding: 24px; }
h2 { color: #713f12; }
.card { background: #fff; border-radius: 10px; padding: 20px; margin: 14px 0;
box-shadow: 0 2px 10px rgba(0,0,0,.07); }
.card h3 { color: #ca8a04; margin: 0 0 10px; }
pre { background: #1c1917; color: #fde68a; border-radius: 6px; padding: 12px;
margin: 0; font-size: 13px; white-space: pre-wrap; }
.inline-demo { display: flex; gap: 12px; flex-wrap: wrap; margin-top: 10px; }
input { border: 2px solid #fbbf24; border-radius: 6px; padding: 8px 12px;
font-size: 14px; width: 100px; }
button { background: #f59e0b; color: #fff; border: none; border-radius: 6px;
padding: 8px 16px; cursor: pointer; font-size: 14px; }
button:hover { background: #d97706; }
.output { margin-top: 10px; padding: 10px; background: #fef3c7;
border-radius: 6px; font-family: monospace; color: #78350f; }
</style>
</head>
<body>
<h2>函数调用与返回值</h2>
<div class="card">
<h3>① 无返回值(隐式返回 undefined)</h3>
<pre>function printInfo(name, age) {
console.log(name + ' 今年 ' + age + ' 岁');
// 没有 return ------ 隐式返回 undefined
}
var result = printInfo('Alice', 25);
console.log(result); // undefined</pre>
<div class="output" id="o1"></div>
</div>
<div class="card">
<h3>② 有返回值</h3>
<div class="inline-demo">
<input type="number" id="numA" value="12" placeholder="数字A">
<input type="number" id="numB" value="8" placeholder="数字B">
<button onclick="calcAll()">计算全部</button>
</div>
<div class="output" id="o2">点击按钮查看结果</div>
</div>
<div class="card">
<h3>③ return 提前终止(Early Return 模式)</h3>
<pre id="o3"></pre>
</div>
<script>
// ① 无返回值
function printInfo(name, age) {
return name + ' 今年 ' + age + ' 岁';
}
var result = printInfo('Alice', 25);
document.getElementById('o1').textContent =
'printInfo("Alice", 25) 的返回值 = "' + result + '"';
// ② 有返回值
function calcAll() {
var a = parseFloat(document.getElementById('numA').value);
var b = parseFloat(document.getElementById('numB').value);
function add(x, y) { return x + y; }
function sub(x, y) { return x - y; }
function mul(x, y) { return x * y; }
function div(x, y) { return y === 0 ? '不能除以零' : x / y; }
document.getElementById('o2').textContent = [
a + ' + ' + b + ' = ' + add(a, b),
a + ' - ' + b + ' = ' + sub(a, b),
a + ' × ' + b + ' = ' + mul(a, b),
a + ' ÷ ' + b + ' = ' + div(a, b)
].join('\n');
}
// ③ Early Return
function getGrade(score) {
if (score < 0 || score > 100) return '分数无效';
if (score >= 90) return 'A(优秀)';
if (score >= 75) return 'B(良好)';
if (score >= 60) return 'C(及格)';
return 'D(不及格)';
}
var testScores = [105, 95, 82, 67, 45, -5];
var gradeLines = testScores.map(function(s) {
return '分数 ' + s + ' → ' + getGrade(s);
});
document.getElementById('o3').textContent = gradeLines.join('\n');
</script>
</body>
</html>
代码解析
行 / 片段 说明 return name + ' 今年 ' + age + ' 岁'函数有返回值时,调用表达式的值就是返回的字符串;没有 return时隐式返回undefinedfunction div(x, y) { return y === 0 ? '...' : x / y; }守卫检查(Guard Clause):在执行核心逻辑前先验证输入合法性,防止除以零等运行时错误 parseFloat(document.getElementById('numA').value)input元素的value属性始终是字符串 ,必须用parseFloat转为数字才能做算术运算`if (score < 0 if (score >= 90) return 'A...'每个 return执行后函数立即终止 ,后续代码不再执行,因此不需要elsetestScores.map(function(s) { return '分数 ' + s + ' → ' + getGrade(s); })将 map与具名函数结合:把getGrade作为映射逻辑,将分数数组转为评级描述数组Early Return 的价值: 与"箭头形代码"(不断嵌套 if-else)相比,Early Return 使代码层级更浅、更易读,是现代 JavaScript 开发的主流风格。
