DAY_21JavaScript 深度解析:数组(Array)与函数(Function)(一)
作者按: 本文以 ECMAScript 官方规范(MDN Web Docs)、V8 官方博客(v8.dev)为基础,融合掘金、腾讯云开发者社区等技术社区的精华内容,对 JavaScript 中最核心的两大结构——数组(Array) 与 函数(Function)——进行系统、深度的梳理。内容覆盖:基础概念 → 内存模型 → V8 引擎底层优化 → 函数提升与执行上下文 → arguments 对象 → 函数式编程入门,并附大量完整可运行示例与 Mermaid 图解。力求让每一位读者从"会用"进化到"真正理解"。
目录
- 前置:流程控制语句回顾
- 数组(Array)
- 函数(Function)
- 经典练习题解析
- 综合应用案例
- 知识总结与脑图
- 附:练习题完整答案
- 理论深化:数组与函数的本质认知
- 经典业务场景全解析
- 业务价值:为什么这两者是前端开发的基石
前置:流程控制语句回顾
在正式进入数组与函数之前,先对已学过的流程控制做一次系统梳理——它们是操作数组时最常用的工具。
名词解释
| 术语 | 英文 | 定义 |
|---|---|---|
| 循环语句 | Loop Statement | 在满足条件的情况下,重复执行某段代码块的语句 |
| 跳转语句 | Jump Statement | 改变程序正常顺序执行流程的语句 |
| 迭代 | Iteration | 每一次对过程的重复执行,常用于遍历集合 |
| break | — | 立即终止最近的一层循环或 switch 语句 |
| continue | — | 跳过当前迭代,继续下一次循环 |
三种循环的对比
循环执行流程图
break 与 continue 的核心区别
经典案例:while 实现九九乘法表
此案例来自课堂练习,展示循环语句在实际中的应用。
<!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 中的数组本质上是一个对象:
typeof []; // "object"
[] instanceof Array; // true
Array.isArray([]); // true ← 推荐的判断方式
数组与普通对象的差异在于:
- 数组的属性名(键)是非负整数(索引)
- 数组有自动维护的
length属性 - 数组继承了
Array.prototype上大量便利方法
数组的物理结构
核心特性速记:
- 索引从 0 开始,最大为 2³²- 2(约 42 亿)
length始终等于最大索引 + 1(不是元素数量!稀疏数组时有区别)- 元素可以是任意类型,同一个数组里可以混放数字、字符串、布尔、对象、函数
- 数组是引用类型,赋值操作传递的是引用而非值的副本
数组的内存模型
理解数组在内存中的存储方式,有助于避免常见的引用陷阱。
数组的物理结构
说明: 上图用标准
flowchart表达「下标 → 槽位里的值」的一一对应关系。若你使用的工具较旧,可将 Mermaid 升级到 v10+;block-beta等实验语法在不少渲染器中仍会失败,故此处已改为通用写法。
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()。
创建数组的三种方式
① 字面量方式(推荐)
这是最简洁、最无歧义的创建方式,也是官方推荐的写法。
// 空数组
var arr1 = [];
// 混合类型的数组(JS 允许不同类型共存)
var arr2 = [100, 45.101, 'hello', false, null, undefined, [100, 200, 300]];
// 直接用字面量访问元素(无需赋值给变量)
[100, 200, 300, 400, 500][3]; // 400
② Array 函数方式
Array(); // [] 空数组
Array(200, 300, 'Hello', [10, 20]); // 4个元素的数组
Array(17); // [空×17] ← 陷阱!单数字参数=指定长度
Array('hello'); // ['hello'] ← 字符串参数=正常元素
③ new Array 构造函数方式
与 Array() 函数行为完全一致,new 关键字可省略但通常保留以表语义清晰。
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()彻底解决了单数字参数的二义性问题:Array.of(7); // [7] —— 永远是包含该元素的数组,无歧义 Array(7); // [空×7] —— 长度为7的空数组
完整可运行示例
<!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),可同时完成创建和填充。
数组元素的读写
语法
// 读取
数组[索引] // 返回该位置的值,不存在则返回 undefined
// 写入/修改
数组[索引] = 新值; // 修改已有元素,或在新位置添加元素
索引访问的底层原理
多维数组访问
var matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]];
matrix[1][2]; // 6 —— 第二行,第三列
完整可运行示例
<!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。
产生稀疏数组的三种途径
空槽 vs undefined 的微妙差别
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
工程实践警告
稀疏数组完整示例
<!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 循环 |
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 的危险陷阱
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 自定义属性 ← 多出了属性!
遍历数组完整示例
<!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,工程中使用频率极高。
方法分类总览
各方法性能特点
返回值说明
| 方法 | 返回值 |
|---|---|
push() |
新数组的 length |
pop() |
被删除的那个元素 |
unshift() |
新数组的 length |
shift() |
被删除的那个元素 |
splice() |
被删除元素组成的新数组 |
完整可运行示例
<!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):二维数组,通常用于表示表格、棋盘、图像像素等具有行列结构的数据。
多维数组内存结构
工程实战:渲染数据表格
多维数组在前端开发中极为常见——电商的商品分类、日历的日期网格、数据仪表盘的报表,本质上都是二维数据的渲染。
<!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; padding: 24px; }
h2 { color: #1e293b; }
.container { display: flex; gap: 24px; flex-wrap: wrap; }
.panel { background: #fff; border-radius: 10px; padding: 20px; flex: 1;
min-width: 300px; box-shadow: 0 2px 12px rgba(0,0,0,.08); }
.panel h3 { color: #6366f1; margin: 0 0 12px; }
table { width: 100%; border-collapse: collapse; }
th { background: #6366f1; color: #fff; padding: 10px 12px; text-align: left; font-size: 13px; }
td { padding: 9px 12px; border-bottom: 1px solid #e2e8f0; font-size: 13px; }
tr:last-child td { border-bottom: none; }
tr:nth-child(even) td { background: #f8fafc; }
tr:hover td { background: #ede9fe; }
.score-high { color: #16a34a; font-weight: bold; }
.score-mid { color: #d97706; font-weight: bold; }
.score-low { color: #dc2626; font-weight: bold; }
.matrix-wrap { display: grid; gap: 2px; }
.matrix-cell { background: #ddd6fe; color: #3730a3; padding: 8px;
text-align: center; border-radius: 4px; font-size: 12px; font-weight: bold; }
.matrix-cell.highlight { background: #6366f1; color: #fff; }
</style>
</head>
<body>
<h2>多维数组工程应用</h2>
<div class="container">
<div class="panel">
<h3>学生成绩表(二维数组 → HTML 表格)</h3>
<table id="scoreTable"></table>
</div>
<div class="panel">
<h3>乘法矩阵(嵌套循环生成)</h3>
<div id="matrixWrap"></div>
</div>
</div>
<script>
// ── 成绩表 ──
var headers = ['姓名', '数学', '英语', '编程', '平均'];
var students = [
['Alice', 95, 88, 97],
['Bob', 72, 65, 83],
['Carol', 88, 91, 92],
['Dave', 58, 70, 61],
['Eve', 99, 96, 98]
];
// 计算平均值并添加到每行
var tableData = students.map(function(row) {
var scores = [row[1], row[2], row[3]];
var avg = (scores.reduce(function(s, v) { return s + v; }, 0) / scores.length).toFixed(1);
return row.concat([avg]);
});
var table = document.getElementById('scoreTable');
var thead = '<tr>' + headers.map(function(h) { return '<th>' + h + '</th>'; }).join('') + '</tr>';
var tbody = tableData.map(function(row) {
return '<tr>' + row.map(function(cell, i) {
if (i === 0) return '<td>' + cell + '</td>';
var cls = '';
var n = parseFloat(cell);
if (n >= 90) cls = 'score-high';
else if (n >= 70) cls = 'score-mid';
else cls = 'score-low';
return '<td class="' + cls + '">' + cell + '</td>';
}).join('') + '</tr>';
}).join('');
table.innerHTML = thead + tbody;
// ── 乘法矩阵 ──
var size = 5;
var matrix = [];
for (var i = 1; i <= size; i++) {
var row = [];
for (var j = 1; j <= size; j++) {
row.push(i * j);
}
matrix.push(row);
}
var wrap = document.getElementById('matrixWrap');
wrap.style.gridTemplateColumns = 'repeat(' + size + ', 1fr)';
wrap.className = 'matrix-wrap';
matrix.forEach(function(row, ri) {
row.forEach(function(val, ci) {
var cell = document.createElement('div');
cell.className = 'matrix-cell' + (ri === ci ? ' highlight' : '');
cell.textContent = val;
wrap.appendChild(cell);
});
});
</script>
</body>
</html>

代码解析
行 / 片段 说明 students.map(function(row) { ... row.concat([avg]) })map对每行学生数据处理:计算平均值后用concat在末尾追加,concat不修改原数组scores.reduce(function(s, v) { return s + v; }, 0)reduce累加器:初始值0,每次将当前值v加到累加器s上,最终返回总和(total / scores.length).toFixed(1)toFixed(1)保留 1 位小数并转为字符串,注意返回的是string类型headers.map(h => '<th>' + h + '</th>').join('')将表头数组转换为 HTML 字符串片段, join('')合并无间隔ri === ci ? ' highlight' : ''三元运算符:行索引等于列索引时(对角线元素)添加高亮 class wrap.style.gridTemplateColumns = 'repeat(' + size + ', 1fr)'动态设置 CSS Grid 列数, 1fr表示等分剩余空间嵌套 forEach(function(row, ri) { row.forEach(function(val, ci) {...}) })外层遍历行,内层遍历列,这是访问二维数组所有元素的标准双重循环写法 多维数组工程场景: 电商商品分类树、日历日期格、数据报表、棋盘游戏等都是多维数组的典型应用。访问规则:
matrix[行索引][列索引],即先行后列。
字符串的数组特性与类数组
名词解释
类数组(Array-like / Like-Array):具有
length属性和以数字为键的属性,但不继承自Array.prototype的对象,因此无法使用数组方法(如push、map等)。
字符串与数组的对比
常见的类数组对象
| 类数组对象 | 来源 | 说明 |
|---|---|---|
String |
字符串 | 字符的类数组 |
arguments |
函数内部 | 函数所有实参的集合 |
NodeList |
DOM 查询 | querySelectorAll() 返回值 |
HTMLCollection |
DOM 查询 | getElementsByTagName() 返回值 |
{ 0:'a', 1:'b', length:2 } |
手动构造 | 符合规范的类数组 |
类数组转真数组
// 方式一: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);
完整可运行示例
<!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 等框架的状态管理中,这些方法需要谨慎使用,因为它们会修改原数组,可能导致视图更新不触发。
不改变原数组的方法(Non-Mutating Methods)
深入原理: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降级到HOLEY_ELEMENTS,即使你填满所有空槽,它在 V8 内部永远是 HOLEY,无法恢复到 PACKED。
降级演示
// ① 初始: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 数组中的某个索引时,必须按以下流程检查:
PACKED 数组在第一步找到值就直接返回,HOLEY 数组每次访问都需要额外的原型链查找,带来不可忽视的性能开销。
高性能数组实践指南
<!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 | 变量可被访问的代码区域 |
函数的本质
一等公民(First-class Function):在编程语言中,函数可以像其他数据类型一样被赋值、传递和返回,就称函数是"一等公民"。这是 JavaScript 函数式编程的基础。
函数的组成
创建函数的四种方式
① function 关键字方式(函数声明)
这是最经典的创建函数的方式,具有**函数提升(Hoisting)**特性。
// 即使在声明之前调用也可以(因为提升)
sayHello('Alice'); // 可以正常执行!
function sayHello(name) {
console.log('Hello, ' + name + '!');
}
// 有参数和返回值的函数
function multiply(a, b) {
return a * b;
}
console.log(multiply(6, 7)); // 42
② 函数表达式方式
函数被赋值给一个变量,不具备提升特性,必须先定义再调用。
// 调用必须在定义之后!
var greet = function(name) {
console.log('Hi, ' + name + '!');
};
greet('Bob');
// 有返回值
var square = function(n) {
return n * n;
};
console.log(square(9)); // 81
③ Function() 函数方式(了解)
通过字符串参数动态构建函数,性能较差,实际开发极少使用。
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 关键字可加可不加。
var multiply = new Function('x', 'y', 'return x * y;');
console.log(multiply(5, 6)); // 30
⑤ 箭头函数(ES6,推荐)
// 单参数,单返回值 —— 最简洁
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 对象 |
✅ | ✅ | ✅ | ❌ |
| 可用作构造函数 | ✅ | ✅ | ✅ | ❌ |
| 语法简洁度 | 中 | 中 | 低 | 高 |
| 推荐程度 | ✅✅ | ✅✅ | ⚠️ | ✅✅✅ |
完整可运行示例
<!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()。
函数调用与返回值
调用流程
return 语句的行为
关键点:
return不仅返回值,还会立即终止函数后续代码的执行。这可用于提前退出(Early Return 模式)。
完整可运行示例
<!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 || score > 100) return '分数无效'Early Return(提前返回)模式:遇到不合法情况立即返回,避免后续嵌套 if-else,代码更扁平可读if (score >= 90) return 'A...'每个 return执行后函数立即终止,后续代码不再执行,因此不需要elsetestScores.map(function(s) { return '分数 ' + s + ' → ' + getGrade(s); })将 map与具名函数结合:把getGrade作为映射逻辑,将分数数组转为评级描述数组Early Return 的价值: 与"箭头形代码"(不断嵌套 if-else)相比,Early Return 使代码层级更浅、更易读,是现代 JavaScript 开发的主流风格。

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐



所有评论(0)