• linkedu视频
  • 平面设计
  • 电脑入门
  • 操作系统
  • 办公应用
  • 电脑硬件
  • 动画设计
  • 3D设计
  • 网页设计
  • CAD设计
  • 影音处理
  • 数据库
  • 程序设计
  • 认证考试
  • 信息管理
  • 信息安全
菜单
linkedu.com
  • 网页制作
  • 数据库
  • 程序设计
  • 操作系统
  • CMS教程
  • 游戏攻略
  • 脚本语言
  • 平面设计
  • 软件教程
  • 网络安全
  • 电脑知识
  • 服务器
  • 视频教程
  • JavaScript
  • ASP.NET
  • PHP
  • 正则表达式
  • AJAX
  • JSP
  • ASP
  • Flex
  • XML
  • 编程技巧
  • Android
  • swift
  • C#教程
  • vb
  • vb.net
  • C语言
  • Java
  • Delphi
  • 易语言
  • vc/mfc
  • 嵌入式开发
  • 游戏开发
  • ios
  • 编程问答
  • 汇编语言
  • 微信小程序
  • 数据结构
  • OpenGL
  • 架构设计
  • qt
  • 微信公众号
您的位置:首页 > 程序设计 >JavaScript > 轻松理解Javascript变量的相关问题

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

作者: 字体:[增加 减小] 来源:互联网 时间:2017-05-11

通过本文主要向大家介绍了javascript变量,javascript变量类型,javascript变量提升,javascript变量命名,javascript变量名等相关知识,希望对您有所帮助,也希望大家支持linkedu.com www.linkedu.com

前言

再说本文的内容之前,我们先回溯到1995年,当Brendan Eich在设计第一版JavaScript时,他搞错了许多东西,当然这也包括曾属于语言本身的一部分,例如Date对象,对象相乘被自动转换为NaN等。然而现在回过头看,语言最重要的部分都是设计合理的:对象、原型、具有词法作用域的一等函数、默认情况下的可变性等。语言的骨架非常优秀,甚至超越了人们对它的初步印象。

话说回来,正是Brendan当初的设计错误才诞生了今天这篇文章。我们这次关注的目标非常小,在你使用这门语言多年后可能根本不会注意到这个问题,但是它又如此重要,因为我们可能会误认为这个错误就是语言设计中的“the good parts”(译者注:请参考《JavaScript语言精粹》一书中附录A:毒瘤中有关作用域的描述)。

今天我们一定要把这些与变量有关的问题拿下。

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

请看这样一条规则: 在JS函数中的var声明,其 作用域 是函数体的全部 。乍一听没什么问题,但是如果碰到以下两种情况就不会得到令人满意的结果。

其一,在代码块内声明的变量,其作用域是整个函数作用域而不是块级作用域。

你之前可能没有关注到这一点,但我担心这个问题确实是你不能够轻易忽视的。我们一起重现一下由这个问题引发的bug。

假如你现在的代码使用了一个变量t:

function runTowerExperiment(tower, startTime) {
 var t = startTime;
 tower.on("tick", function () {
 ... 使用了变量t的代码 ...
 });
 ... 更多代码 ...
 }
</div>

到目前为止,一切都很顺利。现在你想添加测量保龄球速度的功能,所以你在回调函数内部添加了一个简单的if语句。

function runTowerExperiment(tower, startTime) {
 var t = startTime;
 tower.on("tick", function () {
 ... 使用了变量t的代码 ...
 if (bowlingBall.altitude() <= 0) {
  var t = readTachymeter();
  ...
 }
 });
 ... 更多代码 ...
 }
</div>

哦,亲爱的,之前那段“使用了变量t的代码”运行良好,现在你无意中添加了第二个变量t,这里的t指向的是一个新的内部变量t而不是原来的外部变量。

JavaScript中var声明的作用域像是Photoshop中的油漆桶工具,从声明处开始向前后两个方向扩散,直到触及函数边界才停止扩散。你想啊,这种变量t的作用域甚广,所以一进入函数就要马上将它创建出来。这就是所谓的提升(hoisting)。变量提升就好比是,JS引擎用一个很小的代码起重机将所有var声明和function函数声明都举起到函数内的最高处。

