1. this的说明
this是在运行时绑定的。在绝大多数情况下,this值的确定与函数的调用方式有关。
2. 全局执行上下文中的this
在全局执行环境中(在任何函数体外部)this 都指向全局对象。在JS引擎运行global code之前,会创建一个全局执行上下文压入执行栈的栈底,这个全局执行上文的ThisBinding绑定的是全局对象,在浏览器里指的就是window。
3. 函数执行上下文中的this
JavaScript是静态作用域,词法环境是由代码结构决定的,开发把代码写成什么样,词法环境就是怎么样,跟方法在哪里调用没有关系。但是对于函数的this刚好反过来,跟代码在哪里定义没有关系,而跟代码在哪里调用有关系。一般我们调用函数有以下四种方式:
- 普通函数调用,比如foo()或者(functon(){})()
- 作为对象方法调用,比如obj.foo()
- 构造函数调用,比如new foo()
- 使用call、apply、bind等方法调用
在介绍着几种函数调用之前,我们先来看下ECMAScript对this的规范:
ECMAScript规范: 严格模式时,函数内的this绑定严格指向传入的thisArgument。非严格模式时,若传入的thisArgument为undefined或null时,函数内的this绑定指向全局的this;不为undefined或null时,函数内的this绑定指向传入的this。
3.1 普通函数调用
普通函数的调用,包括函数调用foo()和立即调用函数表达式(functon(){})(),传到函数里的thisArgument是undefined。根据ECMAScript规范,如果在非严格模式下,普通函数里的this就是全局对象,而在严格模式下就为undefined。
var a = 2;
function foo(val) {
console.log(this.a); //2
console.log(val); //3
}
foo(3);
/**
foo(3) 相当于是:foo().call(undefined,3) 的简化版本。
为了代码方便,所以有直接普通函数调用,thisArgument 默认是undefined,不用每次都敲这个undefined。
**/
改变一下代码:
var a = 2;
function foo() {
"use strict"
console.log(this.a)
}
foo();
这段代码会报错:Cannot read property ‘a’ of undefined
3.2 对象方法调用
作为对象方法调用,传到函数里的thisArgument是该对象。比如有如下代码:
function foo() {
console.log( this.a );
}
var obj = {
a: 2,
foo: foo
};
obj.foo(); //2
需要注意的是,只有离函数最近的这个对象,才是该函数的this,比如有代码:
function foo() {
console.log( this.a );
}
var obj2 = {
a: 42,
foo: foo
};
var obj1 = {
a: 2,
obj2: obj2
};
obj1.obj2.foo(); //42
还有一种比较看起来像对象方法调用,实际上是普通函数调用:
function foo() {
console.log(this.a);
}
var obj = {
a: 2,
foo: foo
}
var bar = obj.foo;
var a = "global variable";
bar(); // global variable
3.3 构造函数调用
new functionname()构造函数调用,this指的是构造出来的新对象。
function foo(a) {
this.a = a;
}
var bar = new foo( 2 );
console.log( bar.a ); // 2
3.4 使用call、apply、bind等方法调用
call、apply、bind调用,可以显示传递对象给函数的thisArg,默认这几个函数的第一个形参是thisArg:
Function.prototype.apply( thisArg, argArray )
Function.prototype.call( thisArg , arg1, [ arg2, ... ] )
Function.prototype.bind( thisArg , [ arg1, [ arg2, ... ] ] )
需要注意的是当thisArg为null或者undefined,在非严格模式下,this是全局对象。
var obj = {
a: 1
};
function print() {
console.log(this);
}
print.call(null);//window
print.call(undefined);//window
print.call(obj);//obj
3.5 箭头函数
箭头函数在调用的时候不会绑定this,它会去词法环境链上寻找this(parent scope),所以箭头函数的this取决于它定义的位置(包裹箭头函数的第一个普通函数的this),也就是箭头函数会跟包着它的作用域共享一个词法作用域。
window.a = 10
const foo = () => { //箭头函数在这里定义
console.log(this.a)
}
foo.call({a: 20}) // 10
let obj = {
a: 20,
foo: foo
}
obj.foo() //10
function bar() {
foo()
}
bar.call({a: 20}) //10
4. 总结
- 非严格模式下,函数普通调用,this指向全局对象。严格模式下,函数普通调用,this为undefined。
- 函数作为对象方法调用,this指向这个对象。
- 函数作为构造方法使用new调用,this指向新创建的对象
- 非严格模式下,函数通过call,apply,bind间接调用,this指向传入的第一个参数,传入的参数如果为undefined或者null,则this指向全局对象。严格模式下,通过call,apply,bind间接调用,this严格指向第一个参数。
- 箭头函数中没有this绑定,this的值取决于其创建时所在词法环境链中最近的this绑定