一次关于执行上下文的深入了解

  最近在刷 冴羽 大大的JavaScript深入系列文章 很良心的文章,再看到第8章 JavaScript深入之执行上下文 的时候发现一个很有趣的题目。

   这里做个笔记。把之前的内容串起来。。毕竟看文章不如自己写一遍心得,好记性不如烂笔头。。。

  以下的内容很多参考了 冴羽 大大的JavaScript深入系列文章 以及 九死蚕传人bo的前端基础进阶目录

 

 

底下的一条评论吸引了我的眼光。。 ,checkscope已经出栈了,为啥子checkscope上下文入栈时创建的变量对象还能访问= =。。 后面才想清楚是关于数据结构的问题。 执行上下文是保存在栈结构的。一般来说,执行上下文出栈,那么入栈的过程中创建的变量也会被回收= = 。。。后面才想到。 可执行代码执行完了,执行上下文就出栈了,变量的回收基于垃圾回收机制,只有当变量没有再被引用的时候才会被回收。这是两种不同的数据结构问题。 简单说就是执行上下文的出栈取决于可执行代码执行完了,变量的回收取决于垃圾回收机制,虽然变量是在执行上下文入栈的时候创建的,但是这是两码子关系。就好比:大明生了一个儿子小明,但是小明寄样在婆婆家(堆结构),大明住在自己家(栈结构),大明家着火大明去世(出栈)并不会造成小明也跟着去世(被垃圾回收机制回收)

 

 

 

  两端代码看似结果一样,但是内在的执行上下文栈缺不一样。 这里尽量还原一下为啥是这种结果,个人心得。推荐小伙伴们直接去看原链接

1.数据结构:堆,栈,队列

  首先js中JavaScript的执行上下文沿用了栈结构,即我们经常说的执行上下文栈,栈是一个什么样的数据结构呢。简单说就是:先进后出,后进后出。就像高中时化学实验的量筒,假设往量筒中放入乒乓球的模型一样

  一号球是最开始放进去的,5好球是最后放入的。但是如果要把所有球拿出来,那么就是5号球先拿出来,一号球最后。执行上下文栈就是这种结构。

 

  堆的结构类似于图书馆的藏书架子一样,书本整齐的放在书架上,你只需要知道相对应的书架行号和列号,那么就可以直接拿到这本书,类似于对象的数据格式一样 。

  js中的变量都以堆的形式放在内存中

  

var obj = {name:'cat',age:2} //假设我要拿到cat这个值,我只要知道name这个参数 直接使用obj.name就可以拿到

队列 

    队列的机构类似于人的消化系统一样。。先吃进去的东西先消化,后出的东西必须等前面吃的消化完了之后才能消化。总结就是:先进先出,后进后出。 js中事件的循环机制依托于队列结构。

 

 

