DAY_21JavaScript 深度解析:数组(Array)与函数(Function)(一)

作者按: 本文以 ECMAScript 官方规范(MDN Web Docs)、V8 官方博客(v8.dev)为基础,融合掘金、腾讯云开发者社区等技术社区的精华内容,对 JavaScript 中最核心的两大结构------数组(Array)函数(Function)------进行系统、深度的梳理。内容覆盖:基础概念 → 内存模型 → V8 引擎底层优化 → 函数提升与执行上下文 → arguments 对象 → 函数式编程入门,并附大量完整可运行示例与 Mermaid 图解。力求让每一位读者从"会用"进化到"真正理解"。


目录


前置:流程控制语句回顾

在正式进入数组与函数之前,先对已学过的流程控制做一次系统梳理------它们是操作数组时最常用的工具。

名词解释

术语 英文 定义
循环语句 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 到 9
var 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  ← 推荐的判断方式

数组与普通对象的差异在于:

  1. 数组的属性名(键)是非负整数(索引)
  2. 数组有自动维护的 length 属性
  3. 数组继承了 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() 彻底解决了单数字参数的二义性问题:

javascript 复制代码
Array.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.101
arr[2] = '修改了第3个元素' 直接通过索引赋值修改,原值被覆盖,数组 length 不变
arr[7] = '新增的第8个元素' 给超出范围的索引赋值会自动扩展 数组,length 变为 8
JSON.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 自动变为 13
0 in arr2 in 运算符检查属性是否存在于对象 (不检查值),对于真正的空槽返回 false,是区分空槽与 undefined 的可靠方法
arr3.length = 10 直接赋值扩大 length,新增位置 6~9 全变为空槽;反之,缩小 length 会截断并删除末尾元素(不可恢复)
var sparse = [1, , , 4] 连续逗号语法创建稀疏数组,索引 1 和 2 是真正的空槽
sparse.forEach(...) forEach 对空槽的回调不会执行,因此输出只有索引 0 和 3
dense.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头部 插入,所有现有元素索引后移一位,返回新 length
taskArr.splice(2, 0, '插入任务' + counter++) 第二参数 0 表示删除 0 个元素(纯插入),在索引 2 处插入新元素
taskArr.pop() 删除并返回最后一个 元素,length 自动减 1;空数组调用返回 undefined
taskArr.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 表格)

乘法矩阵(嵌套循环生成)

``` ![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/bafaadf5e38b45e98feef62944dd5da4.png) > **代码解析** > > | 行 / 片段 | 说明 | > |--------------------------------------------------------------------------|--------------------------------------------------------| > | `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 => '' + h + '').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` 等)。 ##### 字符串与数组的对比 不同点 字符串 length 只读 数组 length 可写 字符串单个字符不可修改 数组元素可修改 字符串没有 push/pop 等方法 数组有完整方法集 相同点 有 length 属性 可用索引访问字符/元素 str\[0\], arr\[0

可用 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 bad2false delete 之后该索引真正不存在,in 运算符返回 false,而访问 bad2[2] 仍返回 undefined
bad3[4] = 5(跳跃赋值) 跨越中间索引赋值,索引 1~3 成为空槽,length 自动变为 5
JSON.stringify(bad2)null JSON.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&lt;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 时隐式返回 undefined
function 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 执行后函数立即终止 ,后续代码不再执行,因此不需要 else
testScores.map(function(s) { return '分数 ' + s + ' → ' + getGrade(s); }) map 与具名函数结合:把 getGrade 作为映射逻辑,将分数数组转为评级描述数组

Early Return 的价值: 与"箭头形代码"(不断嵌套 if-else)相比,Early Return 使代码层级更浅、更易读,是现代 JavaScript 开发的主流风格。


相关推荐
XinZong2 小时前
【AI社交】基于OpenClaw自研轻量化AI社交平台实战
前端
Le_ee2 小时前
ctfweb:php/php短标签/.haccess+图片马/XXE
开发语言·前端·php
爱上好庆祝2 小时前
学习js的第七天(wed APIs的开始)
前端·javascript·css·学习·html·css3
KaMeidebaby3 小时前
卡梅德生物技术快报|冻干工艺开发:注射用心肌肽全流程参数优化与工程化方案
前端·其他·百度·新浪微博
ooseabiscuit3 小时前
Laravel6.x核心优化与特性全解析
android·开发语言·javascript
哆啦A梦15884 小时前
20, Springboot3+vue3实现前台轮播图和详情页的设计
javascript·数据库·spring boot·mybatis·vue3
Moment4 小时前
面试官:如果产品经理给你多个需求,怎么让AI去完成❓❓❓
前端·后端·面试
每天吃饭的羊4 小时前
JSONP
前端
gogoing4 小时前
ESLint 配置字段说明
前端·javascript