首页 > 编程语言 >你不知道的javascript-13(var的接替者let与const)

你不知道的javascript-13(var的接替者let与const)

时间:2025-01-18 15:58:02浏览次数:3  
标签:13 const 变量 作用域 javascript let console 声明

1.let与const的基本使用

  • 在ES5中我们声明变量都是使用的var(variable)关键字,从ES6开始新增了两个关键字可以声明变量:let、const
  • let、const在其他编程语言中都是有的,所以也并不是新鲜的关键字
  • 但是let、const确确实实给JavaScript带来一些不一样的东西
  • 从使用角度来说,只是在原有基础上换一个名字而已,使用的位置和方式是一样的
var name = 'zs'
let name = 'zs'
const name = 'zs'
  • let和const的出现,其实是顺延了ES6所想表达的思想,在这里主要表达为消除二义性、提高可读性,以及弥补ES5及之前的缺陷问题
  • 从直观的角度来说,let和var是没有太大的区别的,都是用于声明一个变量
  • 而const关键字是constant单词的缩写,表示常量、衡量的意思,意味着保存的数据一旦被赋值,就不能被修改,但是如果赋值的是引用类型,那么可以通过引用找到对应的对象,修改对象的内容
  • let、const与var最大的共同区别在于前两者不允许重复声明变量
  • 这一点其实很重要,使用var可以重复声明变量其实是一个早期的缺陷,这并不能算作特性
  • 每一个变量都有属于自己的作用,我们不会莫名其妙声明一个意义不明的变量出来。而声明变量和修改变量,是两个完全不同的操作,也代表了两种不同的思路走向
  • 当进行变量声明(let)时,会确定该变量的作用性质。一旦在不注意的情况下声明相同变量,两个变量可能表达的含义很可能完全不同:
  • 例如我声明一个Names变量,第一次声明的同时,我进行赋值['xiaoyu','coderwhy'],那Names变量所表达的含义是所有具体名字,但在后面我又声明了一遍Names,我赋值为2,这次的内容变为了数字,表达一共有几个名字。同样的变量名导致了不同的含义
    • 进行单纯的赋值操作,只能够说明该变量的作用性质已经决定了,我们改变内容并不会影响其使用的本质
    • 在开发的时候,未必不会出现这种情况,并且在越大型的合作项目中,越容易出现该问题,因为不清楚该变量名是否已经被同事所使用,会造成不便与排查难度提升,提早的发现有助于排除隐患
  • 因此声明和赋值所具备的含义是可以进行拆分使用的,只是大多数情况我们会合并使用
  • 但有些情况,也会提前进行声明占位,确定变量/常量名的性质,等到后续在进行使用
let name
//赋值
name = 'xiaoyu'
// 重复声明报错
let name
  • 常量const有一个容易令人误会的地方,很多人往往会认为放在常量里的数据是不可改变的,但事实并非如此,const本质上并不阻止对内容的修改。想要理解 const 的底层实现的话,更需要关注对引用的保护机制而不是对数据本身的保护
  • 在一开始的时候,我们学习过使用var声明时,在内存中的表达形式,常量在这方面是类似的
    • 数据在JS中分简单数据和复杂数据,前者放在栈中,后者放在堆中
    • 常量所做的事情是锁住栈内容,在这种情况下,如果是简单数据类型,本身数据就在栈中无法改动。如果是复杂数据类型,则存储在栈中的内存地址 如0xb00将无法改变,这个表达形式为引用无法改变,但const常量并不对堆内容进行封锁,导致位于堆位置的内容是可以改变的
  • 因此在使用常量来存储复杂数据类型,例如:Object、Array、Function、Date、RegExp(正则表达式)、Map、Set的时候,需要谨慎使用
const person = {name:'xiaoyu'}
person.name = 'coderwhy'
console.log(person);//{ name: 'coderwhy' }

//其他复杂数据类型例如Set Map 后续会学习到
const set = new Set([1, 2, 3]);
set.add(4); // 可以:添加新元素
set.delete(1); // 可以:删除元素
console.log(set); // Set(3) { 2, 3, 4 }

1.let与const不允许声明变量

2.const只能锁住简单数据类型,而不能锁住复杂数据类型

2.var的作用域提升

  • 新的声明方式在替代var的时候,同时也改变了其内在声明逻辑,这和语法糖是完全不同的
  • var在进行声明的时候,作用域会进行提升到全局,而赋值不会。导致var一旦声明就能够在所有地方进行使用,哪怕在声明之前使用也可以
    • 在这种情况下,声明之前进行调用就会产生默认结果undefined,直到运行到赋值的位置,变量值才发生对应的改变
  • 这种方式其实是不合理的,因为在变量不存在的情况(未声明),是直接报错
  • 报错通常是:ReferenceError: 变量 is not defined,没有引用到对应的变量名而报错
    • 而var声明变量之前,变量其实因为作用域提升因素,导致可访问,容易使人误会该变量已经可以使用,但使用的位置如果在赋值之前,就会因为调用JS引擎赋予变量的默认值undefined而报错
    • 因此造成错误的原因会不明确,不清晰,从而造成误导、不必要的排查
