一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第6天,点击查看活动详情。
大家好,我是前端西瓜哥。今天在一个前端交流群里,一个群友发了这么一道题。
function fn(a) {
console.log(a);
var a = 2;
function a() { }
console.log(a);
}
fn(1);
一看就知道是变量提升题。然后我试着猜了下,果不其然做错了。var 和它的变量提升果然让人迷惑。
我们先学习一些底层的一些知识。
var
var 是 ES6 之前用来声明变量的关键字。var 这人看起来挺机灵,但是干起活来却非常离谱。
块作用域中用 var 写的变量声明,会往外跑,跑到最近的函数作用域或全局作用域中。
当然也有一些比较特殊的情况,比如 with 和 try...catch,它们可以形成特殊的块作用域,让变量不往外跑。
一道非常经典的循环使用定时器的题目就是考察了这个特性:
for (var i = 0; i < 5; i++) {
setTimeout(function(){
console.log(i);
}, i * 1000);
}
这题我不讲,因为太经典了,没做过的朋友可以试着做一下。
解决方案是用 IIFE(立即执行函数表达式)来形成一个 var 不会外溢的函数作用域,当然实际开发中更常见的做法是抛弃 var,使用支持块作用域的 let/const。
如果一个变量名没有被声明过,且没有使用 var 就赋予了初始值,它就会成为全局作用域下的变量。
function setA() {
a = 1;
}
setA();
console.log(a); // 1
如果多次声明同一个变量, 之后的 var 声明会被忽略掉。
var a = 1;
var a = 2;
var a = 3;
等价于
var a = 1;
a = 2;
a = 3;
函数声明你也可以认为是一个 var 声明,千万不要以为后面的 var 声明能够覆盖掉同名的函数声明。
声明的提升
变量声明和函数声明都是会发生 提升 (hoist)的。
怎么理解,就是在执行代码前,会先扫描一下代码,将其中类似 var a = 1 的语句中声明的部分,也就是 var a 先找出来,先执行。
这是编译器的行为,所以在表现上,就是声明被放到了代码的开头,成为 提升。
除了 var / let / const 声明的变量会提升,函数声明也会提升,且函数声明会优先于变量提升,即放到变量声明的上方。
b();
var a = 1;
function b() { console.log(a) };
等价于
function b() { console.log(a) };
var a;
b(); // 输出 undefined
a = 1;
有一个易错点,就是把函数表达式当成了函数声明。函数表达式是不会发生变量提升的,切记。
回到本题
function fn(a) {
console.log(a);
var a = 2;
function a() { }
console.log(a);
}
fn(1);
首先是函数的参数 a,它等价于一个外部的一个变量,上面代码等价于:
var a = 1;
(function() {
function a() { }
console.log(a);
a = 2;
console.log(a);
})();
这里函数声明做了提升,跑到最顶部。接下来考虑给 var a = 2 做提升。但我们的函数 a 的声明已经提升了,所以这里的 var 算是重复声明了,直接去掉 var,最终得到上述代码。
我们很容易看出答案是:
ƒ a() { }
2
本题除了考察比较常规的声明提升,还考察了一个比较特别的情况,就是 函数的参数声明应该放在哪里?
答案是放到一个额外的包裹着函数体的中间层中,而不是直接放到函数体的顶部。
说真的,谁要是在工作中这样写代码,我可能要好好问候一下他。
最后
正经前端开发在实际工作中都是使用 let / var 的,然后交给编译器转换为兼容 ES5 的使用 var 的代码,再发布到生产环境。
var 是个坏文明,总是在秀它那没有下限的骚操作,尤其是在嬉皮笑脸的面试官的操纵下。
文章首发于我的公众号:前端西瓜哥