手记

[JS]大话执行环境和作用域链

十分钟快速掌握版

在学习JS的过程中,我们经常会碰到一个概念就是JS的执行环境、作用域链。

今天我们用十分钟的时间,彻彻底底搞懂JS的执行环境、作用域链的概念。

JS执行环境

JS的执行环境分为两种:

1、全局环境

2、局部环境

全局环境

全局环境是最外围的执行环境,在Web浏览器中,全局环境被认为就是我们众所周知的window对象。因此所有的全局变量和函数都挂载在window对象下面。例如:

var name='DaMai';window.name;     //由于var name在全局环境中定义,所以是全局变量,输出:'DaMai'function sayName(){    console.log(name);   //由于name是全局变量,根据作用域链(这个稍后再讲)输出:'DaMai'}
winsow.sayName            //由于sayName在全局环境定义,所以sayName作为了全局方法挂载在window对象下,输出:function sayName()window.sayName()           //调用了sayName方法,输出:'DaMai'

上面这个代码片段,其实很真切的让我们感受到了全局环境,在Web浏览器中,所有的变量声明和方法声明其实都被挂载在了window对象下。

局部环境

没有块级作用域

在讲局部环境之前,我相信很多同学像我一样学习过Java的同学,刚开始看到下面这个代码,会觉得不可思议。

