Skip to content

变量声明

声明

JavaScript 中,一共存在 3 种声明变量的方式:

  • var ES6 之前(不包括 ES6) 使用的均为 var
  • let ES6 后
  • const ES6 后

最初声明变量的关键字就是 var,但是为了解决作用域的问题,所以后面新增了 letconst 的方式.

作用域

首先我们来了解一下作用域.

ES5 中的作用域有:全局作用域函数作用域,ES6 中新增了块级作用域.
函数作用域块级作用域都是通过块语句实现的,块作用域由 { } 包括, if 语句和 for 语句里面的 { } 也属于块作用域.

var 关键字

MDN 传送门

1. 没有块级作用域的概念

js
//Global Scope
{
  var a = 10;
}
console.log(a); //10

上面代码中,在 Global Scope(全局作用域)中,且在 Block Scope(块级作用域) { } 中,a 输出结果为 10,由此可以看出 var 声明的变量不存在 Block Scope 的概念

2. 有全局作用域、函数作用域的概念

js
//Global Scope
var a = 10;
function checkscope() {
  //Local Scope
  var b = 20;
  console.log(a); //10
  console.log(b); //20
}
checkscope();
console.log(b); //ReferenceError: b is not defined

上面代码中,在 Global Scope 中用 var 声明了 a,在 checkscope 函数中的 Local Scope(本地作用域、函数作用域)中打印出了 10,但是在 Global Scope 中打印的变量 b 报错了.

3. 不初始化值默认为 undefined

js
//Global Scope
var a;
console.log(a); //undefined

上面代码中,在 Global Scope 中用 var 声明了 a,但没有初始化值,它的值默认为 undefined,这里是 undefinedundefined 类型,而不是字符串.

4. 存在变量提升

js
//Global Scope
console.log(a); //undefined
var a = 10;

checkscope();
function checkscope() {
  //Local Scope
  console.log(a); //undefined
  var a;
}

上面代码中,先打印了 a,然后用 var 声明变量 a.变量提升是因为 js 需要经历编译和执行阶段.而 js 在编译阶段的时候,会搜集所有的变量声明并且提前声明变量.

可以将这个过程形象地想象成所有的声明(变量)都会被“移动”到各自作用域的最顶端,这个过程被称为提升.

至于 checkscope 函数中的变量 a 为什么输出 undefined,

  1. 当进入 checkscope() 函数时,创建了一个新的作用域
  2. 在这个作用域中,变量 a 一开始是未定义的
  3. 声明了 a 后,它的值还是 undefined,因为没有立即赋值
  4. 执行完函数后,这个作用域就消失了,对外部作用域没有影响

5. 全局作用域用 var 声明的变量会挂载到 window 对象下

为什么不使用 var 声明的变量可以删除?

通过 var 声明的变量 和不用 var 声明的变量 window 上面 有一个不同的地方 . 最主要的体现就是 不使用 var 声明的变量可以通过 delete 删除

var 声明变量 的删除
这张图片害羞了
var 声明变量 的删除

下面是原因

var 声明变量 的不同之处
这张图片害羞了
var 声明变量 的不同之处
js
//Global Scope
var a = 10;
console.log(a); //10
console.log(window.a); //10
console.log(this.a); //10

上面代码中,打印出了 310,访问 awindow.a 或是 this.a 都是等价的.

举个例子:比如我要访问 location 对象,使用 location 可以访问,使用 window.location 也可以访问,只不过 window 对象可以省略不写,就像 new Array( )new window.Array( ) 是等价的.

  1. 同一作用域中允许重复声明
js
//Global Scope
var a = 10;
var a = 20;
console.log(a); //20

checkscope();
function checkscope() {
  //Local Scope
  var b = 10;
  var b = 20;
  console.log(b); //20
}

上面代码中,在 Global Scope 中声明了 2a,以最后一次声明有效,打印为 20.同理,在 Local Scope 也是一样的.

let 关键字

MDN 传送门

1. 有块级作用域的概念

js
{
  //Block Scope
  let a = 10;
}
console.log(a); //ReferenceError: a is not defined

上面代码中,打印 a 报错,说明存在 Block Scope 的概念.

2. 不存在变量提升

js
{
  //Block Scope
  console.log(a); //ReferenceError: Cannot access 'a' before initialization
  let a = 10;
}

