沐光

记录在前端之路的点点滴滴

变量提升完全解读

前言

变量提升这个问题不论是以前还是现在,总是作为面试常考题型出现,因为 ES6 的新特性的推行,对于 var 所导致的变量提升问题在项目中就很少存在了。但是为什么 letconst 能解决这个变量提升的问题呢,静下心来品一品,其实还是有很多内容可以学到的。

变量提升的本质

首先为什么会有变量提升?先前在深入了解 this一文中有简单提到过:“JS 因为是运行时编译的,会粗略的提取变量声明,因此会有变量提升”。在变量提升后,当程序执行到对应行在进行赋值操作,因此大概流出如下:

  • 变量声明提升
  • 函数声明提升(已经指向了函数声明的内存块)
  • 变量赋值

这也就解释了为什么函数和变量一起提升,但是在后面使用的时候,结果都会是函数。

小结:变量声明的提升优于函数声明的提升。

变量提升的位置

其实变量提升的问题除了提升的顺序外,另一个就是提升的位置了。其实这个只需要记住 JS 中有哪些作用域了。在 ES6 之前,JS 的作用域仅有两个:

  • 函数作用域
  • 全局作用域

在引入 ES6 后有新增了一个 块级作用域。在变量提升时,其声明仅仅能够提升到当前作用域的最顶端(具体原因可查阅第二篇参考文章)。所以在确定了作用域的情况下,结合提升的顺序,我们就能很明确的了解到变量的顺序了。

小结:JS 仅有 全局作用域函数作用域块级作用域

小试牛刀

在作用域和变量提升顺序都清楚的情况下,跟着思路过一道题加深一下印象:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
console.log(bb); // function bb() { console.log('change'); }

var aa = 123;
var bb = 'string';
let cc = {};

function bb() {
console.log('change');
}

(function () {
var ee = dd = [];
cc = 'new String';
})();

console.log(aa); // 123
console.log(bb); // 'string'

var aa;
var bb = function() {
console.log('who am I');
}

console.log(aa); // 123
console.log(bb); // "function bb() { console.log('who am I'); }"
console.log(cc); // 'new String'
console.log(dd); // []
console.log(ee); // Error: ee is not defined

这里又几个注意的小细节:

  1. JS 赋值操作为“自右向左”进行的
  2. JS 取值操作取最近的一次赋值的值
  3. 未声明的变量会报错,而非 undefined

参考一下处理后的结构(建议开两个页面分屏看):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
var aa = undefined;
var bb = undefined;
let cc; // 仅声明,产生临时索取(取不到值)

function bb() {
console.log('change');
}

console.log(bb); // function bb() { console.log('change'); }

aa = 123;
bb = 'string';
cc = {};

// console.log(dd) // Error: ee is not defined

(function () {
var ee = undefined;
dd; // 无声明默认挂在到 window 了(此时才挂载上 window 的,前面拿不到)

dd = [];
ee = dd;

// 能拿到上级作用域中的 cc 变量
cc = 'new String';
})();

console.log(aa); // 123
console.log(bb); // 'string'

bb = function() {
console.log('who am I');
}

console.log(aa); // 123
console.log(bb); // "function bb() { console.log('who am I'); }"
console.log(cc); // 'new String'
console.log(dd); // []
console.log(ee); // Error: ee is not defined

总结

遇到变量提升相关问题时,只需关注一下几点基本都能解答上来:

  • 变量所在作用域位置;
  • 区分函数提升和变量提升,取 console 时最近的一次赋值位置;
  • 小心全局变量声明的陷阱;
  • 无变量声明的情况下,打印会报错;

参考文章