执行上下文

执行上下文


看一组例子:

img

这三句代码里。第一句代码,a没有定义过,报错很正常。第二第三句,a显示的都是undefined,说明我们知道a被定义过了,但是找不到a的值。但是我们对a的定义其实是在console.log(a)之后的,代码如果一句一句从上到下执行其实是不会定义好a的。

那我们其实知道,能够定义a是因为var定义的变量能够进行变量提升。也就是可以先声明变量但是不赋值。

不单单是我们的变量会这样。函数定义时也有不同。

img

我们可以看到,第一种函数声明方式里,我们直接能够把函数进行赋值。但是用var给函数进行声明时,我们只是声明,却并不赋值。这就是函数提升

而这两种情况的本质其实就是我们说的执行上下文。


执行上下文:

我们会有这种提升情况的产生,其实是因为我们JavaScript在执行一个代码段之前,我们会做一些准备工作也就是要创建一个相应的执行上下文对象,来对数据进行预处理。

给执行上下文环境下一个通俗的定义——在执行代码之前,把将要用到的所有的变量都事先拿出来,有的直接赋值了,有的先用undefined占个空。

我们之前所说的代码段其实分为三种:全局代码,函数体,eval代码。(eval不常用也不推荐)

解释一下为什么这些是代码段。所谓“代码段”就是一段文本形式的代码。

所以全局代码是一种,因为它是我们手写文本进入script标签里的

eval代码不谈

函数体本质上是代码段是因为函数在创建时,本质上是new Function(…)得来的,我们在其中需要传入一个文本形式的参数作为函数体。

img

全局执行上下文:

在执行全局代码前,我们会将window确定为全局执行上下文,并对全局数据进行预处理:

  • 声明var定义的全局变量,但赋值为undefined,添加为window的属性。
  • 用function声明的方法function fun(){},都对它们赋值,添加为window 的方法。
  • 把this赋值为window

函数执行上下文:

如果在函数中,除了以上数据,我们还会有其他的数据进行预处理。

img

以上代码展示了,在函数体的语句执行之前,arguments变量和函数的参数都已被赋值好了。**其实从这里我们也能够知道,函数每被调用一次,都会产生一个新的执行上下文环境。**因为不同的调用可能就会有不同的参数。

另外一点不一样的地方在于,函数在定义的时候(不是调用),其实就已经确定了函数体内部自由变量的作用域。

举个例子:
img

那我们总结一下函数执行上下文的数据内容:

  • 形参变量===》赋值为实参,添加为执行上下文的属性
  • var定义的局部变量===》声明但值为undefined,添加为执行上下文的属性
  • 函数声明,如function fun(){}===》赋值(fun),添加为执行上下文的属性。
  • this===》赋值为调用函数的对象
  • arguments===》赋值(实参列表),添加为执行上下文的属性。

对这些数据进行好了预处理,我们就可以开始执行函数体代码了。

我们明白了执行上下文,但是又来了新的问题————我们在执行JavaScript代码的时候,每调用一个函数我们就会创建一个执行上下文环境,那我们调用函数十分频繁,执行上下文环境简直太多了,我们该怎么管理,怎么销毁来释放内存呢?

这个问题,我们就需要用执行上下文栈来解释。


执行上下文栈

我们所有的执行上下文对象,都用一个栈来进行管理。这个栈就是我们说的执行上下文栈。

在全局执行上下文对象(window)确定后,我们将window压入栈中。

也就是说window始终都在栈底

再然后我们遇到函数的调用时,我们又创建了一个执行上下文对象,我们就再把它压入栈中。

而当这个函数被执行完毕之后,我们就会让他出栈,也就会把这个执行上下文对象进行销毁。

举个例子:

let a = 'Hello World!';
function first() {
  console.log('Inside first function');
  second();
  console.log('Again inside first function');
}
function second() {
  console.log('Inside second function');
}
first();
console.log('Inside Global Execution Context');

执行上下文栈的操作:

img

但是有一种情况,无法说销毁就销毁,而且这种情况很常用。

这种情况就是———闭包。


执行上下文栈面试题

console.log('fa:' + i)
var i = 1
foo(1)
function foo(i){
    if(i == 4){
        return
    }
 	console.log('fb:' + i)
    foo(i+1)
    console.log('fc:' + i)
}
console.log('fd:' + i)

读上边的程序,问该程序依次输出什么?

依次输出:
fa:undefined
fb:1
fb:2
fb:3
fc:3
fc:2
fc:1
fd:1
console.log('fa:' + i)
var i = 1  //变量提升,使得fa输出的时undefined
foo(1)
function f(i){
    if(i == 4){
        return   //递归调用结束条件
    }
 	console.log('fb:' + i)
    f(i+1)  //递归调用
    console.log('fc:' + i)
}
console.log('fd:' + i)  //之前的f函数全部被执行完之后都销毁了,i还是最开始的全局变量里的i,所以是1,

靠执行上下文栈来理解一下:


					 f(4)
				f(3) f(3) f(3)
		   f(2)	f(2) f(2) f(2) f(2)
	 f(1)  f(1) f(1) f(1) f(1) f(1) f(1)
全局  全局	全局	全局  全局 全局  全局  全局 全局
function a(){}
var a
console.log(typeof a)

输出什么?

输出:function 这个题考的其实是函数提升和变量提升哪个在先的问题。 那其实**函数提升在先,变量提升在后。**也就是说我们这三行代码可以看作是这样:

var a
function a(){}
console.log(typeof a)

a函数后定义,所以打印出来的a是函数的数据形式

if(!(b in window)){
    var b = 1;
}
console.log(b)

问输出的b是多少?

答案是undefined。我们可以这么看上边这写代码:

var b
if(!(b in window)){
    b = 1
}
console.log(b)

变量提升把if中的var b提前了。所以(b in window)== true 那if条件句中的赋值就不会执行。我们之后打印出来的b也就只是undefined

var c = 1
function c(c){
    console.log(c)
    var c = 3
}
c(2)

问输出什么?

答案:报错!

原因很简单,本质其实还是变量提升和函数提升谁在前的问题。我们知道变量提升在前,函数提升在后,所以其实可以把代码改写一下:

var c
function c(c){
    console.log(c)
    var c = 3
}
c = 1
c(2)

在代码执行中,当执行到c = 1时,c就不再是函数了,而成了变量。所以自然后边调用c函数的时候会报错。

c(2)


问输出什么?

答案:报错!

原因很简单,本质其实还是变量提升和函数提升谁在前的问题。我们知道变量提升在前,函数提升在后,所以其实可以把代码改写一下:

```js
var c
function c(c){
    console.log(c)
    var c = 3
}
c = 1
c(2)

在代码执行中,当执行到c = 1时,c就不再是函数了,而成了变量。所以自然后边调用c函数的时候会报错。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值