console.log(person.foo());//undefined.foo() 调用失败 TypeError: Cannot read properties of undefined (reading 'foo')
var person = {
  foo(){
    console.log('coderwhy');
  }
}
  • 在优化后的let身上,如果在声明之前的位置进行使用,会报准确的错误(无法在初始化之前访问person)
console.log(person.foo());//ReferenceError: Cannot access 'person' before initialization
//let与const的效果在这里一样
let person = {
  foo(){
    console.log('coderwhy');
  }
}
  • 所以,从效果来看,let/const是没有作用域提升的,但这句话在社区是有争议的
    • 没有作用域的提升,是否意味着person变量只有在代码执行阶段才会创建?
    • 事实上并不是这样的,我们可以看一下 ECMA262 对 let 和 const 的描述
    • 这些变量会被创建在包含他们的词法环境被实例化时,但是是不可以访问它们的,直到词法绑定被求值



图20-1 ECMA262 对 let 和 const 的描述

- 这要如何理解?我们需要抽离一下关键信息:`创建`与`访问` - let/const被划分为两个阶段,从该内容所暴露出来的信息,可以确定`创建阶段`是不变的 - 关键的改变在于`访问阶段`:词法绑定被求值之前(赋值过程之前),无法访问 - 这么看来,在执行上下文的词法环境创建出来的时候,变量事实上已经被创建了,只是这个变量是不能被访问的 - 那么变量已经有了,但是不能被访问,是不是一种作用域的提升呢? - 事实上维基百科并没有对作用域提升有严格的概念解释,那么我们自己从字面量上理解 - 作用域提升:在声明变量的作用域中,如果这个变量可以在声明之前被访问,那么我们可以称之为作用域提升 - 在这里,它虽然被创建出来了,但是不能被访问,我认为不能称之为作用域提升 - 所以我的观点是let、const没有进行作用域提升,但是会在执行上下文创建阶段被创建出来

var声明的变量,具有作用域提升的特性,let与const声明变量没有作用域提升

但这并不意味着let与const声明的变量要在代码执行阶段才会被创建,

事实上这些变量在其词法环境被实例化的时候就已经被创建了,但是是不可以访问它们的,直到词法绑定被求值

3.var,let,const与window

var、let、const对比总结

特性/声明类型

var

let

const

存储位置

变量环境

词法环境的块级作用域

词法环境的块级作用域

作用域

函数作用域或全局作用域

块级作用域

块级作用域

提升行为

变量提升,初始化为 undefined

无提升,暂时性死区直到声明处

