JavaScript 中的 this
关键字通常用于引用当前执行上下文中的对象,但它的值是动态的,并且在不同的上下文中有不同的值。理解 this
的词法原理是理解 JavaScript 中对象和函数之间关系的关键。
在箭头函数出现之前,每一个新函数都重新定义了自己的 this 值(在构造函数中是一个新的对象;在严格模式下是未定义的;在作为“对象方法”调用的函数中指向这个对象;等等)。以面向对象的编程风格,这样着实有点恼人。😓
以下是 this
词法的几种情况。
全局作用域中的 this 指向什么?
在全局作用域中,this 指向全局对象(在浏览器中为 window 对象,在 Node.js 中为 global 对象)。
代码如下:
console.log(this === window); // true
函数中的 this 指向什么?
在函数内部,this 的指向取决于函数的调用方式。如果函数作为普通函数调用,this 将指向全局对象。如果函数作为对象的方法调用,this 将指向该对象。如果函数使用 call、apply 或 bind 方法调用,this 将指向指定的上下文对象。
代码如下:
1.函数作为普通函数调用。
function foo() {
console.log(this === window);
}
foo(); // true
2.函数作为对象的方法调用
var obj = {
bar: function() {
console.log(this === obj);
}
};
obj.bar(); // true
3.函数使用 call、apply 或 bind 方法调用
var baz = {
qux: function() {
console.log(this === baz);
}
};
var quux = baz.qux.bind(baz);
quux(); // true
构造函数中的 this 指向什么?
在使用 new 关键字调用构造函数时,this 将指向新创建的对象实例。
代码如下:
function Person(name) {
this.name = name;
}
var person = new Person('Alice');
console.log(person.name); // Alice
箭头函数中的 this 指向什么?
箭头函数的 this 绑定在函数定义时的词法环境中,即在函数所在的父级作用域中。因此,在箭头函数内部使用的 this 与它所在的上下文无关。
var obj = {
foo: function() {
setTimeout(() => {
console.log(this === obj);
}, 0);
}
};
obj.foo(); // true
在上面的例子中,首先,foo 作为“对象方法”调用,此时 this 指向这个 obj 对象。然后,setTimeout 中定义了一个箭头函数,该箭头函数中的 this 绑定在函数定义时的词法环境中,即在函数所在的父级作用域 foo 中,所以 console.log(this === obj);
输出为 true
.
这个复杂示例你学会了吗?
学会了以上这些规则,我们来看个复杂点的示例: 下面这个例子中,我们结合了构造函数、定时器,先不看注释,看看你知道答案吗?
function Person() {
// 构造函数 Person() 将`this`定义为自身
this.age = 0;
setInterval(function growUp() {
// 在非严格模式下,growUp() 函数将`this`定义为“全局对象”,
// 这与 Person() 定义的`this`不同,
// 所以下面的语句不会起到预期的效果。
this.age++;
}, 1000);
}
var p = new Person();
我们都知道,定时器 setInterval() 方法可以重复调用一个函数或执行一个代码片段,在每次调用之间具有固定的时间间隔。
上例代码中,setInterval()
方法的第一个参数传入了 growUp 函数,看过基本类型和引用类型区别一文的老铁都知道,这第一个参数传入的实际上是 growUp 函数的内存地址。在 1000 毫秒后再执行函数 growUp 时,this 已经指向了“全局对象”,而非 Person() 定义的this
。
那么,想要达到预期的效果,我们要如何解决呢?
2 种解决方法
方法一:在 ECMAScript 3/5 里,通过把this的值赋值给一个变量可以修复这个问题。
代码如下:
function Person() {
var self = this; // 有的人习惯用`that`而不是`self`,
// 无论你选择哪一种方式,请保持前后代码的一致性
self.age = 0;
setInterval(function growUp() {
// 以下语句可以实现预期的功能
self.age++;
}, 1000);
}
方法二:箭头函数捕捉闭包上下文的this值,所以下面的代码工作正常。
代码如下:
function Person(){
this.age = 0;
setInterval(() => {
this.age++; // 这里的`this`正确地指向 person 对象
}, 1000);
}
var p = new Person();
写在最后
总之,this
的值在函数被调用时动态计算,而不是在函数定义时静态计算。在理解 this 的工作原理时,理解 JavaScript 中执行上下文、作用域链、原型链等概念也很重要。
如果这篇文章对你有帮助,请不要吝啬你手中的赞👍!你的鼓励将是我不断分享的动力!😄 前端更多知识及内容请关注如下【同步更新】: