• Javascript 执行环境和作用域


      *javascript引擎内部在执行代码以前到底做了些什么?为什么某些函数以及变量在没有被声明以前就可以被使用?以及它们的最终的值是怎样被定义的?

      伴随着这些问题,再深入学习一下Javascript的执行环境、作用域,以备后续的闭包学习。

    一、执行上下文

      在Javascript中有三种代码执行环境:

    • Global Code

      全局级别的代码。这个是最默认的代码执行环境,是最外围的一个执行环境(在web浏览器中,全局执行环境被认为是window对象)。一旦代码被载入,引擎最先进入的是这个环境。

    • Function Code

      函数级别的代码。当执行一个函数时,运行函数体中的代码。

    • Eval Code

      在Eval函数中运行的代码

      先看一个栗子:

           var color = "blue";
            function changeColor(){
                var anotherColor = "red";
                function swapColors(){
                    var tempColor = anotherColor;
                    anotherColor = color;
                    color = tempColor;
                }
                swapColors();
            }
            changeColor();
            alert("color is now:"+color);  //red
            alert("tempColor:"+tempColor); //wrong

      在这个例子中,总共有三个执行环境:全局环境、changeColor()的局部环境、swapColors()的局部环境。为什么在swapColors()函数中,可以访问幷改变变量color的值?最后在全局环境中却不能访问swapColors()函数中定义的变量tempColor?

      这又得学习执行上下文堆栈。

    二、执行上下文堆栈

      在浏览器中,javascript引擎的工作方式是单线程的。也就是说,某一时刻只有唯一的一个事件是被激活处理的,其它的事件被放入队列中,等待被处理。而每个函数都有自己的执行环境,当执行流进入一个函数时,函数的环境就会被推入一个环境栈中。而在函数执行之后,栈将其环境弹出,把控制权返回给之前的执行环境。

      

     三、建立阶段和代码执行阶段

      每当调用一个函数时,一个新的执行上下文就会被创建出来。然而,在javascript引擎内部,这个上下文的创建过程具体分为两个阶段:(1)建立阶段;(2)执行阶段

      Javascript引擎在每个阶段都做什么工作呢?

      先看几个例子:

        alert(message);
            var message;

      结果:

      

      恩??不是应该报错吗?可是却没有,Javascript引擎在执行alert函数时,貌似已经知道message已经被声明了,那如果在后边给初始化一下呢,在执行alert函数时,会不会知道这个message变量在后边被初始化了呢?

      测试下:  

        alert(message);
           var message = 10;

      结果:

      恩??看来Javascript引擎在执行alert函数前就已经声明了变量message,所以顺序是:声明变量message->执行alert()->初始化变量message。

      再看个例子:

         console.log(fun);
            function fun(a,b){
                return a+b;
            }

      结果:

      

      由此可知,在Javascript引擎执行console.log()方法时就已经知道fun是一个方法,而且还知道方法里的具体操作。

      那再看:

         console.log(f);
            var f = function fun(a,b){
                return a+b;
            }

      结果:

      

      在我们定义一个函数有几种方式,其中函数声明和函数表达式这两种情况在Javascript引擎工作时却有着差别。后者的f会被先申明。

      根据查看的资料可知:Javascript引擎在建立阶段主要做三件事:

    1. 建立变量,函数,arguments对象,参数
    2. 建立作用域链(后续还得继续学习)
    3. 确定this的值(后续还得继续学习)

      实际上,可以把执行上下文看做一个对象,其下包含了以上3个属性:

     executionContextObj = {
       variableObject: { /* 函数中的arguments对象, 参数, 内部的变量以及函数声明 */ },
       scopeChain: { /* variableObject 以及所有父执行上下文中的variableObject */ },
       this: {}
     }

      而在代码执行阶段主要工作就是变量赋值,函数引用以及执行其它代码。

    四、执行流程(http://blogread.cn/it/article/6178)

      确切地说,执行上下文对象(上述的executionContextObj)是在函数被调用时,但是在函数体被真正执行以前所创建的。函数被调用时,就是我上述所描述的两个阶段中的第一个阶段 - 建立阶段。这个时刻,引擎会检查函数中的参数,声明的变量以及内部函数,然后基于这些信息建立执行上下文对象(executionContextObj)。在这个阶段,variableObject对象,作用域链,以及this所指向的对象都会被确定。

    上述第一个阶段的具体过程如下:

    1. 找到当前上下文中的调用函数的代码

    2. 在执行被调用的函数体中的代码以前,开始创建执行上下文

    3. 进入第一个阶段-建立阶段:

      • 建立variableObject对象:

        1. 建立arguments对象,检查当前上下文中的参数,建立该对象下的属性以及属性值

        2. 检查当前上下文中的函数声明:

          • 每找到一个函数声明,就在variableObject下面用函数名建立一个属性,属性值就是指向该函数在内存中的地址的一个引用

          • 如果上述函数名已经存在于variableObject下,那么对应的属性值会被新的引用所覆盖。

        3. 检查当前上下文中的变量声明:

          • 每找到一个变量的声明,就在variableObject下,用变量名建立一个属性,属性值为undefined。

          • 如果该变量名已经存在于variableObject属性中,直接跳过(防止指向函数的属性的值被变量属性覆盖为undefined),原属性值不会被修改。

      • 初始化作用域链

      • 确定上下文中this的指向对象

    4. 代码执行阶段:

      • 执行函数体中的代码,一行一行地运行代码,给variableObject中的变量属性赋值。

      通过例子看下:

         var a =10,
                   fn,
                   bar = function(x){
                       var b = 5;
                       fn(x+b);
                   }
            fu = function(y){
                var c = 5;
                console.log(y+c);
            }
            bar(10);

      在执行代码之前,首先会创建全局的上下文环境。

    全局上下文环境
    a undefined
    fn undefined
    bar undefined
    this window

      

      然后是代码执行。在代码执行bar(10);之前,上下文的变量都被赋值。

    全局上下文环境
    a 10
    fn function
    bar function
    this window

      然后就是执行bar(10);。在执行函数体语句之前,Javascript引擎先会进入准备阶段。创建一个新的执行上下文环境。

    bar函数的上下文环境
    b undefined
    x 10
    arguments

    {

    0:10,

    length:1

    }

    this window
    scope {...}

      执行开始,走到语句var b = 5;时,函数上下文变化为;

      

    bar函数的上下文环境
    b 5
    x 10
    arguments

    {

    0:10,

    length:1

    }

    this window
    scope {...}

      然后又走到fn(x+b);时调用函数fn,此时又会创建fn函数的上下文。

    fn函数的上下文环境
    c undefined
    y 15
    arguments

    {

    0:15,

    length:1

    }

    this window
    scope {...}

      然后开始执行fn函数的代码,fn函数的上下文变为:

    fn函数的上下文环境
    c 5
    y 15
    arguments

    {

    0:15,

    length:1

    }

    this window
    scope {...}

      fn函数的代码执行完后,fn函数的上下文就会被销毁,然后继续执行bar函数的代码,执行完bar函数的代码后,销毁bar函数的上下文。

      整个上下文的堆栈过程如图所示:

      

      这下,我们就知道为什么在调用之后声明都不会出错了~

    推荐:

    http://www.cnblogs.com/wangfupeng1988/

    http://web.jobbole.com/84044/

  • 相关阅读:
    nodejs express hi-cms
    写让别人能读懂的代码
    统计学和数据挖掘的关系
    假设检验
    相关性探索
    领域驱动设计分层类图
    未能加载文件或程序集 Ninject.Web.Common, Version=3.2.0.0
    论文阅读笔记
    《Computational Statistics with Matlab》硬译2
    C#抽象类和接口
  • 原文地址:https://www.cnblogs.com/niulina/p/5695950.html
Copyright © 2020-2023  润新知