2.执行上下文

  竟然知道了执行上下文是以栈的结构存在的,那么执行上下文到底是啥。

  我们都知道js引擎是按顺序一行一行的去编译代码的,每当js引擎遇到可执行代码的时候就会创建一个执行上下文,可以理解为当前代码执行所处的环境。而这个可执行代码的环境分为三种

  1. 全局环境:JavaScript代码运行起来会首先进入该环境 (script标签)
  2. 函数环境:当函数被调用执行时,会进入当前函数中执行代码( fn() )
  3. eval(不建议使用,可忽略)

  这里注意一点。。。是函数调用的时候才会创建一个执行上下文 函数声明的时候并不会 

  当创建一个执行上下文的时候又会分两个步骤 1:创建阶段 2执行阶段

  创建阶段干了三件事  

  • 创建变量对象 (vo variable object)
  • 对this的赋值 (  这里说明了为啥this是在调用的时候确定值的,而不是函数声明的时候确定值得. 函数调用--创建上下文 -- this进行赋值 )
  • 建立作用域链  (   作用域是在函数声明时候确定的  js的作用域是词法作用域 词法作用域 = 声明时就已经确定了 动态作用域 = 在调用的时候确定,类似于this的赋值= =  注意一下作用域链是作用域的嵌套关系确定的。。 老哥们去看大大们的文章吧= = 我也算是一点点懂 )

   其中变量对象的创建又干了三件事情 。。。为啥这么多事情 懵逼中

  1. 建立arguments对象。检查当前上下文中的参数,建立该对象下的属性与属性值。
  2. 检查当前上下文的函数声明,也就是使用function关键字声明的函数。在变量对象中以函数名建立一个属性,属性值为指向该函数所在内存地址的引用。如果函数名的属性已经存在,那么该属性将会被新的引用所覆盖。
  3. 检查当前上下文中的变量声明,每找到一个变量声明,就在变量对象中以变量名建立一个属性,属性值为undefined。如果该变量名的属性已经存在,为了防止同名的函数被修改为undefined,则会直接跳过,原属性值不会被修改。

   总结:函数声明优先于变量声明,函数声明会覆盖前面的声明,变量声明如果前面已经声明过了就跳过不会覆盖  (这里涉及到了声明提升的面试问题啦。。。)

   举个栗子:

  

 

 

  执行流程是这样滴

 

 

 

  皮拉巴拉创建完之后就进入到了执行阶段,当可执行代码全部执行完成当前执行上下文出栈,也就是说当函数内的代码执行完毕之后它就出栈释放了。ok 看一下最开始放的两端代码的执行上下文栈结构

 

 代码分析

  第一部分

 1 var scope = "global scope";
 2 function checkscope(){
 3     var scope = "local scope";
 4     function f(){
 5         return scope;
 6     }
 7     return f(); // 相当于 var _content = f() ; return _content 就是先执行f() 然后把f()执行后返回的scope 再一次返回出去
 8 }
 9 checkscope();
10 
11 //执行上下文栈的情况
12 /*  ECStack 栈结构
13 *   1首先全局执行上下文global context 入栈在栈底      ---ECStack = [global context]
14 *   2执行到底16行 checkscope函数调用执行 checkscope context 入栈  ---ECStack = [global context , checkscope context]
15 *   3这时候进入第13行checkscope函数内部执行 遇到第17行 f()函数调用 f context入栈 --- ECStack = [global context,checkscope context, f context]
16 *   4进入f函数内部 执行到第16行 f函数可执行代码完成全部执行完毕出栈 --- ECStack = [global context,checkscope context]
17 *   5这时候重新返回checkscope函数第18行 返回f函数执行完返回的scope  checkscope可执行代码完成全部执行完毕出栈 --- ECStack = [global context]
18 *   6ok 这时候只剩下全局上下文。。 全局上下文只有在网页关闭的时候才出栈
19 *
20 */

 

  第二部分

  

var scope = "global scope";
function checkscope(){
    var scope = "local scope";
    function f(){
        return scope;
    }
    return f;
}
checkscope()(); //相当于 var _test = checkscope() ;
                //  _test()

//执行上下文栈的情况
/*  ECStack 栈结构
*   1首先全局执行上下文global context 入栈在栈底      ---ECStack = [global context]
*
*   2执行到底9行 checkscope函数调用执行 checkscope context 入栈  ---ECStack = [global context , checkscope context]
*
*   --------------------这里都与前面的第一段代码一样 ------------------------------------
*
*   3这时候进入第9行checkscope函数内部执行  执行到第7行全部代码执行完毕了 checkscope context出栈 ---ECStack = [global context] 。 但是因为返回了一个f函数的引用 虽*然出栈了,但是垃圾回收机制因为函数f的引用还在被占用(下面f函数会被调用) 没法进行回收形成闭包(相当于第九行的注释 )
*
*   4 第九行第二个括号相当于第十行 f函数进行调用 f context 入栈 --- ECStack = [global context,f context] 。注意这里和上面第一部分代码的区别 checkscope已经出栈了
*
*   5 f函数可执行代码完成全部执行完毕出栈  --- ECStack = [global context]
*
*   6 ok 这时候只剩下全局上下文。。 全局上下文只有在网页关闭的时候才出栈
*
*/

 

   这时候就回答了这个问题啦 第二部分的代码checkscope确实是出栈了 只是因为形成了闭包 返回了一个f函数的引用 垃圾回收机制无法进行回收  好咯 吃饭去咯

  

posted @ 2018-04-11 19:17  carrot萝卜  阅读(507)  评论(0编辑  收藏  举报