# 前言
相信很多同学都能够说出var,let,const
三者之间的区别,即便说的不全,也总能说出其中几点内容,但如果问你var
为什么可以重复声明,而let、const却不能重复声明呢,以及在全局作用域中,用let
和const
声明的变量没在 window
上,那在哪里呢?我们如何去获取呢?这些应该有不少人不清楚吧,今天我们就一起来看看这三者之间的区别,以及揭开后面这几个问题的答案吧~
如果这篇文章有帮助到你,❤️关注+点赞❤️鼓励一下作者,文章公众号首发,关注 前端南玖
第一时间获取最新的文章~
# var
# 全局变量
在ES6出来之前,声明变量就只有var
这一个关键词,并且当时没有块级作用域的概念,所以顶层对象的属性与全局对象的属性是一样的,用var
声明的全局变量也就是顶层变量。
顶层变量,在浏览器
中是指window
对象,在node
环境中是指Global
对象
var name = 'nanjiu'
console.log(window.name) // 'nanjiu'
# 局部变量
在函数中用var
声明的变量是局部变量,通过window
或global
访问不到
function sayName() {
var s_name = '南玖'
console.log(s_name) // '南玖'
}
sayName()
console.log(window,window.s_name) // undefined
所以当时为了解决全局变量混乱的问题,一般都会借用函数作用域在解决,这一点,你可以去看jq的源码,它的代码都是放在一个自执行函数中的,与外界隔开~
如果在函数中不使用var
声明变量,该变量也是全局的
function sayName() {
s_name = '南玖'
console.log(s_name) // '南玖'
}
sayName()
console.log(window.s_name) // ‘南玖
# 变量提升
使用var
声明的变量会存在变量提升的情况,也就是在变量声明之前,你可以访问到它,只不过它的值是undefined
console.log(s_name) // undefined
var s_name = '南玖'
上面的代码在编译时会变成以下代码,这也就是为什么你可以访问到它,但它的值是undefined
var s_name
console.log(s_name) // undefined
s_name = '南玖'
# 可以重复声明
使用var
关键字声明的变量可以重复声明,后者会覆盖前者
var s_name = 'nanjiu'
var s_name = 'frontend'
console.log(s_name) // 'frontend'
# let
该关键字是ES6新增的,用来声明变量,用法与ES5中的var类似,但是所声明的变量,只在
let
命令所在的代码块中生效。(块级作用域)
# 块级作用域
ES5 只有全局作用域和函数作用域,没有块级作用域,这带来很多不合理的场景。
{
let s_name = 'nanjiu'
var age = 18
}
console.log(s_name) // 报错 Uncaught ReferenceError: s_name is not defined
console.log(age) //18
# 不存在变量提升
var
命令会发生“变量提升”现象,即变量可以在声明之前使用,值为undefined
。这种现象多多少少是有些奇怪的,按照一般的逻辑,变量应该在声明语句之后才可以使用。
为了纠正这种现象,let
命令改变了语法行为,它所声明的变量一定要在声明后使用,否则报错。
console.log(s_name) //报错
let s_name = 'nanjiu'
# 暂时性死区
只要块级作用域内存在let
命令,它所声明的变量就“绑定”(binding)这个区域,不再受外部的影响。
在代码块内,使用let
命令声明变量之前,该变量都是不可用的。这在语法上,称为“暂时性死区”(temporal dead zone,简称 TDZ)。
var name = 'nanjiu'
{
console.log(name) // 报错
let name = '南玖'
}
在上面代码中声明了一个全局变量name
和一个块级作用域中的局部变量name
,导致后者绑定这个块级作用域,所以在let
声明变量前,对name
访问会报错。在let声明变量name
之前都是变量name
的暂时性死区
ES6 明确规定,如果区块中存在let
和const
命令,这个区块对这些命令声明的变量,从一开始就形成了封闭作用域。凡是在声明之前就使用这些变量,就会报错。
ES6 规定暂时性死区和let
、const
语句不出现变量提升,主要是为了减少运行时错误,防止在变量声明前就使用这个变量,从而导致意料之外的行为。这样的错误在 ES5 是很常见的,现在有了这种规定,避免此类错误就很容易了。
总之,暂时性死区的本质就是,只要一进入当前作用域,所要使用的变量就已经存在了,但是不可获取,只有等到声明变量的那一行代码出现,才可以获取和使用该变量。
# 不允许重复声明
let
不允许在相同作用域内,重复声明同一个变量。
// 报错
function sayName() {
let name = 'nanjiu'
var name = '南玖'
}
// 报错
// 重新声明函数参数也不行
function say (name) {
let name
}
# const
该关键字是ES6新增的,用来声明常量,一旦声明,该常量的值就不能改变。
# 声明常量
const PI = 3.14
PI // 3.14
PI = 3 // 报错
const
实际上保证的并不是变量的值不得改动,而是变量指向的那个内存地址所保存的数据不得改动
对于简单类型的数据,值就保存在变量指向的那个内存地址,因此等同于常量
对于复杂类型的数据,变量指向的内存地址,保存的只是一个指向实际数据的指针,const
只能保证这个指针是固定的,并不能确保改变量的结构不变
const obj = {
name: 'nanjiu',
age: 18
}
obj.name = 'hahaha' //可以正常运行
obj = {} // 报错
# 声明与赋值必须同时进行
const
声明的变量不得改变值,这意味着,const
一旦声明变量,就必须立即初始化,不能留到以后赋值。
const name
// 报错 Uncaught SyntaxError: Missing initializer in const declaration
# 块级作用域
const
的作用域与let
命令相同:只在声明所在的块级作用域内有效。
{
const name = 'nanjiu'
console.log(name) // 'nanjiu'
}
console.log(name) // 报错
# 不存在提升,暂时性死区
与let
类似,const
命令声明的常量也是不提升,同样存在暂时性死区,只能在声明的位置后面使用,否则报错。
console.log(name) // 报错
const name = 'nanjiu'
# 不允许重复声明
const
声明的常量,也与let
一样不可重复声明。
var name = 'nan'
const name = 'jiu' // 报错
# 三者的区别
# 变量提升、暂时性死区
var
声明的变量存在变量提升,即变量可以在声明之前调用,但是值为undefined
let
和const
不存在变量提升,即它们所声明的变量一定要在声明后使用,否则就会报错
var
不存在暂时性死区
let
和const
存在暂时性死区,只有等到声明变量的那一行代码出现,才可以获取和使用该变量
// var
console.log(name1) // undefined
var name1 = 'xiaoming'
// let
console.log(name2) // Cannot access 'name2' before initialization
let name2 = 'xiaohong'
// const
console.log(name3) // Cannot access 'name3' before initialization
const name3 = 'xiaobai'
# 块级作用域
var
不存在块级作用域
let
和const
存在块级作用域
// var
{
var name1 = 'xiaoming'
}
console.log(name1) // 'xiaoming'
// let
{
let name2 = 'xiaohong'
}
console.log(name2) // 报错 Uncaught ReferenceError: name2 is not defined
// const
{
const name3 = 'xiaobai'
}
console.log(name3) //报错 Uncaught ReferenceError: name3 is not defined
# 重复声明
var
允许重复声明变量
let
和const
在同一作用域不允许重复声明变量
// var
var name1 = 'xiaoming'
var name1 = 'xiaoming2' //xiaoming2
//let
{
let name2 = 'xiaohong'
let name2 = 'xiaohong2' //报错
}
//const
{
const name3 = 'xiaobai'
const name3 = 'xiaobai' // 报错
}
# 修改变量
var
和let
声明的变量可以修改
const
声明一个只读的常量。一旦声明,常量的值就不能改变
// var
var name1 = 'xiaoming'
var name1 = 'xiaoming2' //'xiaoming2'
//let
{
let name2 = 'xiaohong'
name2 = 'xiaohong2' // 'xiaohong2'
}
//const
{
const name3 = 'xiaobai'
name3 = 'xiaobai2' // 报错 Uncaught TypeError: Assignment to constant variable.
}
# 为什么var可以重复声明?
我们有时候可能会觉得JS很奇怪,与其它语言差别很大,容错率很高,一些其他语言常见的小错误JS都能大度得包容,我们可以来看下面两段代码:
var name = 'nanjiu'
var name = '南玖'
console.log(name) // '南玖'
使用var重复声明一个变量时,后面会覆盖前者,所以上面会打印出南玖
var name = 'nanjiu'
var name
console.log(name) // 'nanjiu'
这里却是打印出nanjiu
,而不是undefined
,这个可以由我们上面讲到的变量提升来解释,但它能够重复声明是为什么呢?
我们得从JS代码的运行机制说起:
在JS代码运行过程中:
引擎负责整个代码的编译和执行,编译器负责语法分析、词法分析、代码生成等,而作用域则负责维护所有的标识符(变量)
当执行上面的代码时,可以简单的理解为给新变量分配一块内存,命名为name
,并赋值为nanjiu
;但在运行的时候编译器与引擎还会进行两项额外的操作,即判断变量是否已经声明:
- 首先编译器对代码拆解,从左往右遇到了
var name
, 然后去询问作用域是否存在这个变量,如果不存在就让作用域声明一个新的变量name
,如果存在就忽略var
继续往下编译,这时name='nanjiu'
被编译成可执行的代码供引擎使用 - 引擎遇见
name='nanjiu'
时也会去询问作用域是否存在这个变量,若存在,则赋值为nanjiu
,若不存在,就沿着作用域往上查找,若找到了,赋值为nanjiu
,若没找到,让作用域声明一个新的变量nanjiu
用代码解释就是:
var name
name = 'nanjiu'
// var name // 忽略
name = '南玖'
console.log(name) // '南玖'
var name
name = 'nanjiu'
// var name // 忽略
console.log(name) // 'nanjiu'
# 为什么let、const不能重复声明?
在ES6规范有一个词叫做Global Enviroment Records
(也就是全局环境变量记录),它里面包含两个内容,一个是Object Enviroment Record
(它不等同于window对象),另一个是Declarative Enviroment Record
。
函数声明和使用
var
声明的变量会添加进入Object Enviroment Record
中。使用
let
声明和使用const
声明的变量会添加入Declarative Enviroment Record
中。下面是ECMAscript规范中对var
,let
,const
的一些约束。使用
var
声明时,V8引擎只会检查Declarative Enviroment Record
中是否有该变量,如果有,就会报错,否则将该变量添加入Object Enviroment Record
中。使用
let
和const
声明时,引擎会同时检查Object Enviroment Record
和Declarative Enviroment Record
是否有该变量,如果有,则报错,否则将将变量添加入Declarative Enviroment Record
中。
##在全局作用域中,用 let 和 const 声明的变量没在 window 上,那在哪里呢?我们如何去获取呢?
# ES5与ES6的区别
在ES5
中,顶层对象的属性与全局变量是等价的,var
与function
声明的全局变量也是顶层变量,我们可以通过window
或global
来访问
var s_name = 'nanjiu'
function say() {}
console.log(window.s_name) // 'nanjiu'
console.log(window.say) // ƒ say() {}
但是ES6
规定,var
与function
声明的全局变量,依旧是顶层对象的属性,但let
,const
、 class
声明的全局变量,不属于顶层的属性。
let s_name2 = 'nanjiu'
const age = 18
class Person{}
console.log(window.s_name2) // undefined
console.log(window.age) // undefined
console.log(window.Person) // undefined
###用 let 和 const 声明的变量在哪里呢?
既然用 let 和 const 声明的变量通过window访问不到,我们可以来看下浏览器是如何处理的:
var age = 20
let s_name2 = '南玖'
const gender = 'man'
{
let gzh = '前端南玖'
debugger
}
通过上图也可以看到,在全局作用域中,用var
声明的变量存在于全局变量window
上,用 let
和 const
声明的全局变量并没有在全局对象中,只是一个块级作用域(Script)中,而用 let
或 const
配合{}
声明的变量,则存在于块级作用域Block
中。