这段时间一直在看一些关于JavaScript的书,看到不明白的地方就满世界搜解答,结果今天晚上在搜索一个奇怪的语法的时候不小心点开一道面试题,是一道考察作用域的十年老题,于是我试着做了一下,果断被坑,结束后看解析,看得也不是很明白,于是赶紧回去看基础书,结果发现以前很多自己一眼扫过的知识点自己完全没有掌握,瞬间后悔万分,所以趁势赶紧重新学一下,同时把这些这些点记录下来。
原题是这样的:
var tt = ‘aa‘; function test(){ alert(tt); var tt = ‘dd‘; alert(tt); } test();
答案是undefined,dd。
刚刚看到答案的时候感觉坑爹无比,后面看解析,感觉也是云里雾里,不过好在知道去哪里找答案,这题主要涉及执行环境、变量对象、声明提前等知识点,这里简单回顾一下
执行环境定义了变量或函数有权访问的其它数据,决定了他们各种的行为。每个执行环境都拥有一个与之关联的变量对象。环境中定义的所有变量和函数都保存在这个环境中。然而我们无法直接访问这个对象。
全局执行环境一般被认为是window对象,所以所有的全局变量和函数也都是作为window的属性和方法来创建的。
每个函数都有自己的执行环境,当执行流进入一个函数时,函数的执行环境就会被推入一个环境栈中,而在函数执行之后,栈将环境推出,把控制权交还给之前的执行环境。
当代码在一个执行环境中执行时,会创建变量对象的一个作用域链,作用域链的作用,是保证对执行环境有权访问的变量和函数的有序访问。
作用域链的最前端总是当前执行环境的变量对象,其次是上一层执行环境的变量对象,再其次是上上一层的执行环境的变量对象,知道全局环境。
如果执行环境是函数,则将其活动对象作为变量对象,活动对象最开始时只包含一个arguments对象,函数内部定义的变量和函数则会在函数执行前加入到这个活动对象中。
声明提前是指解析器在解析一个作用域中的代码时,会将变量和函数的定义提前到作用域的顶端,而赋值则留在原处。
好了,有了以上的理论知识,我们再来看这道题。
首先,声明一个全局变量tt,并复赋值为‘aa‘。
然后,创建函数test(),根据上面讲到的内容,这个函数拥有一个自己的执行环境,并关联一个变量对象,这个变量对象拥有一个arguments对象,此时,这个arguments对象为null。在解析器解析这个函数时,会将函数中定义的变量和函数都添加到这个变量对象中,并把变量定义语句提前到作用域顶部。
之后在函数执行时,会创建作用域链,作用域链把函数自己的变量对象放在作用域链的最前端,后面是它的上一层作用域的变量对象,也就是window对象。此时两个变量对象都有一个变量tt,在前端的函数内部定义的tt会屏蔽掉全局的tt,然后根据声明提前,此时var tt=‘dd‘会被分割成两部分,声明部分(var tt)被提前到函数开头,而赋值语句(tt=‘dd‘)仍留在原地。此时执行第一个alert(tt)时会弹出undefined,因为此时tt只有声明,尚未赋值,而第二个alert会弹出dd,因为此时tt已经赋值好了。