PhysChen.com
主页
物理
科普 研究
教学
IB 课程
编程
笔记 项目
随笔
所感 所思
摄影
Shenzhen Portrait Cats Others Wuhan Japan
关于
主页
物理
科普 研究
教学
IB 课程
编程
笔记 项目
随笔
所感 所思
摄影
Shenzhen Portrait Cats Others Wuhan Japan
关于
文章目录
    JavaScript 2. 函数 陈华的个人主页

    文章信息

    • 标题: JavaScript 2. 函数
    • 发布时间: 2026 年 7 月 11 日
    • 来源: https://physchen.com/zh-Hans/programming/notes/javascript-function/
    • 摘要: 函数是 JavaScript 的核心组成部分,其实质是可调用的函数对象。掌握函数是理解 JavaScript 作用域链、闭包、异步机制以及面向对象特性的核心基础。

    目录

      JavaScript 2. 函数

      2026 年 7 月 11 日
      • JavaScript 基础
      • JavaScript

      函数是 JavaScript 的核心组成部分,其实质是可调用的函数对象。掌握函数是理解 JavaScript 作用域链、闭包、异步机制以及面向对象特性的核心基础。

      2.1 函数的定义与类型

      JavaScript 提供了多种定义函数的方式,其底层初始化时机和作用域表现存在差异。

      2.1.1 函数声明

      函数声明(Function Declaration)是独立的语句结构,具有作用域提升(Hoisting)特征。

      {
        // 1. 验证作用域提升:函数声明在初始化阶段即被创建,可以在定义语句之前成功调用
        console.log("--- 函数声明提升测试 ---");
        greet("Alice", "Prof."); 
      
        function greet(name, title) {
          console.log(`Hello, ${title} ${name}!`);
        }
      
        // 2. 验证严格模式下的块级作用域约束
        (() => {
          "use strict";
          if (true) {
            function blockScopedFunc() {
              return "Inside Block";
            }
            console.log(`块内调用: ${blockScopedFunc()}`); 
          }
          
          try {
            blockScopedFunc(); // 尝试在块外部访问
          } catch (e) {
            // 触发 ReferenceError
            console.log(`块外调用捕获异常: ${e.name} - ${e.message}`);
          }
        })();
      }

      其中,function 是关键字,greet 是函数名,name 和 title 是形参(Parameters),花括号内的代码为函数体。

      底层运行机制与规范:

      1. 作用域提升:在脚本或代码块开始执行前的环境初始化阶段, JavaScript 引擎会扫描当前作用域,创建并初始化所有的函数声明。因此,函数声明可以在其定义语句之前被成功调用。
      2. 严格模式下的块级作用域:在 "use strict" 模式下,如果函数声明位于一个代码块内(如 if 语句、for 循环内部),该函数将绑定到该块级作用域。块外部无法访问此函数。

      2.1.2 函数表达式

      函数表达式(Function Expression)是将函数作为右值赋值给变量的行为。

      {
        console.log("--- 函数表达式初始化时机测试 ---");
        try {
          greetExpr("Alice", "Prof."); // 尝试在赋值语句前调用
        } catch (e) {
          // 触发 ReferenceError
          console.log(`赋值前调用捕获异常: ${e.name} - ${e.message}`);
        }
      
        const greetExpr = function(name, title) {
          console.log(`Hello, ${title} ${name}!`);
        };
        
        greetExpr("Bob", "Dr."); // 赋值后调用正常
      }

      底层运行机制与规范:

      1. 执行时创建:与函数声明不同,函数表达式在执行流运行到该赋值语句时才会被创建并初始化。在语句之前调用,若变量使用 let/const 声明会触发 ReferenceError;若使用 var 声明,由于变量已被提升但尚未赋值(此时值为 undefined),对其进行函数调用会因尝试对非函数类型执行 [[Call]] 操作而触发 TypeError。
      2. 具名函数表达式(Named Function Expression, NFE):右侧函数可以包含名称(如 let sayHi = function func(me) {...})。根据 ECMAScript 规范,引擎会为该函数实例化一个独立的外部词法环境,并将名称 func 以不可变绑定(Immutable Binding)的形式写入该环境中。因此该名称仅在函数内部可见,常用于递归调用或在调用栈调试中标识函数,避免因外部变量引用变更导致递归中断。
      {
        console.log("--- 具名函数表达式行为测试 ---");
        let sayHi = function func(who) {
          if (who) {
            console.log(`Hello, ${who}`);
          } else {
            // 内部名称 func 仅在自身函数体内可见,
            // 用于确保递归调用不因外部变量改变而失效
            func("Guest"); 
          }
        };
      
        sayHi("Alice"); // 输出: Hello, Alice
      
        try {
          func("Bob"); // 尝试直接在外部作用域调用内部名称
        } catch (e) {
          // 输出: ReferenceError
          console.log(`外部调用内部名捕获异常: ${e.name}`);
        }
      
        let welcome = sayHi;
        sayHi = null; // 显式置空原引用变量
      
        // 物理结构与规范限制:此时外部 sayHi 变量已不指向该函数,
        // 但内部通过特定的词法环境保持了对函数自身的直接引用
        welcome(); // 依然成功输出: Hello, Guest
      }

      2.1.3 箭头函数(Arrow Function)

      箭头函数是 ECMAScript 6 引入的语法,旨在提供一种更简洁的单行或简短函数书写形式。

      {
        console.log("--- 箭头函数基础与字面量返回测试 ---");
        const greet = (name) => {
          console.log(`Hello, ${name}!`);
        };
        greet("Alice");
      
        const add = (a, b) => a + b;
        console.log(`单行隐式返回 add(1, 2): ${add(1, 2)}`); // 3
      
        // 对象字面量的隐式返回陷阱验证
        const f1 = () => { foo: 1 }; 
        console.log(`未包裹圆括号 f1(): ${f1()}`); // undefined
      
        const f2 = () => ({ foo: 1 });
        console.log(`包裹圆括号后 f2(): ${f2()}`); // [object Object]
        console.log(`包裹圆括号后 f2(): ${JSON.stringify(f2())}`); // {"foo":1}
      }

      核心限制与设计差异:

      • 词法 this 绑定(Lexical this): 箭头函数本身没有独立的 this 存储空间。其内部的 this 完全取决于该函数被定义时所处的外部词法环境。由于这种静态绑定机制,使用 call()、apply() 或 bind() 试图动态改变箭头函数的 this 指向将被引擎静默忽略。 普通函数的 this 取决于调用方式(非严格模式下独立调用指向全局对象,严格模式下是 undefined)。箭头函数则直接继承外层词法作用域的 this。

        {
          console.log("--- 箭头函数 This 词法绑定测试 ---");
          const obj = {
            normalFunc: function() {
              console.log(`普通函数 Method This 指向 obj: ${this === obj}`);
              },
            arrowFunc: () => {
              console.log(`箭头函数 Method This 指向 obj: ${this === obj}`);
              } 
          };
          obj.normalFunc(); // true
          obj.arrowFunc();  // false (向外层寻找词法作用域环境)
        
          // 异步回调环境下的 This 锁定制
          const timerObj = {
            id: 42,
            init: function() {
              // 普通函数作为异步回调,执行上下文在运行时脱离宿主对象,退化为全局环境(非严格模式下)
              setTimeout(function() {
                console.log(`setTimeout 普通函数回调 this.id: ${this.id}`); // undefined
              }, 10毫秒);
        
              // 箭头函数没有自己的 this,锁定定义时的外层词法环境(即 init 方法的 this)
              setTimeout(() => {
                console.log(`setTimeout 箭头函数回调 this.id: ${this.id}`); // 42
              }, 10毫秒);
            }
          };
          timerObj.init();
        }
      • 缺少 arguments 变量对象: 箭头函数内部不会创建传统的类数组对象 arguments。若在其内部访问 arguments,实际访问的是外层常规函数的 arguments 变量。若外层非函数作用域,访问 arguments 将取决于宿主环境,在标准脚本环境中会触发 ReferenceError。

        {
          console.log("--- Arguments 行为差异测试 ---");
          function outerNormal() {
            // 箭头函数直接借用外层普通函数的 arguments 对象
            const arrow = () => {
              console.log(`箭头函数内读取到的外层 arguments: ${JSON.stringify([...arguments])}`); 
            };
            arrow();
          }
          outerNormal("Param1", "Param2"); // 输出: ["Param1", "Param2"]
        
          // 剩余参数 (Rest Parameters) 替代方案
          const logArgs = (...args) => {
            console.log(`箭头函数配合剩余参数获取自身实参: ${JSON.stringify(args)}`); 
          };
          logArgs(1, "Alice", 2); // 输出: [1, "Alice", 2]
        }
      • 构造能力缺失(无 [[Construct]]): 普通函数在创建时会拥有内部方法 [[Call]](支持正常执行)和 [[Construct]](支持通过 new 实例化)。而箭头函数底层仅具备 [[Call]] 方法,因此无法作为构造函数被 new 调用(执行时抛出 TypeError),并且其不具备 prototype 原型属性。

      • 无 super 绑定:无法在类(Class)的继承体系中通过箭头函数触发 super 关键字。

      • 对象字面量的隐式返回限制: 单行箭头函数若需隐式返回一个对象字面量,必须将该对象包裹在圆括号 () 内。否则,引擎会将对象的花括号 {} 解析为函数的语句块(Block),导致返回 undefined 或触发语法解析错误。

      2.1.4 函数命名规范

      函数代表抽象行为,其命名通常应采用动词前缀(如 get、set、calc、create)以概括其内部核心逻辑。若一个函数承载了多项不相关的行为,应将其拆分为多个单一职责的函数。

      2.2 参数管理机制

      JavaScript 的函数参数处理不强制要求实参与形参的数量及类型完全一致。

      2.2.1 形参与实参的传递

      • 形参 (Parameters):函数定义中列出的变量名。
      • 实参 (Arguments):函数调用时实际传入的具体数值。
      • 传递机理: JavaScript 始终遵循值传递 (Pass-by-value) 规则。当实参为引用类型(如对象、数组)时,传递的“值”是该对象的内存地址引用。因此,在函数内部修改对象属性会直接作用于外部对象,但若在内部重写该形参的引用(如 obj = {}),则不会改变外部变量。

      2.2.2 默认参数值(Default Parameters)

      在定义形参时,可以通过赋值运算符 = 为其指定默认值。

      {
        console.log("--- 默认参数运行时动态评估测试 ---");
        let evaluationCount = 0;
        
        // 默认参数表达式可以是一个带有副作用的函数调用
        const getDynamicDefault = () => {
          evaluationCount++;
          return "Prof.";
        };
      
        const greet = function(name, title = getDynamicDefault()) {
          console.log(`Hello, ${title} ${name}!`);
        };
      
        greet("Alice"); // 未传值,触发评估,评估计数 +1
        greet("Bob", "Dr."); // 传值,跳过评估,评估计数不变
        greet("Charlie", undefined); // 传入 undefined 视为缺省,触发评估,评估计数 +1
      
        console.log(`动态默认参数执行总次数: ${evaluationCount}`); // 输出: 2
      }

      默认参数不仅限于字面量,可以是任意复杂的表达式或函数调用。默认参数的评估是在运行时动态触发的,即只有在对应的实参缺省或显式传入 undefined 时,才会执行并计算默认参数对应的表达式。

      2.2.3 剩余参数(Rest Parameters)与 arguments

      在旧标准中,处理不定数量实参依赖内置的类数组对象 arguments。现代标准推荐使用剩余参数。

      {
        console.log("--- 剩余参数基本测试 ---");
        function sumAll(...args) { // args 是一个标准的 Array 实例
          return args.reduce((sum, current) => sum + current, 0);
        }
        console.log(`sumAll(1, 2, 3) 结果: ${sumAll(1, 2, 3)}`); // 6
      }

      技术对比:

      特征剩余参数 (...args)传统 arguments 对象
      类型标准 Array 实例,支持所有数组原型方法类数组对象 (Array-like Object),仅支持索引与 length
      语法限制必须位于形参列表的最末尾可以在任何位置通过关键字直接读取
      箭头函数支持完全支持不支持(箭头函数内部无此绑定)

      2.2.4 参数解构赋值

      通过在形参位置直接进行解构,可以清晰地表达复杂对象或数组的内部依赖结构,通过默认值防止解构异常。

      • 参数顺序设计:非解构形参若包含默认值,应将带有默认值的参数置于形参列表右侧(Trailing Default Parameters),以确保正常省略实参。
      • 解构防御约束:解构对象的属性是无序的,应为可能缺失的属性声明默认初始值,并为整个解构对象赋予默认值 {},避免因未传实参导致运行时报错。
      {
        console.log("--- 参数解构防御机制测试 ---");
      
        // 防御性写法:声明属性默认值,并赋予形参对象整体默认值 `{}`
        function greetFlexibly({ name = "Alice", title = "Visitor" } = {}) {
          console.log(`Hello, ${title} ${name}!`);
        }
      
        greetFlexibly({ title: "Prof." }); // 属性缺省防御成功 -> Hello, Prof. Alice!
        greetFlexibly(); // 实参对象整体缺省防御成功 -> Hello, Visitor Alice!
      }

      2.3 执行上下文与 this 绑定机制

      在 JavaScript 中,this 不是静态绑定的,其具体指向在函数执行(调用)阶段根据调用上下文动态确定。

      2.3.1 全局上下文与自由函数调用

      • 全局作用域:在全局上下文直接评估 this,其指向全局对象(浏览器中为 window, Node.js 环境中为 global,通用规范下为 globalThis)。
      • 自由函数调用:在 ECMAScript 规范中,独立执行未显式绑定宿主的标识符调用时(如 func()),this 的指向取决于当前是否启用了严格模式:
        • 非严格模式:引擎在执行函数上下文的 OrdinaryCallBindThis 操作时,会将传入的 undefined 或 null 显式替换为全局对象 globalThis。
        • 严格模式 ("use strict"):this 严格保持为 undefined。

      2.3.2 方法调用与“引用记录(Reference Record)”丢失风险

      当函数作为对象的方法被调用时(如 obj.method()),this 指向该点号前的对象 obj。

      底层结构与风险: JavaScript 引擎在解析 obj.method() 时,属性访问运算符会返回一个 ECMAScript 规范内部专有的 Reference Record (引用记录) 结构,可抽象表达为 [[Base]]: obj, [[ReferencedName]]: "method", [[Strict]]: true。

      • 如果紧随其后的是小括号 () 触发调用,引擎会直接提取该结构的 [[Base]] 字段并将其赋予执行上下文的 this。
      • 如果将该方法提取赋值给另一个变量(如 let f = obj.method),或者作为参数传递,会触发内部的 GetValue 抽象操作。该操作会丢弃 Reference Record 结构,仅返回纯粹的函数对象引用,导致 [[Base]] 信息丢失,后续调用回退到自由函数调用的逻辑。
      {
        console.log("--- Reference Record 丢失测试 ---");
        "use strict"; // 开启严格模式以观察稳态表现
      
        const user = {
          name: "John",
          hi() { console.log(this ? `This 寻址成功: ${this.name}` : "This 丢失,变为 undefined"); }
        };
      
        user.hi(); // 点号运算符直接触发调用,保留基准对象 -> This 寻址成功: John
      
        let hi = user.hi; // 赋值操作导致隐式引用记录解构,丢失宿主对象引用
        try {
          hi(); 
        } catch (e) {
          console.log(`独立调用赋值后的函数触发异常: ${e.name} - ${e.message}`); // TypeError
        }
        
        // 使用硬绑定锁定上下文
        let safeHi = user.hi.bind(user);
        safeHi(); // 硬绑定成功 -> This 寻址成功: John
      }

      2.3.3 显式绑定与硬绑定

      为了控制或修正函数的 this 上下文,可利用 Function 原型上的三种方法:

      • call(thisArg, ...args):立即执行该函数,显式指定 this 为 thisArg,后续参数以逗号分隔传入。
      • apply(thisArg, argsArray):立即执行该函数,显式指定 this 为 thisArg,后续参数以数组或类数组形式传入。
      • bind(thisArg, ...args):不立即执行,而是返回一个全新的绑定函数对象(Exotic Bound Function Object)。该新函数内部锁定了 this 上下文为 thisArg,并支持预设部分参数(柯里化/Currying)。

      2.4 返回值约束与控制流

      • 默认隐式返回:如果函数没有写明 return 语句,或者仅书写了不带表达式的空 return;,函数在执行完毕后会默认向调用者返回 undefined。
      • ASI (自动分号插入) 的换行限制: JavaScript 引擎会在单行 return 关键字后自动插入分号。因此,不能在 return 关键字与待返回的表达式之间换行。若表达式过长需要换行,需使用小括号包裹表达式以表明连续性。
      {
        console.log("--- ASI 自动分号插入测试 ---");
      
        function badReturn() {
          return  // 引擎在此处自动补全分号变更为 return; 导致后续代码变为无法触达的语句
            { name: "John" };
        }
        console.log(`受 ASI 错误截断的返回值: ${badReturn()}`); // undefined
      
        function goodReturn() {
          return ( // 使用括号显式声明表达式连续性,阻止 ASI 插入
            { name: "John" }
          );
        }
        console.log(`正确换行的对象返回值: ${JSON.stringify(goodReturn())}`); // {"name":"John"}
      }

      2.5 高阶函数(Higher-Order Functions)

      在 JavaScript 中,函数是一等公民(First-Class Citizens),本质上属于特殊的对象。这意味着函数可以被作为值进行存储、作为实参传递给其他函数(回调函数),或者作为其他函数的返回值。

      {
        console.log("--- 高阶函数基本测试 ---");
        function repeat(n, action) {
          for (let i = 0; i < n; i++) action(i);
        }
      
        repeat(3, (index) => console.log(`Action trigger index: ${index}`)); 
      }

      2.5.1 定时器嵌套延迟限制

      宿主环境提供了 setTimeout 与 setInterval 实现异步调度。

      • 底层嵌套约束:根据 HTML 标准,当定时器嵌套层级大于 5 (即处于第 6 次及以上的嵌套回调),且设定的延迟时间小于 4 毫秒 时,引擎会自动强制将延迟提升至 4 毫秒(4ms minimum delay)。
      • 零延迟定时器:setTimeout(..., 0) 并不意味着立即同步执行,而是将其作为异步宏任务(MacroTask)排入事件循环队列,等待当前同步调用栈彻底清空、微任务队列清空后才会被触发。
      {
        console.log("--- 零延迟定时器宏任务排队测试 ---");
        setTimeout(() => console.log("异步宏任务执行"), 0);
        console.log("同步栈清空前"); // 优先打印
      }

      2.5.2 函数装饰器与柯里化

      • 装饰器模式(Decorator Pattern):一种高阶函数技术,用于在不修改原函数的前提下,包装函数以实现状态暂存(Memoization)或控制转发。
      • 柯里化(Currying):将一个接收多参数的函数,转换为一系列接收单参数的连续调用结构。
      {
        console.log("--- 函数装饰器与柯里化测试 ---");
      
        // 1. 缓存装饰器实现(Memoization)
        function memoize(func) {
          const cache = new Map();
          return function(x) {
            if (cache.has(x)) return cache.get(x);
            const result = func.call(this, x);
            cache.set(x, result);
            return result;
          };
        }
      
        const slowSquare = (x) => x * x;
        const fastSquare = memoize(slowSquare);
        console.log(`装饰器首次计算结果: ${fastSquare(5)}`);
        console.log(`装饰器二次命中缓存: ${fastSquare(5)}`);
      
        // 2. 通用自动化柯里化转换器
        function curry(func) {
          return function curried(...args) {
            if (args.length >= func.length) {
              return func.apply(this, args);
            } else {
              return function(...args2) {
                return curried.apply(this, args.concat(args2));
              };
            }
          };
        }
      
        const sum = (a, b, c) => a + b + c;
        const curriedSum = curry(sum);
        console.log(`全参数组合调用: ${curriedSum(1, 2, 3)}`);
        console.log(`分步柯里化链式调用: ${curriedSum(1)(2)(3)}`);
      }

      2.6 词法环境(Lexical Environment)与闭包机制

      闭包(Closure)是 JavaScript 基于词法作用域设计衍生出的必然结果。理解闭包必须深入到引擎的底层运行时规范。

      2.6.1 词法环境的基本理论

      在 JavaScript 中,每个运行中的函数、代码块({...})以及脚本本身,都有一个与之关联的内部隐藏对象,称为词法环境 (Lexical Environment)。 一个词法环境对象由两个部分构成:

      1. 环境记录 (Environment Record):在函数作用域下特化为函数环境记录(Function Environment Record),专门存储当前作用域内所有局部变量、形参、this 绑定以及函数声明的内部对象。
      2. 外部词法环境引用 (Outer Lexical Environment Reference):指向其外层父级词法环境的指针,建立了作用域链的物理基础。

      2.6.2 函数属性 [[Environment]] 与闭包本质

      1. 创建阶段:当一个函数在某个词法环境中被声明或创建时,它会获得一个隐藏的、不可直接更改的属性,命名为 [[Environment]]。该属性永久性地保存了它被创建时所处的外部词法环境的引用。
      2. 执行阶段:当该函数被调用时,引擎会为此次执行自动创建一个全新的函数词法环境。这个全新词法环境的“外部词法环境引用”正是直接从该函数的 [[Environment]] 属性中复制而来的。

      闭包的计算机定义: 闭包是指一个函数能够记住并持续访问其外部词法环境的能力,即使该函数在其原有的词法作用域之外被触发调用。

      {
        console.log("--- 闭包运行时状态持久化测试 ---");
      
        function makeCounter() {
          let count = 0; // 该变量保存在 makeCounter 的函数环境记录中
      
          // 返回的内部函数通过 [[Environment]] 维持对上级词法环境的物理引用
          return function() { 
            return count++;   
          };
        }
      
        let counter1 = makeCounter();
        let counter2 = makeCounter(); // 每次调用 makeCounter 都会生成全新的独立词法环境
      
        console.log(`Counter1 第一次评估: ${counter1()}`); // 0
        console.log(`Counter1 第二次评估: ${counter1()}`); // 1
        
        console.log(`Counter2 独立评估: ${counter2()}`);   // 0 (环境记录在堆内存中相互隔离)
      }

      2.6.3 内存管理与引擎优化

      • 环境生存期:正常情况下,函数执行完毕后其执行上下文退栈,相关的内部变量随词法环境的孤立而被回收。但如果该函数内部创建并返回了一个新函数(闭包),且这个新函数依然被外部变量引用,那么新函数的 [[Environment]] 属性就会持续维持对老函数词法环境的物理引用,导致老函数的词法环境无法被垃圾回收机制(GC)清理,从而实现了状态的持久化。
      • 引擎优化(静态作用域分析):现代高效引擎(如 V8)在编译期会对闭包进行静态作用域分析。如果一个外部局部变量在内部所有闭包中均未被引用,引擎会将其留在栈上空间(随外层退栈而销毁),而不会将其捕获到堆内存的 Context 对象中。这导致了一个调试现象:在浏览器 DevTools 中下断点调试内部闭包时,那些在外层存在但未在内部闭包中使用的变量会显示为不可查看(无法通过作用域链寻址)。
      上一篇 JavaScript 1. 基础语法 2026 年 7 月 10 日 下一篇
      Xiaohongshu Streamline Icon: https://streamlinehq.com Xiaohongshu
      © 2026 CHEN Hua All rights reserved
      闽ICP备2026003335号 · 粤公网安备44030002014022号
      © Hua Chen / PhysChen.com