let、const、var区别原理

595 阅读4分钟
console.log(a); // undefind
var a = 1;
console.log(a); // 1

前言

变量提升可以说是我们在 ES6 出来之前必须要面对的一个坑点. 13天就实现的脚本语言确实是有这样那样让人费解的现象的.

但是只要我们能够了解其中的规律, 就能够更好的利用它. 也更加能够明白 letconst 给JS开发人员带来的巨大幸福感.

下面的内容大量是我基于你不知道的javascript和红宝书中的理解而来. 只是个人当下的理解,不一定对.请辩证阅读.(虽然没啥人看,哈哈)

一、变量提升

想要说清楚这个问题,涉及到一点 编译原理的知识.

  • Tokens -> AST -> 可执行语言
  • LHS查询和RHS查询

很多文章说的letconstvar的差别,第一条都是说,letconst没有变量提升.

明确一点,这样的说话是有问题的, 它们都是存在变量提升的过程的.

var存在如下两个特性:

  1. 没有暂时性死区:
console.log(a); // undefined
var a = 2;
  1. 没有块级作用域:
console.log(a); // undefined
{
var a = 1;
}
console.log(a); // 1

javaScript 也是一门编译语言,只是它不会提前编译而已. 它的编译和执行到了宿主环境才开始的.在执行之前才进行编译.

所以说它是“动态” 语言是不准确的. 也基于此,它的编译过程和其他的高级语言大体是相同的.

词法分析(Tokenizing) - 也叫分词

将代码分成最小单位 token. 比如说 var a = 2拆分为vara=2这四个token.

空格取决于你的语言,比如说py这种靠空格来表示缩进的玩意儿.空格就不能忽略掉

语法分析(Parsing) - 也叫解析

就是形成语法抽象树AST. 这个和babel的实现过程一致的.长的样子, 可以说和 DOM 树,CSSOM 树 很像

代码生成

将 AST 树转化为可执行的代码

然后到了第二个大的部分, 运行时, 它有两种查询的方式: LHSRHS.

具体可以查询你不知道的javaScript

回到varlet以及const上面. 这三个有他们自己的执行的生命周期.

var

var.png

let

let.png

const

const.png

变量的生命周期和编译过程的对应:

词法解析的过程中,出现了如下的情况:

  1. var完成了变量的声明,分配了内存,并且对内存进行了初始化.然后再提升到作用域的最上面.

  2. let完成了变量的声明, 分配到了内存.就提升到了作用域最上面. 没有进行初始化,所以状态是initialization. 该状态下, LHS查询会报错,也就是暂时性死区. 如何重复命名的话,人家看你想要开辟两个内存,但是都不初始化.也是给拒绝了,即重复命名报错.

  3. const完成了变量的声明,分配了内存,提升到作用域最上.此时状态是initialization,所以会出现和let一样的现象.暂时性死去重复赋值.

解析并生成可执行代码之后,运行时:

  1. cosnt锁定的是栈内存中的值,至于这个栈内存所执行的堆内存中的属性没有进行锁定.

  2. 也是基于第一条,在明确可能会对变量进行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里用函数表达式代替函数声明即可.深入研究就是浪费时间.