手机版

轻松理解与Javascript变量相关的问题

时间:2021-09-06 来源:互联网 编辑:宝哥软件园 浏览:

在谈论这篇文章的内容之前,让我们回到1995年。当Brendan Eich在设计第一个版本的JavaScript时,他犯了很多错误。当然,这也包括语言本身的一部分,比如Date对象,它通过对象相乘自动转换为NaN。然而,现在回想起来,语言最重要的部分都设计得很好:对象、原型、具有词法范围的一级函数、默认的可变性等等。语言的骨架是极好的,甚至超越了人们对它的最初印象。

另一方面,正是布兰登最初的设计错误,才催生了今天这篇文章。我们这次的关注点很小,使用这种语言很多年后你可能没有注意到这个问题,但它是如此重要,因为我们可能会错误地认为这个错误是语言设计中的“好的部分”。

今天,我们必须记下这些与变量有关的问题。

问题1: JS没有块级别的作用域

请看这样一个规则:JS函数中var声明的范围就是整个函数体。乍一看没什么问题,但如果遇到以下两种情况,就不会得到满意的结果。

首先,在代码块中声明的变量的范围是整个函数范围,而不是块级范围。

这个你可能之前没有注意过,但这个问题恐怕真的是你不能轻易忽视的。让我们重新创建由这个问题引起的bug。

假设您当前的代码使用变量t:

函数runtowerexperience(tower,start time){ var t=start time;Tower.on('tick ',function(){ 0.使用变量t的代码.});更多代码.}到目前为止,一切都很顺利。现在您想添加测量保龄球速度的函数,所以您在回调函数中添加了一个简单的if语句。

函数runtowerexperience(tower,start time){ var t=start time;Tower.on('tick ',function(){ 0.使用变量t的代码.如果(保龄球。海拔()=0){ var t=read converter();} });更多代码.}哦,亲爱的,之前的“使用变量t的代码”运行良好,现在你不小心添加了第二个变量t,其中t指向一个新的内部变量t,而不是原来的外部变量。

JavaScript中var声明的作用域就像Photoshop中的油漆桶工具,从声明开始向前和向后扩散,直到接触到函数边界才停止扩散。想想看,这个变量t的范围很广,所以一进入函数就应该马上创建。这就是所谓的吊装。变量提升就像JS引擎使用一个小的代码吊车把所有的var声明和函数声明提升到函数的顶部。

现在看来,升降功能有自己的优势。如果没有提升动作,很多在全局范围内看似合理的完美技术,在即时调用函数表达式(IIFE)中就会失效。然而,在上面演示的例子中,提升会导致一个令人不快的错误:所有使用变量t的计算都会以NaN结束。这种问题很难定位,尤其是当你的代码量远远超过上面的玩具例子时,你会发疯崩溃。

在原始代码块之前添加新的代码块会导致奇怪的错误。这个时候,我会想,到底是谁的问题,是我的问题,还是制度的问题?我们不想搞砸这个系统。

这个问题与下一个相比相形见绌。

问题2:循环中变量的过度共享

您可以猜测当您执行以下代码时会发生什么,这非常简单:

Var messages=['嗨!',‘我是网页!’,alert()方法很有趣!’];for(var I=0;一.信息.长度;i ) { alert(消息[I]);}如果您一直关注本专栏,您知道我喜欢在示例代码中使用alert()方法。可能你也知道alert()不是一个好的API,它是一种同步方法,所以当弹出警告对话框时,不会触发输入事件,你的JS代码,包括你的整个UI,都会被完全挂起,直到用户点击OK确认。

请不要轻易使用alert()来实现网页中的功能。我在代码中使用它,因为alert()的特性使它成为一个非常有指导意义的工具。

而且,如果放弃所有烦琐的方法和不良的行为,就能做出会说话的猫,何乐而不为呢?