现在看来,提升特性自有它的优点。如果没有提升的动作,许多在全局作用域范围内看似合理的完美技术在立即调用函数表达式( IIFE )中通通失效。但在上面演示的这种情况下,提升会引发令人不愉快的bug:所有使用变量t进行的计算最终的结果都是NaN。这种问题极难定位,尤其是当你的代码量远超上面这个玩具一般的示例,你会发狂到崩溃。

在原有代码块之前添加新的代码块会导致诡异的错误,这时候我就会想,到底是谁的问题,我的还是系统的?我们可不希望自己搞砸了系统。

而这个问题与接下来这个问题相比就相形见绌了。

问题 #2:循环内变量过度共享

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

var messages = ["嗨!", "我是一个web页面!", "alert()方法非常有趣!"];
 for (var i = 0; i < messages.length; i++) {
 alert(messages[i]);
 }
</div>

如果你一直跟随这个专栏的文章,你知道我喜欢在示例代码中使用alert()方法。可能你也知道alert()不是一个好的API,它是一个同步方法,所以当弹出一个警告对话框时,输入事件不会触发,你的JS代码,包括你的整个UI,直到用户点击OK确认之前完全处于暂停状态。

请不要轻易使用alert()来实现Web页面中的功能,我之所以在代码中使用是因为alert()特性使它变成一个非常有教学意义的工具。

而且,如果放弃所有笨重的方法和糟糕的行为就可以做出一只会说话的猫,何乐而不为呢?

var messages = ["喵!", "我是一只会说话的猫!", "回调(callback)非常有趣!"];
 for (var i = 0; i < messages.length; i++) {
 setTimeout(function () {
 cat.say(messages[i]);
 }, i * 1500);
 }
</div>

然而一定是哪里不对,这只会说话的猫并没有按照预期连说三条消息,它说了三次“undefined”。

你知道问题出在哪里么?

你能看到树上的毛毛虫(bug)吗?(图片来源: nevil saveri )

事实上,这个问题的答案是,循环本身及三次timeout回调均共享唯一的变量i。当循环结束执行时,i的值为3(因为messages.length的值为3),此时回调尚未被触发。

所以当第一个timeout执行时,调用cat.say(messages[i]) ,此时i的值为3,所以猫咪最终打印出来的是messages[3]的值亦即undefined。

解决这个问题有很多种方法( 这里有一种 ),但是你想,var作用域规则接连给你添麻烦,如果能在第一时间彻底解决掉这个问题多好啊!

let是更完美的var

JavaScript的设计错误(其它语言也有,奈何JavaScript太突出)多半不能被修复。保持向后兼容性意味着永不改变JS代码在Web平台上的行为,即使连标准委员会都无权要求修复JavaScript中自动插入分号这种怪异的特性;浏览器厂商也从来不会做出突破性的改变,因为如此一来伤害的是他们的忠实用户。

所以大约十年以前,Brendan Eich决定修复这个问题,但只有唯一的解决方案。

他添加了一个新的关键词:let。let与var一样,也可以用来声明变量,但它有着更好的作用域规则。

它看起来是这样的:

let t = readTachymeter();
</div>

或者这样的:

for (let i = 0; i < messages.length; i++) {
 ...
 }
</div>

let与var还是有不同之处的,所以如果你只是在代码中将var全局搜索替换为let,一些依赖var声明的独特特性(可能你不是故意这样写)的代码可能无法正常运行。但对于绝大多数代码来说,在ES6的新代码模式下,你应该停止使用var声明变量,能使用let就用吧!从现在起,请记住这句口号:“let是更完美的var”。

那到底let和var有什么不同呢?非常高兴你提出这个问题!

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

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

let声明仍然保留了提升的特性,但不会盲目提升。在runTowerExperiment这个示例中,通过将var替换为let可以快速修复问题,如果你处处使用let进行声明,就不会遇到类似的bug。

