执行上下文,作用域, 闭包

执行上下文,作用域链与闭包

引用自链接: https://yangbo5207.github.io/wutongluo/ji-chu-jin-jie-xi-lie/si-3001-zuo-yong-yu-lian-yu-bi-bao.html?q=.

引用自链接: https://blog.csdn.net/yukamilo/article/details/107166526.

执行上下文以及作用域

  • 在JavaScript中,我们可以将作用域定义为一套规则,这套规则用来管理引擎如何在当前作用域以及嵌套的子作用域中根据标识符名称(变量名,函数名)进行变量查找。
  • JavaScript中只有全局作用域函数作用域
  • 作用域与执行上下文是完全不同的两个概念。详见下图:
    在这里插入图片描述
执行上下文
  • js代码的整个执行过程,首先被编译器翻译成可执行代码,然后确定作用域规则,这部分我们不用关注,我们更关注js引擎进行的上下文创建过程以及执行过程,而垃圾回收有助于我们理解闭包。
    在这里插入图片描述
  • 如上图所示, 当js引擎自上而下遇到可执行语句时,就会生成一个执行上下文,并且采用函数调用栈的方式处理这些上下文的执行顺序,每遇到一句可执行语句,就创建对应的 执行上下文,然后传入栈顶,从当前上下文首行代码开始执行。调用栈中栈底永远是全局上下文,当前执行的永远是栈顶上下文,栈顶上下文执行完毕后出栈。
    注意:函数中,遇到return能直接终止可执行代码的执行,因此会直接将当前上下文弹出栈。
  • for example:
function f1(){
    var n=999;
    function f2(){
        alert(n);
    }
    return f2;
}
var result=f1();
result(); // 999

在这里插入图片描述
-执行上下文包含三部分内容:变量对象VO,作用域链ScopeChain, this。
- 创建

  • 执行上下文的生命周期包括创建执行
    在这里插入图片描述
  • 首先创建: 按照变量提升和函数提升规则按照自上而下的顺序生成变量对象,建立作用域链,确定this指向。
  • 执行阶段: 首先进行变量赋值,然后函数引用,最后执行其他代码,执行完毕后,等待垃圾回收。
    -执行上下文我们简单理解为一个上下文对象,创建他的过程就是在其内部创建变量对象,创建作用域链,this三个属性的过程
  • 变量对象是啥?
    变量对象上下文内部函数以及声明变量的集合。
  • 变量对象的创建,依次经历了以下几个过程。
  1. 建立arguments对象。检查当前上下文中的参数,建立该对象下的属性与属性值。

  2. 检查当前上下文的函数声明,也就是使用function关键字声明的函数。在变量对象中以函数名建立一个属性,属性值为指向该函数所在内存地址的引用。如果函数名的属性已经存在,那么该属性将会被新的引用所覆盖。

  3. 检查当前上下文中的变量声明,每找到一个变量声明,就在变量对象中以变量名建立一个属性,属性值为undefined。如果该变量名的属性已经存在(包括同名函数),为了防止同名的函数被修改为undefined,则会直接跳过,原属性值不会被修改。

function a(){console.log('a')}
var a = 10
//我们将上述代码按照创建上下文变量对象时的顺序来重写
function a(){console.log('a')}
//var a = undefined  因为变量名和函数名重合了,为了防止函数a被篡改为undefined, 该句声明跳过。
// ------------------
//下面是一个普遍情况
function a(){console.log('a')}
var b = 10
//按照创建执行上下文变量对象的顺序来写
function a(){console.log('a')}
var b = undefined

所以以上例子对应的变量对象分别是

//例一
VO = {
	arguments = {...},
	a: <a reference> //a函数地址的引用
}
//例二
VO = {
	arguments = {...},
	a: <a reference>,
	b: undefined
}

上图中的变量对象只有在执行上下文时候才能被访问,换句话说,只有处于函数调用栈栈顶的执行上下文中的变量对象,才会变成活动对象。

作用域链
  • 作用域链,是由当前环境与上层环境的一系列变量对象组成,它保证了当前执行环境对符合访问权限的变量和函数的有序访问
var a = 20;

function test() {
    var b = a + 10;

    function innerTest() {
        var c = 10;
        return b + c;
    }

    return innerTest();
}

test();
  • 在上面的例子中,全局,函数test,函数innerTest的执行上下文先后创建。我们设定他们的变量对象分别为VO(global),VO(test), VO(innerTest)。而innerTest的作用域链,则同时包含了这三个变量对象,所以innerTest的执行上下文可如下表示。
innerTestEC = {
    VO: {...},  // 变量对象
    scopeChain: [VO(innerTest), VO(test), VO(global)], // 作用域链
}

在这里插入图片描述

  • 作用域链是由一系列变量对象组成,我们可以在这个单向通道中,查询变量对象中的标识符,这样就可以访问到上一层作用域中的变量了。
闭包
  • 通过闭包,我们可以在其他的执行上下文中,访问到函数的内部变量
  • 立即执行函数的作用是:1.创建一个独立的作用域,这个作用域里面的变量,外面访问不到,这样就可以避免变量污染。2.闭包和私有数据。提到闭包,不得不提下那道经典的闭包问题。
	//要求输出1-5
      for (var i = 1; i <= 5; i++) {
        setTimeout(function timer() {
          console.log(i);
        }, i * 1000);
      }
    // 输出结果是5个6
  • 想解决以上问题,需要通过闭包来实现
  • 如果我们直接这样写,根据setTimeout定义的操作在函数调用栈清空之后才会执行的特点,for循环里定义了5个setTimeout操作。而当这些操作开始执行时,for循环的i值,已经先一步变成了6。因此输出结果总为6。而我们想要让输出结果依次执行,我们就必须借助闭包的特性,每次循环时,将i值保存在一个闭包中,当setTimeout中定义的操作执行时,则访问对应闭包保存的i值即可。而我们知道在函数中闭包判定的准则,即执行时是否在内部定义的函数中访问了上层作用域的变量。我们需要包裹一层自执行函数为闭包的形成提供条件。因此,我们只需要2个操作就可以完成题目需求,一是使用自执行函数提供闭包条件,二是传入i值并保存在闭包中

      for (var i = 1; i <= 5; i++) {
      //利用立即执行函数可以生成独立作用域的特点,立即执行函数生成的上下文需要访问全局上下文中的变量i,所以i会被保留
		  (function (i) {
		    setTimeout(function timer() {
		      console.log(i);
		    }, i * 1000);
		  })(i)
      }
      

最后着重补充一下作用域和上下文的关系

  • 全局作用域之外,每个函数都会创建自己的作用域,作用域在函数定义时就已经确定了。而不是在函数调用时

  • 全局执行上下文环境是在全局作用域确定之后, js代码马上执行之前创建

  • 函数执行上下文是在调用函数时, 函数体代码执行之前创建

  • 作用域是静态的, 只要函数定义好了就一直存在, 且不会再变化

  • 执行上下文是动态的, 调用函数时创建, 函数调用结束时就会自动释放

  • 联系:
    执行上下文(对象)是从属于所在的作用域

    全局上下文环境==>全局作用域

    函数上下文环境==>对应的函数使用域

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值