你有没有觉得,JavaScript 的 this 就像一个职场老油条?
——谁叫它,它就听谁的;没人管,它就躺平装死(或者干脆报错)。
今天,我们就来扒一扒这个 JS 世界里最“见风使舵”的关键字:this。
它不是变量,不是函数,甚至不是个正经对象——但它偏偏无处不在,还总在你最意想不到的时候给你整出点“惊喜”。
🧠 this 是什么?
简单说:this 是一个指针,指向当前函数的调用者。
但问题来了—— “谁调用了我?” 这个问题的答案,直接决定了 this 指向谁。
JS 不像其他语言(比如 Java 或 C++),它的 this 不是在写代码时就定死的,而是在运行时根据调用方式动态决定的!
这就导致了一个经典场景:
var bar = {
myName: 'time.geekbang.com',
printName: function () {
console.log(this.myName);
}
};
bar.printName(); // ✅ 输出 "time.geekbang.com"
let fn = bar.printName;
fn(); // ❌ 输出 undefined(严格模式)
同一个函数,换个姿势调用,this 就变了脸!
这哪是编程语言?分明是川剧变脸!
🔍 this 的五种常见命运
1️⃣ 作为对象的方法被调用 → this 指向该对象
obj.method(); // this === obj
这是最“正常”的情况,符合直觉:方法属于谁,this 就是谁。
2️⃣ 作为普通函数被调用 → this 指向全局对象(或 undefined)
function foo() { console.log(this); }
foo();
// 非严格模式:this === window(浏览器)
// 严格模式:this === undefined 💥
早期 JS 设计者偷了个懒:“反正总得有个 this,那就指向全局吧!”
结果导致无数 bug —— 函数里不小心用了 this.xxx,结果污染了 window!
后来有了 严格模式('use strict') ,终于让这种“裸奔调用”直接报错,算是亡羊补牢。
📌 小知识:
var声明的变量会自动挂到window上,let/const不会。所以:var myName = '极客邦'; console.log(window.myName); // '极客邦' let yourName = '掘金'; console.log(window.yourName); // undefined
3️⃣ 用 call / apply / bind 强制指定 → this 听你的!
fn.call(obj); // this === obj
fn.apply(obj); // this === obj
const bound = fn.bind(obj); bound(); // this === obj
这是 JS 给你的一把“尚方宝剑”——你可以强行让 this 指向任何对象。
手动绑定,天下我有!
4️⃣ 作为构造函数被调用(new)→ this 指向新创建的实例
function Person(name) {
this.name = name; // this 指向新对象
}
const p = new Person('小明');
这时候 this 就像个刚出生的婴儿,等着你给它赋值。
注意:如果构造函数 return 一个对象,this 就白忙活了!
5️⃣ 事件处理函数 → this 指向触发事件的 DOM 元素
<button onclick="handleClick()">点我</button>
<script>
function handleClick() {
console.log(this); // <button> 元素!
}
</script>
在浏览器中,事件回调的 this 自动绑定到目标元素,非常贴心(但也容易让人混淆)。
🤯 自由变量 vs this:别搞混了!
很多人以为 this.myName 和直接写 myName 是一回事——大错特错!
myName是自由变量,遵循词法作用域(Lexical Scope) ,编译阶段就确定了查找路径。this.myName是动态绑定,运行时才决定从哪个对象上取值。
举个栗子🌰:
var myName = '极客邦'; // 全局变量,挂到 window 上
var bar = {
myName: 'time.geekbang.com',
printName: function () {
console.log(myName); // ✅ 词法作用域 → '极客邦'
console.log(this.myName); // ✅ 动态绑定 → 取决于谁调用!
}
};
如果你把 printName 赋值给一个变量再调用:
let fn = bar.printName;
fn();
此时函数是以普通函数形式被调用的。在非严格模式下,this 会指向全局对象(浏览器中是 window)。
而由于之前使用了 var myName = '极客邦' ,这个变量会被自动挂载到 window 上,即:
window.myName === '极客邦'; // true
所以:
console.log(myName)
→ 查找的是词法作用域中的自由变量,结果是'极客邦'(这是确定的、可预测的)。console.log(this.myName)
→ 实际等价于window.myName,结果也是'极客邦',但这只是因为巧合:恰好有一个同名的全局变量被var声明了。
⚠️ 关键区别在于:
- 第一个
'极客邦'来自作用域链(静态绑定,代码结构决定); - 第二个
'极客邦'来自全局对象的属性(动态绑定,运行时调用方式决定)。
如果换成 let myName = '极客邦',或者在严格模式下执行,或者在 Node.js 环境中,结果就会完全不同 —— 第二个可能变成 undefined 或直接报错。
💡 正确结论:
不要因为两个输出“看起来一样”,就认为它们是等价的。
这种“巧合一致”是脆弱的、不可靠的,一旦环境或声明方式改变,程序就会出错。
永远不要依赖this在全局调用时能“碰巧”访问到你想要的变量。
💡 所以:不要依赖全局变量和
this的“巧合匹配” ,那是 bug 的温床!
🛑 为什么说 this 是个“不好的设计”?
正如你上传的 readme.md 中所说:
“JS 做了一个不好的设计:
this由函数的调用方式决定……JS 作者忘了处理情况,偷懒直接让this指向全局。”
确实,this 的行为违背了函数式编程的纯粹性——同一个函数,输入相同,输出却可能不同(因为 this 变了)。
现代 JS 已经提供了更好的替代方案:
- 使用 箭头函数(没有自己的
this,继承外层) - 用 闭包 捕获变量
- 用 模块化 避免全局污染
- 用 class 明确方法归属(虽然 class 底层还是函数,但语义更清晰)
✅ 总结:如何驯服 this 这匹野马?
- 永远开启严格模式:
'use strict',避免this意外指向window。 - 明确调用方式:知道你是用
obj.fn()还是fn()。 - 需要固定
this?用bind! - 不确定
this是谁?打印出来看!console.log(this) - 能不用
this就不用:用参数传值、用闭包、用模块,代码更可靠。
🎉 最后彩蛋
问:如果
this会说话,它第一句会说什么?
答:“Who the hell called me? ”
所以,下次写 JS 时,请对 this 多一分敬畏,少一分放纵。
毕竟——你永远不知道,下一个调用你函数的人,是不是想让你的 this 指向 null。