当前位置: 首页 > 后端技术 > Node.js

块级作用域绑定(临时死区)(var&&let&&const区别与联系)

时间:2023-04-03 23:48:43 Node.js

介绍以往,javascript的变量声明机制一直让我们感到困惑。大多数类C语言在声明时也会创建变量(绑定)。在前面的javascrpit中,什么时候创建变量取决于变量的声明方式。es6的新语法可以帮助你更好地控制范围。本文将解释为什么经典的var容易混淆。1.var声明&提升机制(Hoisting)在函数作用域或全局作用域中通过关键字var声明的变量,无论实际声明在何处,都会被视为声明在当前作用域顶部的变量,即提升我们常说的机制
例如:
```functiongetValue(){if(0){varvalue='blue';返回值;}else{returnnull}}`解释:如果你不熟悉javascript,你可能会认为只有当condition的值为真时才会创建变量值。事实上,无论如何都会创建变量值。在预编译阶段,js引擎会将上面的getValue函数修改为:`functiongetValue(){varvalue;如果(0){值='蓝色';返回值;}else{返回null;}}variable声明被提升到函数作用域的顶部,而初始化操作保留在原地,这意味着变量也可以在else子句中访问,并且由于此时变量还没有被初始化,其valueisundefined.##2.块级声明块级声明用于声明在指定块范围之外不能访问的变量。块级作用域存在于:{}在函数的内部块中。很多类C语言都有块级作用域,而es6的引入是为了让js更加灵活和通用###letdeclarationlet声明的用法和var是一样的。通过使用let而不是var来声明变量,您可以将变量的范围限制在当前代码块内,(我们将在稍后的临时死区部分讨论其他几个细微的语法差异),因为let声明不会提升,因此开发人员将let声明语句放在封闭代码块的顶部,以便可以访问整个代码块,例如:```functiongetValue(条件){if(条件){letvalue='blue';返回值;}else{//变量值不存在returnnull}//此处变量值不存在}现在这个getValue函数的结果更像是类C语言。变量值通过关键字let声明后,就不再使用Hoist到函数顶部。执行流程离开if块值立即销毁。如果condition的值为false,则value永远不会被声明和初始化。禁止重新申报。假设范围内已经存在某个标识符。这时候如果用let关键字声明,就会报错,例如:varcount=30;//抛出错误letcount=10;说明:在同一个作用域内,不能用let重复定义一个已经存在的标识符,所以这里的let语句会报错。但如果当前作用域嵌入了另一个作用域,则可以使用let在嵌入作用域中声明一个同名变量,例如:varcount=30;if(1){//不会抛出错误letcount=10;}因为这里的let在if块中声明了新变量count,所以不会抛出任何错误。内部块中的计数将影响全局范围内的计数,后者只能在if块之外访问。const声明es6标准也提供了const关键字。使用const声明的变量是常量,其值一旦设置就不能更改。因此,const声明的每个常量都必须初始化,例如//validconstantconstmaxItem=30;//syntaxerror,constantuninitializedconstname;const和let一样,const和let都是声明块级标识符字符,所以常量只在当前代码块内有效,一旦在块外执行,就会立即销毁。使用const声明同一作用域中已有的标识符也会导致语法错误,无论标识符是使用var还是let声明例如,varmessage='hello';letage=25;//这两个都会throwanerrorconstmessage='goodbye';constage=30最后两个const声明没问题,但是由于前面用var声明了两个和let同名的变量,结果代码无法执行。虽然有很多相似之处,但是有一个很大的区别,就是无论在严格模式还是非严格模式下,为const定义的常量都不能被重用。赋值,否则会抛出错误constmaxItems=5;//会抛出错误maxItem=6;但是js与其他语言不同的是,如果javascript中的变量是一个对象,那么这个对象中的值是可以修改的。使用const声明对象const语句不允许修改绑定,但允许修改值,如constperson={name:'lihua'}//可以修改对象属性person.name='hanmeimei'//throwsanerrorperson={name:'hanmeimei''}记住:如果直接给person赋值,也就是要改变person的值,会抛错。const声明不允许修改绑定,但允许修改值TemporalDeadZone与var不同的是,let和const声明的变量不会被提升到作用域的顶部,如果在声明之前访问它们,它们是相对安全的typeof操作符也会触发引用错误,例如:if(1){console.log(typeofvalue);//referenceerrorletvalue='blue'}会由于控制台抛出错误。log(typeofvalue)statement,所以用let定义和初始化变量value的语句不会被执行。此时的值还在js所谓的临时死区(TDZ)。es标准虽然没有明确提到TDZ,但是经常用来描述let和const的非提升作用。说明:当javaScript引擎扫描代码并找到变量声明时,它要么将它们提升到范围的顶部(遇到var声明时),要么将声明放在TDZ中(遇到let和const声明)。访问TDZ中的变量会触发运行时错误。只有执行完变量声明语句后,变量才会从TDZ中移除,才能正常访问。在声明之前访问由let定义的变量就是这样做的。从前面的例子可以看出,即使是比较容易出错的typeof操作符,也无法避免引擎抛出的错误。但是,如果在let声明范围之外的变量上使用typeof,则不会报错,如下:console.log(typeofvalue);//undefinedif(1){letvalue='blue'}typeof是声明值的代码在块外执行,此时值不在TDZ中。这意味着没有值绑定,typeof操作最终将返回未定义的循环块级绑定。下面所有实例的运行环境都是浏览器,不是node,所以放在node上可能会显得不合适。在这种情况下,开发者可能最希望实现for循环的块级作用域,因为可以在循环内部限制随意声明的counter变量。例如:for(vari=0;i<10;i++){process(item[i]);}//这里仍然可以访问到变量iconsole.log(i);//10默认是块级的效果在其他领域的语言中,这个例子也可以正常运行,变量i只能在for循环中访问。在javascript中,变量i在循环结束后仍然可以访问,因为var声明已被提升。如果使用let来声明变量,可以得到想要的结果,如:for(leti=0;i<10;i++){process(item[i]);}//这里不能访问i,抛出一个错误console.log(i);解释:在本例中,变量i只存在于for循环中,一旦循环结束,变量循环中的函数在其他地方是无法访问的。长期以来,var语句让开发人员在循环内创建函数变得异常困难,因为变量仍然可以在循环外访问。看这段代码:varfuncs=[];for(vari=0;i<10;i++){funcs.push(function(){console.log(i);});}funcs.forEach((func)=>{func();//打印10乘以数字10})您可能希望输出数字0-9,但它打印了一系列10乘以10。这是因为循环的每次迭代也共享变量i,并且在循环内创建的函数都保留对同一个变量的引用。循环结束时,变量i的值为10,所以每次调用console.log(i)都会输出数字10。解决方案1:自执行函数为了解决这个问题,开发人员在循环中使用立即执行函数表达式,强制复制计数器变量,如下所示:varfuncs=[];for(vari=0;i<10;i++){funcs.push((function(value){returnfunction(){console.log(value)}}(i));}funcs.forEach(function(func){func();//output0then1,2,....to9});Explanation:insidetheloop自执行函数创建它接受的每个变量i的副本并将其存储为变量值,这个变量的值是相应迭代创建的函数使用的值,因此调用每个函数都会得到类似0到9循环值的期望值。es6中let和const提供的块级绑定使我们不必这样做。方案二:let&&constlet语句模仿了上面例子中自执行函数所做的一切,简化了循环过程。循环的每次迭代都会创建一个新变量,并使用前一次迭代中同名变量的值对其进行初始化。这意味着你完全删除自执行函数后仍然可以得到预期的结果,就像这样:varfuncs=[];for(leti=0;i<10;i++){funcs.push(function(){console.log(i);})}funcs.forEach(func=>{func();//Output012...9})这个循环和之前的循环一样,结合了var和自执行functions结果是一样的,但是更简洁。let声明每次通过循环创建一个新变量i并将其初始化为i的当前值。所以在循环内创建的每个函数都有自己的i副本。for-in和for-of也是一样。了解循环内let声明的行为在标准中有明确定义是至关重要的,它不一定与let的非提升性质有关。事实上,早期的let实现不包括这种行为,它是后来添加的全局块作用域绑定。let和const与var之间的另一个区别是它们在全局范围内的行为。当var在全局范围内使用时,它会创建一个新的全局变量作为全局对象(浏览器环境中的window对象)的属性。这意味着使用var很可能会无意中覆盖现有的全局变量,如下所示:varRegExp="hello!"在浏览器中;console.log(window.RegExp);//“你好”varncz=“嗨!”;console.log(window.ncz);//“你好!”即使在window上定义了全局对象RegExp,也逃不过被var语句覆盖。实例中声明的全局对象RegExp会覆盖之前的。同样,ncz被定义为全局变量,并立即成为window的属性。JavaScript一直都是这样。如果您在全局范围内使用let或const,则会在全局范围内创建一个新绑定,但该绑定不会添加为全局对象的属性。换句话说,全局变量不能被let或const覆盖,只能被隐藏。例子如下://在浏览器中让RegExp="hello!";console.log(RegExp);//"hello"console.log(RegExp===window.RegExp)//falseconstncz="Hi!";console.log(ncz);//“你好!”console.log("ncz"inwindow)//false这里由let声明的RegExp创建一个绑定并隐藏全局RegExp变量。结果是window.RegExp和RegExp不一样,但这不会破坏全局范围。同样,ncz的const声明创建一个绑定,而不是全局对象的属性。如果不想为全局对象创建属性,使用let和const会安全得多。注意:如果你想在全局对象下定义变量,你仍然可以使用var。这种情况在浏览器跨框架或跨窗口访问代码时很常见。小型块级作用域绑定let和const将词法作用域引入JavaScript,它们声明的变量不会提升,只能在声明它们的代码块内使用。这样一来,javascript中声明变量的语法与其他语言更加相似。它还减少了出错的可能性。同时,在声明之前访问块级变量会导致错误,因为该变量仍处于临时死区(TDZ)中。let和const的行为通常与var一致。但是,它们在循环中的行为非常不同。在for循环中,let和const都会在每次迭代时创建一个新的绑定,这样在循环体中创建的函数可以访问相应迭代的值,而不是上次迭代后的值(如var声明)。关于作者本文部分借鉴了其他大佬的作品(哈哈),仅供分享和评论,侵权必删