发表于: 2020-07-26 23:30:14

0 1129


(1)背景介绍:


       JS中,每个执行环境都有一个与之关联的变量对象。环境中定义的所有变量和函数都保存在这个对象中。每个函数都有自己的执行环境。当执行流进入函数时,函数的环境就被推入一个栈中。在函数执行之后,栈将其环境弹出,把控制权返回到当前的执行环境。当代码在一个环境中执行时,会创建变量对象的一个作用域链(上下文),作用域链的用途是保证对执行环境有权访问的所有变量和函数有序访问。


(2)知识剖析:


       执行环境:所有JavaScript代码都是在一个执行环境中被执行的。执行环境是一个概念,一种机制,用来完成JavaScript运行时在作用域、生存期等方面的处理,它定义了变量或函数有权访问的其他数据(包含了外部数据),决定他们各自的行为。


       全局执行环境: 全局环境是最外围的一个执行环境,根据ECMAScript实现所在的宿主环境不同,表示执行环境的对象也不一样,在web中,全局执行环境被认为是window对象。


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


       变量对象:每个执行环境都有一个变量对象与之关联,执行环境中定义的所有变量及函数(只包含在当前函数内定义的函数,局部变量)都保存在这个对象中,我们编写的代码无法直接访问这个对象,但解析器在处理数据时会在后台使用它。(变量对象就是作用域为该执行环境的函数,变量的集合对象)。


       作用域:变量或方法有访问权限的代码空间,即变量或函数起作用的区域。


       作用域链:由当前环境栈中对应的变量对象组成。作用域的用途,是保证对执行环境有权访问的所有变量和函数的有序访问,作用域前端,始终是当前执行的代码所在的环境对应的变量对象,下一变量对象来自包含(外部)环境,而再下一变量对象则来自下一包含环境,一直延续到全局执行环境。


(3)常见问题:


如何理解上面提到的几个概念?


(4)解决方案:


 1.执行环境与变量对象之间的对应关系:

      每个执行环境会有一个变量对象与之关联,该变量对象保存了执行环境中定义的所有局部变量及函数,变量对象具有动态性,只有在定义变量的语句得到执行才会将该变量添加到变量对象中。


function showA() {

    console.log(app);  // 变量对象中没有保存app


}

showA();


2.程序执行时,环境栈与执行环境的关系:

       当执行流进入一个函数时,即该函数正在执行,函数的执行环境就会被推入一个环境栈中,所有处在执行流的执行环境将有次序地保存在环境栈中,在函数执行之后,栈将其执行环境弹出,把控制权交给原来的执行环境。所以在程序执行中,环境栈是不断变化的,伴随着执行环境的出入。


3.理解环境栈,执行环境与作用域链之间的关系:

        在某一时刻,环境栈中保存的执行环境是一定的。


function Fn1() {

    var a = 1;

    var b = 2;

    function Fn2() {


        console.log(b);

    }

    Fn2();// 当程序执行到此时


}

Fn1();


4.作用域链的作用(标识符解析机制):

       作用域链的用途,是保证对执行环境有权访问的所有变量和函数的有序访问(包括外部数据)。

       标识符解析是沿着作用域链一级一级地搜索标识符的过程,搜索始终从作用域链的前端开始,然后逐级地向后回溯,直到找到标识符为止。


    var app=1;

function showA() {

    var app=9;

    console.log(app);  

    

}

showA();//9


       由例子中可以看出作用域链保证对执行环境有权访问的所有变量和函数的有序访问的原理:作用域链有序地保存了变量对象,由前向后,局部变量和函数往往保存在较前的变量对象中,因此被标识符解析的机会大于全局变量,也就有了局部变量会覆盖全局变量的现象,这样就保证了变量的有序访问。


5.作用域与变量对象,执行环境的关系:

      由定义可知,作用域即函数或变量的作用区域。

      变量或函数具有作用域的原因,就是在环境中定义的变量仅保存在了该执行环境对应的对象变量中,执行环境在环境栈中弹出之后,作用域链中找不到该对变量对象。


function showColor () {

    var  color = 'red';

 }

 showColor(); // red

 console.log(color); // color is undefined


      为什么在全局环境下showColor()内定义的变量不可访问呢,这是因为当函数执行到该语句时,color变量保存在了showColor()环境对应的变量对象中,现在showColor()已经执行完毕,该执行环境也从环境栈中弹出并销毁,所以此时的作用域链也不包括showColor()的执行环境对应的变量对象了,因为标识符解析是顺着作用域链查找变量的,所以这个过程不再能搜索到color变量,所以color变得只有定义该变量的函数中才能访问,具有一定范围的作用域。


(5)编码实战:

function test() {

    console.log(foo);

    console.log(bar);

    var foo = 'Hello';

    console.log(foo);

    var bar = function () {

        return 'world';

    }

    function foo() {

        return 'ok';

    }

}

test();


(6)拓展思考:


js中有块级作用域吗?


ES5只有全局作用域没和函数作用域,没有块级作用域。

ES6新增了let命令用来进行变量声明,使用let命令声明的变量只在let命令所在代码块内有效。

function test() {

    var arr = new Array();

    for (let i = 0; i < 6; i++) {

        arr[i] = function () {

            return i; //当var变为let时,i只在for循环内有效

        }

    }

    return arr;

}

var arrObj = new test();

console.log(arrObj[0]());//0


(7)参考文献:


参考一:浅谈JS执行环境及作用域


https://www.cnblogs.com/formercoding/p/5881304.html


参考二:js中的块级作用域


http://www.imooc.com/article/74688


参考三:JS变量对象详解


https://www.cnblogs.com/lsgxeva/p/7976034.html


(8)更多讨论:


问:作用域和闭包 有什么关联?


答:每次调用函数,都会为之创建一个新的对象来保存局部变量,然后把该对象添加至作用域链中(每次调用就创建一个新的,调用多少次,创建多少个,执行结果互不影响)。当函数返回时,本来应该是直接从作用域链中将这个对象删除,但是闭包的出现让这一切变得不简单。当返回的是一个嵌套函数的时候,就会有一个外部的引用指向这个嵌套的函数,可以理解为外部对它进行调用,或者赋值给某个变量,在js垃圾回收机制中,一旦某个变量不再被引用,那么这个变量将会被回收。由此可见之前绑定在作用域链上的对象由于闭包的关系不会被当做垃圾回收,这也就是闭包能够让局部变量的值始终保持在内存中


问:闭包这些都用在哪些应用场景上


答:闭包具有非常强大的功能,函数内部可以引用外部的参数和变量,但其参数和变量不会被垃圾回收机制回,常驻内存,会增大内存使用量,使用不当很容易造成内存泄露。但,闭包也是javascript语言的一大特点,主要应用闭包场合为:设计私有的方法和变量。 


问:作用域链怎样延长?


答:一般来说,执行上下文中的作用域链是不会改变的,但是在JS中,with和try-catch语句可以临时的延长作用域链。

      对 with 来说,将会指定对象添加到作用域链中。将特定的变量对象保存在作用域链的最上层。

      对 catch 来说,会创建一个新的变量对象,其中包含的是被抛出的错误对象的声明。



返回列表 返回列表
评论

    分享到