JavaScript中`this` 的“千层套路”:从默认绑定到箭头函数的五种指向

JavaScript中this 的"千层套路":从默认绑定到箭头函数的五种指向

摘要this 是 JavaScript 最让人头疼的概念。本文从一段 setTimeout 的"惊喜"代码入手,拆解 this 的五种绑定规则------普通函数、对象方法、事件处理、call/apply/bind 手动绑定、箭头函数------帮你彻底告别 this 困惑。

📑 目录

  • 一段让你怀疑人生的 this 代码
  • 规则一:默认绑定------普通函数调用
  • 规则二:隐式绑定------作为对象方法调用
  • 规则三:事件处理函数绑定------this 指向触发元素
  • 规则四:手动绑定------call、apply、bind
  • 规则五:箭头函数------没有 this 的 this
  • 一点总结
  • 互动讨论

一段让你怀疑人生的 this 代码

先来看这段代码:

javascript

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

你本期待看到"姆巴佩",结果却是"梅西"。

setTimeout 里的函数明明是写在 obj 里面的,为什么 this 指向了外面的 梅西

这段代码覆盖了 this 的多个关键规则:普通函数、对象方法、setTimeout 异步执行、全局变量污染。理解了它,就理解了一大半的 this 规则。

我们从最基础的规则开始拆解。

规则一:默认绑定------普通函数调用

当一个函数作为普通函数(不是对象方法、不是构造函数、不是事件处理函数)被调用时,this 指向全局对象(浏览器中是 window,Node.js 中是 global)。

javascript

ini 复制代码
var name = '小许';
function say() {
    console.log(this.name);
}
const fn = obj.say;
fn(); // 输出:小许(this 指向 window)

这里有一个完整的示例:

javascript

javascript 复制代码
let obj = {
    name: "大许",
    say: function() {
        console.log(this);
        console.log(`${this.name}`);
    }
}
const fn = obj.say; // 引用式赋值
fn(); // 这里函数是作为普通函数被调用,this 指向全局 window

obj.say 本来是对象的方法,但通过引用赋值给 fn 后,调用方式变成了"普通函数调用",this 丢失了原来的指向,回到了默认绑定。

如何在 window 上找到 name

javascript

ini 复制代码
var name = '梅西'; // var 会挂载到 window
// 或
window.name = '梅西';

letconst 声明的变量不会挂载到 window,即使是在全局作用域。common.js 的注释明确说明了这一点:

var 声明会在变量环境中,会直接作为全局对象 window 的属性。let 声明会在词法环境中,即使是全局变量也是与 window 对象隔离开的。

严格模式下的区别

可以在js文件第一行加上一句:"use strict"; 来强制切换为严格模式

严格模式下,普通函数调用时,this 无法通过默认绑定绑到外层作用域的全局对象上,这时 this 的值会变成 undefined

javascript

javascript 复制代码
"use strict";
function say() {
    console.log(this); // undefined
}
say();

这其实是一种"纠错"设计------避免你在普通函数中误用全局对象。

规则二:隐式绑定------作为对象方法调用

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

javascript

javascript 复制代码
let obj = {
    name: "姆巴佩",
    say: function() {
        console.log(this.name);
    }
}
obj.say(); // "姆巴佩"

这是最直观的场景:谁调用了函数,this 就指向谁

高度总结:

this 是一个指针,指向函数的调用者。函数运行时指定(不是声明的时候),不是像变量那样在编译阶段指定。

规则三:事件处理函数绑定------this 指向触发元素

当函数作为事件处理函数被调用时,this 指向触发事件的元素对象。

javascript

javascript 复制代码
document.querySelector('.lnk').addEventListener('click', goBaidu);

function goBaidu(e) {
    console.log(this); // 指向 <a href="..."> 元素
    e.preventDefault(); // 阻止跳转
}

作为事件处理函数被调用,this 指向触发事件的元素对象。

关键点

  • 事件处理函数的 this 不是 window,也不是定义函数的对象------而是触发事件的 DOM 元素。
  • 这就是为什么 addEventListener 里的回调函数不能用箭头函数,因为它会丢失 this(后面会讲)。

为什么 e.preventDefault() 很重要?

背景说明:

form 表单提交会默认提交,即使没有指定 action,也会在本页面提交,导致刷新页面。e.preventDefault() 阻止默认行为,页面不会刷新,可以后续在 JS 中处理数据提交,实现动态页面。

