js经典面试题--变量提高、执行环境、做用域链

js经典面试题--变量提高、执行环境、做用域链

今天记录一个js的经典面试题,该编程题涉及到了js的变量提高、执行环境、做用域链问题。前端

一、变量提高
js没有块级做用域,使用var声明的变量会自动添加到最接近的环境中。在函数内部,最接近的环境就是函数的局部环境。若是初始化变量时没有使用var变量,该变量会自动被添加到全局环境。下面两幅图是等价的,结果都是控制台打印出1 2 3 4 5
图片描述面试

二、 执行环境
每一个函数都有本身的执行环境。当执行流进入一个函数时(即调用该函数),函数的环境就会被推入一个环境栈中。而在函数执行以后,将其环境弹出栈,把控制权返回给以前的执行环境。全局执行环境是最外围的一个执行环境。全局执行环境被认为是window对象,全局执行环境直到应用程序退出--例如关闭网页或浏览器---时才会被销毁。编程

function a(){
    //执行a功能代码
  }
  a();       //函数a的环境被推入一个环境栈中。
  
  function b(){
    //执行b功能代码
    var c=function(){
      //执行c功能代码
      function d(){
        //执行d功能代码
      }
      retrun d();
    }
    return c();
  }
  b();     //函数b、c、d依次被推入一个环境栈中,当调用b()函数时,其依次被弹出

其执行的具体流程以下图所示:
图片描述浏览器

三、做用域链
当代码在一个环境中执行时,会建立变量对象的一个做用域链。做用域链的用途,是保证对执行环境有权访问的全部变量和函数的有序访问。做用域链的前端,始终都是当前执行的代码所在环境的变量对象。做用域链中的下一个变量对象来自包含(外部)环境,而再下一个变量对象则来自下一个包含环境。这样,一致延续到全局执行环境;全局执行环境的变量对象始终都是做用域链中的最后一个对象。函数

var a=1;
function b(){
    //执行b功能代码
    var bVar=1;
    var c=function(){
      //执行c功能代码
      var cVar=2;
      function d(){
        //执行d功能代码
        var dVar=3;
        cVar=3;
      }
      retrun d();
    }
    return c();
 }
 b();

图片描述

以上代码共涉及4个执行环境:全局环境,b()的局部环境、c()的局部环境、d()的局部环境。全局环境有一个变量a和一个函数b()。b()的局部环境中有一个变量bVar和一个函数c.....依次。位于最里边的函数能够访问外部环境的全部变量和函数,由于外部环境是它的父执行环境。总结:内部环境能够经过做用域链访问全部的外部环境,但外部环境没法访问到内部环境中的任何变量和函数。这些环境之间的联系是线性、有次序的。每一个环境均可以向上搜索做用域链,以查询变量和函数名(服从就近原则);但任何环境都不能经过向下搜索做用域而进入另外一个执行环境。spa

经过上面介绍执行环境与做用域的两幅图能够看出,浏览器在执行js时,首先会将window对象(全局执行环境)压入环境栈,每次执行一个函数时,被调用的函数(按照调用的前后顺序)依次压入环境栈中。而压入栈中的环境相似于容器,往栈底方向的容器包含了上面的容器。容器中存放的是本身的变量和函数以及上面的容器。咱们能够把容器的玻璃的材质想象为车窗户(能够从里边看到外面,可是没法从外面看到里边),当在某个环境(容器)中执行代码块时,就比如咱们站在当前容器里,此时咱们能够看到外部容器(父级环境)的变量和函数,但却看不到内部容器的任何东西,这就是做用域链。code


下面进入正题,说下我对该面试题的理解对象

1    var foo = {n:1};
2    (function (foo) {
3        console.log(foo.n);
4        foo.n=3;
5        var foo = {n:2};
6        console.log(foo.n);
7    })(foo);
8    console.log(foo.n);

上面的代码其实能够写成这样:blog

1    var foo = {n:1};
2    (function (foo) {
3         var foo;
4        console.log(foo.n);
5        foo.n=3;
6        var foo = {n:2};
7        console.log(foo.n);
8    })(foo);
9    console.log(foo.n);

一、声明一个变量,为引用类型
2和八、声明一个匿名函数,并当即执行,传递的参数是第1行中的foo。将一个对象类型赋值给一个新的变量,因为对象是引用类型,实质上是指将对象的地址赋值给该变量(也就是说这两个变量指向同一个地址空间),所以改变新的变量中的属性值或方法,对应的原来对象的值也会改变。
三、原题中的第5行,因为存在变量提高,所以会在函数开始就声明,此时为undefined;然而因为一个变量的声明优先级低于形参,因此这行没有任何效果
四、打印形参的foo.n,打印1
五、改变第1行变量foo的属性n的值为3;
六、从新声明并定义了一个变量,开辟了新的内存空间,n为2
七、因为js中的代码是自上而下执行,因此此时输出2
九、上面的函数调用结束后,局部变量被销毁,而以前的内存空间值已经变为3,因此输出3
因此最终的结果为:1 2 3图片