返回

js变量提升原理及es6中let和const变量的优势

日志

js中有一个很有趣的现象,就是我们可以在声明一个变量之前就使用它,而不会报错。这就是所谓的变量提升。例如:

console.log(a); // undefined
var a = 1;

这段代码不会报错,而是打印出undefined。这是因为js在执行代码之前,会先对所有的var变量进行声明,但不赋值。所以相当于:

var a;
console.log(a); // undefined
a = 1;

这就是变量提升的定义:js会把所有的var变量声明提升到当前作用域的最顶端,但不赋值。

那么为什么js会这样做呢?这背后其实涉及到了js的执行上下文和作用域链的概念。

执行上下文是js执行代码时的环境,它包括三个部分:变量对象、作用域链和this指向。每当进入一个新的执行上下文时,js会做两件事:一是创建变量对象,二是建立作用域链。

变量对象是存储当前执行上下文中所有变量和函数声明的对象。它分为两个阶段:创建阶段和激活阶段。在创建阶段,js会扫描当前执行上下文中所有的var变量和函数声明,并将它们添加到变量对象中,此时var变量的值为undefined,函数声明则保留整个函数体。在激活阶段,js会按照代码顺序逐行执行,并给var变量赋值。

作用域链是一系列的变量对象,它决定了当前执行上下文中可以访问哪些变量和函数。它由当前执行上下文的变量对象和所有父级执行上下文的变量对象组成。当我们访问一个变量时,js会先在当前执行上下文中查找,如果找不到,就沿着作用域链向上查找,直到找到或者到达全局执行上下文为止。

由于js在创建执行上下文时就已经确定了所有的var变量和函数声明,并将它们添加到变量对象中,所以我们可以在声明之前就使用它们,这就是变量提升的原理。

然而,这种机制也带来了一些问题。比如:

  • 变量提升可能导致意外的结果或者错误,因为我们无法保证在声明之前使用变量时它们是否已经被赋值。
  • 变量提升可能导致变量污染或者覆盖

接下来我们来看看es6中的let和const变量是如何解决变量提升的问题的。

let和const变量是es6新增的两种声明变量的方式,它们有以下几个特点:

  • let和const变量不会被提升,它们只在声明所在的块级作用域内有效。
  • let和const变量在声明之前不能被访问,否则会报错。这就是所谓的暂时性死区(temporal dead zone)。
  • const变量必须在声明时就赋值,并且不能被修改。let变量可以在声明后赋值或者修改。

由于let和const变量不会被提升,所以我们可以避免变量提升导致的意外结果或者错误。例如:

console.log(a); // ReferenceError: a is not defined
let a = 1;

这段代码会报错,因为let变量a在声明之前不能被访问,所以不存在变量提升。

由于let和const变量只在块级作用域内有效,所以我们可以避免变量污染或者覆盖。例如:

var a = 1;
if (true) {
  let a = 2;
  console.log(a); // 2
}
console.log(a); // 1

这段代码中,let变量a只在if语句块内有效,不会影响外部的var变量a,所以打印出两个不同的值。

由于const变量必须在声明时就赋值,并且不能被修改,所以我们可以避免意外修改或者重新赋值。例如:

const a = 1;
a = 2; // TypeError: Assignment to constant variable.

这段代码会报错,因为const变量a不能被重新赋值。

综上所述,let和const变量相比var变量有很多优势,它们可以让我们的代码更加清晰、安全和可靠。所以建议大家在编写js代码时尽量使用let和const变量,而不是var变量。