在面试题之前先做一下名词解释
词法作用域
- 什么是作用域
作用域就是指一个变量可以被使用的范围. 即从什么地方开始可以被访问到. 到什么地方结束, 不能在访问到, 这个范围称为变量的作用域. - 什么是块级作用域
块级作用域就是使用代码块来限定变量的使用范围.在 js 中( ES5 以前 )没有块级作用域.在 es6 中引入了 let 命令, 来代替 var 声明变量. 而 let 命令声明的变量就具有块级作用域.所谓的块级作用域就是从变量声明开始, 到变量所在的最近的(最小的)花括号结束为止. - js 中的词法作用域
在 js 中 js 的代码需要经过"预解析"( 提前解析 ), 再逐步的解释执行.所以在 js 中所谓的词法作用域从预解析开始全局起作用, 只有函数可以限制作用域的范围.
代码预解析
- js 中的声明
声明就是 变量的声明和函数的声明, 其目的是让 js 解释引擎知道有什么东西.声明时是不参与运算和执行的, 是在预解析阶段就完成的.
- 变量的声明
// 变量的声明就是 var 变量名.
var num = 123;
// 这是一个语法糖,可以理解成
var num;
num = 123;
- 函数的声明
function 函数名 () { ... }
// 在一个独立于任何语句( 表达式, if 结构, while 结构 等 )的独立结构中, 或函数中出现的代码, 为函数声明.
- js 预解析代码如何执行
js 的代码执行要经历两个步骤, 首先是预解析. 预解析会通读所有代码. 如果发现错误则停下, 如果遇到声明则记录.在声明的时候, 如果是变量名的声明, 解析器内部就会记录下这个变量. 如果使用则检查是否有记录.
在声明的时候, 如果是函数声明, 则解析器会先记录函数的名字( 相当于变量声明 ), 然后将函数的名字与函数体联系在一起.
在预解析中, 如果出现重复声明, 则第一次声明起作用, 其后所有的同名的声明无效.
// 例如:
var num = 1;
var num = 2;
// 等价
var num = 1;
num = 2;
声明结束后代码就会再从第一句话开始一句一句的执行.
理论知识介绍完了,下面来看面试题:
题目一
代码分析如下:
- 1-5行定义函数fun
- 6行定义变量n
- 7行执行函数并传入变量n
注意:fun函数内部有预解析。
预解析及执行步骤:
- Fun函数开始执行前,将var n提前执行,初始化为undefined。
- 由于函数传入参数n并没有使用,忽略。
- 开始执行第2行,输出为undefined。
- 执行第3行,此时即n = 456,即将n值重置为456。
- 执行第4行,输出改变后的n。
通过以上步骤分析,即可得知预解析的原理了(针对有var的变量提前赋初始值)
题目二
代码分析如下:
- 29行定义一个全局变量
- 30-32行定义一个函数f1
- 33-36行定义一个函数f2
- 37行执行函数f2
- 38行输出结果n
预解析及执行步骤:
- 代码执行前,预解析先初始化变量n, f1, f2,将它们都置为undefined.
- 接着执行第29行,为变量n赋值
- 接着执行第30-32行,为函数变量f1赋值,即f1为函数了
- 接着执行第33-36行,为函数变量f2赋值
- 执行第37行,即执行f2函数。
6.f 2函数执行前,同样预解析,先将n初始化为undefined,接着把n赋值为456,接着调用f1函数执行。 - f1在f2中执行,那f1的作用域应该是f2,应该输出456?
- 35行执行f1函数时无调用者,即f1函数为全局作用域,输出全局n为123
- 第38行直接输出全局变量n,即123
题目三
代码分析如下:
预解析只针对var和function定义的变量
预解析及执行步骤:
- 预解析先初始化变量length, obj, f1并赋值为undefined
- 接着为变量length赋值为100
- 接着为函数变量f1赋值为函数
- 接着为变量obj赋值为对象
- 第52行,调用对象obj的f2函数执行,传入形参f1和1
- 第47行,f2函数接收实参为f1, 接着执行f1函数
- 同上,f1函数执行无调用者,作用域为全局,this指向window,输出全局变量length,即100
- 第47行,f2函数接收实参f1,若要取到所有实参则需要arguments对象,第一个参数arguments[0]==f1,第二个arguments[1]==1,依此类推。
- 第49行,arguments[0] ()看上去是f1(),那也应该输出100?
- 注意arguments[0]作用域与f1的作用域并不相同,第48行直接执行f1,无调用者,作用域为全局作用域,但arguments[0]作用域为arguments对象,即this为arguments,则应输出2,因为arguments对象的属性length为2。