深入理解JS:var、let、const的异同
itomcoil 2024-12-14 12:41 30 浏览
目录
- 序言
- var 与 let 的区别作用域重复声明绑定全局对象变量提升与暂存死区
- let 与 const 异同
- 参考
1.序言
var、let 和 const 都是 JavaScript 中用来声明变量的关键字,并且 let 和 const 关键字是在 ES6 中才新增的。既然都是用来声明变量的,那它们之间有什么区别呢?让我们来一探究竟。
2.var 与 let 的区别
(1)作用域
用 var 声明的变量的作用域是它当前的执行上下文,即如果是在任何函数外面,则是全局执行上下文,如果在函数里面,则是当前函数执行上下文。换句话说,var 声明的变量的作用域只能是全局或者整个函数块的。
而 let 声明的变量的作用域则是它当前所处代码块,即它的作用域既可以是全局或者整个函数块,也可以是 if、while、switch等用{}限定的代码块。
另外,var 和 let 的作用域规则都是一样的,其声明的变量只在其声明的块或子块中可用。
示例代码:
function varTest() {
var a = 1;
{
var a = 2; // 函数块中,同一个变量
console.log(a); // 2
}
console.log(a); // 2
}
function letTest() {
let a = 1;
{
let a = 2; // 代码块中,新的变量
console.log(a); // 2
}
console.log(a); // 1
}
varTest();
letTest();
从上述示例中可以看出,let 声明的变量的作用域可以比 var 声明的变量的作用域有更小的限定范围,更具灵活。
(2)重复声明
var 允许在同一作用域中重复声明,而 let 不允许在同一作用域中重复声明,否则将抛出异常。
var 相关示例代码:
var a = 1;
var a = 2;
console.log(a) // 2
function test() {
var a = 3;
var a = 4;
console.log(a) // 4
}
test()
let 相关示例代码:
if(false) {
let a = 1;
let a = 2; // SyntaxError: Identifier 'a' has already been declared
}
switch(index) {
case 0:
let a = 1;
break;
default:
let a = 2; // SyntaxError: Identifier 'a' has already been declared
break;
}
从上述示例中可以看出,let 声明的重复性检查是发生在词法分析阶段,也就是在代码正式开始执行之前就会进行检查。
(3)绑定全局对象
var 在全局环境声明变量,会在全局对象里新建一个属性,而 let 在全局环境声明变量,则不会在全局对象里新建一个属性。
示例代码:
var foo = 'global'
let bar = 'global'
console.log(this.foo) // global
console.log(this.bar) // undefined
那这里就一个疑问, let 在全局环境声明变量不在全局对象的属性中,那它是保存在哪的呢?
var foo = 'global'
let bar = 'global'
function test() {}
console.dir(test)
在Chrome浏览器的控制台中,通过执行上述代码,查看 test 函数的作用域链,其结果如图:
由上图可知,let 在全局环境声明变量 bar 保存在[[Scopes]][0]: Script这个变量对象的属性中,而[[Scopes]][1]: Global就是我们常说的全局对象。
(4)变量提升与暂存死区
var 声明变量存在变量提升,如何理解变量提升呢?
要解释清楚这个,就要涉及到执行上下文和变量对象。
在 JavaScript 代码运行时,解释执行全局代码、调用函数或使用 eval 函数执行一个字符串表达式都会创建并进入一个新的执行环境,而这个执行环境被称之为执行上下文。因此执行上下文有三类:全局执行上下文、函数执行上下文、eval 函数执行上下文。
执行上下文可以理解为一个抽象的对象,如下图:
Variable object:变量对象,用于存储被定义在执行上下文中的变量 (variables) 和函数声明 (function declarations) 。
Scope chain:作用域链,是一个对象列表 (list of objects) ,用以检索上下文代码中出现的标识符 (identifiers) 。
thisValue:this 指针,是一个与执行上下文相关的特殊对象,也被称之为上下文对象。
一个执行上下文的生命周期可以分为三个阶段:创建、执行、释放。如下图:
而所有使用 var 声明的变量都会在执行上下文的创建阶段时作为变量对象的属性被创建并初始化,这样才能保证在执行阶段能通过标识符在变量对象里找到对应变量进行赋值操作等。
而用 var 声明的变量构建变量对象时进行的操作如下:
- 由名称和对应值(undefined)组成一个变量对象的属性被创建(创建并初始化)
- 如果变量名称跟已经声明的形式参数或函数相同,则变量声明不会干扰已经存在的这类属性。
上述过程就是我们所谓的“变量提升”,这也就能解释为什么变量可以在声明之前使用,因为使用是在执行阶段,而在此之前的创建阶段就已经将声明的变量添加到了变量对象中,所以执行阶段通过标识符可以在变量对象中查找到,也就不会报错。
示例代码:
console.log(a) // undefined
var a = 1;
console.log(a) // 1
let 声明变量存在暂存死区,如何理解暂存死区呢?
其实 let 也存在与 var 类似的“变量提升”过程,但与 var 不同的是其在执行上下文的创建阶段,只会创建变量而不会被初始化(undefined),并且 ES6 规定了其初始化过程是在执行上下文的执行阶段(即直到它们的定义被执行时才初始化),使用未被初始化的变量将会报错。
let and const declarations define variables that are scoped to the running execution context’s LexicalEnvironment. The variables are created when their containing Lexical Environment is instantiated but may not be accessed in any way until the variable’s LexicalBinding is evaluated. A variable defined by a LexicalBinding with an Initializer is assigned the value of its Initializer’s AssignmentExpression when the LexicalBinding is evaluated, not when the variable is created. If a LexicalBinding in a let declaration does not have an Initializer the variable is assigned the value undefined when the LexicalBinding is evaluated.
在变量初始化前访问该变量会导致 ReferenceError,因此从进入作用域创建变量,到变量开始可被访问的一段时间(过程),就称为暂存死区(Temporal Dead Zone)。
示例代码 1:
console.log(bar); // undefined
console.log(foo); // ReferenceError: foo is not defined
var bar = 1;
let foo = 2;
示例代码 2:
var foo = 33;
{
let foo = (foo + 55); // ReferenceError: foo is not defined
}
注:首先,需要分清变量的创建、初始化、赋值是三个不同的过程。另外,从 ES5 开始用词法环境(Lexical Environment)替代了 ES3 中的变量对象(Variable object)来管理静态作用域,但作用是相同的。为了方便理解,上述讲解中仍保留使用变量对象来进行描述。
小结
- var 声明的变量在执行上下文创建阶段就会被「创建」和「初始化」,因此对于执行阶段来说,可以在声明之前使用。
- let 声明的变量在执行上下文创建阶段只会被「创建」而不会被「初始化」,因此对于执行阶段来说,如果在其定义执行前使用,相当于使用了未被初始化的变量,会报错。
3.let 与 const 异同
const 与 let 很类似,都具有上面提到的 let 的特性,唯一区别就在于 const 声明的是一个只读变量,声明之后不允许改变其值。因此,const 一旦声明必须初始化,否则会报错。
示例代码:
let a;
const b = "constant"
a = "variable"
b = 'change' // TypeError: Assignment to constant variable
如何理解声明之后不允许改变其值?
其实 const 其实保证的不是变量的值不变,而是保证变量指向的内存地址所保存的数据不允许改动(即栈内存在的值和地址)。
JavaScript 的数据类型分为两类:原始值类型和对象(Object类型)。
对于原始值类型(undefined、null、true/false、number、string),值就保存在变量指向的那个内存地址(在栈中),因此 const 声明的原始值类型变量等同于常量。
对于对象类型(object,array,function等),变量指向的内存地址其实是保存了一个指向实际数据的指针,所以 const 只能保证指针是不可修改的,至于指针指向的数据结构是无法保证其不能被修改的(在堆中)。
示例代码:
const obj = {
value: 1
}
obj.value = 2
console.log(obj) // { value: 2 }
obj = {} // TypeError: Assignment to constant variable
相关推荐
- Python Qt GUI设计:将UI文件转换Python文件三种妙招(基础篇—2)
-
在开始本文之前提醒各位朋友,Python记得安装PyQt5库文件,Python语言功能很强,但是Python自带的GUI开发库Tkinter功能很弱,难以开发出专业的GUI。好在Python语言的开放...
- Connect 2.0来了,还有Nuke和Maya新集成
-
ftrackConnect2.0现在可以下载了--重新设计的桌面应用程序,使用户能够将ftrackStudio与创意应用程序集成,发布资产等。这个新版本的发布中还有两个Nuke和Maya新集成,...
- Magicgui:不会GUI编程也能轻松构建Python GUI应用
-
什么是MagicguiMagicgui是一个Python库,它允许开发者仅凭简单的类型注解就能快速构建图形用户界面(GUI)应用程序。这个库基于Napari项目,利用了Python的强大类型系统,使得...
- Python入坑系列:桌面GUI开发之Pyside6
-
阅读本章之后,你可以掌握这些内容:Pyside6的SignalsandSlots、Envents的作用,如何使用?PySide6的Window、DialogsandAlerts、Widgets...
- Python入坑系列-一起认识Pyside6 designer可拖拽桌面GUI
-
通过本文章,你可以了解一下内容:如何安装和使用Pyside6designerdesigner有哪些的特性通过designer如何转成python代码以前以为Pyside6designer需要在下载...
- pyside2的基础界面(pyside2显示图片)
-
今天我们来学习pyside2的基础界面没有安装过pyside2的小伙伴可以看主页代码效果...
- Python GUI开发:打包PySide2应用(python 打包pyc)
-
之前的文章我们介绍了怎么使用PySide2来开发一个简单PythonGUI应用。这次我们来将上次完成的代码打包。我们使用pyinstaller。注意,pyinstaller默认会将所有安装的pack...
- 使用PySide2做窗体,到底是怎么个事?看这个能不能搞懂
-
PySide2是Qt框架的Python绑定,允许你使用Python创建功能强大的跨平台GUI应用程序。PySide2的基本使用方法:安装PySide2pipinstallPy...
- pycharm中conda解释器无法配置(pycharm安装的解释器不能用)
-
之前用的好好的pycharm正常配置解释器突然不能用了?可以显示有这个环境然后确认后可以conda正在配置解释器,但是进度条结束后还是不成功!!试过了pycharm重启,pycharm重装,anaco...
- Conda使用指南:从基础操作到Llama-Factory大模型微调环境搭建
-
Conda虚拟环境在Linux下的全面使用指南:从基础操作到Llama-Factory大模型微调环境搭建在当今的AI开发与数据分析领域,conda虚拟环境已成为Linux系统下管理项目依赖的标配工具。...
- Python操作系统资源管理与监控(python调用资源管理器)
-
在现代计算环境中,对操作系统资源的有效管理和监控是确保应用程序性能和系统稳定性的关键。Python凭借其丰富的标准库和第三方扩展,提供了强大的工具来实现这一目标。本文将探讨Python在操作系统资源管...
- 本地部署开源版Manus+DeepSeek创建自己的AI智能体
-
1、下载安装Anaconda,设置conda环境变量,并使用conda创建python3.12虚拟环境。2、从OpenManus仓库下载代码,并安装需要的依赖。3、使用Ollama加载本地DeepSe...
- 一文教会你,搭建AI模型训练与微调环境,包学会的!
-
一、硬件要求显卡配置:需要Nvidia显卡,至少配备8G显存,且专用显存与共享显存之和需大于20G。二、环境搭建步骤1.设置文件存储路径非系统盘存储:建议将非安装版的环境文件均存放在非系统盘(如E盘...
- 使用scikit-learn为PyTorch 模型进行超参数网格搜索
-
scikit-learn是Python中最好的机器学习库,而PyTorch又为我们构建模型提供了方便的操作,能否将它们的优点整合起来呢?在本文中,我们将介绍如何使用scikit-learn中的网格搜...
- 如何Keras自动编码器给极端罕见事件分类
-
全文共7940字,预计学习时长30分钟或更长本文将以一家造纸厂的生产为例,介绍如何使用自动编码器构建罕见事件分类器。现实生活中罕见事件的数据集:背景1.什么是极端罕见事件?在罕见事件问题中,数据集是...
- 一周热门
- 最近发表
-
- Python Qt GUI设计:将UI文件转换Python文件三种妙招(基础篇—2)
- Connect 2.0来了,还有Nuke和Maya新集成
- Magicgui:不会GUI编程也能轻松构建Python GUI应用
- Python入坑系列:桌面GUI开发之Pyside6
- Python入坑系列-一起认识Pyside6 designer可拖拽桌面GUI
- pyside2的基础界面(pyside2显示图片)
- Python GUI开发:打包PySide2应用(python 打包pyc)
- 使用PySide2做窗体,到底是怎么个事?看这个能不能搞懂
- pycharm中conda解释器无法配置(pycharm安装的解释器不能用)
- Conda使用指南:从基础操作到Llama-Factory大模型微调环境搭建
- 标签列表
-
- ps图案在哪里 (33)
- super().__init__ (33)
- python 获取日期 (34)
- 0xa (36)
- super().__init__()详解 (33)
- python安装包在哪里找 (33)
- linux查看python版本信息 (35)
- python怎么改成中文 (35)
- php文件怎么在浏览器运行 (33)
- eval在python中的意思 (33)
- python安装opencv库 (35)
- python div (34)
- sticky css (33)
- python中random.randint()函数 (34)
- python去掉字符串中的指定字符 (33)
- python入门经典100题 (34)
- anaconda安装路径 (34)
- yield和return的区别 (33)
- 1到10的阶乘之和是多少 (35)
- python安装sklearn库 (33)
- dom和bom区别 (33)
- js 替换指定位置的字符 (33)
- python判断元素是否存在 (33)
- sorted key (33)
- shutil.copy() (33)