无提升,暂时性死区直到声明并初始化

  • 当 JavaScript 代码执行时:
    1. 解析器会先通过词法分析过程识别所有的变量和函数声明
    2. 对于 var 和函数声明,它们会被添加到变量环境中,并在函数执行之前完成初始化(函数完整地、变量为 undefined
    3. 对于 letconst,它们被添加到词法环境中,对应的作用域是它们所在的块级作用域。直到执行到它们的具体声明语句之前,它们都不可用
  • 这种机制确保 letconst 提供了比 var 更严格和更清晰的作用域管理,减少因变量提前使用导致的潜在问题

var声明的变量会绑定在windows的全局变量中

  1. 变量环境 (VE):主要用于存储由 var 声明的变量和函数声明。

而let与const则有自己的块级作用域之中

  1. 词法环境 (LE):用于存储由 letconst 声明的变量,以及包含块级作用域的其他信息

4.let与const的块级作用域

  • 在上述词法环境中,我们可以看到let/const的变化有很大程度来自存储的位置,也就是词法环境的块级作用域中
  • 在ES5中,JavaScript只会形成两个作用域:全局作用域和函数作用域
    • 所谓的块级作用域在这个阶段是形同虚设的,外层依旧可以进行访问,没法产生实际的意义
//块级作用域(ES5中不存在)
{
  var foo = 'coderwhy'
}
console.log(foo);//可以访问到
  • 我们想要说明的是块级作用域是和函数作用域具备类似能力的,或者说后者就是前者的一种表达形式

1、内层能够链式访问外层

2、外层无法访问内存

  • 这是一个单项不可逆的过程,在ES6中,块级作用域对let/const/function/class声明的类型是有效的
{
  let foo = 'coderwhy'
}
console.log(foo);//无法访问
  • 但这不是绝对的,比如说我在块级作用域中书写一个函数,在外层能否访问
    • 根据规范,块级作用域对function声明的类型是有效的,所以外层是无法访问内层函数的
    • 但实际上可以访问,因为不同的浏览器有不同的实现方式,大部分浏览器为了兼容以前的代码,放开该层限制,让function不具备块级作用域
  • 从而产生了一些奇特的效果,比如在node环境下不可以的操作,在浏览器中就能实现
{
  function foo(){
    console.log('zs');
  }
}

foo()//浏览器可以访问 node环境不可访问
  • 这种操作很容易产生二义性,让人无法正确预估产生结果,所以类似这种有可能令人误解效果的代码应该少进行
    • 但我们知道函数声明是有多种方式的,我们可以配合let声明来实现具备块级作用域
{
  let foo = function(){
    console.log('zs');
  }
}

foo()//不管是浏览器还是node环境,都无法访问
  • 包括说和{}所结合的结构,比如if判断语句、switch循环语句、for循环语句,其内部都是具备块级作用域效果的
  • 我们在使用变量名的时候,很多情况下不可避免会遇到重复的情况,有一些地方会产生冲突,有些地方不会,由块级作用域明确的冲突界限
    • 在不会冲突的地方,不应该让重复变量会产生关联,理解一个变量主要取决于两点:1.变量名本身 2.变量所处环境
  • 在不同环境下,相同的变量名是不同的含义(例如:初见的笑容是喜悦,离别的笑容是不舍,笑容的释义除了本身的含义还有场景的因素)
  • let变量在解决变量名冲突的时候,是根据第二点的因素去决定的
    • 以场景作为切割,会让目的清晰明确
if (true) {
  let message = "Hello, World!";
  console.log(message);  // 输出:Hello, World!
}
// console.log(message); // ReferenceError: message is not defined

for (let i = 0; i < 3; i++) {
  console.log(i);  // 输出:0, 1, 2
}
// console.log(i); // ReferenceError: i is not defined

switch ('coderwhy') {
  case 'coderwhy':
    let xiaoyu = "Switch Case";
    console.log(xiaoyu);  // 输出:Switch Case
    break;
  default:
    // 不可访问 'xiaoyu' 变量
    break;
}
// console.log(xiaoyu); // ReferenceError: example is not defined
  • 在应用场景清晰明确的情况下,会解决很多隐形的bug,例如for循环
  • 在使用let的情况下,每一次循环,都是一次独立的块级调用,彼此之间是无法干涉的
    • 同时也无法影响到外层的内容,因为遍历的情况非常普遍,遍历的索引通常都为i(item缩写),通俗易懂,也避免的每一次循环都需要重新想变量名的苦恼,但使用var就会影响到之后同索引变量名,产生矛盾冲突及bug
const names = ['coderwhy','xiaoyu','JS高级']
for (let i = 0; i < names.length; i++) {
  console.log(names[i]);  
}

//产生如下效果
// {
//   let i = 0
//   console.log(names[i])
// }
// {
//   let i = 1
//   console.log(names[i])
// }
// {
//   let i = 2
//   console.log(names[i])
// }
  • for循环中,一般采用let,而非const的原因主要是在这的索引一般是递增或者以某种规律进行改变
  • 采用const,则无法正常的进行变化
    • 但在其他的场景下,也有采用const进行声明的形式,例如for of遍历,该遍历的使用目标为可迭代对象,这一点我们后面还会进行说明
    • 从目前的角度来说,for of是直接遍历集合的元素而不是索引的方法,所以可以不使用let声明一个变量来记录索引,对于不想要改变的数据此时就可以使用const来进行声明,直接访问元素最直观的就是简化迭代
    • 但这不是其最大的作用,最大的作用是ES6引入统一的迭代协议:可迭代协议和迭代器协议,这一点在之后会专门讲解
const names = ['coderwhy','xiaoyu','JS高级']
//ES6新增遍历数组(对象)
for(const item of names) {
  console.log(item);
}

使用let,const声明的变量在{}中产生了块级作用域,外部是无法访问到的

5.const,let的暂时性死区

暂时性死区(Temporal Dead Zone,TDZ)是 JavaScript 中与 letconst 声明相关的一个行为特征。这个概念主要涉及到变量的生命周期,在变量声明初始化之间存在一个时间段,在这段时间里,变量虽然已经被声明,但还不能被访问或使用

  • 通俗的说就是这段访问不了的区域叫做暂时性死区
  • 在这块区域中的访问,会返回明确的错误指示:Cannot access 'bar' before initialization,也就是初始化成功之前无法访问
    • 这说明我们正处于变量声明到初始化之间的一个阶段,而变量声明前调用会报错未定义、初始化之后则可以正常访问,在该周期中访问有三种不同的结果
function foo() {
  // console.log(this);//指向global
  console.log(1111);
  console.log(a); //Cannot access 'a' before initialization
  //在这段声明代码但没有初始化的阶段我们叫做暂时性死区
  let a = 'zs';
  console.log(a);
}
foo();

在我们给a赋值为'zs'之前我们是无法访问到a的这段区域我们称之为,暂时性死区

 

标签:13,const,变量,作用域,javascript,let,console,声明
From: https://blog.csdn.net/weixin_67448099/article/details/145161721

相关文章

  • ABB机器人3HNE00313-1示教器黑屏故障维修
    随着工业自动化的快速发展,ABB机器人示教器在生产线上的应用越来越广泛。然而,在使用过程中,示教器偶尔也会出现故障,其中比较常见的一种是ABB工业机械手示教器黑屏故障。一、ABB工业机器人示教盒黑屏故障原因分析1.硬件故障:硬件故障是导致示教器黑屏的主要原因之一。显示屏损坏、......
  • 13篇金融风控论文复现:涵盖SCI顶刊、中文核心期刊及985/211高校毕业论文
    作者Toby,原文来源公众号Python风控模型《13篇金融风控论文复现》大家好,我是重庆未来之智的Toby老师,今天为大家带来12篇论文专利复现文章。其中包含5篇顶刊,3篇普刊,2篇985高校研究生毕业论文,1篇211高校毕业论文,1篇算法专利文章,1篇会议的审稿论文集。1.顶刊复现-客户分组对商......
  • 113_基于springboot的疫情物资捐赠和分配系统
    ......
  • JavaScript中通过array.map()实现数据转换、创建派生数组、异步数据流处理、复杂API请
    目录JavaScript中通过array.map()实现数据转换、创建派生数组、异步数据流处理、复杂API请求、DOM操作、搜索和过滤等,array.map()的使用详解(附实际应用代码)一、什么时候该使用Array.map(),与forEach()的区别是什么?1、什么时候该用Array.map()2、Array.map()与Array.forEach()的......
  • P1982 [NOIP2013 普及组] 小朋友的数字 题解
    题目传送锚点在博客园食用更佳题意有一列小朋友,他们每个人都有一个值。定义每个小朋友的特征值为祂及祂前面人值的最大子段和。又定义每个小朋友的数字为祂前面人中的一个人的特征值加本身值的最大值。。。思路把题意用人话说出来即为思路:先输入每个小朋友的值\(a_i\);再......
  • 2025.1.18 JavaScript基础
    1、变量的定义var变量名例如:<html> <body> <scripttype="text/javascript"> functionzhaoling(){ n=Number(document.form1.txt1.value); if(n!=parseInt(n/1)||n<1||n>100) { alert("请输入一个1-100的整数"); ......
  • 锐捷路由器网关RG-NBR6135-E和锐捷交换机 Ruijie Reyee RG-ES224GC 电脑登录web方法
    2025年1月17日22:29:35最近淘了点东西,准备在家里搞一套深度学习的服务器,先把网关和交换机搞到了锐捷路由器网关RG-NBR6135-E电脑登录web方法在拿到机器的时候,如果不是全新建议拿根牙签,差入reset5-10秒,灯光会全部闪几下,重置机器,因为有些机器会配置的ip和网段无法访问默认的w......
  • 【洛谷P1303】高精度乘法
    A*BProblem题目背景高精度乘法模板题。题目描述给出两个非负整数,求它们的乘积。输入格式输入共两行,每行一个非负整数。输出格式输出一个非负整数表示乘积。样例#1样例输入#112样例输出#12提示每个非负整数不超过10^{2000}。入坑OI这么久发现还没有写过......
  • 【洛谷训练记录】【LGR-213-Div.4】洛谷入门赛 #31
    训练情况赛后反思模拟题差点红温,差一道字符串模拟题AKA题问一个数\(a\)加多少后的个位数变成\(b\),取出\(a\)的个位数,再用\(b\)去减,如果小于零答案再加十。#include<bits/stdc++.h>//#defineintlonglong#defineendl'\n'usingnamespacestd;voidsolve()......
  • P1135 - 【入门】歌德巴赫猜想 -
    难度:4-题目描述任一个大于等于4的偶数都可以拆分为两个素数之和。(5.1.40)输入格式一个整数n(4<=n<=200)输出格式将小于等于n的偶数拆分为2个质数之和,列出所有方案!输入数据110输出数据14=2+26=3+38=3+510=3+710=5+5代码:#include<iostream>usingname......