1.为什么要真正理解this?
因为在JavaScript之中,this是动态绑定的,或者称为运行期绑定的,它极为灵活但是正因为如此它绑定的函数很多人来说并不清楚,这需要真正的理解才能更好的使用它。
在使用this的过程中我们有一句话是这么说的:谁调用了该函数,那么this就指向谁。这句话在大部分情况下都是适用的,我们后面也将根据这句话进行探讨。
2.函数在不同情况下this的值
2.1函数作为普通函数调用
//非严格模式下
function fn1() {
return this
}
//在浏览器中
fn1() === window
//在Node中
fn1() === global
//严格模式下
"use strict";
function fn2() {
return this
}
fn2() === undefined //true
fn1()
等价于window.fn1()
或者 global.fn1()
这样的话就不难理解为什么它们的指向会是全局变量了,只是严格模式禁止了该行为,所以返回了undefined
(严格模式如果显示调用也不会显示undefined
)。
2.2作为对象的方法调用
var name = 'window'
var obj = {
name: 'obj',
fn1: function() {
console.log(this.name)
}
}
obj.fn1() // obj
这里是对象的显式调用,很明显我们能看到obj
直接调用了fn1
方法,所以this
指向的就是obj
。
var name = 'window'
var obj = {
name: 'obj',
fn1: function() {
console.log(this.name)
}
}
var fn2= obj.fn1
fn2() // window
从这里我们可以看出,虽然fn1
是在obj
里面定义的,但是最终赋值给了fn2
,当fn2
执行的时候this
的指向就不再是obj
了,而是window
,类似于2.1的例子。
2.3关于setTimeout和setInterVal
2.3.1 基本使用
var name = 'window'
var obj = {
name: 'obj',
fn1: function() {
setTimeout(function() {
console.log(this.name)
},0)
}
}
obj.fn1() // window
//严格模式下
"use strict";
var name = 'window'
var obj = {
name: 'obj',
fn1: function() {
setTimeout(function() {
console.log(this.name)
},0)
}
}
obj.fn1() // window
setTimeout
和setInterVal
的this
指向是window
(全局对象),这是因为调用的代码运行在与所在函数完全分离的执行环境上导致的。
而且值得注意的是即使是在严格模式下,setTimeout
的回调函数里面的this
仍然默认指向window
对象, 并不是undefined
。
现在我们对上述的代码进行修改,使其能够正确的指向obj:
2.3.2 解决方案一
// 解决方案一:使用箭头函数
var name = 'window'
var obj = {
name: 'obj',
fn1: function() {
setTimeout(() => {
console.log(this.name)
},0)
}
}
obj.fn1() // obj
我们知道,箭头函数是从自己的作用域链的上一层继承this
的,所以它取的是fn1
里的this
,而fn1
是由obj
调用的,所以this
指向的就是obj
。
2.3.3 解决方案二
// 解决方案二:使用闭包
var name = 'window'
var obj = {
name: 'obj',
fn1: function() {
var that = this
setTimeout(function() {
console.log(that.name)
},0)
}
}
obj.fn1() // obj
在fn1
里将this
赋值给了that
,然后在setTimeout
函数的回调里使用了that
,使得能正确地取到obj
的值。这个乍一看有点神奇,但是慢慢剖析就会明白原理其实很简单。我们从作用域、作用域链、执行上下文、执行上下文栈、变量对象、活动对象全了解可以知道fn1
的作用域在定义这个函数的时候已经确定,那么该作用域里that
这个变量的值也就随之确定了(也就是this
),然后setTimeout
函数在调用的时候使用了that
,这样一来,我们就能正确取到obj
里的值了。
2.3.4 解决方案三
// 解决方案三:使用bind/apply/call
var name = 'window'
var obj = {
name: 'obj',
fn1: function() {
setTimeout(function() {
console.log(this.name)
}.bind(obj ),0)
}
}
obj.fn1() // obj
这一个是最简单的我们最好理解的方式了,它显式地指定this
指向了obj
,那问题自然很轻松地解决了。
2.4 作为构造函数调用
var name = 'window'
function Person(name) {
this.name = name ;
}
var p = new Person('obj')
console.log(p.name) // obj
当使用new
关键字的时候this
会被绑定在正在构造的新对象。当使用new
关键字的时候this
会被绑定在正在构造的新对象。
基于上面的了解,我把他们整理成了这一张图: