JavaScript函数调用的四种基本模式详解

作者:异步漫步

时间:2026-10-30

浏览:356

深入解析JavaScript函数调用的四种基本模式:函数调用模式、方法调用模式、构造器调用模式与间接调用模式。详解this绑定机制、执行原理、常见陷阱与判定决策树。

文章目录

在 JavaScript 中,函数内部 this 的指向并不是在函数定义声明时确定的,而是在代码执行到函数调用时,由当前的调用模式动态绑定的。相同的函数代码在不同的调用方式下,内部的 this 会指向截然不同的对象。理解 JavaScript 的四种基本函数调用模式——函数调用模式、方法调用模式、构造器调用模式和间接调用模式,是掌握执行上下文、规避作用域丢失以及编写可维护代码的核心基础。

JavaScript函数调用的四种基本模式详解
图 1:四种基本调用模式的调用形态与 this 绑定核心映射

一、函数调用模式(Function Invocation Pattern)

函数调用模式是最朴素的调用方式。当一个函数不挂载在任何对象属性上,而是直接通过函数名加圆括号执行时,即属于函数调用模式。

在非严格模式下,函数内部的 this 会默认绑定到全局对象(浏览器环境为 window,Node.js 运行环境为 global)。若在严格模式('use strict')下运行,JavaScript 引擎为了防止全局污染,会将默认的 this 绑定为 undefined。

function showStatus() {
  console.log(this);
}

showStatus(); // 非严格模式输出: window (浏览器) 或 global (Node.js)

function showStrictStatus() {
  'use strict';
  console.log(this);
}

showStrictStatus(); // 严格模式输出: undefined

常见陷阱:方法内部嵌套函数丢失 this

在编写业务代码时,最容易出现混淆的场景是在对象方法内部嵌套子函数。很多初学者会误以为内层子函数的 this 会继承外层方法的上下文,但实际上只要内层函数是以独立函数的形式调用的,它的 this 就会退化为函数调用模式。

const counter = {
  count: 0,
  start: function() {
    function increment() {
      // 这里的 increment() 是独立调用的普通函数!
      // 非严格模式下 this 指向 window,导致 counter.count 根本没有被修改
      this.count++;
    }
    increment();
  }
};

counter.start();
console.log(counter.count); // 输出仍为 0

解决此问题的标准做法有两种:一是通过词法作用域在外层缓存上下文变量(例如 const self = this),二是使用 ES6 箭头函数。箭头函数本身不具备自己的 this 绑定,它会在定义时自动继承外层词法作用域的 this。

二、方法调用模式(Method Invocation Pattern)

当一个函数被赋值给某个对象的属性,并且通过该对象引用与点号(.)或中括号([])操作符来执行时,该调用即为方法调用模式。

在此模式下,this 会被精确绑定到调用该方法的宿主对象。在多层嵌套对象的情况下,this 仅指向距离调用圆括号最近的那个直接对象。

const user = {
  name: 'Alice',
  role: 'Developer',
  greet: function() {
    console.log(`Hello, I am ${this.name}, a ${this.role}.`);
  },
  department: {
    name: 'Core Engineering',
    showDept: function() {
      // this 指向直接调用它的 department 对象,而不是外层的 user 对象
      console.log(`Department: ${this.name}`);
    }
  }
};

user.greet();                 // 输出: Hello, I am Alice, a Developer. (this 指向 user)
user.department.showDept();   // 输出: Department: Core Engineering (this 指向 department)

常见陷阱:方法引用传递导致的隐式丢失

当对象的方法被赋值给一个独立的普通变量,或者作为回调函数传递给事件监听器、定时器时,原本的方法调用会转变为独立的函数调用,导致宿主对象的绑定丢失。

const profile = {
  name: 'Bob',
  getName: function() {
    return this.name;
  }
};

// 1. 赋值给独立变量
const getStandaloneName = profile.getName;
console.log(getStandaloneName()); // 输出: undefined(退化为函数调用模式)

