解密 JavaScript 中的 this:谁才是真正的调用者?

彻底搞懂 JavaScript 中的 this 关键字

从一个实战场景说起

最近在做一个基于 localStorage 的待办事项应用,核心功能是通过表单收集用户输入并保存到本地存储。在处理表单提交事件时,遇到了一个经典问题:this 的指向到底是谁?

html 复制代码
<form class="add-items">
  <input type="text" name="item" placeholder="Add a new tapas" required>
  <input type="submit" value="+ Add Item">
</form>
<a href="#" class="lnk">跳转百度</a>
javascript 复制代码
document.querySelector(".lnk").addEventListener("click", goBaidu);

function goBaidu(e) {
  console.log(this);  // this 指向什么?
  e.preventDefault();
}

如果您也经常遇到类似困惑,那么这篇文章将帮助您彻底理解 this 的绑定规则。

this 的核心原则

this 是在函数运行时确定的,而非声明时!

this 的指向取决于调用函数的方式 ,而非函数定义的位置。这是理解 this 的关键。

六种绑定规则

规则一:普通函数调用

当函数作为普通函数被调用时,this 指向全局对象 window(非严格模式下)。

javascript 复制代码
var name = "李刚";

function sayName() {
  console.log(this.name);  // 输出:李刚
}

sayName();  // this 指向 window

严格模式下的变化:

javascript 复制代码
"use strict";

function sayName() {
  console.log(this);  // 输出:undefined
}

sayName();

启用严格模式 "use strict" 可以避免 this 意外指向 window,是现代 JavaScript 开发的最佳实践。

规则二:对象方法调用

当函数作为对象的方法被调用时,this 指向调用该方法的对象。

javascript 复制代码
let obj = {
  name: "王文",
  say: function() {
    console.log(this.name);  // 输出:王文
  }
};

obj.say();  // this 指向 obj

注意:引用式赋值的陷阱

javascript 复制代码
const fn = obj.say;  // 引用式赋值,函数脱离了对象
fn();  // this 指向 window(非严格模式)

当对象的方法被单独赋值给变量后,this 的绑定关系会丢失。

规则三:构造函数调用

当函数作为构造函数使用 new 关键字调用时,this 指向新创建的实例对象。

javascript 复制代码
function Person(name) {
  this.name = name;  // this 指向新创建的实例
}

const person = new Person("张三");
console.log(person.name);  // 输出:张三

规则四:事件处理函数调用

在 DOM 事件处理函数中,this 指向触发事件的元素。

javascript 复制代码
document.querySelector(".lnk").addEventListener("click", function(e) {
  console.log(this);  // this 指向 <a> 元素
  e.preventDefault();
});

规则五:手动指定 this(call/apply/bind)

call 和 apply

两者都能手动指定 this 指向,区别在于参数传递方式:

javascript 复制代码
let obj = {
  name: "王文",
  say: function() {
    console.log(this.name);
  },
  speak: function(a, b) {
    console.log(a, b);
    console.log(this);
  }
};

let obj2 = {
  name: "金大轩"
};

obj.say.call(obj2);     // 输出:金大轩
obj.say.apply(obj2);    // 输出:金大轩

obj.speak.call(obj2, 1, 2);      // 参数逐个传递
obj.speak.apply(obj2, [1, 2]);   // 参数通过数组传递
bind

bind 方法不会立即执行函数,而是返回一个绑定了 this 的新函数:

javascript 复制代码
const fn2 = obj.speak.bind(obj2);  // 返回新函数,不执行
fn2(1, 2);  // 调用新函数时才执行

规则六:箭头函数中的 this

箭头函数没有自己的 this

箭头函数的 this 继承自外层作用域的 this,这是它与普通函数的本质区别。

javascript 复制代码
let obj = {
  name: "姆巴佩",
  say: function() {
    setTimeout(() => {
      console.log(this.name);  // 输出:姆巴佩(继承外层 this)
    }, 1000);
  }
};