Var messages=['喵!'“我是一只会说话的猫!”,‘回调很有意思!' ];for(var I=0;一.信息.长度;I){ setTimeout(function(){ cat . say(messages[I]);},I * 1500);}不过,肯定有问题。会说话的猫连预期的三条信息都没说。它说了三次“未定义”。

你知道问题出在哪里吗?

你能看见树上的毛毛虫吗?(来源:内维尔萨维里)

事实上,这个问题的答案是循环本身和三个超时回调都共享一个唯一的变量I,当循环完成执行时,I的值是3(因为messages.length的值是3),回调还没有被触发。

因此,当执行第一次超时时,调用cat.say(messages[i]),此时I的值为3,因此cat最终打印messages[3]的值,这是未定义的。

有很多方法可以解决这个问题(这里有一个),但是你认为var范围规则已经给你带来了一个又一个的麻烦。如果能在第一时间彻底解决这个问题就好了!

Let是一个更完美的var

JavaScript中的大多数设计错误(也有其他语言,但JavaScript太突出)都无法修复。保持向后兼容意味着永远不改变Web平台上JS代码的行为,即使标准委员会无权要求修复JavaScript中自动插入分号的奇怪特性;浏览器制造商从来不会做出突破性的改变,因为这伤害了他们的忠实用户。

所以大约十年前,布兰登艾希决定解决这个问题,但只有一个解决办法。

他增加了一个新的关键词:let。和var一样,let也可以用来声明变量,但是它有更好的范围规则。

看起来是这样的:

让t=read tachymeter();或者类似这样的东西:

for(设I=0;一.信息.长度;I) {.}let与var不同,所以如果只是在代码中将var的全局搜索替换为let,那么一些依赖var声明的独特特性的代码(可能不是你故意写的)可能无法正常工作。但是对于大多数代码来说,在ES6的新代码模式下,应该停止使用var来声明变量,如果可以使用let就使用它!从现在开始,请记住这句口号:“让是一个更完美的var”。

那么let和var有什么区别呢?很高兴你提出了这个问题!

这条规则可以帮助你捕捉bug。除了NaN错误,每个异常都将在当前行中抛出。

let声明的变量具有块级作用域。也就是说,用let声明的变量范围只是外块,而不是整个外函数。

让申报仍然保留推广的特点,但不会盲目推广。在runTowerExperiment的例子中,这个问题可以通过用let替换var来快速修复。如果用let到处声明,就不会遇到类似的bug。

let声明的全局变量不是全局对象的属性。这意味着您不能通过窗口访问这些变量。变量名。它们只存在于一个不可见块的范围内,理论上这是网页中运行的所有JS代码的外部块。

(让x.)在每次迭代中为x创建一个新的绑定。

这是一个非常微妙的区别。以我们会说话的猫为例。如果一封信.)循环被执行多次,并且循环保持一个闭包,那么每个闭包将捕获循环变量的不同值作为副本,而不是所有的闭包都捕获循环变量的相同值。

因此,在会说话的猫的例子中,也可以通过用let替换var来修复bug。

这种情况适用于现有的三种循环模式:for-of、for-in和用分号分隔的传统C类循环。

在控制流到达定义变量的代码行之前,let声明的变量不会被加载,因此在变量到达之前使用它会触发错误。例如:

