在 JavaScript 中,this
是一个关键字,其值取决于函数的调用方式。理解 this
的行为对于编写健壮和可维护的代码至关重要。本文将深入探讨 this
的不同情况,并特别关注其在定时器(如 setTimeout
和 setInterval
)中的应用。
1. 基本概念
1.1 全局上下文
在全局执行上下文中,this
指向全局对象。在浏览器中,全局对象是 window
。
console.log(this); // 输出: Window {...}
1.2 函数上下文
在函数内部,this
的值取决于函数的调用方式。
-
简单调用:如果函数是直接调用的,
this
指向全局对象(在严格模式下为undefined
)。function simpleFunction() { console.log(this); } simpleFunction(); // 输出: Window {...} (非严格模式) simpleFunction(); // 输出: undefined (严格模式)
-
作为对象方法调用:如果函数是作为对象的方法调用的,
this
指向调用该方法的对象。const obj = { name: 'Alice', sayHello: function() { console.log(`Hello, ${this.name}!`); } }; obj.sayHello(); // 输出: Hello, Alice!
-
作为构造函数调用:如果函数是通过
new
关键字调用的,this
指向新创建的对象。function Person(name) { this.name = name; } const person = new Person('Bob'); console.log(person.name); // 输出: Bob
-
指定
this
调用方式:使用call
、apply
和bind
方法可以显式地设置this
值。function greet() { console.log(`Hello, ${this.name}!`); } const person = { name: 'Charlie' }; greet.call(person); // 输出: Hello, Charlie! greet.apply(person); // 输出: Hello, Charlie! const greetCharlie = greet.bind(person); greetCharlie(); // 输出: Hello, Charlie!
-
箭头函数:箭头函数没有自己的
this
绑定,它会捕获其所在上下文的this
值。const obj = { name: 'David', sayHello: function() { const innerFunction = () => { console.log(`Hello, ${this.name}!`); }; innerFunction(); } }; obj.sayHello(); // 输出: Hello, David!
-
练习
"use strict";
var x = 2;
var obj = {
x: 1,
foo: function() {
console.log(this);
console.log(this.x);
}
}
var foo = obj.foo;
var obj2 = {
x: 5,
foo: foo
};
obj2.foo();
obj.foo();
foo();
- 分析
x
是一个全局变量,值为2
。obj
是一个对象,包含一个属性x
,值为1
,以及一个方法foo
。foo
现在引用了obj
对象中的foo
方法。obj2.foo()
调用时,this
指向obj2
,因此this.x
的值为5
。obj.foo()
调用时,this
指向obj
,因此this.x
的值为1
。- 在严格模式下,直接调用
foo()
时,this
是undefined
,因为没有对象来调用这个函数。 - 因此,
this.x
会输出undefined
。 - 输出
{ x: 5, foo: [Function: foo] } 和 5
{ x: 1, foo: [Function: foo] } 和 1
{} 和 undefined
2. 定时器中的 this
定时器函数 setTimeout
和 setInterval
用于延迟执行代码。理解 this
在这些函数中的行为对于避免常见的错误非常重要。
2.1 普通函数在 setTimeout
中
当普通函数在 setTimeout
的回调中执行时,this
指向全局对象(非严格模式)或 undefined
(严格模式)。
var name = "周杰伦";
var a = {
name: "薛之谦",
func1: function() {
console.log(this.name);
},
fun2: function() {
setTimeout(function() {
this.func1(); // this 指向全局对象(非严格模式)或 undefined(严格模式)
}, 1000);
}
};
a.fun2();
输出(非严格模式):
Uncaught TypeError: this.func1 is not a function
输出(严格模式):
Uncaught TypeError: Cannot read property 'func1' of undefined
解释:
- 在
fun2
方法中,setTimeout
被调用,并传入一个普通函数。 - 这个普通函数在
setTimeout
的回调中执行。 - 在
setTimeout
的回调中,this
指向全局对象(非严格模式)或undefined
(严格模式)。 - 因此,
this.func1()
会报错,因为全局对象(或undefined
)没有func1
方法。
2.2 箭头函数在 setTimeout
中
箭头函数没有自己的 this
绑定,它会捕获其所在上下文的 this
值。
var name = "刀郎";
var a = {
name: "薛之谦",
func1: function() {
console.log(this.name);
},
fun2: function() {
setTimeout(() => {
this.func1(); // this 指向对象 a
}, 1000);
}
};
a.fun2();
输出:
薛之谦
解释:
- 在
fun2
方法中,setTimeout
被调用,并传入一个箭头函数。 - 箭头函数没有自己的
this
绑定,它会捕获其所在上下文的this
值。 - 在
fun2
方法中,this
指向对象a
,因为fun2
是作为对象a
的方法被调用的。 - 因此,箭头函数中的
this
也指向对象a
。 this.func1()
调用了对象a
的func1
方法,输出"薛之谦"
。
2.3 使用 call
、apply
和 bind
指定 this
你可以使用 call
、apply
和 bind
方法来显式地设置 this
值。
使用 bind
var name = "刀郎";
var a = {
name: "薛之谦",
func1: function() {
console.log(this.name);
},
fun2: function() {
setTimeout(this.func1.bind(this), 1000);
}
};
a.fun2();
输出:
薛之谦
解释:
- 在
fun2
方法中,setTimeout
被调用,并传入this.func1.bind(this)
。 bind
方法返回一个新的函数,并永久地绑定this
值为对象a
。- 因此,
setTimeout
的回调函数中的this
指向对象a
。 this.func1()
调用了对象a
的func1
方法,输出"薛之谦"
。
总结
- 对象的方法调用:
this
指向调用该方法的对象。 - 普通函数调用:
this
指向全局对象(非严格模式)或undefined
(严格模式)。 - 构造函数调用:
this
指向新创建的对象。 - 指定
this
调用方式:使用call
、apply
和bind
方法可以显式地设置this
值。 - 箭头函数:箭头函数没有自己的
this
绑定,它会捕获其所在上下文的this
值。