彻底掌握 JavaScript 的 this:从机制到实践

76 阅读4分钟

🧭 一次把 this 讲透:JS 开发者的进阶指北

如果说 JavaScript 是一座城市,
this 就是它的交通规则。
新手靠直觉开车,老手靠规则通行。
而高手,早已把规则内化于心。

你已经写过不少 JS 代码,知道 this 大致指向调用者;
你也用过箭头函数、bind、甚至在类和事件处理器里处理过上下文问题。
但每当遇到嵌套回调、动态绑定、或框架封装下的 this
是不是仍会心头一紧,暗自嘀咕:

“这次 this 到底绑的是谁?”

这篇文章不讲基础语法,也不重复“谁调用就指向谁”的入门口诀。
我们聚焦于 this 绑定机制的深层逻辑与实战陷阱
帮你从“能用”走向“精通”,彻底告别 this 的不确定性。

准备好了吗?让我们深入 this 的运行时世界。

一、先抛结论:this 到底是什么?

一句话先记住:

this 不是函数定义时决定的,而是在函数被调用时动态绑定的

可以把 this 理解成一句话里的

  • 我在吃饭
  • 我在跑步

“我是谁?”
要看这句话是谁说的。


二、最基础的一条规则:谁调用,this 指向谁

先看一个非常典型的代码场景:

var name = 'windowName';

var a = {
    name: "Cherry",
    func1: function(){
        console.log(this.name);
    }
}

a.func1();

输出结果是:

Cherry

为什么?

因为:

  • func1 是通过 a.func1() 调用的
  • 所以 this === a
  • this.name === "Cherry"

👉 对象.方法() → this 指向这个对象

就像你喊:

“我是 Cherry”

那“我”自然是 Cherry 本人。


三、this 真正让人崩溃的地方:定时器

在 JavaScript 中,this 丢失是常见问题(如异步回调函数赋值后调用等场景),其核心原因是:函数的实际调用方式发生了改变,导致 this 指向意外切换。下面以定时器为例,介绍几种常用防止 this 丢失的方法

接下来,进入重灾区。

var name = 'windowName';

var a = {
    name: "Cherry",
    func1: function(){
        console.log(this.name);
    },
    func2:function(){
        setTimeout(function(){
            console.log(this);
            this.func1();
        },1000)
    }
}

a.func2();

很多人以为 1 秒后会输出:

Cherry

但实际情况是

image.png

  • this 指向 window
  • this.func1 根本不存在
  • 程序直接报错 💥

四、为什么 this 在定时器里“变了”?

这不是 JS 在针对你,而是调用方式变了

我们拆开来看:

setTimeout(function(){
    ...
},1000)

这个函数是谁调用的?

👉 不是 a,而是由宿主环境(如浏览器)在全局上下文中调用的。

setTimeout 的回调函数是一个普通函数,它在将来被宿主环境(如浏览器)以“独立函数调用”的形式执行(即 callback()),而不是作为某个对象的方法调用。根据 JavaScript 的 this 绑定规则,这种调用方式会将 this 绑定到全局对象(非严格模式下)或 undefined(严格模式下)。

等价于:

window.setTimeout(fn, 1000);

于是:

  • 定时器里的 this === window
  • 和外面的 a 已经没有关系了

五、解决方案一:that = this(最朴素)

这是最老牌、最原始、但最好理解的一种方式。

var a = {
    name: "Cherry",
    func1: function(){
        console.log(this.name);
    },
    func2:function(){
        let that = this;
        setTimeout(function(){
            that.func1();
        },1000)
    }
}

a.func2(); // 1秒后,输出cherry

原理非常简单:

  • this 会随调用方式改变;
  • 但普通变量(如 that)不会变;
  • 所以提前捕获并复用。

📌 this 会变,但变量不会


六、解决方案二:bind —— 给 this 订婚 💍

var a = {
    name: "Cherry",
    func1: function(){
        console.log(this.name);
    },
    func2:function(){
        setTimeout(function(){
            this.func1();
        }.bind(a),1000)
    }
}
a.func2();// 1秒后,输出cherry

bind 做了什么?

bind 返回一个新函数,其 this 永远被固定为你传入的对象

  • 不会立刻执行
  • this 永远绑定为你指定的对象

call / apply / bind 的关键区别

a.func1.call(a);   // 立即执行
a.func1.apply(a); // 立即执行

const fn = a.func1.bind(a); // 不执行
fn(); // 现在才执行
方法是否立即执行this 是否固定
call
apply
bind

📌 bind 更像婚约,call/apply 更像临时邀请


七、解决方案三:箭头函数(现代 JS 首选)

var a = {
    name: "Cherry",
    func1: function(){
        console.log(this.name);
    },
    func2:function(){
        setTimeout(() => {
            console.log(this);
            this.func1();
        },1000)
    }
}

a.func2();// 1秒后,输出cherry

为什么箭头函数可以?


八、箭头函数的本质:它根本没有 this

箭头函数不绑定自己的 this,而是词法地继承外层作用域的 this

const func = () => {
    console.log(this);
}

func();
new func();

输出结果: image.png

这意味着:

  • 箭头函数中的 this = 定义时所在上下文的 this
  • 不会被 callapplybind 或 new 改变;
  • 不能作为构造函数使用。

九、三种解决方案怎么选?

方式特点适合场景
that = this直观、好理解学习 / 老代码
bindthis 固定、安全回调 / 事件
箭头函数最简洁现代开发首选

📌 能用箭头函数,就优先用箭头函数


十、最后总结(一定要记住)

this 的三条铁律

1️⃣ this 永远和“调用方式”有关
2️⃣ 普通函数的 this 会变
3️⃣ 箭头函数没有 this,只继承外层


🌟 写在最后

this 并不可怕,
真正可怕的是:

“凭感觉写 this”

一旦你开始用:

  • “谁调用?”
  • “是不是普通函数?”
  • “是不是箭头函数?”

这三句话去分析,
你会发现:

this 其实非常诚实——它只是忠实地反映了调用现场