this,是一个对象,this的值取决于code是怎么执行的。
在开始之前,我们需要理解一下JavaScript运行时环境和JavaScript code是如何执行的。
执行环境
代码行被执行时所处的环境被称为执行环境,JavaScript runtime维护着一个栈,栈里面包含这些执行环境,位于栈顶的执行环境是当前正在执行的环境。this指向的对象随着执行环境的改变而改变。
this指向全局对象
执行环境默认是全局的——意味着,如果代码作为简单函数调用执行,那么this指向全局对象。
在浏览器中,全局对象指window对象。在Node.js环境里,有一个叫做global的特殊对象是全局对象。
例如:
function foo() {
console.log(“这就叫做简单函数调用”);
console.log(this===window);
}
foo(); //true
console.log(this === window) //true
立即调用的函数表达式(IIFE)
(function() {
console.log(“Anonymous function invocation”);
console.log(this === window);
})();
//true
如果函数是严格模式,那么this的值将会被标记为 undefined。全局对象指向undefined而不是window对象。
例如:
function foo() {
‘use strict’;
console.log(“simple function call”);
console.log(this === window);
}
foo(); //false
严格模式下,全局执行环境中的this值为undefined。
“this”指向一个新实例
当使用new关键字调用函数的时候,这个函数就被作为构造器函数并且返回一个新实例。在这类情况下,this的值指向那个新创建的实例。
比如:
function Persion(fn, ln) {
this.first_name = fn;
this.last_name = ln;
this.displayName = function() {
console.log(‘Name:${this.first_name} ${this.last_name}’);
}
}
let person = new Persion(“John”, “Reed”);
persion.displayName(); //John Reed
let persion2 = new Persion(“Paul”, “Adams”);
persion2.displayName(); //Paul Adams
“this”指向主调对象(父对象)
在JavaScript中,对象的属性可以是方法和简单的值。当对象调用方法时,方法里面的this就指向这个对象。
在本例中,我们将使用到foo方法(在第一个例子中定义的)
function foo() {
‘use strict’;
console.log(“Simple function call”);
console.log(this === window);
}
let user = {
count: 10,
foo:foo,
foo1: function() {
console.log(this ===window);
}
}
user.foo(); //false, 因为现在this指向user对象而不是全局对象
let fun1 = user.foo1;
user.foo1() //false, foo1作为一个对象的方法被调用.
“this”与Call和Apply方法
JavaScript中,函数也是特殊的对象。每个函数都有call,bind,apply方法。这三个方法可以被用来给this设定一个自定义的值。
我们将使用到在解释call使用的之前定义的第二个例子(代码如下):
function Person(fn, ln) {
this.first_name = fn;
this.last_name = ln;
this.displayName = function(){
console.log(‘Name:${this.first_name} ${this.last_name}’);
}
}
let person = new Person(“John”, “Reed”);
person.displayName(); // John Reed
let persion2 = new Person(“Paul”, “Adams”);
person2.dispalyName(); // Paul Adams
person.displayName.call(person2); // 在这里我们设置this的值为person2对象
// 打印: Paul Adams
call方法和apply方法唯一的不同是参数传递的方式, 对于apply,第二个参数是参数数组,而对于call,参数被单独传递。
“this” 与 bind方法
bind方法返回一个新方法,this指向传递的第一个参数。我们将使用上面的例子来解释bind方法。
function Person(fn, ln) {
this.first_name = fn;
this.last_name = ln;
this.displayName = function(){
console.log(‘Name:${this.first_name} ${this.last_name}’);
}
}
let person = new Person(“John”, “Reed”);
person.displayName(); // John Reed
let persion2 = new Person(“Paul”, “Adams”);
person2.dispalyName(); // Paul Adams
let person2Display = person.displayName.bind(person2); // 创建一个函数,this的值等于person2对象
person2Dispaly(); // 打印: Paul Adams
“this”和宽箭头(Fat-Arrow)函数
在ES6中,引入了一种新方式来定义函数。
let dispalyName = (fn, ln) => {
console.log('Name:${fn} ${ln}');
console.log(this === window); // true
};
当宽箭头被使用时, 它并没有为this创建新值。this继续引用它在函数外面引用的同一对象(什么叫在函数外面引用的对象?)。
更新: 解释为什么 => 箭头函数没有this, this会作为变量一直往上级词法作用域查找, 经常导致undefined错误(来自vue.js)
我们来看更多的例子来测试this的知识
function multiply(p, q, callback) {
callback(p*q); // 译者: 简单函数调用,所以callback方法内的this为全局对象
}
let user = {
a:2,
b:3,
findMultiply: function(){
multiply(this.a, this.b, function(total) {
console.log(total);
console.log(this === window);
})
}
}
user.findMultiply();
// 打印 6
// 打印 true
因为callback 在一个乘法函数中被当作简单函数调用来引用, this指向callback方法的执行环境里面的全局对象window
var count =5;
function test() {
console.log(this.count===5);
}
test() // 打印 true,因为count变量声明发生在全局执行环境中,所以count将成为全局对象的一部分
Summary
所以现在你能理解this的值,通过下面这些简单的规则:
- 默认情况下,this指向一个全局对象,其对于浏览器是window,对于Nodejs是global
- 当函数作为对象的属性调用时,this就指向这个父对象
- 当用new关键字调用函数时,this指向新创建的实例
- 当使用call和apply方法调用函数时,this指向call或apply方法的第一个参数传递的值
如你所见,this的值有时候是很迷惑的,但是以上的规则能够帮助你理解this的值。
本文为英文article的翻译: Understanding the “this” Keyword in JavaScript
翻译不易,客官点赞/收藏/关注呀,giao~