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

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

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

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

2、 执行环境
每个函数都有自己的执行环境。当执行流进入一个函数时(即调用该函数),函数的环境就会被推入一个环境栈中。而在函数执行之后,将其环境弹出栈,把控制权返回给之前的执行环境。全局执行环境是最外围的一个执行环境。全局执行环境被认为是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()函数时,其依次被弹出

其执行的具体流程如下图所示:
图片描述

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

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.....依次。位于最里边的函数可以访问外部环境的所有变量和函数,因为外部环境是它的父执行环境。总结:内部环境可以通过作用域链访问所有的外部环境,但外部环境无法访问到内部环境中的任何变量和函数。这些环境之间的联系是线性、有次序的。每个环境都可以向上搜索作用域链,以查询变量和函数名(服从就近原则);但任何环境都不能通过向下搜索作用域而进入另一个执行环境。

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


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

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); 

上面的代码其实可以写成这样:

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); 

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值