// 2. 作为回调函数传入 setTimeout
setTimeout(profile.getName, 100);  // 内部执行时 this 不再是 profile

三、构造器调用模式(Constructor Invocation Pattern)

如果在调用普通函数时,在其前面加上 new 运算符,该调用就会以构造器调用模式运行。JavaScript 并没有像其他面向对象语言那样独立的“类实例化机制”,所谓的构造器仅仅是一个使用 new 操作符调用的普通函数。

JavaScript函数调用的四种基本模式详解
图 2:new 运算符底层实现的四个关键环节

构造器模式的常规用法与返回值特性

在构造器内部,通过 this.property = value 可以直接为新创建的实例注入属性与状态。

function Car(model, year) {
  this.model = model;
  this.year = year;
}

const myCar = new Car('Model 3', 2024);
console.log(myCar.model); // 输出: Model 3
console.log(myCar.year);  // 输出: 2024
注意构造函数的显式 return 规则:

如果构造函数显式返回了一个非原始类型(对象、数组、函数),new 表达式的计算结果会直接被该对象替换;如果返回的是原始类型(数字、字符串、布尔值、undefined、null),该返回值会被忽略,依然返回新创建的实例对象。

四、间接调用模式(Indirect Invocation Pattern)

间接调用模式也被称为上下文绑定调用。JavaScript 的函数也是对象,继承自 Function.prototype,因此每个函数实例都拥有 call()、apply() 以及 ES5 引入的 bind() 方法。通过这些方法,开发者可以显式指派函数执行时的 this 上下文。

JavaScript函数调用的四种基本模式详解
图 3:三种间接调用 API 的执行时机与参数结构对比

代码示例:方法借用与硬绑定

间接调用允许我们在不侵入原有对象结构的前提下,借用已有方法处理不同对象的数据:

function calculateTax(rate, currency) {
  const total = this.amount * (1 + rate);
  return `${currency}${total.toFixed(2)}`;
}

const invoice = { amount: 1000 };

// 1. call: 逗号传参,立即执行
const resCall = calculateTax.call(invoice, 0.1, '$');
console.log(resCall); // 输出: $1100.00

// 2. apply: 数组传参,立即执行
const resApply = calculateTax.apply(invoice, [0.15, '¥']);
console.log(resApply); // 输出: ¥1150.00

// 3. bind: 返回锁定上下文的新函数
const bindInvoice = calculateTax.bind(invoice, 0.2);
console.log(bindInvoice('€')); // 输出: €1200.00

五、四种调用模式的判定决策与排查

在排查复杂的生产环境代码或阅读第三方开源库时,面对任意一段函数的调用,可以按照以下决策流程从高到低逐级排查:

JavaScript函数调用的四种基本模式详解
图 4:函数调用模式判定与 this 指向推导决策树

核心差异对照表

调用模式 典型语法形式 非严格模式下的 this 严格模式下的 this 主要应用场景
函数调用模式 fn() window / global undefined 独立工具函数、纯计算逻辑
方法调用模式 obj.method() 直接宿主对象 obj 直接宿主对象 obj 对象封装、状态变更与行为组织
构造器调用模式 new Constructor() 新生成的实例对象 新生成的实例对象 类型实例化、原型链路初始化
间接调用模式 fn.call() / apply() 传入的 thisArg 对象 传入的 thisArg 对象 跨类型方法借用、上下文硬绑定

综上所述,JavaScript 中判断 this 值的唯一标准,就是观察函数被调用的具体位置与具体语法形态。通过依序排查是否含有 new 运算符、是否通过 call/apply/bind 指定、是否由对象属性驱动,还是独立执行,即可快速锁定函数的执行上下文,从根本上解决 this 丢失或指向异常的各类问题。

实用工具推荐

电子包浆

绿化、压缩、噪点和虚拟水印,自由调节做旧程度。

使用 ›
图片压缩

选择画质和输出格式,预览压缩前后的大小。

使用 ›
相关文章