for(var i=0;i<10;i++){
    console.log(i);       //依次输出:1,2,3,4,5,6,7,8,9}
console.log(i);           //输出:10
                         //不可思议的点,由于Js没有块级作用域的概念,所以其实var i还是在全局环境中定义的。window.i;                //输出:10。很好的说明了i是在全局环境中被定义,并被挂在了window对象下。

这段代码片段,我其实想在讲局部环境之前,想给大家一个概念:

JS中没有块级作用域的概念,只有全局作用域和函数作用域,与之对应的就是全局环境和局部环境

由于没有块级作用域,所以for循环中的var i=0其实是在全局环境定义的。因此被挂在在window对象之下,所以我们通过window.i;能输出10。

回到正题,

局部环境(函数环境,更直观),每个函数都有自己的执行环境,当执行流进入该函数时,函数的执行环境就被推入到环境栈中,当函数代码执行完毕之后,该环境被销毁,保存在其中的所有变量和函数定义也随之销毁,环境栈就将该执行环境弹出,将控制权返回给之前的执行环境。

注释:全局环境直到应用退出,——例如关闭网页或者浏览器,才会被销毁。

这句话可能比较晦涩难懂,我用下面这个例子,直白的说明这句话:

        var name='DaMai';    function getName(){
        return name;
    }    function sayName(){
        var firstName='Da';
        console.log(firstName);
        console.log(getName());         //输出'DaMai',局部环境能访问全局环境中的变量和函数
        console.log(name);             //输出:'DaMai',局部环境能访问全局环境中的变量和函数
        function sayFirstName(){
            console.log(firstName);
        }
        sayFirstName();
    }
    console.log(name);//由于是全局环境声明的name变量,所以输出:'DaMai'

    sayName();    //由于是在全局环境声明的方法,所以全局环境能调用,
                  //调用后执行函数代码,sayName局部环境进入环境栈,输出:'Da'、'DaMai'、'DaMai'和'Da'。
                  //在执行流执行完所有sayName的代码后,销毁sayName的局部环境,又回到了全局环境

    console.log(firstName);   //由于firstName是在局部环境中声明,所以当函数执行完毕后,
                            //sayName中定义的局部变量声明firstName和局部环境中的方法sayFirstName已经被销毁,
                            //在全局环境中已经无法访问

上面这段代码很好说明了前面那段晦涩难懂的话,由于在局部变量中被定义的变量和函数,会在代码被执行完成后销毁;局部执行环境被弹出环境栈。

作用域链

作用域链其实可以理解成由于全局作用域和局部作用域、局部作用域和局部作用域相互嵌套,形成作用域链。各个作用域像链条一般串在了一起。当前执行的环境能够通过作用域链去访问链条上的变量和函数。

        var red='red';    function sayColor(){
        var blue='blue';
        console.log(red);   //当执行流在sayColor局部环境时,能够访问上一个环境,即全局环境window的参数和函数
        console.log(blue);   //同上
        // console.log(green);//报错,因为当sayColor被调用,执行流在sayColor局部环境中时,
                              //不能访问下一个环境sayOtherColor,因为sayOtherColor还未进入环境栈

        function sayOtherColor(){
            var green='green';     
            //当sayOtherColor被调用时,执行流进入sayOtherColor环境,
            //sayOtherColor环境被推入环境栈,
            //所以此时的环境栈从顶部到底部依此是:sayOtherColor->sayColor->全局环境。
            //根据作用域链的规则:内部环境能够通过作用域链访问所有外部环境的变量和函数。
            //所以:sayOtherColor能够访问sayColor环境和全局环境中的变量和方法。
            console.log(red);      
            console.log(blue);
            console.log(green);
        } 
        sayOtherColor();   //调用sayOtherColor
    }
    sayColor();
    console.log(blue);    //输出:ReferenceError: blue is not defined,因为外部环境不能访问内部环境的任何变量和函数

将代码中的环境串成作用域链,如下图:

内部环境能够通过作用域链访问所有外部环境变量和函数,外部环境不能访问内部环境的任何变量和函数

内部环境能够通过作用域链访问所有外部环境变量和函数

所以我们可以根据上面那图来理解:

当sayColor函数被调用的时候,此时环境栈从顶部到底部:sayColor->全局环境,
所以sayColor能访问自身的变量var blue='blue'和自身函数sayOtherColor()以及外部环境的变量和函数,即全局环境的变量和函数。

当sayOtherColor函数被调用的时候,此时的环境栈从顶部到底部:sayOtherColor->sayColor->全局环境,所以sayOther能访问自身变量var green='green'
以及通过作用域链,访问外部所有环境的变量和函数,即访问sayColor和全局环境的所有变量和函数。

外部环境不能访问内部环境的任何变量和函数

外部环境不能访问内部环境的任何变量和函数,所以在全局环境访问blue变量,会报ReferenceError: blue is not
的错误。

同理,在sayColor环境访问green变量也会报这个错误。

标识符搜索沿着作用域链一级一级的向上搜索

        var color="red";    function sayColor(){
        var color="green";
        console.log(color);  //由于在局部环境搜索到color变量,则停止向上搜索,所以不会访问全局环境的color变量,
                            //输出:green
        function sayHi(){
            console.log('sayColor:sayHi');
        }
        sayHi();   //由于在局部环境搜索到sayHi()函数,则停止向上搜索,
                 //所以不会访问全局环境的color变量,输出:sayColor:sayHi
    }    function sayHi(){
        console.log('全局环境:sayHi');
    }
    sayColor();
    console.log(color);

由于在局部环境找到color标识符和sayHi标识符,所以局部作用域会停止向上搜索,因此输出:green
sayColor:sayHi
再看下一个例子:

        var color="red";    function sayColor(){
        console.log(color);  //由于局部环境没有找到color标识符,向上查找,找到全局环境中的color变量,因此输出:'red'  
        sayHi();     //由于局部环境没有找到color标识符,向上查找,找到全局环境中的SayHi()函数,因此输出:'全局环境:sayHi'
    }    function sayHi(){
        console.log('全局环境:sayHi');
    }
    sayColor();
    console.log(color);  输出:'red'

由于在局部环境找到color标识符和sayHi标识符,所以局部作用域会向上搜索,在全局环境中找到了color变量和sayHi()函数,因此输出:red全局环境:sayHi

作用域链总结:

1、作用域链其实就是全局作用域和局部作用域、局部作用域和局部作用域嵌套,形成一个作用域链条。环境通过作用域链可以访问链条上的变量和函数。

2、内部环境能够通过作用域链访问所有外部环境变量和函数,外部环境不能访问内部环境的任何变量和函数。

3、标识符搜索沿着作用域链一级一级的向上搜索

我是大麦,如果喜欢我的文章,请给我一个小心心。

原文出处

0人推荐
随时随地看视频
慕课网APP