javascript 执行上下文与上下文this

上下文this是一个面试频率非常高问题,今天我们一起来了解了解this

执行上下文

首先,我们先来看看什么是执行上下文
前一篇文章我们有说到过,代码运行环境主要分为两类

  • 全局运行环境
  • 函数运行环境

而javascript代码的运行,主要分为两个阶段

  • 编译阶段
  • 执行阶段

代码编译主要由编译器完成,而代码执行,由js引擎完成,而执行上下文,其实就是在执行阶段创建的。
代码每进入到一个运行环境,都会创建一个当前环境的执行上下文。而执行上下文在创建的时候,会做以下事情

  • 首先会生成变量对象:上篇文章我们说过,变量对象内部存储着当前环境的所有变量和函数,而变量对象最初是只有arguments参数列表的。所以在生成变量对象时,其实会先创建arguments, 然后检查function函数声明, 再检查var 变量声明。故变量提升,也是在这个阶段实现的。
  • 之后,会确定this的指向。所以这个时候,我们就能想到可能经常会听过的一句话:当一个函数没有被调用时,你永远不知道他的this指向了谁
  • 再之后,会进行变量的赋值以及函数的引用等。

所以this,其实只是执行上下文中的一个属性。它只有在函数被调用的时候才会被确定。

this

下面我们来看下this的使用场景
this的使用其实主要分为以下几种情况

1、全局上下文中

全局上下文中,this始终指向window

var a = 10
console.log(this.a)
输出
10
2、 普通函数调用 (以()的形式直接调用)

当一个函数直接以()的形式调用时,函数内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

3、 作为对象的方法被调用

当函数作为某个对象的方法被对象调用时,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>
4、new 关键字实例化对象时调用

使用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
5、 通过call, apply 改变this指向

当通过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])
6、bind绑定this指向

通过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指向是不会再发生变化的。

7、箭头函数

箭头函数内部的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就写到这了,喜欢就点个赞吧

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值