在es5及之前的版本中,变量的声明都是使用var关键字,而在es6后JS引入了let和const作为变量声明关键字。但是在许多已经上线的大型项目中,仍然有许多的初始文件使用的是var声明变量,而现在很多项目在优化底部代码,因此我们对于var的一些特性也要做到熟悉。(以下只展示let声明,const与let特性相同)
变量提升
说到var我们首先必须要提及的便是变量提升
console.log(a); // 输出undefined
var a = 1;
为什么会这样呢,这就是var在预处理的的阶段出现了变量提升。在预处理阶段,JavaScript引擎在解释这段代码时,会将所有的var声明提升到其所在作用域的最顶部,但赋值操作不会提升。可以看作为:
var a; *// 变量声明被提升
console.log(a); // 此时a未被赋值,所以输出undefined
a = 1; *// 赋值操作保留在原地
而使用let关键字则不会出现这个问题
console.log(a); //error: Cannot access 'a' before initialization
let a = 1;
当JavaScript引擎遇到let或const声明的变量时,它会在代码执行到该声明之前创建一个所谓的“暂时性死区”。在这个区域里,尽管引擎已经知道即将有一个名为a的变量存在,但实际上在变量被正式声明并初始化之前,是无法访问它的。
块级作用域 vs 函数级作用域
var: 具有函数作用域(function scope),如果没有在任何函数内部声明,就默认具有全局作用域。
let: 引入了块级作用域(block scope),这意味着变量只在定义它们的那个代码块(例如if语句、for循环或者一对大括号内)内有效。
暂时性死区
{
var a=1
}
console.log(a)//输出1
{
let a=1
}
console.log(a); *// a is not defined 暂时性死区
这个功能有人说好,有人认为不严谨。但是相对C++/C等语言这样的而言确实是不严谨的。因为在{}内部形成了一个块级作用域,在es5中只有块级作用域和函数作用域两种,而在es6中加上了一个块级作用域的形成。它的形成主要就是在{}内使用,最直接的就是if()在其内部创建let ret变量在外部打印是输出not defined的。
重新声明与赋值
var a = 1;
var a = 2; *// 不会报错,a的值变为2
let b = 1;
let b = 2; *// 报错 SyntaxError: Identifier 'b' has already been declared
b = 3; *// 这是允许的,b的值变为3*
var: 允许在同一作用域内多次声明同名变量,不会报错,但这种做法可能导致意外的行为。var声明的变量也可以被重新赋值。
const的说明
const a //SyntaxError: Missing initializer in const declaration
const 声明时必须赋值,而let是可以在声明时不进行赋值的
const a=1
const a=2 //SyntaxError: Identifier 'a' has already been declared
const对于简单数据类型一旦声明就不能再更改,复杂类型(数组、对象等)指针指向的地址不能更改,内部数据可以更改。
const所说的一旦声明值就不能改变,实际上指的是:变量指向的那个内存地址所保存的数据不得改动所以只要不重新赋值整个数组/对象, 因为保存的是一个指针,所以对数组使用的push、shift、splice等方法也是允许的。
这里聊到存储方式的问题我们不再细聊,后面会单独出一篇文章细聊不同数据的存储方式。
let: 不允许在同一作用域内重新声明同名变量,会抛出语法错误。但是,已经用let声明的变量可以被重新赋值。
let、const声明的全局变量不会挂在顶层对象下面
- 浏览器环境顶层对象是: window
- node环境顶层对象是: global
- var声明的全局变量会挂在顶层对象下面,而let、const不会挂在顶层对象下面。
var a = 1; // 如果在 Node环境,可以写成 global.a
// 或者采用通用方法,写成 this.a window.a
let b = 1; window.b // undefined
const c= 1; window.c // undefined
总结
let提供了更严格的变量管理机制,有助于编写更清晰、更安全的代码,尤其是在处理异步编程、循环迭代等场景下。而var则因版本的原因,在一些老代码或特定场景中仍然许多可见,但在新项目中一般推荐使用let或const。