obj.say();

经典面试题:setTimeout 中的 this 陷阱

这是一个非常经典的面试题,比较两种写法的区别:

写法一:普通函数 + bind

javascript 复制代码
var name = "梅西";
let obj = {
  name: "姆巴佩",
  say: function() {
    setTimeout((function() {
      console.log(this.name)  // 输出:姆巴佩
    }).bind(this), 1000);
  }
};

obj.say();

通过 bind(this) 手动绑定了 setTimeout 回调函数中的 this

写法二:箭头函数

javascript 复制代码
var name = "梅西";
let obj = {
  name: "姆巴佩",
  say: function() {
    setTimeout(() => {
      console.log(this.name)  // 输出:姆巴佩
    }, 1000);
  }
};

obj.say();

箭头函数继承了外层 say 方法中的 this,无需手动绑定。

如果不用箭头函数也不绑定 this

javascript 复制代码
var name = "梅西";
let obj = {
  name: "姆巴佩",
  say: function() {
    setTimeout(function() {
      console.log(this.name)  // 输出:梅西(this 指向 window)
    }, 1000);
  }
};

obj.say();

普通函数作为 setTimeout 回调时,this 默认指向 window

深度分析:var 与 let 对 this 的影响

var 声明的变量会挂载到 window 对象

javascript 复制代码
var name = "全局变量";
console.log(window.name);  // 输出:全局变量
console.log(this.name);    // 输出:全局变量(在全局作用域中)

这就是为什么普通函数调用时 this.name 能访问到用 var 声明的变量。

let/const 声明的变量不会污染 window

javascript 复制代码
let name = "全局变量";
console.log(window.name);  // 输出:undefined
console.log(this.name);    // 输出:undefined

letconst 声明的变量不会成为 window 对象的属性,这是 ES6 带来的重要改进。

this 指向速查表

调用方式 this 指向
普通函数调用 window(非严格模式)/ undefined(严格模式)
对象方法调用 调用该方法的对象
构造函数调用 新创建的实例对象
事件处理函数 触发事件的 DOM 元素
call/apply/bind 手动指定的对象
箭头函数 外层作用域的 this

总结

理解 this 的关键在于:关注函数的调用方式,而非定义位置

在实际开发中,推荐以下实践:

  1. 始终启用严格模式 "use strict"
  2. 使用箭头函数避免 this 绑定问题
  3. 使用 bind 预绑定 this(适合需要复用的函数)
  4. 优先使用 let/const 而非 var

掌握 this 的绑定规则,不仅能让您写出更优雅的代码,也是通过前端面试的必备技能。


相关推荐
黄敬峰22 分钟前
一文搞懂 Agentic RAG:用 LangGraph 打造会思考的智能检索架构
面试
我家猫叫佩奇31 分钟前
🦭 厌倦了千篇一律的线性图标?Naive Icons 正式开源
前端·javascript·css
晚安日记wanna1 小时前
SSR 为什么不适合登录态从水合冲突到缓存串号
前端·react.js·面试
晚安日记wanna1 小时前
压测 TPS 卡在 800CPU 只跑四成六步定位法
运维·面试·测试
晚安日记wanna1 小时前
批量请求失败只弹一个 Toast面试官想听五层
前端·面试·架构
晚安日记wanna1 小时前
TS const 类型参数一道面试题的四层追问
前端·面试·typescript
晚安日记wanna1 小时前
大表 DDL 面试翻车现场Online DDL 为什么还会锁死业务
数据库·面试·架构
晚安日记wanna1 小时前
MySQL 主从延迟别只答并行复制
数据库·面试·架构
SamDeepThinking2 小时前
Java 17内存屏障:为什么需要,怎么用
java·后端·面试
CC数分2 小时前
2026年金融数据分析岗位需要哪些工具?2027届秋招备考指南
面试·数据分析