JavaScript变量or循环中的var和let详解

目录
  • 在for循环中使用var声明初始化带来的问题
  • 解决方法
    • 使用闭包
    • 使用let变量初始化
  • for循环怎么处理用let和var声明的初始化变量?
  • 总结

在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. let
for (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循环会将圆括号内的变量值往循环体内传递。

首先看第一个结论规范是这么说的: 

可以看到,规范对于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详解的文章就介绍到这了,更多相关JS var和let内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

时间: 2022-09-10

JavaScript es6中var、let以及const三者区别案例详解

首先,一个常见的问题是,ECMAScript 和 JavaScript 到底是什么关系?         ECMAScript是一个国际通过的标准化脚本语言.JavaScript由ECMAScript和DOM.BOM三者组成.可以简单理解为:ECMAScript是JavaScript的语言规范,JavaScript是ECMAScript的实现和扩展.         2011 年,ECMAScript 5.1 版发布.之前我们大部分人用的也就是ES5         2015 年 6 月,ECM

JavaScript中var与let的区别

目录 1.作用域表现形式不同 2.是否变量提升的区别 3.暂时性死区上的区别 4.在同一个上下文中var可以重复声明,let不行 前言: var是JavaScript刚出现时就存在的变量声明关键字,而let作为ES6才出现的变量声明关键字,无疑两者之间存在着很大的区别.那么具体有哪些区别呢? 1.作用域表现形式不同 var是函数作用域,let是块级作用域 { var monkey='熏悟空'; let pig='猪扒盖'; } console.log(monkey); //输出undefined

JavaScript&nbsp;ES6语法中let,const&nbsp;,var&nbsp;的区别

一.变量声明的方式let / const let / const 共同点 1.都是块级作用域2.在同一个作用域下,变量名不允许重复3.他们声明的全局变量并没有挂在 window对象上4.都没有预编译 let / const 不同点 1.let 声明的变量值可以改变2.const 声明的变量值不能改变,必须声明后立即赋值 (如:const a = 3.14;)3.const 存引用数据类型时,内容可以发生改变(地址不能改变) 优先考虑使用const , 如果变量会发生改变,就使用let , 最后使

详解javascript中var与ES6规范中let、const区别与用法

随着ES6规范的到来,Js中定义变量的方法已经由单一的 var 方式发展到了 var.let.const 三种之多.var 众所周知,可那俩新来的货到底有啥新特性呢?到底该啥时候用呢?下面就是小编总结出的关于javascript中var与ES6规范中let.const区别详解 我们先来絮叨絮叨 var 方式定义变量有啥 bug ? Js没有块级作用域 请看这样一条规则:在JS函数中的var声明,其作用域是函数体的全部. for(var i=0;i<10;i++){ var a = 'a'; }

javascript的var与let,const之间的区别详解

目录 作为全局变量时 变量提升 暂时性死区 块级作用域 重复声明 修改声明的变量(常量与变量声明) 总结 说到JavaScript中声明变量的几种方法也就是var.let.const了,let和const是es6中新增的命令.那么它们之间有什么区别呢? 我们先整体说一下三者的区别,在详细介绍,var.let.const的区别主要从以下几点分析: 作为全局变量时的不同 变量提升 暂时性死区 块级作用域 重复声明 修改声明的变量 作为全局变量时 在ES5中,顶层对象的属性和全局变量是等价的,用var

面试官常问之说说js中var、let、const的区别

前言 关于 var.let 和 const 三个关键字的区别,是一个老生常谈的问题,也是经典的面试题.本篇文章将全面讲解三者的特性,以及它们之间的区别,由浅入深让你彻底搞懂这个知识点. 变量声明 ECMAScript 变量是松散类型的,意思就是变量可以用于保存任何类型的数据,每个变量只不过是一个用于保存任意值的命名占位符. 有3个关键字可以声明变量:var.let和const,var在 ECMAScript 的所有版本中都可以使用,而let和const只能在 ES6 及更晚的版本中使用. var

JavaScript中var let const的用法有哪些区别

目录 1.重复声明 1.1 var 1.2 let 1.3 const 2.变量提升 2.1 var 2.2 let 2.3 const 3.暂时性死区 3.1 var 3.2 let 3.3 conset 4. window对象的属性和方法 5.块级作用域 1.重复声明 var支持重复声明,let.const不支持重复声明. 1.1 var var a = 1; var a = 2; console.log(a); 输出结果: 2 1.2 let let b = 3; let b = 4; c

JavaScript中apply与call的用法意义及区别说明

apply和call,它们的作用都是将函数绑定到另外一个对象上去运行,两者仅在定义参数的方式有所区别: Function.prototype.apply(thisArg,argArray); Function.prototype.call(thisArg[,arg1[,arg2-]]); 从函数原型可以看到,第一个参数都被取名为thisArg,即所有函数内部的this指针都会被赋值为thisArg,这就实现了将函数作为另外一个对象的方法运行的目的.两个方法除了thisArg参数,都是为Funct

JavaScript中var、let、const区别浅析

下面通过代码给大家介绍JavaScript中var.let.const区别,具体代码如下所述: //1.var定义的变量可以修改,如果不初始化会输出undefined,不会报错. var a; console.log(a); //undefined //2.let是块级作用域,函数内部使用let定义后,对函数外部无影响. let c = 3; console.log(c) function change(){ let c = 6; console.log(c) } change(); (1)只要

JavaScript中的普通函数和箭头函数的区别和用法详解

最近被问到了一个问题: javaScript 中的箭头函数 ( => ) 和普通函数 ( function ) 有什么区别? 我当时想的就是:这个问题很简单啊~(flag),然后做出了错误的回答-- 箭头函数中的 this 和调用时的上下文无关,而是取决于定义时的上下文 这并不是很正确的答案--虽然也不是完全错误 箭头函数中的 this 首先说我的回答中没有错误的部分:箭头函数中的 this 确实和调用时的上下文无关 function make () { return ()=>{ consol

JavaScript中SetInterval与setTimeout的用法详解

setTimeout 描述 setTimeout(code,millisec) setTimeout() 方法用于在指定的毫秒数后调用函数或计算表达式. 注:调用过程中,可以使用clearTimeout(id_of_settimeout)终止 参数 描述 code 必需,要调用的函数后要执行的 JavaScript 代码串. millisec 必需,在执行代码前需等待的毫秒数. setTimeinterval setInterval(code,millisec[,"lang"]) 参数

JavaScript中字符串分割函数split用法实例

本文实例讲述了JavaScript中字符串分割函数split用法.分享给大家供大家参考.具体如下: 先来看下面这段代码: <script type="text/javascript"> var str="How are you doing today?" document.write(str.split(" ") + "<br />") document.write(str.split("&q

javascript中innerText和innerHTML属性用法实例分析

本文实例讲述了javascript中innerText和innerHTML属性用法.分享给大家供大家参考.具体分析如下: 几乎所有DOM元素都有innerText,innertHTML属性(注意大小写),分别是元素标签内 的文本表示形式和HTML源代码,这两个属性是可读可写的 innerHTML也可以取代createElement,属于简单,粗放型,后果自负的创建 <html xmlns="http://www.w3.org/1999/xhtml"> <head>

javascript中call,apply,bind函数用法示例

本文实例讲述了javascript中call,apply,bind函数用法.分享给大家供大家参考,具体如下: 一.call函数 a.call(b); 简单的理解:把a对象的方法应用到b对象上(a里如果有this,会指向b) call()的用法:用在函数上面 var Dog=function(){ this.name="汪星人"; this.shout=function(){ alert(this.name); } }; var Cat=function(){ this.name=&qu

Javascript中数组sort和reverse用法分析

本文实例讲述了Javascript中数组sort和reverse用法.分享给大家供大家参考.具体分析如下: sort() 方法用于对数组的元素进行排序. reverse()将数组中的元素逆序 首先我们来试试以下这段代码: 复制代码 代码如下: var values = [1, 0, 5, 15, 10]; values.reverse(); console.log(values); 输出结果会是什么呢: [ 10, 15, 5, 0, 1 ] reverse()也就是很简单的把数组倒过来而已,那

javascript中2个感叹号的用法实例详解

在javascript代码中经常会见到!!的情况,本文即以实例形式较为深入的分析javascript中2个感叹号的用法.分享给大家供大家参考之用.具体分析如下: javascript中的!!是逻辑"非非",即是在逻辑"非"的基础上再"非"一次.通过!或!!可以将很多类型转换成bool类型,再做其它判断. 一.应用场景:判断一个对象是否存在 假设有这样一个json对象: { color: "#E3E3E3", "fon