教程 浅谈 JS 进阶之作用域链

running_runtu · 2017年11月16日 · 233 次阅读

前言

在前端应聘中,相信会有不少面试官都会问你,说说你理解的js作用域,或者作用域链。显然,这是一道经典的js面试题,对于老司机而言可谓是小菜一碟,而对于前端新人,尤其是js基础不扎实的新人来说,则会说的模棱两可,含糊其辞。当然这样青涩的答案显然不会入面试官的法眼,甚至会让你白白丢掉一次面试机会。那么作用域链究竟好不好理解呢,接下来正文开始。

在说作用域前,我们先来说说什么是执行环境。新人容易把执行环境和作用域这两个概念搞混了,要知道函数的每次调用都有与之相关的作用域和执行环境。从根本上来说,作用域是基于函数的,而执行环境是基于对象的,举个栗子,全局执行环境是window对象。

执行环境,同行们也喜欢叫执行上下文。每个函数都有自己的执行环境,当执行流进入一个函数时,函数的环境就会被推入一个环境栈中。而在函数执行之后,栈将其环境弹出,把控制权返回给之前的执行环境。

执行环境分为创建和执行两个阶段。在创建阶段,解析器首先会创建一个变量对象,也叫活动对象,俗称AO。它由定义在执行环境中的变量、函数声明和参数组成。在此期间,作用域链会被初始化,this的值也会被确定。而在执行阶段,代码便会被解释执行。

如果这时面试官问你作用域链的作用是什么?你是不是会瞬间懵逼?别急。且听闰土怎么说。那么这个问题的标准答案就是,作用域链的用途是用于解析标识符。(知道了吧,是不是该用笔记下来,或者划划重点啥的,闰老师说了,这是必考题,嘿嘿~)

标识符的解析是从作用域链的前端开始,沿着作用域链一级一级向后回溯,直至找到标识符为止。(如果找不到,则会报错)。

说了这么久,给大家举个栗子:

var food = '麻辣烫';

function changeFood(){
    var anotherFood = '过桥米线';

    function swapFood(){
        var tempFood = anotherFood;
        anotherFood = food;
        food = tempFood;

        //这里可以访问food、anotherFood、tempFood
    }

    //这里可以访问food、anotherFood
    swapFood();
}
//这里只能访问food
changeFood();

以上代码中,在swapFood()函数的局部环境中有个变量tempFood,这个变量只能在这个环境中访问到。无论全局环境还是changeFood()的局部环境都无权访问tempFood。而在swapFood()内部则可以访问其他两个环境中的所有变量,因为那两个环境是它的父执行环境。

总结一下,内部环境可以通过作用域链访问所有的外部环境,但外部环境不能访问内部环境中的任何变量和函数。

接下来,我们再来讲讲块级作用域。

在ES6没出来之前,javascript是没有块级作用域的。在这一点上,很多从后端转过来的前端人员都比较困惑,因为在C++或者java中,由花括号封闭的代码块都有自己的作用域,用ES自己的话来说,就是它们自己的执行环境。这也是我认为的js的一个缺陷。让我们一起来看看下面这个for循环的栗子:

for(var i=0;i<10;i++){
    console.log('i: ' + i);
}
console.log(i);  // 10

对于有块级作用域的语言来说,i变量会在for循环执行结束之后销毁,换句话说,它只会存在循环体内。而对于javascript来说,变量i即使在for循环执行结束以后,也依旧会存在于循环体外部的执行环境。所以最后console出来的i 是 10。扩展一下,如果用ES6的let来声明变量i,那么console出来的结果就是 i is not defined。因为ES6中的let声明的变量具有块级作用域的特性。

在javascript编码过程中,不声明而直接初始化一个变量是一个常见的错误做法,因为这样可能会导致意外。所以,想要了解大公司的javascript编码规范,首先要从细节开始,从初始化变量前先声明开始。

小结

每天进步一点点,每天消化掉一个小知识点,才能积少成多,最后量变导致质变。愿大家都能成为更好的前端,做更好的自己。

暂无回复。
需要 登录 后方可回复, 如果你还没有账号请点击这里 注册