JavaScript变量or循环中的var和let怎么使用

在for循环中使用var声明初始化带来的问题

// 一道经典面试题:var funcs = [];for (var i = 0; i < 3; i++) {  funcs[i] = function() {    console.log("My value: " + i)  };}for (var j = 0; j < 3; j++) {  funcs[j]();}/*输出结果:> My value: 3> My value: 3> My value: 3*/

会出现这种现象的原因就是:

  • var声明的作用域是函数作用域而不是块级作用域,因此在for循环的循环体之外依然能访问到在初始化for循环时定义的var变量

  • 且在循环结束后访问时,访问到的var变量是已经完成循环后的值。

解决方法

使用闭包

ES5时代的解决办法就是通过IIFE创建一个闭包,把变量在函数体内保存起来,再执行函数时就不会去访问外层的var变量了。

var funcs = [];for (var i = 0; i < 3; i++) {    // 1. 闭包    funcs[i] = (function (i) {        return function () {            console.log("My value: " + i);        };    })(i);}for (var j = 0; j < 3; j++) {    funcs[j]();}

使用let变量初始化

let声明是块级作用域,循环体内的变量不会泄露到块语句之外。

因此在循环结束后再去访问变量i时,没有外层作用域变量的干扰,访问到的自然就是函数体内保存下来的变量值。

var funcs = [];// 2. letfor (let i = 0; i < 3; i++) {  funcs[i] = function() {    console.log("My value: " + i);  };}for (var j = 0; j < 3; j++) {  funcs[j]();}

从这里也可以看出,使用var来初始化for循环本身就是违反直觉的。

用来初始化for循环的变量理应是for循环的局部变量,在循环结束以后这个变量就应该没有意义了才对。

但是如果使用var来初始化,由于var声明的变量的作用域是函数作用域,这个初始化变量就和for循环处于同一作用域了,不受for循环的限制。

本应是for循环的局部变量,却暴露在了和for循环同层的作用域,且变量值已经被循环次数改变,自然会影响循环结束后其他代码对该变量的访问。

而如果使用let来初始化for循环,就不会有这个困扰了,因为let声明的作用域是块级作用域,这个初始化变量会如愿成为for循环的局部变量。

for循环怎么处理用let和var声明的初始化变量?

先上结论:

  • 用var初始化时,for循环会直接使用创建的var初始化变量;

  • 用let初始化时,圆括号会自成一个作用域,for循环会将圆括号内的变量值往循环体内传递。

首先看名列前茅个结论规范是这么说的:

JavaScript变量or循环中的var和let怎么使用

可以看到,规范对于var初始化变量没有什么特别的处理,直接就拿来用了。 此时这个变量就是个普通的var变量,和for循环处于同一作用域。

我们用代码来佐证一下:

var funcs = [];for (var i = 0; i < 3; i++) {    // !!!重复声明了一个同名的var变量    var i = 5;    console.log("My value: " + i);}/*只会输出一次:> My value: 5*/

var可以重复声明且值会覆盖,因此在循环体内再声明一个var i = 5,循环变量被作没了,会直接跳出for循环。

var funcs = [];for (var i = 0; i < 3; i++) {    // 用let声明了一个和循环变量同名的变量    let i = 5;    console.log("My value: " + i);}/*一共输出了3次:> My value: 5> My value: 5> My value: 5*/

初始化var变量在函数作用域,循环体内的let变量在块作用域,循环体内优先访问块作用域里的let变量,因此循环体内的i值会被覆盖。

又由于var变量实际上处于let变量的外层作用域,因此let变量没有重复声明,不会报错;var变量也会如期完成自己作为循环变量的使命。

再看第二个结论,同样是先看规范:

很明显可以发现,使用let来初始化会比使用var多了一个叫perIterationLets的东西。

perIterationLets是什么?

从规范上可以看到,perIterationLets来源于LexicalDeclaration(词法声明)里的boundNames

而这个LexicalDeclaration(词法声明),其实就是我们用来初始化的let声明。

可以理解为,如果我们用let声明来初始化for循环,for循环内部不会像直接使用var变量一样来直接使用let变量,而是会先把let变量收集起来,以某种形式转换为perIterationLets,再传递给循环体。

perIterationLets被用来做什么的?

从规范上可以看到,我们的let变量以perIterationLets的身份,作为参数被传进了ForBodyEvaluation,也就是循环体里。

在循环体里,perIterationLets只做了一件事情,那就是作为CreatePerIterationEnvironment的参数:

从字面上理解,CreatePerIterationEnvironment意思就是每次循环都要创建的环境

要注意,这个环境不是{...}里的那些执行语句所处的环境。 {...}里的执行语句是statement,在规范里可以看到,stmt有自己的事情要做。

这个环境是属于圆括号的作用域,也就是我们定义的let初始化变量所在的作用域。

再看看每次循环都要创建的环境被用来干嘛了:

逐步分析一下方法:CreatePerIterationEnvironment这个

  • 首先,把当前执行上下文的词法环境保存下来,作为lastIterationEnv(上一次循环时的环境)

  • 创建一个和lastIterationEnv同级的新作用域,作为thisIterationEnv(本次循环的环境);

  • 遍历我们定义的let初始化变量,也就是perIterationLets,在thisIterationEnv(本次循环的环境)里创建一个同名的可变绑定,找到它们在lastIterationEnv(上一次循环时的环境)里的终值,作为这个同名绑定的初始值;

  • 最后,将thisIterationEnv(本次循环的环境)交还给执行上下文。

简而言之就是,for循环会在迭代之前创建一个和初始化变量同名的变量,并使用之前迭代的终值将这个变量初始化以后,再交还给执行上下文

用伪代码理解一下这个过程就是:

到这里又有一个问题,既然把圆括号内的变量向循环体里传递了,那如果在循环体里又重复声明了一个同名变量,算不算重复声明,会不会报错?

答案是不会。

因为CreatePerIterationEnvironment在执行时,在新环境里创建的是一个可变的绑定,因此如果在循环体内重复声明一个名字为i的变量,只是会影响循环体内执行语句对i值的访问。

var funcs = [];for (let i = 0; i < 3; i++) {    // !!!用let声明了一个和循环变量同名的变量    let i = 5;    console.log("My value: " + i);}/*一共输出了3次:> My value: 5> My value: 5> My value: 5*/

总结

在for循环中使用var声明来初始化的话,循环变量会暴露在和for循环同一作用域下,导致循环结束后还能访问到循环变量,且访问到的变量值是经过循环迭代后的值。

解决这个问题的方法如下:

  • 使用闭包将循环变量的值作为局部变量保存起来;

  • 使用ES6的let声明,将循环变量的作用域限制在for循环内部,初始化变量始终是for循环的局部变量,不能在外界被访问到。

for循环是怎么处理用let和var声明的初始化变量的?

  • 用var初始化时,for循环会直接使用创建的var初始化变量;

  • 用let初始化时,圆括号会自成一个作用域,for循环会将圆括号内的变量值往循环体内传递。

以上就是关于“JavaScript变量or循环中的var和let怎么使用”这篇文章的内容,相信大家都有了一定的了解,希望小编分享的内容对大家有帮助,若想了解更多相关的知识内容,请关注亿速云行业资讯频道。

文章标题:JavaScript变量or循环中的var和let怎么使用,发布者:亿速云,转载请注明出处:https://worktile.com/kb/p/24968

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
亿速云的头像亿速云认证作者
上一篇 2022年9月15日 上午1:37
下一篇 2022年9月15日 上午1:38

相关推荐

  • Docker资源限制Cgroup怎么使用

    1.Cgroup简介 _cgroups,是一个非常强大的linux内核工具,他不仅可以限制被namespace隔离起来的资源, 还可以为资源设置权重、计算使用量、操控进程启停等等。所以cgroups (Control groups) 实现了对资源的配额和度量。 cgroups有四大功能: 资源限制:…

    2022年9月15日
    68300
  • Mysql锁的内部实现机制是什么

    注:所列举代码皆出自Mysql-5.6 虽然现在关系型数据库越来越相似,但其背后的实现机制可能大相径庭。实际使用方面,因为SQL语法规范的存在使得我们熟悉多种关系型数据库并非难事,但是有多少种数据库可能就有多少种锁的实现方法。 Microsoft Sql Server2005之前只提供页锁,直到20…

    2022年9月15日
    64200
  • css中的选择器包不包括超文本标记选择器

    不包括。css选择器有:1、标签选择器,是通过HTML页面的元素名定位具体HTML元素;2、类选择器,是通过HTML元素的class属性的值定位具体HTML元素;3、ID选择器,是通过HTML元素的id属性的值定位具体HTML元素;4、通配符选择器“*”,可以指代所有类型的标签元素,包括自定义元素;…

    2022年9月1日
    62300
  • PHP程序中怎么运行Python脚本

    一、exec() 执行一个外部程序 exec ( string $command [, array &$output [, int &$return_var ]] ) : string 参数说明: command:要执行的命令,其中包括三个子串,名列前茅个子串为使用的当前系统的解释器…

    2022年9月10日
    67600
  • windows microsoft edge能不能卸载

    “microsoft edge”不能卸载是没有影响的;“microsoft edge”是微软与windows10同步推出的一款浏览器,其中支持内置Cortana语音功能,该浏览器是系统中自带的应用程序,无法通过程序选项完成卸载。 本教程操作环境:windows10系统、DELL G3电脑。 micr…

    2022年9月15日
    1.6K00
  • windows会声会影如何导出视频高清

    会声会影导出视频高清的方法 1、首先我们要保证我们的源视频是高清的,否则无论如何操作都无法导出高清的视频。 2、接着我们在导入视频前要设置高清的项目环境,这样我们就能在高清的环境中编辑视频了。 3、点击左上角“设置”,选择“项目属性”。 4、根据我们需要的参数设置相应的项目格式。 5、当我们编辑完成…

    2022年9月15日
    50300
  • 电脑键盘锁住了如何解决

    键盘锁住了的解决方法 1、如果着急打字的话,按“ctrl”加“win”键加“o”键,调出软键盘打字。 2、右键点击此电脑的“管理”,点击进入“系统工具”。 3、点击“设备管理器”,点击“键盘”。 4、右键点击“更新驱动程序”即可。 读到这里,这篇“电脑键盘锁住了如何解决”文章已经介绍完毕,想要掌握这…

    2022年9月6日
    52400
  • MySQL数据库视图的作用是什么

    1 视图的介绍与作用 视图的介绍: 视图 view 是一个虚拟表,非真实存在,其 本质是根据SQL语句获取动态的数据集,并为其命名, 用户使用时只需要使用视图名称即可获取结果集,并可以将其当作表来使用。 数据库中只存放了视图的定义,而并没有存放视图中的数据。 数据还存在于原来的数据表中。 使用视图查…

    2022年9月15日
    1.4K00
  • ae预合成快捷键是什么

    ae预合成快捷键: 答:ae预合成快捷键为:ctrl+shift+c。 当用户需要将两个素菜合成一个的时候,需要选这两个,然后直接合成。 ae预合成快捷键操作方法: 1、首先使用快捷键“ctrl+左键”来选择你要合成的素材,然后右击选“预合成”。 2、进入合成界面之后,可以为其创建一个名称。 3、搞…

    2022年8月29日
    3.1K00
  • windows nvidia驱动如何更新

    nvidia驱动更新方法: 1、首先我们下载一个nvidia GeForce experience软件。 2、下载安装完成后,点击右下角的nvidia图标,打开nvidia GeForce experience。 3、打开后,进入上方的“驱动程序”选项。 4、然后点击“检查更新文件”可以扫描到最新的…

    2022年8月29日
    46300
站长微信
站长微信
电话联系

400-800-1024

工作日9:30-21:00在线

分享本页
返回顶部