console.log(a); // undefind
var a = 1;
console.log(a); // 1
前言
变量提升可以说是我们在 ES6 出来之前必须要面对的一个坑点. 13天就实现的脚本语言确实是有这样那样让人费解的现象的.
但是只要我们能够了解其中的规律, 就能够更好的利用它. 也更加能够明白 let 和 const 给JS开发人员带来的巨大幸福感.
下面的内容大量是我基于你不知道的javascript和红宝书中的理解而来. 只是个人当下的理解,不一定对.请辩证阅读.(虽然没啥人看,哈哈)
一、变量提升
想要说清楚这个问题,涉及到一点 编译原理的知识.
- Tokens -> AST -> 可执行语言
- LHS查询和RHS查询
很多文章说的let和const和var的差别,第一条都是说,let和const没有变量提升.
明确一点,这样的说话是有问题的, 它们都是存在变量提升的过程的.
var存在如下两个特性:
- 没有暂时性死区:
console.log(a); // undefined
var a = 2;
- 没有块级作用域:
console.log(a); // undefined
{
var a = 1;
}
console.log(a); // 1
javaScript 也是一门编译语言,只是它不会提前编译而已. 它的编译和执行到了宿主环境才开始的.在执行之前才进行编译.
所以说它是“动态” 语言是不准确的. 也基于此,它的编译过程和其他的高级语言大体是相同的.
词法分析(Tokenizing) - 也叫分词
将代码分成最小单位 token. 比如说 var a = 2拆分为var、a、=、2这四个token.
空格取决于你的语言,比如说py这种靠空格来表示缩进的玩意儿.空格就不能忽略掉
语法分析(Parsing) - 也叫解析
就是形成语法抽象树AST. 这个和babel的实现过程一致的.长的样子, 可以说和 DOM 树,CSSOM 树 很像
代码生成
将 AST 树转化为可执行的代码
然后到了第二个大的部分, 运行时, 它有两种查询的方式: LHS和RHS.
具体可以查询你不知道的javaScript
回到var和let以及const上面. 这三个有他们自己的执行的生命周期.
var
let
const
变量的生命周期和编译过程的对应:
词法解析的过程中,出现了如下的情况:
-
var完成了变量的声明,分配了内存,并且对内存进行了初始化.然后再提升到作用域的最上面. -
let完成了变量的声明, 分配到了内存.就提升到了作用域最上面. 没有进行初始化,所以状态是initialization. 该状态下,LHS查询会报错,也就是暂时性死区. 如何重复命名的话,人家看你想要开辟两个内存,但是都不初始化.也是给拒绝了,即重复命名报错. -
const完成了变量的声明,分配了内存,提升到作用域最上.此时状态是initialization,所以会出现和let一样的现象.暂时性死去和重复赋值.
解析并生成可执行代码之后,运行时:
-
cosnt锁定的是栈内存中的值,至于这个栈内存所执行的堆内存中的属性没有进行锁定. -
也是基于第一条,在明确可能会对变量进行RHS查询的时候,才使用
let. 其他时候使用const就可以了.
二、函数提升
函数表达式可以看作上面的变量提升. 这里的函数提升一般来说,是指函数声明.
foo(); // 1
function foo() {
console.log(1);
}
函数声明可以在函数声明之前调用函数,这个就是函数提升.
套用上面的编译过程中,在词法解析阶段:
-
函数声明 完成了变量的声明,分配内存, 进行了初始化,且赋值了. 再提升到作用域的最上面.
-
这就意味着,此时变量
foo已经进行了赋值了.这就是函数提升先于变量提升的由来.console.log(foo) // Funtion function foo() {} var foo = 1 console.log(foo) // 1
三、块级作用域中的变量提升和函数提升
{
function foo(){}
foo=1;
}
console.log(foo);
{
function foo(){}
foo=1;
function foo(){}
}
console.log(foo);
{
function foo(){}
foo=1;
function foo(){}
foo=2;
}
console.log(foo);
- 记住有这样的情况就可以. 它只是为了兼容旧版浏览器才这么设置的.好拧巴的东西.
- .基于这种糟粕,不同浏览器实现不同,建议在if里用函数表达式代替函数声明即可.深入研究就是浪费时间.