let声明的全局变量不是全局对象的属性。这就意味着,你不可 以通过window.变量名的方式访问这些变量。它们只存在于一个不可见的块的作用域中,这个块理论上是Web页面中运行的所有JS代码的外层块。

形如for (let x...)的循环在每次迭代时都为x创建新的绑定。

这是一个非常微妙的区别,拿我们的会说话的猫的例子来说,如果一个for (let...)循环执行多次并且循环保持了一个闭包,那么每个闭包将捕捉一个循环变量的不同值作为副本,而不是所有闭包都捕捉循环变量的同一个值。

所以在会说话的猫示例中,也可以通过将var替换为let修复bug。

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

let声明的变量直到控制流到达该变量被定义的代码行时才会被装载,所以在到达之前使用该变量会触发错误。举个例子:

function update() {
 console.log("当前时间:", t); // 引用错误(ReferenceError)
 ...
 let t = readTachymeter()
 }
</div>

不可访问的这段时间变量一直处于作用域中,但是尚未装载,它们位于临时死区(Temporal Dead Zone,简称TDZ)中。我一直想用科幻小说来类比这个脑洞大开的行话,但是还没想好怎么搞。

(脆弱的性能细节:在大多数情况下,查看代码就可以区分声明是否已经执行,所以事实上,JavaScript引擎不需要在每次代码运行时都额外执行 一次变量可访问检查来确保变量已经被初始化。然而在闭包内部有时不是透明的,这时JavaScript引擎将会做一个运行时检查,也就意味着let相对var而言比较慢。)

(脆弱的平行宇宙作用域细节:在一些编程语言中,一个变量的作用域始于声明之处,而非前后覆盖整个封闭代码块。标准委员会曾考虑过将这

分享到:QQ空间新浪微博腾讯微博微信百度贴吧QQ好友复制网址打印

您可能想查找下面的文章:

  • javascript 的变量、作用域和内存问题
  • JavaScript中值类型和引用类型的区别
  • Javascript 链式作用域详细介绍
  • 一篇文章搞定JavaScript类型转换(面试常见)
  • 轻松理解Javascript变量的相关问题

相关文章

  • 2017-05-11详解NodeJs支付宝移动支付签名及验签
  • 2017-05-11JS仿JQuery选择器功能
  • 2017-05-11bootstrap配合Masonry插件实现瀑布式布局
  • 2017-05-113分钟掌握常用的JS操作JSON方法总结
  • 2017-05-11JS实现的数字格式化功能示例
  • 2017-05-11作为老司机使用 React 总结的 11 个经验教训
  • 2017-05-11JS获取浮动(float)元素的style.left值为空的快速解决办法
  • 2017-05-11javascript闭包功能与用法实例分析
  • 2017-05-11微信小程序开发之选项卡(窗口底部TabBar)页面切换
  • 2017-08-02js undefined 和 null

文章分类

  • JavaScript
  • ASP.NET
  • PHP
  • 正则表达式
  • AJAX
  • JSP
  • ASP
  • Flex
  • XML
  • 编程技巧
  • Android
  • swift
  • C#教程
  • vb
  • vb.net
  • C语言
  • Java
  • Delphi
  • 易语言
  • vc/mfc
  • 嵌入式开发
  • 游戏开发
  • ios
  • 编程问答
  • 汇编语言
  • 微信小程序
  • 数据结构
  • OpenGL
  • 架构设计
  • qt
  • 微信公众号

最近更新的内容

    • jQuery Validate 相关参数及常用的自定义验证规则
    • ES6新特性八:async函数用法实例详解
    • 详解本地Node.js服务器作为api服务器的解决办法
    • jquery选择器
    • JavaScript中的遍历详解(多种遍历)
    • jquery uploadify如何取消已上传成功文件
    • 如何解决每次向后台发起请求时判断用户是否处于登录状态?
    • JS验证input输入框(字母,数字,符号,中文)
    • 原生js实现轮播图
    • JavaScript组件开发之输入框加候选框

关于我们 - 联系我们 - 免责声明 - 网站地图

©2020-2025 All Rights Reserved. linkedu.com 版权所有