作用域是 JS 中非常基础的概念,在 ES5 中只有全局作用域和函数作用域,在 ES6 中增加了块级作用域。可是,你真的懂作用域吗?
先不要着急回答,先看下面三道题。做不出来或做错了的话,就是不懂作用域,都做对了,作用域大概懂了三分之一吧,因为还有很多概念在里面呢!
题目一
请分别说出下面代码的执行结果:
1 | console.log(fn) |
1 | console.log(fn) |
1 | console.log(fn) |
ES5 规定,函数只能在顶层作用域和函数作用域之中声明,不能在块级作用域声明。但是到了 ES6,是允许在块级作用域内声明函数的,它有以下两个特点:
- 函数名会提升到全局作用域或函数作用域的头部声明(类似于var)
- 函数体提升到所在的块级作用域的头部声明
我们都知道,在 ES5 中函数存在变量提升,但是需要注意函数声明(function declaration)和函数表达式(function expression)的区别:
1 | console.log(fnd) // [Function: fnd] |
可以看到,函数声明是把函数名和方法体都提升了,而函数表达式只是提升了函数名。而在 ES6 中,如果在块级作用域中定义函数的话,只会提升函数名而不会提升方法体,其效果相当于用 var 声明的函数表达式:
1 | if(true) { |
所以答案是:[Function: fn]
、undefined
、undefined
题目二
请说出下面代码的执行结果:
1 | var x = 1, y = 1, z = 1 |
在 ES6 中,如果函数设置了参数的默认值,那么函数进行声明初始化时,参数会形成一个单独的作用域。因此上面的代码在执行到函数内部的时候,已经有了三个作用域,它们由下到上构成了作用域链:
- 全局作用域
x=1,y=1,z=1
- 函数参数作用域
x=4,y=4
- 函数体作用域
x=3
var x=3
改的就是函数体作用域, y=3
改的是函数参数作用域,而 z=3
则改的是全局作用域。此时第一次打印的结果是:
- x 是函数体作用域,值为 3
- y 是函数参数作用域,值为 3
- z 是全局作用域,值为 3
而 f()
执行的时候,它会形成一个新的作用域,要注意,该函数的作用域的上级不是 func
函数体作用域,而是 func
函数参数作用域。因为在 JS 中是词法作用域,即函数在代码中声明和定义的位置,而不是函数执行时的位置。所以执行之后:
- x=2 改的是函数参数作用域下的 x
- y=2 改的是函数参数作用域下的 y
- z=2 改的是全局作用域
然后再打印的时候,结果是:3、2、2。最后函数执行完出栈,由于只有 z 被改了,所以打印结果是:1、1、2。最终答案:
1 | 3 3 3 |
题目三
请说出下面代码的执行结果:
1 | {}+0 ? console.log('yes') : console.log('no') |
此题考查了运算符的优先级、类型转换和 {}
的二义性。三元运算符的优先级是比较低的,因此要先算问号左边的部分。但是此题坑就坑在第一行的 {}
不是空对象而是块级作用域,第二行的 {}
才是空对象。因此第一行等价于:
1 | { |
这就比较明显了,打印 no。而第二行关键是计算 0+{}
,先尝试调用空对象的 valueOf()
方法,返回不是原始值,继续调用 toString()
方法,得到字符串 [object Object]
,所以 0+{}
的结果是 0[object Object]
,因此第二行输出 yes,最终答案是:
1 | no |