上面代码中,打印 a 报错:无法在初始化之前访问.说明不存在变量提升.

3. 暂时性死区

js
{
  //Block Scope
  console.log(a); //ReferenceError: Cannot access 'a' before initialization
  let a = 20;
}

if (true) {
  //TDZ开始
  console.log(a); //ReferenceError: Cannot access 'a' before initialization

  let a; //TDZ结束
  console.log(a); //undefined

  a = 123;
  console.log(a); //123
}

上面代码中,使用 let 声明的变量 a,导致绑定这个块级作用域,所以在 let 声明变量前,打印的变量 a 报错.

这是因为使用 let/const 所声明的变量会存在暂时性死区.

什么叫做暂时性死区域呢?

ES6 标准中对 let/const 声明中的解释 第 13 章,有如下一段文字:

The variables are created when their containing Lexical Environment is instantiated but may not be accessed inany way until the variable’s LexicalBinding is evaluated.

翻译成人话就是:

当程序的控制流程在新的作用域(module、functionblock 作用域)进行实例化时,在此作用域中用 let/const 声明的变量会先在作用域中被创建出来,但因此时还未进行词法绑定,所以是不能被访问的,如果访问就会抛出错误.因此,在这运行流程进入作用域创建变量,到变量可以被访问之间的这一段时间,就称之为暂时死区.

再简单理解就是:

ES6 规定,let/const 命令会使区块形成封闭的作用域.若在声明之前使用变量,就会报错. 总之,在代码块内,使用 let/const 命令声明变量之前,该变量都是不可用的. 这在语法上,称为 “暂时性死区”temporal dead zone,简称 TDZ).

其实上面不存在变量提升的例子中,其实也是暂时性死区,因为它有暂时性死区的概念,所以它压根就不存在变量提升了.

4. 同一块作用域中不允许重复声明

js
{
  //Block Scope
  let A;
  var A; //SyntaxError: Identifier 'A' has already been declared
}
{
  //Block Scope
  var A;
  let A; //SyntaxError: Identifier 'A' has already been declared
}
{
  //Block Scope
  let A;
  let A; //SyntaxError: Identifier 'A' has already been declared
}

const 关键字

MDN 传送门

1. 必须立即初始化,不能留到以后赋值

js
// Block Scope
const a; // SyntaxError: Missing initializer in const declaration }

上面代码中,用 const 声明的变量 a 没有进行初始化,所以报错.

2. 常量的值不能改变

js
//Block Scope
{
  const a = 10;
  a = 20; // TypeError: Assignment to constant variable
}

上面代码中,用 const 声明了变量 a 且初始化为 10,然后试图修改 a 的值,报错.

const 实际上保证的,并不是变量的值不得改动,而是变量指向的那个内存地址所保存的数据不得改动.

特点总结

var 关键字

  1. 没有块级作用域的概念
  2. 有全局作用域、函数作用域的概念
  3. 不初始化值默认为 undefined
  4. 存在变量提升
  5. 全局作用域用 var 声明的变量会挂载到 window 对象下
  6. 同一作用域中允许重复声明

let 关键字

  1. 有块级作用域的概念
  2. 不存在变量提升
  3. 暂时性死区
  4. 同一块作用域中不允许重复声明

const 关键字

  1. let 特性一样,仅有 2 个差别
  2. 区别 1:必须立即初始化,不能留到以后赋值
  3. 区别 2:常量的值不能改变

真题解答

let const var 的区别?什么是块级作用域?如何用?

参考答案:

var 定义的变量,没有块的概念,可以跨块访问, 不能跨函数访问,有变量提升.

let 定义的变量,只能在块作用域里访问,不能跨块访问,也不能跨函数访问,无变量提升,不可以重复声明.

const 用来定义常量,使用时必须初始化(即必须赋值),只能在块作用域里访问,而且不能修改,无变量提升,不可以重复声明.

最初在 JS 中作用域有:全局作用域、函数作用域.没有块作用域的概念.

ES6 中新增了块级作用域.块作用域由 { } 包括,if 语句和 for 语句里面的 { } 也属于块作用域.

在以前没有块作用域的时候,在 if 或者 for 循环中声明的变量会泄露成全局变量,其次就是 { } 中的内层变量可能会覆盖外层变量.块级作用域的出现解决了这些问题.