• javascript高级程序第三版学习笔记【执行环境、作用域】


    执行环境 

     执行环境定义了变量和函数有权访问的其他数据,决定了他们各自的行为。

    每个执行环境都有一个与之关联的变量对象(variable object),环境中定义的所有变量和函数都保存在这个对象上,虽然我们在编写代码的时候无法访问这个对象,但解析器在处理数据时会在后台用到它。

    ECMAScript实现的宿主环境不同,表示的执行环境对象也不一样。

    在javascript中,可执行的JavaScript代码分三种类型:
    1. Global Code,即全局的、不在任何函数里面的代码,例如:一个js文件、嵌入在HTML页面中的js代码等。
    2. Eval Code,即使用eval()函数动态执行的JS代码。
    3. Function Code,即用户自定义函数中的函数体JS代码。
    不同类型的JavaScript代码具有不同的执行环境,这里我们不考虑evel code,对应于global code和function code存在2种执行环境:全局执行环境和函数执行环境。

    全局执行环境

     在一个页面中,第一次载入JS代码时创建一个全局执行环境,全局执行环境是最外围的执行环境,在Web浏览器中,全局执行环境被认为是window对象。因此,所有的全局变量和函数都是作为window对象的属性和方法创建的。

    某个执行环境中的所有代码执行完毕后,该环境被销毁,保存在其中的所有变量和函数定义也随之销毁,全局执行环境直到应用程序退出后---例如关闭浏览器和网页---时才被销毁。

    函数执行环境

     每个函数都有自己的执行环境,当执行进入一个函数时,函数的执行环境就会被推入一个执行环境栈的顶部并获取执行权。当这个函数执行完毕,它的执行环境又从这个栈的顶部被删除,并把执行权并还给之前执行环境。这就是ECMAScript程序中的执行流。

    【定义期】

    函数定义的时候,都会创建一个[[scope]]属性,这个对象对应的是一个对象的列表,列表中的对象仅能javascript内部访问,没法通过语法访问。

    我们定义一全局函数A,那么A函数就创建了一个A的[[scope]]属性。此时,[[scope]]里面只包含了全局对象【Global Object】。而如果, 我们在A的内部定义一个B函数,那B函数同样会创建一个[[scope]]属性,B的[[scope]]属性包含了两个对象,一个是A的活动对象【Activation Object】【对于函数来说】一个是全局对象,A的活动对象上面,全局对象在下面。以此类摧,每个函数的都在定义的时候创建自己的[[scope]],里面保存着一个类似于栈的格式的数据。

    下面是示例代码:

    // 外部函数
    function A(){
         var somevar;
            
         // 内部函数
        function B(){
             var somevar;
         }
    }

    看下示意图:

    【执行期】

    当函数被执行的时候,就是进入这个函数的执行环境,首先会创一个它自己的活动对象【Activation Object】(这个对象中包含了this、参数(arguments)、局部变量(包括命名的参数)的定义,当然全局对象是没有arguments的)和一个变量对象的作用域链[[scope chain]],然后,把这个执行环境的[scope]按顺序复制到[[scope chain]]里,最后把这个活动对象推入到[[scope chain]]的顶部。这样[[scope chain]]就是一个有序的栈,这样保了对执行环境有权访问的所有变量和对象的有序访问。

    // 第一步页面载入创全局执行环境global executing context和全局活动象
    // 定义全局[[scope]],只含有Window对象
    // 扫描全局的定义变量及函数对象:color【undefined】、changecolor【FD创建changecolor的[[scope]],此时里面只含有全局活动对象】,加入到window中,所以全局变量和全局函数对象都是做为window的属性定义的。
    // FD已经定义好所以在此执行环境内任何位置都可以执行changecolor(),color也已经被定义,但是它的值是undefined
    
    // 第二步color赋值"blue"
    var color = "blue";
    
    // 它是不需要赋值的,它就是引用本身
    function changecolor() {
        // 第四步进入changecolor的执行环境
        // 复制changecolor的[[scope]]到scope chain
        // 创建活动对象,扫描定义变量和定义函数,anothercolor【undefined】和swapcolors【FD创建swapcolors的[[scope]]加入changecolor的活动对象和全局活动对象】加入到活动对象,活动对象中同时还要加入arguments和this
        // 活动对象推入scope chain 顶端
        // FD已经定义好所以在此执行环境内任何位置都可以执行swapcolors(),anothercolor也已经被定义好,但它的值是undefined
        
        // 第五anothercolor赋值"red"
        var anothercolor = "red";
        
        // 它是不需要赋值的,它就是引用本身
        function swapcolors() {
            // 第七步进入swapcolors的执行环境,创建它的活动对象
            // 复制swapcolors的[[scope]]到scope chain
            // 扫描定义变量和定义函数对象,活动对象中加入变量tempcolor【undefined】以及arguments和this
            // 活动对象推入scope chain 顶端
            
            // 第八步tempcolor赋值anothercolor,anothercolor和color会沿着scope chain被查到,并继续往下执行
            var tempcolor = anothercolor;
                anothercolor = color;
                color = tempcolor;    
        }
    
        // 第六步执行swapcolors,进入其执行环境
        swapcolors();
    }
    
    // 第三步执行changecolor,进入其执行环境
    changecolor();

    访问标识符:当执行js代码的过程中,遇到一个标识符,就会根据标识符的名称,在执行上下文(Execution Context)的作用域链中进行搜索。从作用域链的第一个对象(该函数的Activation Object对象)开始,如果没有找到,就搜索作用域链中的下一个对象,如此往复,直到找到了标识符的定义。如果在搜索完作用域中的最后一个对象,也就是全局对象(Global Object)以后也没有找到,则会抛出一个错误,提示undefined。

    既然讲到执行期,顺便讲一下javascript的【声明提升机制Hoisting

    先看下面代码:

    var myvar = 'my value';  
    alert(myvar); // my value  

    当然会弹出my value

    再看下面这样

    var myvar = 'my value';  
    (function(){
        alert(myvar); // undefined
        var myvar = 'you value';  
    })()

    结果却是undefined

    这是因为,javascript解析器,进入一个函数执行环境,先对var 和 function进行扫描。相当于会把var或者function【函数声明】声明提升到执行环境顶部。

    上面的代码会被解析成下面这样:

    var myvar = 'my value';  
    (function(){
        var myvar;
        alert(myvar); // undefined
        myvar = 'you value';  
    })()

    根据,标识符找查机制当执行到alert的时候,查找myvar是局部变量的myvar,此时myvar并没有被赋值。所以结果是undefined。
    再看看下面的例子:

    count(1,2); // 3
    function count(a,b)
    {
        alert(a+b);    
    }
    count(1,2); // 会报错误count is not a function
    var count = function (a,b)
    {
        alert(a+b);    
    }

    我们知道,根据声明提升机制,var和function都会被提升到执行环境的顶部,已经扫描完毕。所以,上面那种写法,会把function声明提到执行环境顶部,所以即使调用在声明的前面依然能够执行。

    而下面这种写法会被解析成这样:

    var count;
    count(1,2); // 会报错误count is not a function
    count = function (a,b)
    {
        alert(a+b);    
    }

    因为此时count只是被扫描,但还末被引用到函数对象,所以此时,它不是一个function,所以把它当函数来执行会报错。

    另外一个需要提一下的是,函数的声明是优先于变量的声明的。

    作用域 

    由上,可以得知,当查找一个个标识符的时候,是在作用域链[[scope chain]]里查找的,这个作用域链里只包含上一级的活动对象,但并不包括下一级的活动对象。这就意味着,内部执行环境可以访问外部的执行环境定义的变量,但外部的执行环境是无法访问内部执行环境定义的变量的。并且,内部环境定义的变量会覆盖掉外部环境定义的变量。我们还可以知道,特定执行环境的查找的标识符是在【定义期】就已经完成的,而不是在【执行期】,因为作用域链[[scope chain]]是从定义期的[scope]复制过来的。但是它本身的Activation Object对象是在执行期才推入,作用域链顶端的,所以本身的私有变量是【执行期】完成的。

     还是上面的例子:

    // color全局变量changecolor 和 swapcolors都可以沿着scope chain访问到
    var color = "blue";
    
    function changecolor() {
       
        // anothercolor局部变量,全局访问不到 但 changecolor和swapcolors都可以沿着scope chain访问到
        var anothercolor = "red";
        
        function swapcolors() {
           
            // tempcolo局部变量,全局和changecolor访问不到 但 swapcolors都可以沿着scope chain访问到
            var tempcolor = anothercolor;
                anothercolor = color;
                color = tempcolor;    
        }
    
        swapcolors();
    }
    changecolor();

    没有块级作用域

     与C、C++以及JAVA不同,Javscript没有块级作用域。看下面代码:

    if(true){
            var myvar = "木乙";    
        }
        alert(myvar);// 木乙

    根据上面我们讨论的,如果有块级作用域,外部是访问不到myvar的。再看下面

    for (var i=0;i<5;i++){
                console.log(i)    
            }
            
            alert(i); // 5

    对于有块级作用域的语言来说,i做为for初始化的变量,在for之外是访问不到的,这允分证明了,javascript是没有块级作用域的。

    延长作用域链

    虽然执行环境只有两种——全局作用域和函数作用域,但是还是可以通过某种方式来延长作用域链。因为有些语句可以在作用域链的顶部增加一个临时的变量对象。
    有两种情况会发生这种现象:
    1、try-catch语句的catch块;
    2、with语句;
     
    看下面实例:
    (function bildUrl(){
                var qs = "?debug=true";
                    
                with(location){
                    var url = href + qs;    
                }
                alert(url)
            })()

    with会把location对象的所有属性和方法包含到变量对象中,并加入到作用域链的顶部。此时访问href实际上就是location.href。

    但新声明的“url”变量会加入到最近的执行环境的变量对象里。试下面例子,说明url是可以访问到的,只是此时是undefined

    (function bildUrl(){
                var qs = "?debug=true";
                
                if (!url)
                {
                    alert("这里可以看到url"); //可以正常显示url已经被声明并且被扫描进函数的变量对象
                }
                
                try{
                    if (!a)
                    {
                        alert(1);// 报错因为a根本就不存在
                    }
                }
                catch(e){
                    console.log("作用域链被延长了吧");
                }
                
                with(location){
                    var url = href + qs;    
                }
                
            })()

    对catch语句来说,会创建一个新的变量对象加入到作用域链的顶部,其中包含的是被抛出的错误对象的声明。
    需要说明的是,ie8之前的版本有个不符合标准的地方,就是被势出的错误对象会被加入到执行环境的变量对象。

     
  • 相关阅读:
    win8 需要管理员权限才能删除此应用程序
    windows 8 解决端口(COM和LPT)问题:Prolific USBtoSerial Comm Port(COM4)驱动异常的问题
    sqlite 获取数据库中的所有表
    ListView simpleAdapter的基本使用
    一根长度为133米的材料需要截成长度为19米和23米的短料,求两种短料各截多少根时剩余的材料最少?
    打印输出九九乘法表
    写一个函数,输入一个十六进制,输出相应的十进制数。
    求3100的素数!
    从键盘输入若干个同学的成绩,统计并输出最高成绩 最低成绩 平均分,当输入负数时结束输入
    编写两个函数,分别求由键盘输入两个整数的最大公约数和最小公倍数。用主函数调用这两个函数,并输出结果
  • 原文地址:https://www.cnblogs.com/pigtail/p/2570988.html
Copyright © 2020-2023  润新知