从原理到实践:JavaScript中的this指向,一篇就够了
从原理到实践:JavaScript中的this指向,一篇就够了
前言
最近在复习JavaScript的this指向问题,写了几个小例子来加深理解。很多同学觉得this难,其实是因为this是在函数执行时确定的,而不是定义时。这个特性导致了this指向的“善变”。
今天,就让我通过这几个代码例子,带你由浅入深掌握JavaScript中的this指向。
第一章:基础概念 - this的默认绑定
1.1 全局环境下的this
在浏览器全局环境中,this指向window对象:
var name = "windowName"; // 全局变量
var func1 = function() {
console.log('func1');
}
console.log(this.name); // "windowName"
console.log(window.name); // "windowName"
核心知识点:
- 全局作用域下,
this === window - 用
var声明的全局变量会自动挂载到window对象上
第二章:谁调用我,我就指向谁
2.1 对象方法调用
var a = {
name: "Cherry",
func1: function() {
console.log(this.name);
}
}
a.func1(); // "Cherry"
关键理解: 这里的this指向了对象a。因为func1是由a调用的。
2.2 经典面试题 - 定时器中的this
// 2.html 中的例子
var a = {
name: "Cherry",
func1: function() {
console.log(this.name);
},
func2: function() {
setTimeout(function() {
this.func1(); // 这里会报错!
}, 3000)
}
}
a.func2(); // TypeError: this.func1 is not a function
为什么报错?
定时器的回调函数是由定时器内部调用的,此时this指向了window对象。而window对象上并没有func1方法(虽然有全局变量func1,但这里调用的是对象方法)。
验证一下:
var a = {
// ... 同上
func2: function() {
setTimeout(function() {
console.log(this); // window
}, 3000)
}
}
第三章:解决this丢失的三种方案
3.1 方案一:保存this(that = this)
// 3.html 中的例子
var a = {
name: "Cherry",
func1: function() {
console.log(this.name);
},
func2: function() {
var that = this; // 保存外层的this
setTimeout(function() {
that.func1(); // "Cherry" ✅
}, 3000)
}
}
a.func2(); // 3秒后输出 "Cherry"
原理: 利用闭包的特性,内部函数可以访问外部函数的变量。that保存了正确的this引用。
3.2 方案二:bind绑定
// 2.html 中的例子
var a = {
name: "Cherry",
func1: function() {
console.log(this.name);
},
func2: function() {
setTimeout(function() {
this.func1();
}.bind(a), 3000) // bind永久绑定this为a
}
}
a.func2(); // 3秒后输出 "Cherry" ✅
重要区别:
-
bind():不会立即执行,返回一个新函数,永久绑定this -
call()/apply():立即执行函数,临时绑定this
// 对比演示
a.func1.call(a); // 立即执行
const boundFunc = a.func1.bind(a); // 返回绑定后的函数,不执行
boundFunc(); // 执行时this已经绑定为a
3.3 方案三:箭头函数
// 4.html 中的例子
var a = {
name: "Cherry",
func1: function() {
console.log(this.name);
},
func2: function() {
setTimeout(() => {
console.log(this); // a对象 ✅
this.func1(); // "Cherry" ✅
}, 3000)
}
}
a.func2(); // 3秒后输出 "Cherry"
箭头函数的特点:
- 没有自己的
this - 继承定义时所在作用域的
this -
this是静态的,不会改变
第四章:深入理解箭头函数
4.1 箭头函数没有自己的this
// 5.html 中的例子
const func = () => {
console.log(this); // window(在浏览器环境)
}
func(); // window
4.2 箭头函数不能作为构造函数
const func = () => {
console.log(this);
}
new func(); // TypeError: func is not a constructor ❌
为什么?
箭头函数没有自己的this,也没有prototype属性,无法进行实例化。
4.3 关于arguments对象
const func = () => {
console.log(arguments); // ReferenceError ❌
}
// 箭头函数也没有自己的arguments对象
// 但可以这样获取参数
const func2 = (...args) => {
console.log(args); // [1, 2, 3] ✅
}
func2(1, 2, 3);
第五章:综合实践 - 分析一段复杂代码
让我们来分析1.html中的代码,它包含了多个知识点的综合运用:
var name = "windowName";
var func1 = function() {
console.log('func1');
}
var a = {
name: "Cherry",
func1: function() {
console.log(this.name);
},
func2: function() {
setTimeout(function() {
this.func1(); // 这里原本有问题
return function() {
console.log('hahaha');
}
}.call(a), 3000) // ⚠️ 注意:这里用了call
}
}
这里有个坑:
setTimeout的第一个参数应该是函数,但.call(a)会立即执行这个函数,并把返回值作为第一个参数传给setTimeout。这里返回的是undefined,相当于:
// 实际执行效果
setTimeout(undefined, 3000)
正确写法:
// 使用bind(不会立即执行)
setTimeout(function() {
this.func1();
}.bind(a), 3000)
// 或者使用箭头函数
setTimeout(() => {
this.func1(); // 这里的this继承自func2
}, 3000)
第六章:面试题精选
6.1 经典组合题
var name = 'window';
var obj = {
name: 'obj',
fn1: function() {
console.log(this.name);
},
fn2: () => {
console.log(this.name);
},
fn3: function() {
return function() {
console.log(this.name);
}
},
fn4: function() {
return () => {
console.log(this.name);
}
}
}
obj.fn1(); // 'obj' - 对象方法调用
obj.fn2(); // 'window' - 箭头函数,this指向外层window
obj.fn3()(); // 'window' - 独立函数调用
obj.fn4()(); // 'obj' - 箭头函数,this继承自fn4的this
6.2 优先级问题
function foo() {
console.log(this.name);
}
const obj1 = { name: 'obj1', foo };
const obj2 = { name: 'obj2' };
obj1.foo(); // 'obj1'
obj1.foo.call(obj2); // 'obj2' - call优先于隐式绑定
const bound = foo.bind(obj1);
bound.call(obj2); // 'obj1' - bind绑定后,call无法改变
this绑定优先级:
-
new绑定(最高) -
call/apply/bind显式绑定 - 对象方法调用(隐式绑定)
- 默认绑定(独立函数调用,最低)
总结
this的指向规律其实很简单,记住这几点:
- 函数被调用时才能确定this指向
- 普通函数:谁调用我,我指向谁
- 箭头函数:我在哪里定义,this就跟谁一样
- 可以通过
bind永久绑定this,call/apply临时绑定this
理解了这些,JavaScript的this问题就迎刃而解了。
练习题
// 尝试分析下面的输出
const obj = {
name: 'obj',
say: function() {
setTimeout(() => {
console.log(this.name);
}, 100);
}
}
obj.say(); // 输出什么?
const say = obj.say;
say(); // 输出什么?
答案和解析欢迎在评论区讨论!
如果你觉得这篇文章对你有帮助,请点赞👍收藏⭐,让更多的小伙伴看到!我们下期再见!