一道有点东西的 JS 变量提升题

简介: 前端西瓜哥

大家好,我是前端西瓜哥。今天在一个前端交流群里,一个群友发了这么一道题。

function fn(a) {
  console.log(a);
  var a = 2;
  function a() { }
  console.log(a);
}
fn(1);

一看就知道是变量提升题。然后我试着猜了下,果不其然做错了。var 和它的变量提升果然让人迷惑。

我们先学习一些底层的一些知识。

var

var 是 ES6 之前用来声明变量的关键字。var 这人看起来挺机灵,但是干起活来却非常离谱。

块作用域中用 var 写的变量声明,会往外跑,跑到最近的函数作用域或全局作用域中

当然也有一些比较特殊的情况,比如 withtry...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 是个坏文明,总是在秀它那没有下限的骚操作,尤其是在嬉皮笑脸的面试官的操纵下。

相关文章
|
6月前
|
前端开发 JavaScript Java
除了变量提升,JavaScript还有哪些特性?
【4月更文挑战第4天】JavaScript 特性包括函数作用域、动态类型、原型继承、异步编程、高阶函数、箭头函数、严格模式、对象字面量、模块系统和垃圾回收。这门语言支持多种编程模式,适合各种应用场景。想深入了解某特性,欢迎提问!😄
46 6
|
5月前
|
JavaScript 前端开发 开发者
JavaScript的变量提升是一种编译阶段的行为,它将`var`声明的变量和函数声明移至作用域顶部。
【6月更文挑战第27天】JavaScript的变量提升是一种编译阶段的行为,它将`var`声明的变量和函数声明移至作用域顶部。变量默认值为`undefined`,函数则整体提升。`let`和`const`不在提升范围内,存在暂时性死区。现代实践推荐明确声明位置以减少误解。
43 2
|
16天前
|
存储 JavaScript 前端开发
JS函数提升 变量提升
【10月更文挑战第6天】函数提升和变量提升是 JavaScript 语言的重要特性,但它们也可能带来一些困惑和潜在的问题。通过深入理解和掌握它们的原理和表现,开发者可以更好地编写和维护 JavaScript 代码,避免因不了解这些机制而导致的错误和不一致。同时,不断提高对执行上下文等相关概念的认识,将有助于提升对 JavaScript 语言的整体理解和运用能力。
|
23天前
|
JavaScript 前端开发 安全
JS中const有没有变量提升
JS中const有没有变量提升
13 0
|
5月前
|
JavaScript 前端开发
JavaScript中的变量提升(Hoisting)将`var`声明和函数声明提前到作用域顶部,允许在声明前使用
【6月更文挑战第25天】JavaScript中的变量提升(Hoisting)将`var`声明和函数声明提前到作用域顶部,允许在声明前使用。`let`和`const`不完全提升,存在暂时性死区(TDZ),尝试在初始化前访问会出错。函数声明会被提升,但函数表达式不会。
47 3
|
5月前
|
自然语言处理 JavaScript 前端开发
【JavaScript】JavaScript基础知识强化:变量提升、作用域逻辑及TDZ的全面解析
【JavaScript】JavaScript基础知识强化:变量提升、作用域逻辑及TDZ的全面解析
61 3
|
6月前
|
JavaScript 前端开发 开发者
【Web 前端】什么是JS变量提升?
【5月更文挑战第1天】【Web 前端】什么是JS变量提升?
【Web 前端】什么是JS变量提升?
|
4月前
|
JavaScript 前端开发 安全
20 JavaScript学习:变量提升和严格模式
20 JavaScript学习:变量提升和严格模式
|
5月前
|
JavaScript
JS 块级作用域、变量提升
JS 块级作用域、变量提升
|
6月前
|
JavaScript 前端开发
解释 JavaScript 中的变量提升现象。
JavaScript中的变量提升将变量和函数声明提前到作用域顶部。在代码执行时,即使声明在后,变量和函数也可访问。例如,`var a = 10;`在`console.log(a)`前已声明,故输出`undefined`。变量提升只提升声明,不提升赋值,未赋值变量默认为`undefined`。理解此特性对处理变量声明和作用域至关重要。
36 4