JavaScript this 指向:别再死记硬背,这样理解最轻松!
前言:从一个生活场景说起
想象一下你在不同的场合介绍自己:
// 在公司里
function introduce() {
console.log("我是" + this.name + ",在" + this.company + "工作");
}
// 场景1:作为员工自我介绍
var employee = {
name: "小明",
company: "阿里",
intro: introduce
};
employee.intro(); // "我是小明,在阿里工作" - this 指向 employee
// 场景2:换个环境说这个介绍
var say = employee.intro;
say(); // "我是undefined,在undefined工作" - this 指向 window
同一个"介绍"的方式,在不同场合下,说的是不同的人! 这就是 this 的核心。
一、为什么 this 会指向全局?一个"忘记带名片"的比喻
1.1 先看一个最简单的例子
var myName = "全局名字";
function sayName() {
console.log(this.myName);
}
sayName(); // 输出:"全局名字"
为什么指向全局?
想象一下:
obj.method()就像是小明在公司会议上说:"我是公司的小明"func()就像是小明在街上突然喊:"我是谁?"- 街上的路人(全局环境)问:"你说的是谁的名字?"
- 小明说:"我没带名片,就随便喊喊"
- 于是大家默认他说的是"街上的名人"(全局变量)
1.2 再看我的学习笔记中的例子
// 这是你的 1.html 中的例子
var bar = {
myName: 'time.geekbang.com',
printName: function () {
console.log(myName); // 找变量(作用域链查找)
console.log(this.myName); // 找"当前名片"上的名字
}
}
var myName = '极客邦';
bar.printName();
// 1. "极客邦" - 变量查找,找到全局的
// 2. "time.geekbang.com" - this 指向 bar,用 bar 的名字
// 关键来了!
var _printName = bar.printName; // 只复制了"说话方式",没复制"名片"
_printName();
// 1. "极客邦" - 还是找到全局变量
// 2. undefined - this 指向 window,window.myName 不存在(如果是 var myName='极客邦',这里会输出"极客邦")
二、this 的 5 种"说话场合"(对应你的笔记)
2.1 场合一:作为员工说话(对象方法)
// 你的 2.html 例子
var myObj = {
name: '极客时间',
showThis: function() {
console.log(this); // 指向 myObj
}
}
myObj.showThis(); // 在"公司"说,说的是"公司的事"
比喻:在公司的会议室里,你说"我们公司",指的是你所在的公司。
2.2 场合二:在街上说话(普通函数)
// 你的笔记中提到:"普通函数运行时。this指向全局对象上"
function foo() {
console.log(this); // window
}
foo();
// 严格模式就是:"别乱说话!"
'use strict';
function strictFoo() {
console.log(this); // undefined
}
strictFoo();
比喻:在街上大喊"我",没有人知道你说的是谁,默认就是"街上的人"(全局)。
2.3 场合三:借别人身份说话(call/apply)
// 你的 2.html 第二个例子
function foo() {
this.myName = '极客时间';
}
let bar = {
myName: '极客邦',
test1: 1
};
foo.call(bar); // "借用 bar 的身份说话"
console.log(bar.myName); // "极客时间" - bar 的名字被改了!
比喻:你拿着别人的名片说:"我是XXX",别人就把你当成那个人了。
2.4 场合四:新人自我介绍(构造函数)
// 你的 4.html 例子(修正版)
function CreateObj() {
// new 的时候:创建一个"新人",让 this 指向他
this.name = '极客时间';
console.log(this); // 指向新创建的对象
}
var myObj = new CreateObj();
console.log(myObj.name); // "极客时间"
比喻:新人入职时说"我是新来的小明",这时候的"我"就是指向这个新人自己。
2.5 场合五:按钮被点击时说话(事件处理)
// 你的 5.html 例子
document.getElementById('link').addEventListener('click', function() {
console.log(this); // 指向被点击的 <a> 元素
});
比喻:按钮被按时说"我被按了",说的就是按钮自己。
三、最容易出错的"名片丢失"问题
3.1 的经典案例
var myObj = {
name: "极客时间",
showThis: function() {
this.name = '极客邦'
console.log(this);
}
}
// 第一种:拿着名片说话
myObj.showThis(); // 输出 myObj,this.name 被改为"极客邦"
// 第二种:名片丢了!
var foo = myObj.showThis; // 只复制了"说话方式"
foo(); // 输出 window,this.name 把 window.name 改了!
关键理解:
myObj.showThis是"小明的说话方式"var foo = myObj.showThis只是复制了"怎么说话"- 没有复制"小明的名片"
- 所以
foo()说话时,不知道说谁,默认说全局
3.2 箭头函数:自带名片夹
var myObj = {
name: "极客时间",
showThis: function() {
// 普通函数:名片可能会丢
setTimeout(function() {
console.log(this.name); // undefined,名片丢了
}, 100);
// 箭头函数:把名片夹在身上
setTimeout(() => {
console.log(this.name); // "极客时间",名片还在
}, 100);
}
}
myObj.showThis();
四、给你的学习笔记补充的"避坑指南"
4.1 为什么会有这种设计?
"js的作者忘了处理这种情况...偷懒...直接让js指向全局?"
更准确的理解是: 早期 JavaScript 想让网页脚本编写更简单
// 1995年的网页可能是这样的
<button onclick="changeColor()">变红色</button>
<script>
// 如果没有全局 this,每个函数都要这么写:
function changeColor() {
document.body.style.color = 'red';
// 如果 this 不指向 window,就得写成 window.document...
}
</script>
4.2 对象在内存的位置
//bar 是一个对象,定义在全局作用域
//对象在堆内存 在变量环境的栈内存中存放的只是对象在堆内存中的地址
对象在堆里,变量只是存了个地址。同样:
this也是个"地址",指向当前说话的对象- 函数调用时,才确定这个地址指向谁
4.3 三个实用的记忆口诀
-
"点前是谁就是谁"
obj.method(); // this → obj -
"没人点就找全局"
method(); // this → window(非严格模式) -
"箭头函数看爸爸"
obj.method = () => { console.log(this); // 看定义时的外层 this };
五、实际开发中的"生存法则"
5.1 法则一:不确定时,先打印看看
function test() {
console.log('this 是:', this);
console.log('this 的类型:', typeof this);
}
// 各种情况都试试:
test(); // 独立调用
obj.test = test; obj.test(); // 作为方法
new test(); // 构造函数
test.call(document.body); // 指定 this
5.2 法则二:用 bind 把名片钉死
var obj = {
name: '小明',
say: function() {
console.log('我是' + this.name);
}
};
// 名片容易丢
var say = obj.say;
say(); // "我是undefined"
// 用胶水粘住名片(bind)
var boundSay = obj.say.bind(obj);
boundSay(); // "我是小明" - 名片粘住了!
5.3 法则三:class 中注意 this
class Button {
constructor() {
this.text = '点击';
// ❌ 这样会丢名片
this.element.addEventListener('click', this.handleClick);
// ✅ 用 bind 粘住
this.element.addEventListener('click', this.handleClick.bind(this));
// ✅ 或者用箭头函数(自动粘)
this.element.addEventListener('click', () => {
this.handleClick();
});
}
handleClick() {
console.log(this.text); // 需要正确的 this
}
}
总结:一张图搞定 this
谁在调用函数? → this 指向谁?
----------- -----------
对象.方法() → 这个对象
方法() → 全局(或 undefined)
new 函数() → 新创建的对象
函数.call(对象) → call 的第一个参数
箭头函数 → 定义时的外层 this
最后记住:this 就像是你说话时的"我"。
- 在会议室说"我" → 指你自己
- 在街上说"我" → 街上的人不知道你说谁,默认指"街上的人"
- 拿着别人名片说"我" → 指名片上的人
- 箭头函数说"我" → 永远指你爸爸是谁,你就是谁
多写代码,多打印 console.log(this),很快你就会形成直觉。加油!