上下文this是一个面试频率非常高问题,今天我们一起来了解了解this
首先,我们先来看看什么是执行上下文
前一篇文章我们有说到过,代码运行环境主要分为两类
而javascript代码的运行,主要分为两个阶段
代码编译主要由编译器完成,而代码执行,由js引擎完成,而执行上下文,其实就是在执行阶段创建的。
代码每进入到一个运行环境,都会创建一个当前环境的执行上下文。而执行上下文在创建的时候,会做以下事情
所以this,其实只是执行上下文中的一个属性。它只有在函数被调用的时候才会被确定。
下面我们来看下this的使用场景
this的使用其实主要分为以下几种情况
全局上下文中,this始终指向window(严格模式下是undefind)
var a = 10
console.log(this.a)
输出
10
当一个函数直接以()的形式调用时,函数内this始终指向全局上下文window(严格模式下是undefind)
setTimeout(function () {
aaa()
}, 0)
function aaa () {
console.log(this) // window
}
这里有一种立即指向函数或者回调函数的情况,看下面代码
var promise = new Promise(function (resove, reject) {
console.log(this)
})
setTimeout(function () {
console.log(this)
}, 1000)
输出
window
window
上述代码中,new Promise内部是一个函数,但是这个函数内的this是指向window的,这是因为这个函数在Promise()这个方法内其实是通过 fun() 普通函数的形式去调用的,故指向window(看了我前面写Promise源码的人应该就能知道这个函数是怎么调用的)
而setTimeout的回调函数也是一样,在setTimeout这个方法中也是通过普通调用的方式去调用了这个函数,故this也指向window
当函数作为某个对象的方法被对象调用时,this指向调用该方法的对象
var obj = {
name: 'chen',
say: say
}
function say () {
console.log(this)
console.log(this.name)
}
obj.say()
输出
{
name: 'chen', say: [Function: say] }
chen
还有个是事件方法被触发时,方法内的this指向触发事件的dom元素。因它其实也是属于被dom对象所调用,所以我就把它归为这一类了
<button class="btn">点我</button>
<script>
var btn = document.querySelector('.btn')
btn.onclick = function () {
console.log(this)
}
</script>
输出
<button class="btn">点我</button>
使用new实例化一个对象时调用构造函数,那么函数内的this就会指向new 出来的那个实例
function China (name, age) {
this.name = name
this.age = age
}
const zhangSan = new China('张三', 18)
console.log(zhangSan.name, zhangSan.age)
输出
张三 18
当通过call,apply调用某个方法时,可以指定该方法调用时的this指向。故这种行为也被称为方法借用
var zhang = {
name: '张三',
age: 18,
say: say,
sayAag: sayAag
}
var wang = {
name: '王五',
age: 20
}
function say () {
console.log(this.name)
}
function sayAag () {
console.log(this.age)
}
zhang.say.call(wang)
zhang.sayAag.apply(wang)
输出
王五
上述代码可以看出,zhang对象有个say的方法,而 wang对象并没有,此时zhang调用他的say方法时,使用call方法将this指向wang,那么say内部输出的name其实就是wang的name了,apply也是一样。
call和apply区别在于后面第参数的传递,一个是传递参数列表(一个一个的形式传递),一个是传递包含参数的数组。
obj.fun.call(obj2, a, b, c)
obj.fun.apply(obj2, [a, b, c])
通过bing给一个方法绑定一个对象后,那么这个方法内部this就始终指向被绑定的对象(不会再发生变化)
bind方法和call,apply不同,bind不会立即触发函数的调用,他其实是返回了一个新的函数,并将这个新的函数的this指向了被绑定的对象。
注意:bind只会生效一次,也就是说你如果给新返回的函数再使用bind重新绑定一个对象,那么,是不会生效的(this指向不会再次发生改变)
var name = '张三'
var obj = {
name: '王五',
age: 20
}
var obj2 = {
name: '李四',
age: 28
}
function say () {
console.log(this.name)
}
var fun = say.bind(obj)
fun()
fun.call(obj2)
输出
王五
王五
上述代码可以看出,第一个fun()直接通过普通方式调用时,原本this应该是指向window,输出会是张三的,但通过bind使他的this始终指向了obj,故输出的是obj的name
而此时我们通过call方法去改变this指向时,this指向是不会再发生变化的。
箭头函数内部的this,是指向这个箭头函数所处的上下文中的this指向
obj = {
name: '张三',
say: () => {
console.log(this)
}
}
obj.say()
输出
window
上述代码可以看出,调用obj.say this原本是指向obj的,但因为say方法用的是箭头函数,那么this指向这个箭头函数所处的上下文的this。而这个箭头函数是不是处在全局环境中啊。所以指向window。再看一个例子
var obj = {
say: say
}
function say () {
setTimeout(function () {
console.log(this)
}, 1000)
setTimeout(() => {
console.log(this)
}, 2000)
}
obj.say()
输出
window
obj
可以看出,setTimeout内的回调函数默认是普通方式调用的(这个我们在第一种类型中说过),故第一个输出window,而当回调函数以箭头函数的形式使用时,他的this就指向函数外的上下文this了,而函数外指的是say函数内的this, 当obj.say()调用时,say函数内的this是不是指向obj啊, 所以setTimeout内的this也会指向obj
this就写到这了,喜欢就点个赞吧