同样,<a href=""> 链接也会默认跳转,用 e.preventDefault() 阻止。

规则四:手动绑定------call、apply、bind

callapplybind 可以手动指定函数执行时的 this 指向。

方法 是否立即执行 传参方式 返回
call 立即执行 逐个传递 函数执行结果
apply 立即执行 数组传递 函数执行结果
bind 不执行,返回新函数 逐个传递 新函数

javascript

ini 复制代码
let obj = { name: "大许" };
let obj2 = { name: "大祥" };

obj.say.call(obj2);   // this → obj2,立即执行
obj.say.apply(obj2);  // 同上,传参用数组
const newSay = obj.say.bind(obj2); // 返回新函数,不立即执行
newSay();             // 执行新函数,this → obj2

// 有参数的情况
obj.speak.call(obj2, 1, 2);      // 逐个传
obj.speak.apply(obj2, [1, 2]);   // 数组传
const fn2 = obj.speak.bind(obj2);
fn2(1, 2);

bind 的典型场景:异步回调

关键场景:

bind 手动指定 this,返回一个全新的函数(不是立即执行的),使用场景是异步时的手动修改 this 指向。

回到开头的 setTimeout 问题,bind 可以解决:

javascript

javascript 复制代码
setTimeout((function() {
    console.log(this.name); // this 被绑定到 obj
}).bind(obj), 1000);

因为 bind 返回新函数且不立即执行,正好满足 setTimeout 延迟执行的场景。

规则五:箭头函数------没有 this 的 this

箭头函数不创建自己的 this ,它会从外层作用域中"继承" this

javascript

javascript 复制代码
let obj3 = {
    name: '姆巴佩',
    say: function(){
        setTimeout(() => {
            console.log(this.name); // "姆巴佩"
        }, 1000);
    }
}
obj3.say();

箭头函数里没有 this,所以它不会"丢失" this------它直接使用了外层 say 函数的 this(指向 obj3)。

箭头函数是简化函数,它根本不考虑 this,所以写在箭头函数中的 this 和写在箭头函数外的 this 是一样的。

这就是为什么 setTimeout 中用箭头函数能拿到正确的 this------它根本不创建自己的 this,直接从外层取。

一点总结

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

一句话心法this 不是你在哪里写的,而是你在哪里怎么调用的 。箭头函数是唯一的例外------它根本没有 this

互动讨论

  1. setTimeout 的第一个例子中,为什么 var name = '梅西' 能影响输出,而用 let name = '梅西' 就不行?
  2. 事件处理函数中,为什么不能用箭头函数替代普通函数? 试想如果 addEventListener('click', () => {})this 会指向什么?
  3. callapply 有什么区别? 什么场景下用 apply 更合适?
  4. bindcall 最大的区别是什么? 为什么异步场景需要 bind
  5. 为什么 JavaScript 要设计 this 这个机制? 如果完全没有 this,代码会变成什么样?

📌 一点心得:this 不是 bug,它是一种设计选择------让函数在不同的调用上下文中复用。理解了"谁调用,指向谁"这条铁律,再加上箭头函数这个例外,你就掌握了 this 的核心。

相关推荐
foxire1 小时前
基于nodejs实现服务端内核引擎
javascript
触底反弹4 小时前
🧠 搞懂 Token,才算真正入门大模型——从分词原理到 Embedding 语义实战
javascript·人工智能·算法
free354 小时前
AST Interpreter 的设计:为什么分 evaluate() 和 execute()
javascript
等咸鱼的狸猫4 小时前
JavaScript 隐式类型转换:从入门到精通
javascript
kyriewen7 小时前
我用 Codex 重写了同事维护三年的代码,他没说谢谢——而是找了领导
前端·javascript·ai编程
铁皮饭盒7 小时前
S3已成为文件存储标准,阿里/腾讯/华为云都支持,Bun率先原生支持
前端·javascript·后端
Cobyte7 小时前
22.Vue Vapor 组件 props 的实现
前端·javascript·vue.js
浮生望9 小时前
JS字符串与回文算法:从包装类到双指针的面试进阶之路
javascript·算法
疯狂的魔鬼9 小时前
一套 Schema 驱动四视图:记 useCrudSchemas 的设计与实践
前端·javascript·typescript