js的执行上下文

执行上下文,也叫做代码运行时环境。
理解了它,能让你对js代码的执行逻辑有更清晰的认识。不用在死记硬背面试题,妈妈再也不用担心我的面试了。

什么是执行上下文

简单地说,任何 JavaScript 代码片段在执行前都要进行编译(通常就在执行前)。因此,JavaScript 编译器首先会对 var a
= 2; 这段程序进行编译,然后做好执行它的准备,并且通常马上就会执行它。

这些准备内容就是执行上下文。
执行上下文的生命周期有

  • 创建阶段
  • 执行阶段
  • 销毁阶段

我们主要分析创建阶段,看下编译器在执行上下文的创建阶段都做了哪些工作。

全局执行上下文

先看一个简单的例子

console.log(a)
var a = 10

在这里插入图片描述
想必大部分同学都知道结果。为什么会这样呢?

js在当前的执行上下文的准备阶段,会先进行变量声明(变量提升),这导致在代码的任何阶段你都可以直接使用已经声明的变量,而不用担心报错。

再看一个例子

//函数在准备阶段已经声明
test()
function test(){
    console.log(123)
}
//this在准备阶段已经赋值
console.log(this)

这段代码同样不会报错。因为函数声明也是在执行上下文的准备阶段就已经声明了。
同样在声明阶段也完成了对this 的赋值

我们来总结一下,准备阶段都干了什么

  • 变量的声明
  • 函数的声明
  • this的赋值

注意这里只是全局执行上下文的准备工作,函数执行上下文要比这个复杂一些。

提升优先级

变量声明和函数声明都会被提升,那谁的优先级更高呢?

function test(){
    console.log("hello")
}
var test
test()

在这里插入图片描述
如上图,说明函数的要优先提升。这里需要强调一下,优先提升的含义是,当出现冲突的时候函数声明会覆盖变量声明,而非声明顺序在前。
其实规则要复杂一些,我们再看另一种情况

function test(){
    console.log("a")
}
function test(){
    console.log("b")
}
var test
test()

在这里插入图片描述
如上图,函数重复声明的情况下,新声明的会覆盖老的。
实际上编译器的提升规则是:重复的var声明会忽略,函数优先提升,后面的函数声明会覆盖前面的。

隐式声明

前面说的变量提升,函数提升都是执行上下文的创建阶段完成的。实际上在执行阶段也会发生变量声明,这显然不是最佳实践,因为这回让你的代码出现奇怪的问题。

LHS 和 RHS

在了解隐式声明之前我们要先学习两个概念。LHS和RHS,可以理解成变量的使用方式,赋值还是被赋值。

var a = 1	// a=  使用LHS
var b = a	// b= 使用LHS   =a 使用RHS
console.log(b) //console.log( 使用LHS     (a) 使用RHS

其实很好理解,当我要使用某个变量时需要从作用域链找到该变量,这便是查询。
当我要给这个变量进行赋值操作时我们认为这是一次LHS查询,当我们只是取值时,认为是一次RHS查询。
函数调用可以认为是先去查找函数引用,再使用,所以认为是LHS查询

理解这个概念有什么用呢?
非严格模式下,js引擎执行时,如果从作用域链查询不到某个变量,LHS和RHS下的表现方式不同。

  • LHS:会隐式声明一个变量
  • RHS:会报错 ReferenceError

在LHS下js引擎帮我们声明一个变量并不会影响代码逻辑,但是RHS下会影响。

LHS下的隐式声明

那我们看个例子

function testA(){
    b = 1    // b= 是LHS,从作用域链无法找到变量b,便会在全局作用域隐式声明变量b
    console.log(b)
}

function testB(){
    console.log(b)  // (b) 是RHS,从作用域链可以找到全局作用域下的变量b
}
testA()
testB()

在这里插入图片描述
是不是很神奇,可以看代码里的注释理一下思路。

再看下报错的例子

function testA(){
    console.log(b)  //(b)是RHS,从作用域链无法找到该变量,自然会报错。
}
testA()

在这里插入图片描述

函数执行上下文

在函数上下文的创建阶段也会有变量提升,函数提升,this的赋值,但是要把形参考虑进去。
我们可以把形参看作是隐式的变量声明,只不过形参的赋值在创建阶段,而普通变量的赋值在执行阶段。

看个例子

function test(x,y){
    var x
    console.log(x + y)
}

test(1,2)

在这里插入图片描述
有的人会被 var x 迷惑,认为 x的值是undefined
我们拿之前的结论来分析一下,重复的var声明无效,对于变量x在编译阶段相当于被声明了两次,一次是形参,一次是函数内部变量,所以第二次是无效的
再看一个例子

function test(x,y){
    x =10
    console.log(x + y)
}
test(1,2)
console.log(x)

在这里插入图片描述
这里的x并未在全局作用域下隐式声明,因为在当前作用域的形参里能找到变量。

执行上下文栈

有了执行上下文,js又是如何组织并回收这些上下文呢?这时候就该执行上下文栈上场了。
全局上下文始终存在,所以刚开始栈底是全局上下文,遇到函数调用时会生成一个新的函数上下文并入栈。
程序始终执行栈顶的上下文,当上下文执行完毕则上下文出栈。

举个例子

var a =10
var b =100
function Test(x){
    console.log(x)
}
Test(a)
Test(b)

上下文栈如图,请注意图示的变量并非当前上下文的变量全集。
在这里插入图片描述
需要注意的是函数在调用的时候会生成新的上下文,会对变量重新赋值

This的赋值规则

尽然说到了这里,我们在来探讨下 this 的赋值规则。

function Test(name){
    this.name = name
    this.sayHello = function(){
        console.log("hello I am " + this.name)
    }
}

lb = new Test("迪迦奥特曼")
//这时候 this 指向 lb
lb.sayHello()

// 这时候 this指向了 window  
tmp = lb.sayHello
tmp()

在这里插入图片描述
通过 Object.method的方式调用函数的时候,this始终指向Object,至于上图中的第二中情况,可以理解成window.tmp()。从这个例子里也可以看出来,函数调用时会生成新的执行上下文。

再看下其他的例子

function Test(name){
    this.name = name
    this.sayHello = function(){
        console.log(this)
    }
    
}
dj = new Test("迪迦奥特曼")
sw = new Test("赛文奥特曼")
dj.sayHello.call(sw)

在这里插入图片描述
通过 call 方法和apply 方法可以将 this指向相应的对象。

总结下来就是

  • obj.method的方式调用方法,this指向obj
  • callapply 的方式调用,this指向指定对象
  • 直接调用函数,this 指向window
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

csw_coder

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值