函数update() {console.log('当前时间: ',t);//引用错误.let t=read converter()}不可访问的时间变量始终在范围内,但尚未加载,它们位于时间死区(简称TDZ)。我一直想用科幻小说来类比这种让人脑洞大开的行话,但我还没想好怎么做。

(脆弱的性能细节:在大多数情况下,您可以通过查看代码来判断声明是否已经执行,因此实际上,JavaScript引擎不需要在每次代码运行时执行额外的变量可访问性检查来确保变量已经初始化。但是,有时候闭包不是透明的,所以JavaScript引擎会进行运行时检查,这意味着let比var慢。)

(脆弱并行宇宙作用域细节:在某些编程语言中,变量的作用域从声明处开始,而不是覆盖前后整个封闭的代码块。标准委员会曾经考虑过将这个范围标准赋给let关键字,但是一旦使用了这个标准,预先使用变量的语句就会导致ReferenceError。现在,这个语句不在let t的声明范围内,它在这里根本不会引用变量T,而是引用外部范围内对应的变量。但是,这种方法不能很好地与闭包和函数提升相结合,所以建议最终被拒绝。)

用let重新定义变量会引发语法错误。

这条规则也可以帮助你发现琐碎的问题。诚然,这也是var和let的区别。当您全局搜索var并用let替换它时,也会导致在重新定义let时出现语法错误,因为此规则对全局let变量也有效。

如果在多个脚本中声明相同的全局变量,最好继续用var声明这些变量。如果切换到let,以后加载的脚本将无法执行并引发错误。

或者您可以考虑使用ES6的内置模块机制,这将在下面的文章中详细解释。

(脆弱的语法细节:let是严格模式下的保留词。在非严格模式下,出于向后兼容的目的,仍然可以用let的名称声明变量、函数和参数。虽然你不会傻,但你真的可以写出var let=' q像这样的代码!但是让让;反正是违法的。)

除了这些差异,let和var几乎是相似的。例如,它们都支持多个变量的逗号分隔声明,并且都支持解构。

注意,类声明的行为不同于var,与let一致。如果加载包含同名类的脚本,后面定义的类将引发重定义错误。

常数

是的,有一个新的关键词!

ES6引入的第三个声明关键字类似于let: const。

const声明的变量和let声明的变量类似,不同的是const声明的变量只能在声明的时候赋值,不能随意修改,否则会导致SyntaxError。

const MAX _ CAT _ SIZE _ KG=3000//正确MAX _ CAT _ SIZE _ KG=5000//语法错误(max _ cat _ size _ kg);//虽然改了,但还是会导致语法错误。当然,规范设计足够明智,用const声明变量后必须赋值,否则也会抛出语法错误。

const Fairst;//还是语法错误,你这个不幸的神秘代理命名空间

“命名空间是一个很棒的想法,我们应该更多地利用它!”——蒂姆彼得斯,“这是蟒蛇的禅”

嵌套范围是编程语言背后的核心概念之一。这个概念大约从57年前的ALGOL开始,现在回想起来,当时的决定是极其正确的。

在ES3之前,JavaScript只有全局作用域和函数作用域。(让我们忽略with语句。ES3中引入了Try-catch语句,这意味着语言中诞生了一个新的作用域,只用于catch块中的异常变量。ES5为严格的eval()方法添加了一个范围。ES6增加了块范围、循环范围、新的全局字母范围、模块范围和用于查找参数默认值的附加范围。

自ES3以来添加的所有其他范围都非常重要。它们的加入使得JavaScript的面向过程和面向对象的特性像闭包一样流畅和准确地运行。当然,闭包可以无缝连接这些作用域,实现各种功能。也许在阅读本文之前,您从未注意到这些范围规则的存在。如果是这种情况,那么语言已经很好地完成了它的工作。

我现在可以使用let和const吗?

当然可以。要在网络上使用let和const功能,您需要使用ES6翻译器,如Babel、Traceur或TypeScript。(巴贝尔和特雷西不支持临时死区。)

Io.js支持let和const,但只能在严格模式下编码。Node.js也支持它,但是需要启用- harmony选项。

九年前,Brendan Eich在Firefox中实现了第一个let关键字。在随后的标准化过程中,这一功能被完全重新设计。郭树宇正在根据新标准对原实现进行升级,Jeff Walden等人做代码评审。

摘要

以上就是本文的全部内容。希望这篇文章的内容对你的学习或工作有所帮助。有问题可以留言交流。

版权声明:轻松理解与Javascript变量相关的问题是由宝哥软件园云端程序自动收集整理而来。如果本文侵犯了你的权益,请联系本站底部QQ或者邮箱删除。