在箭头函数出现之前,每个新定义的函数都有它自己的 this值(在构造函数的情况下是一个新对象,在严格模式的函数调用中为 undefined,如果该函数被称为“对象方法”则为基础对象等)。 ES6 引入了支持this词法解析的箭头函数(它在闭合的执行上下文内设置this的值)。
在具体说this的4种应用场景前,先从函数调用开始说起,以方便我们理解之后的代码。当我们执行一个函数,以下几种调用方式都是等价的 :
"use strict"
function fn(a,b){
console.log(this)
}
fn(1, 2)
//等价于
fn.call(undefined, 1, 2)
fn.apply(undefined, [1, 2])
- 在严格模式下, fn 里的 this 就是 call 的第一个参数,也就是 undefined。
- 在非严格模式下(不加”use strict”), call 传递的第一个参数如果是 undefined 或者 null, 那 this 会自动替换为 Window 对象。
var obj = {
fn: function(a, b){
console.log(this)
},
child: {
fn2: function(){
console.log(this)
}
}
}
obj.fn(1, 2)
//等价于
obj.fn.call(obj, 1, 2) // 所以 this 是 obj
obj.fn.apply(obj, [1, 2])
obj.child.fn2()
//等价于
obj.child.fn2.call(obj.chid) // 所以 this 是 obj.child
除去不常用的with和eval等情况,具体到实际应用中,this指向大致可以分为以下4种。
1. 作为对象的方法调用
2. 作为普通函数调用
3. 构造函数调用
4. 箭头函数
作为对象的方法调用
当函数作为对象的方法被调用时,this指向该对象。
var obj = {
a:1,
getA: function(){
console.log(this === obj); //true
console.log(this.a); //1
}
}
obj.getA();
作为普通函数调用
当函数不能作为对象的属性被调用时,也就是我们常说的普通函数调用方式,此时的this总是指向全局对象(window对象), Es5的严格模式下会指向undefined。
window.name = 'globalName';
var getName = function(){
return this.name;
}
console.log(getName()) //globalName
//或者
window.name = 'globalName';
var myObject = {
name: 'sven',
getName: function(){
return this.name;
}
}
var getName = myObject.getName;
console.log(getName()); //globalName
构造函数调用
当用new运算符调用函数时,该函数总会返回一个对象,通常情况下,构造器里的this就指向返回的这个对象。代码如下:
var MyClass = function(){
this.name = 'sven';
}
var obj = new MyClass();
console.log(obj.name); //sven
//或者
console.log(new MyClass().name) //sven
如果构造函数不显示的返回任何数据,或者是返回一个非对象类型的数据,此时this指向没有问题。代码如下:
var MyClass = function(){
this.name = 'sven';
return 'anne'; //返回string类型
}
var obj = new MyClass();
console.log(obj.name); //输出sven
需要注意的是,用new调用构造函数时,如果构造函数显示地返回了一个object类型的对象,那么此次运算结果最终会返回这个对象,而不是之前期待的this。
var MyClass = function(){
this.name = 'sven';
return { //显示地返回一个对象
name: 'anne'
}
}
var obj = new MyClass();
console.log(obj.name); //输出anne
为了更好的理解new的过程,可以参考
箭头函数
箭头功能不会创建自己的this;它使用封闭执行上下文的this值。
在全局代码中,它将被设置为全局对象。
var window = this;
var foo = (() => this);
console.log(foo() === window); //true
当在其他函数中创建的箭头函数:这些箭头函数的this被设置为外层执行上下文。
let app = {
fn1: function(a){
console.log(this) //app
},
fn2(a) {
console.log(this) //app
},
fn3: (a)=>{
console.log(this) //window
}
}
app.fn1();
app.fn2();
app.fn3();
粗略一看,fn1、fn2、fn3 貌似都一样,实际上 fn1和 fn2完全等价,但 fn3是有区别的
app.fn2相当于app.fn2.call(app)
app.fn3相当于app.fn3.call( 它的上一级的 this)。
因为箭头函数不会绑定this,所以会找它的上一级,找到window。
我们可能会写出如下代码:
function Person(){
this.age = 0;
setInterval(function growUp(){
this.age++;
console.log(p.age); // 每隔一秒打印age
},1000)
}
var p = new Person();
上面代码相当于
function Person(){
this.age = 0;
function growUp(){
this.age++;
console.log(p.age); // 每隔一秒打印age
}
// 过1秒后执行
growUp();
}
var p = new Person();
普通函数执行,this为window,而不是Person;在ECMAScript 3/5中,通过将this值分配给封闭的变量,可以解决this问题。代码如下:
function Person(){
this.age = 0;
var that = this;
setInterval(function growUp(){
that.age++;
console.log(p.age); //每隔一秒打印age
},1000)
}
var p = new Person();
有了箭头函数之后,我们可以写出下面的代码:
function Person(){
this.age = 0;
setInterval(() => {
this.age++;
console.log(p.age); //每隔一秒打印age
},1000)
}
var p = new Person();
因为箭头函数不会绑定this,所以会找它的上一级,找到Person对象。
再来个稍微复杂点的例子:
var app = {
init() {
var menu = {
init: ()=>{
console.log(this)
},
bind() {
console.log(this)
}
}
menu.init()
/*相当于 menu.init.call(menu 所在的环境下的 this) , 所以 init 里面的 this 也就是 app。
(假设 app.init 也是箭头函数,想想 menu.init 里面的 this 是什么?)
*/
menu.bind()
/*相当于 menu.bind.call(menu),也就是 menu,所以 bind 里面的 this 就是 menu
*/
}
}
app.init()
最后一个例子:
var app = {
fn1() {
setTimeout(function(){
console.log(this)
}, 10)
},
fn2() {
setTimeout(()=>{
console.log(this)
},20)
},
fn3() {
setTimeout((function(){
console.log(this)
}).bind(this), 30)
},
fn4: ()=> {
setTimeout(()=>{
console.log(this)
},40)
}
}
app.fn1() //window
app.fn2() //app
app.fn3() //app
app.fn4() //window
上面的代码可以转化为:
var app = {
fn1() {
function fn(){
console.log(this)
}
//过10ms 后执行
//fn.call(undefined) ,所以输出 Window
},
fn2() {
//过20ms 执行箭头函数
//箭头函数里面没资格有 自己的 this,借用 setTimeout 外面的 this,也就是 app
},
fn3() {
// 可以转化为
var fn = function(){
console.log(this);
}
var fn2 = fn.bind(this);
setTimeout(fn2,30);
// 创建了一个新函数,这个新函数里面绑定了 外面的this,也就是 app
// 20 ms 后执行新函数,输出 this,也就是刚刚绑定的 app
},
fn4: ()=> {
//过40ms 执行箭头函数
//箭头函数里面没资格有 this,用 setTimeout 外面的 this
//setTimeout 所在的 fn4也是箭头函数,没资格拥有自己的 this,借用外面的 this ,也就是 Window
}
}
注: 因为箭头函数并不绑定this,因此使用箭头函数后的对象尝试使用call,apply,bind设定this是无效的。
参考阅读:
1. JavaScript设计模式与开发实践–曾探著
2. this
3. this-course
4. this 的值到底是什么?一次说清楚
5. What’s this?