在 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值。