JavaScript变量
var、let、const的异同之处
相同点1:都可以声明保存数据的容器
在JS中,这三个关键字都可以用来申明保存数据的容器。对于var
和let
声明的容器叫做变量
。const
声明的容器叫常量
。
**示范:**使用var
,let
声明变量const
声明常量。
js
var age = 18
let name = 'Tom'
const gender = 'male'
不同点1:const声明的常量不能直接修改,var和let可以
好多文章说这三个关键字都可以用来申明变量,我认为这种说法并不严谨。因为const
声明的常量并不能够直接修改。如果直接修改会报错。
js
const gender = 'male'
gender = 'female' 🚫🚫// 报错TypeError: Assignment to constant varibale
但是这种特性仅限于使用const
声明的简单JS数据类型。如:string、number、boolean。
而JS的引用数据类型,即:object
类型中的值是可以修改的。
你可以这么写。
js
// 引用类型 数组;数组也属于对象object类型
const cities = ['上海', '北京']
cities[0] = '深圳'
// 引用类型 对象
const city = {
name:'上海',
pos:'XXX'
}
city.name = '南京'
虽然你可以更改引用数据类型常量中的值,但是你不能对引用数据类型重新赋值。什么意思呢。
js
const city = {
name:'上海',
pos:'XXX'
}
// 这种写法是错误的
city = {}
不同点2:var声明的变量会被添加到全局属性中,let和const不会
var
声明的变量会被添加到全局属性中。在JS代码的不同执行环境中,所添加的全局属性也不同。
在浏览器运行环境下:var
声明的变量会被添加到window
这一全局属性中。
在Node运行环境下:添加到global
这一全局属性中。
js
// 在浏览器中
var age = 18
console.log(window.age) // 18
// 在node中
var age = 18
console.log(global.age) // 18
事实上,我们可以通过一个新的全局属性来访问浏览器或者node运行环境下的全局属性。也就是globalThis
。不论在浏览器中还是node中,我们都可以使用这个全局属性来访问添加到全局属性的var
声明的变量。
js
// 在浏览器中
var age = 18
console.log(globalThis.age) // 18
// 在node中
var age = 18
console.log(globalThis.age) // 18
值得注意的是,Node运行环境指的是使用终端来运行JS代码。如果你在一个js
文件中编写下面这一行代码,会输出undefined
.
js
var age = 18
console.log(globalThis.age) // undefined
原因是,编写在文件中的JS代码会被包裹在一个函数中执行。而由于var
声明的变量仅限于函数内部使用,因此其不会被添加到全局属性中。
不同点3: let和const声明的变量/常量,必须先声明在使用,而var可以先使用在声明。
我们都知道代码的执行总是从上到下的,声明一个变量后再使用是符合我们的直觉的。
例如:
js
let age = 18
console.log(age)
const myName = 'Tom'
console.log(Name)
如果你在没有声明之前就使用了这个变量,既不符合我们的直觉,也不符合编译器的要求。
js
console.log(age)
let age = 18 // 错误
console.log(Name)
const myName = 'Tom' // 错误
编译器会提示你cannot access 'age' before initalization
。翻译一下就是不能够在age未初始化前使用它。
然而,var
声明的变量可以在使用之后才进行声明。
js
console.log(age, myName)
var age = 10
var myName = '张三'
猜猜这段代码会输出什么?
是:"10 张三"吗?
还是和let, const一样的报错信息呢?
都不是,我们的代码会输出两个undefined
。这是为什么呢?
首先:编译器不会提示你cannot access 'age' before initalization
, 这说明我们的变量在使用前声明过了。其次,我们的代码没有输出10和张三,而是输出undefined
,这说明我们声明了两个变量但是并没有进行赋值。
那么为什么我们var
声明的变量写在下面,我们却可以上方使用它呢?
这就不得不引入:变量提升这一概念了。
当 JavaScript 引擎执行代码时,创建了全局执行上下文,它有两个阶段:
- 创建(准备工作)
- 执行
在创建阶段,JavaScript 引擎将
var
声明移到了顶层,这就是 JavaScript 的变量提升。
其实这段话⬆️就是在说JS在执行代码的准备阶段会将var
声明的变量移动到顶层。也就是说我们的代码
js
console.log(age, myName)
var age = 10
var myName = '张三'
相当于⬇️
js
var age, myName
console.log(age, myName)
age = 10
myName = '张三'
可以看到,JS帮我们把var
声明的变量移动到了代码的顶层。但是,JS并不会将我们的初始化语句也移动到代码顶层。这点是需要特别注意的。
除了var
声明的变量会发生变量提升之外,使用function
关键字声明的变量,也会发生变量提升现象。
例如:
js
console.log(add(1,4)) // 5
function add (num1, num2) {
return num1 + num2
}
这段代码会正确的输出5,就是因为编译器帮我们干了变量提升这件事。
但是,❗要注意并不是所有声明的函数都会变量提升。
例如:
1. 函数表达式不会变量提升
js
console.log(add(1,4)) // cannot access 'add' before initalization
// 函数表达式, 不会变量提升
const add = function (num1, num2) {
return num1 + num2
}
使用var
接过来的函数表达式也不能使用,但是报错信息不同。原因和上文的输出undefined
一样。这里就不解释了。
js
console.log(add(1,4)) // TypeError: add is not a function
var add = function(num1, num2) {
return num1 + num2
}
2. 箭头函数不会变量提升
js
console.log(add(1,4))
// cannot access 'add' before initalization
const add = (num1, num2) => {
return num1 + num2
}
js
console.log(add(1,4))
// TypeError: add is not a function
var add = (num1, num2) => {
return num1 + num2
}
不同点4: let和const不能重复声明变量,而var可以
使用var
声明的变量可以重复声明,后者会覆盖前者。
js
const a = 1
// const a = 3
let b =2
// let b =5
var c = 3
var c = 10
除了var
声明的变量外,使用关键字function
声明的函数也存在该机制。即重名的函数,后者会覆盖前者。(函数表达式、箭头函数不能重新声明)
js
console.log(add(10,20)) // 60
function add(num1, num2) {
return num1 + num2
}
function add(num1, num2) {
return num1 + num2 + 30
}
这种机制可能是JS的设计缺陷导致的,按理来说不应该出现这种允许重复声明的机制。
不同点5: 作用域不同
首先我们需要知道什么是作用域:
什么是作用域?
作用域是当前的执行上下文,值和表达式在其中"可见"或可被访问。
JavaScript 作用域
JavaScript 的作用域可以分为以下四种:
- 全局作用域:脚本模式运行所有代码的默认作用域
- 函数作用域:由函数创建的作用域
- 块级作用域:用一对花括号(一个代码块)创建出来的作用域
- 模块作用域:模块模式中运行代码的作用域
全局作用域
说白了就是这个作用域内的所有变量可以被其他所有作用域使用。
全局作用域中的变量称为全局变量,可以在任何作用域内访问。
有两种全局变量:
全局声明变量 (declarative variables)是普通变量,在最顶级由 const
、let
和 class
声明的变量。全局对象(object variables)是存储在全局对象中的属性:在最顶级由 var
和 function
声明后创建的变量 全局对象可以通过 globalThis 和 window 访问,它可以对全局对象变量进行增删改查
例如:在浏览器环境下
html
<script>
let a = 10
var b = 20
// 不会挂载到全局 输出undefined
console.log(globalThis.a)
// var挂载到全局 输出20
console.log(globalThis.b)
</script>
<script>
// 受全局作用域影响 输出10
console.log(a)
</script>
函数作用域
说白了就是 函数内部声明的变量,只能在函数内部使用。
js
function scope() {
var address = "Beijing"
console.log(address) // 输出 Beijing
}
console.log(address) // Uncaught ReferenceError: address is not defined
块级作用域
作用域对变量来说,可以简单理解为程序能够访问到变量的范围,超过作用域的就无法访问。
html
<script>
{
let a = 10
{
let b = 20
let c = 30
console.log(a) // 10 块级作用域可以嵌套,里层的可以访问外层的变量
console.log(b) // 20
console.log(c) // 30
}
console.log(b,c) // 外层的不可以访问里层的变量
}
// 不在块里面的也不能访问
console.log(a)
</script>
同块级作用域的变量不允许同名,但是异块作用域的变量可以同名。
html
<script>
{
let a = 10
{
let a = 10
let b = 20
let c = 30
console.log(a) // 10
console.log(b) // 20
console.log(c) // 30
}
}
let a = 10
console.log(a) // 10
</script>
模块作用域
你可以认为每一个JS文件就是一个模块,在这个模块内部定义的变量只能在这个模块内使用。如果要被其他模块使用,就要选择export
导出。
==========================================================================
OK介绍了这么多作用域。具体来讲一讲var
,let
,const
这三者的作用域有什么区别。首先var
的作用域比较特别,是函数作用域
。只要在一个函数中声明的var
变量,不论它嵌套在块级作用域的哪一层,都可以被使用。
html
<script>
function add() {
{
{
{
var a = 10
}
console.log(a) // 10
}
console.log(a) // 10
}
}
add()
</script>
而let
和const
声明的变量/常量满足块级作用域的特性。也就说只能在当前块中使用。刚才的例子,我们将var换成let或者const就会报错。
js
function add() {
{
{
{
let a = 10
}
console.log(a)
}
console.log(a)
}
}
add()
===========================================================================
用一张表格总结一下:
定义 | 作用域 | 先声明在使用 | 重复声明 | 全局属性 | |
---|---|---|---|---|---|
const | 常量 | 块级 | Y | X | X |
let | 变量(ES6新增) | 块级 | Y | X | X |
var | 变量 | 函数内 | N | Y | Y |