【js篇】深入理解 JavaScript 中的 this:四种调用模式与绑定优先级全解析

25 阅读4分钟

在 JavaScript 中,this 是一个让无数开发者困惑的关键字。它不是一个静态的值,而是在函数执行时动态绑定的,其指向取决于函数是如何被调用的

本文将系统性地讲解 this 的四种调用模式、绑定规则、优先级以及实际应用,帮你彻底掌握这一核心机制。


一、this 是什么?

this 是执行上下文中的一个属性,它指向“谁调用了当前函数”

  • 它不是函数定义时决定的,而是运行时(调用时)决定的;
  • 它指向最后一次调用该函数的对象(更准确地说,是调用上下文);
  • 理解 this 是掌握面向对象编程、事件处理、闭包等高级特性的基础。

二、this 的四种调用模式

✅ 1)函数调用模式(Function Invocation)

场景:函数独立调用,不作为对象的属性。

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

standalone(); // 浏览器中输出: window
              // 严格模式下输出: undefined

📌 规则

  • 非严格模式:this 指向全局对象(浏览器中是 window);
  • 严格模式('use strict'):thisundefined

❗ 这是 this 最容易出错的场景,尤其是在回调函数中。


✅ 2)方法调用模式(Method Invocation)

场景:函数作为对象的属性被调用。

const obj = {
  name: 'Alice',
  greet: function() {
    console.log(`Hello, I'm ${this.name}`);
  }
};

obj.greet(); // 输出: Hello, I'm Alice ✅

📌 规则

  • this 指向调用该方法的对象(即 . 左边的对象);
  • 这是最符合直觉的 this 使用方式。

⚠️ 注意:如果方法被赋值给变量,this 会丢失:

const fn = obj.greet;
fn(); // 输出: Hello, I'm undefined(非严格模式下 `this` 指向 `window`)

✅ 3)构造器调用模式(Constructor Invocation)

场景:使用 new 关键字调用函数。

function Person(name) {
  this.name = name;
  this.greet = function() {
    console.log(`Hi, I'm ${this.name}`);
  };
}

const p = new Person('Bob');
p.greet(); // 输出: Hi, I'm Bob ✅

📌 规则

  • new 会创建一个新对象;
  • this 指向这个新创建的实例对象
  • 如果构造函数没有显式返回对象,则默认返回 this

✅ 4)显式绑定模式(Explicit Binding)—— callapplybind

场景:手动指定 this 的指向。

(1)call(thisArg, arg1, arg2, ...)

function introduce(age, city) {
  console.log(`I'm ${this.name}, ${age} years old, from ${city}`);
}

const person = { name: 'Charlie' };
introduce.call(person, 25, 'Beijing'); 
// 输出: I'm Charlie, 25 years old, from Beijing

(2)apply(thisArg, [argsArray])

introduce.apply(person, [25, 'Beijing']);
// 效果同上,但参数以数组形式传入

(3)bind(thisArg, arg1, arg2, ...)

const boundFn = introduce.bind(person, 25, 'Beijing');
boundFn(); // 输出同上
// bind 返回一个新函数,`this` 和参数已预设

📌 关键区别

方法是否立即执行参数形式
call✅ 是逐个传参
apply✅ 是数组传参
bind❌ 否,返回新函数逐个传参

三、this 绑定的优先级

你提到的优先级顺序完全正确!我们来系统梳理:

new > bind > call/apply > 方法调用 > 函数调用

🔝 1. new 的优先级最高

function foo() {
  console.log(this.a);
}

foo.prototype.a = 3;

const boundFoo = foo.bind({ a: 1 });
const obj = new boundFoo(); // 忽略 bind 的 this,指向新对象
// obj.a 是 undefined,但原型上有 a=3
console.log(obj.a); // 3

📌 结论new 会忽略 bindcallapply 设置的 this

🔝 2. bind > call/apply

const obj1 = { a: 1 };
const obj2 = { a: 2 };

function foo() {
  console.log(this.a);
}

foo.call(obj1);        // 1
foo.bind(obj1)();      // 1
foo.bind(obj1).call(obj2); // 仍然输出 1,bind 的 this 不可被覆盖

📌 注意bind 返回的函数,其 this永久绑定的,无法再用 call/apply 修改。

🔝 3. 方法调用 > 函数调用

const obj = {
  a: 1,
  foo: function() {
    console.log(this.a);
  }
};

const fn = obj.foo;
fn();     // undefined(或报错,取决于上下文)
obj.foo(); // 1

四、箭头函数中的 this

⚠️ 重要补充:箭头函数没有自己的 this

  • 它的 this 继承自外层作用域(词法作用域);
  • 不能用 call/apply/bind 修改;
  • 不能作为构造函数使用。
const obj = {
  a: 1,
  regular: function() {
    console.log(this.a); // 1
  },
  arrow: () => {
    console.log(this.a); // undefined(继承自全局)
  }
};

obj.regular(); // 1
obj.arrow();   // undefined

五、实战技巧:如何判断 this 指向?

使用“调用点分析法”:

  1. new 调用?this 指向新对象;
  2. call/apply/bind 调用?this 指向第一个参数;
  3. 作为对象方法调用?this 指向该对象;
  4. 否则? → 函数调用模式,非严格模式指向 window,严格模式为 undefined

六、常见陷阱与解决方案

❌ 陷阱1:事件处理中 this 丢失

const button = document.querySelector('button');
button.addEventListener('click', obj.greet); // this 指向 button

解决方案

// 方案1:使用 bind
button.addEventListener('click', obj.greet.bind(obj));

// 方案2:使用箭头函数
button.addEventListener('click', () => obj.greet());

❌ 陷阱2:setTimeout 中的 this

obj.delayGreet = function() {
  setTimeout(function() {
    console.log(this.name); // undefined
  }, 1000);
};

解决方案

// 方案1:缓存 this
const self = this;
setTimeout(function() { console.log(self.name); }, 1000);

// 方案2:使用 bind
setTimeout(function() { console.log(this.name); }.bind(this), 1000);

// 方案3:使用箭头函数(推荐)
setTimeout(() => console.log(this.name), 1000);

七、总结:this 核心规则一览

调用模式this 指向优先级
new Foo()新创建的实例⭐⭐⭐⭐⭐(最高)
foo.bind(obj)obj(永久绑定)⭐⭐⭐⭐
foo.call(obj) / foo.apply(obj)obj⭐⭐⭐
obj.foo()obj⭐⭐
foo()全局对象 / undefined⭐(最低)
() => {}外层作用域的 this特殊

💡 结语

this 不是魔法,而是规则。掌握调用模式,你就掌握了 this。”

记住:永远不要猜测 this,而是分析函数是如何被调用的

在实际开发中:

  • 优先使用箭头函数避免 this 问题;
  • 善用 bind 固定上下文;
  • 理解 new 的行为,避免误用。