JavaScript中const、let、var的异同及变量提升和作用域讲解

JavaScript变量

var、let、const的异同之处

相同点1:都可以声明保存数据的容器

在JS中,这三个关键字都可以用来申明保存数据的容器。对于varlet声明的容器叫做变量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 引擎执行代码时,创建了全局执行上下文,它有两个阶段:

  1. 创建(准备工作)
  2. 执行

在创建阶段,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 的作用域可以分为以下四种:

  1. 全局作用域:脚本模式运行所有代码的默认作用域
  2. 函数作用域:由函数创建的作用域
  3. 块级作用域:用一对花括号(一个代码块)创建出来的作用域
  4. 模块作用域:模块模式中运行代码的作用域
全局作用域

说白了就是这个作用域内的所有变量可以被其他所有作用域使用。

全局作用域中的变量称为全局变量,可以在任何作用域内访问。

有两种全局变量:

全局声明变量 (declarative variables)是普通变量,在最顶级由 constletclass 声明的变量。全局对象(object variables)是存储在全局对象中的属性:在最顶级由 varfunction 声明后创建的变量 全局对象可以通过 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>

letconst声明的变量/常量满足块级作用域的特性。也就说只能在当前块中使用。刚才的例子,我们将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
相关推荐
天下无贼!39 分钟前
2024年最新版Vue3学习笔记
前端·vue.js·笔记·学习·vue
Jiaberrr39 分钟前
JS实现树形结构数据中特定节点及其子节点显示属性设置的技巧(可用于树形节点过滤筛选)
前端·javascript·tree·树形·过滤筛选
赵啸林42 分钟前
npm发布插件超级简单版
前端·npm·node.js
罔闻_spider1 小时前
爬虫----webpack
前端·爬虫·webpack
吱吱鼠叔1 小时前
MATLAB数据文件读写:1.格式化读写文件
前端·数据库·matlab
爱喝水的小鼠2 小时前
Vue3(一) Vite创建Vue3工程,选项式API与组合式API;setup的使用;Vue中的响应式ref,reactive
前端·javascript·vue.js
WeiShuai2 小时前
vue-cli3使用DllPlugin优化webpack打包性能
前端·javascript
Wandra2 小时前
很全但是超级易懂的border-radius讲解,让你快速回忆和上手
前端
ice___Cpu2 小时前
Linux 基本使用和 web 程序部署 ( 8000 字 Linux 入门 )
linux·运维·前端
JYbill2 小时前
nestjs使用ESM模块化
前端