跳至正文
首页 » 博客 » New Solutions to Old JavaScript Problems: 1) Variable Scope

New Solutions to Old JavaScript Problems: 1) Variable Scope

导言

我喜欢JavaScript,但我也很清楚,作为一种编程语言,它远非完美。Douglas Crockford的《JavaScript: 好的部分》和David Herman的《有效的JavaScript》这两本书对我理解和找到一些最奇怪的行为的解决方法有很大帮助。但是Crockford的书现在已经有7年多的历史了,在web开发领域已经有很长一段时间了。ECMAScript 6 (又名ES6,ECMAScript2015和其他一些东西),最新的JavaScript标准,提供了一些新功能,可以更简单地解决这些旧问题。我打算在本文和后续文章中说明其中的一些功能,它们解决的问题的示例及其局限性。

本系列中介绍的几乎所有新解决方案都涉及新的JavaScript语法,而不仅仅是可以填充的其他方法。正因为如此,如果你想今天使用它们,并让你的JavaScript代码在各种浏览器上工作,那么你唯一真正的选择是使用像BabelTraceur这样的转换器将你的ES6转换为有效的es5。如果你已经在使用像GulpGrunt这样的任务运行器,这可能没什么大不了的,但是如果你只想写一个简短的脚本来执行一个简单的任务,那么使用旧的语法和解决方案可能会更容易。浏览器正在快速发展,您可以在此处查看哪些浏览器支持哪些新功能。如果您只是想尝试新功能并尝试了解它们的工作原理,那么本系列中使用的所有代码都可以在Chrome Canary中使用。

在第一篇文章中,我将介绍变量作用域和新的letconst关键字。

问题与var

可能JavaScript中最常见的错误来源之一 (至少是我经常绊倒的原因) 是由于使用var关键字声明的变量具有全局范围函数范围 ,而不是块范围。为了说明为什么这可能是一个问题,让我们先看一个非常基本的C程序:

# include <string># include <iostream>使用std:: coout;使用std::endl;使用std::string;int main(){string myVariable = "global";Cot << "1) myVariable是" << myVariable << endl;			{string myVariable = "local";Cot << "2) myVariable是" << myVariable << endl;}		cout << "3) myVariable是" << myVariable << endl;返回0;}

编译该程序并执行它,它打印出以下内容:

1) myVariable是全局的2) myVariable是局部的3) myVariable是全局的

如果你从C (或类似的语言,也有块范围) 来到JavaScript,那么你可能合理地期望下面的代码打印出相同的消息。

var myVariable = "global";console.log("1) myVariable是" myVariable);		{var myVariable = "local";console.log("2) myVariable是" myVariable);}	console.log("3) myVariable是" myVariable);

它实际上打印出来的是:

1) myVariable是全局的2) myVariable是局部的3) myVariable是局部的

问题是,在大括号内重新声明myVariable不会改变范围,它仍然是全局的。这不仅仅是与未附加到任何流控制关键字的大括号相关的问题。例如,你会得到相同的结果与以下小的变化:

var myVariable = "global";console.log("1) myVariable是" myVariable);		if(true){var myVariable = "local";console.log("2) myVariable是" myVariable);}	console.log("3) myVariable是" myVariable);

在实现事件的回调函数时,缺乏块范围也会导致错误和混乱。由于回调函数不会立即调用,因此这些错误可能特别难以定位。假设您在表单中有五个按钮。

<form id = "my-form"><button type = "button"> 按钮1</Button><button type = "button"> 按钮2</Button><button type = "button"> 按钮3</Button><button type = "button"> 按钮4</Button><button type = "button"> 按钮5</Button></form>

你可能会认为下面的代码会让点击任何按钮会弹出一个恼人的警报对话框,告诉你你按下了哪个按钮号码:

var buttons = document.querySelectorAll("# my-form button");for(var i = 0, n = buttons.length; i<n; i ){按钮 [i].addEventListener("click",函数 (evt){alert("Hi! I'm button" (i 1 ));}, false);}

实际上,单击五个按钮中的任何一个都会弹出一个烦人的警报对话框,告诉您该按钮声称是神话按钮6。

问题是的范围不限于 (for) 块,每个回调都认为具有相同的值,即for循环终止时的值。

这个问题的一个解决方案是使用立即调用函数表达式 (IIFE) 为循环的每次迭代创建一个闭包 ,其中存储当前循环索引值:

for(var i = 0, n = buttons.length; i<n; i ){(函数 (索引) {按钮 [index].addEventListener("click",函数 (evt){alert (“嗨!我是button” (索引1 ));}, false);})(i);}

letconst

ES6为上面的for循环问题提供了一个更优雅的解决方案。只需将var交换为新的let关键字。

for (设i = 0,n = buttons.length; i<n; i ){按钮 [i].addEventListener("click",函数 (evt){alert("Hi! I'm button" (i 1 ));}, false);}

使用let关键字声明的变量是块范围的,其行为更像C,C和Java等语言中的变量。在for循环之外, i不存在,而在循环的每次迭代中,都有一个新的绑定: 每个函数实例中的i值反映了它被声明的循环迭代的值,而不管它何时被实际调用。

使用let也可以解决原始问题。代码

让myVariable = "global";console.log("1) myVariable是" myVariable);		{让myVariable = "local";console.log("2) myVariable是" myVariable);}	console.log("3) myVariable是" myVariable);

确实给出了输出

1) myVariable是全局的2) myVariable是局部的3) myVariable是全局的

除了let ,ES6还引入了const。像let一样, const具有块范围,但声明导致 “只读引用值” 的创建。您不能将值从7更改为8或从 “Hello” 更改为 “Goodbye” 或从布尔值更改为数组。因此,以下内容会引发TypeError:

for(const i = 0, n = buttons.length; i<n; i ){按钮 [i].addEventListener("click",函数 (evt){alert("Hi! I'm button" (i 1 ));}, false);}

请注意,使用const关键字声明对象不会使其不可变 ,这一点很重要 (也许令人困惑)。您仍然可以更改存储在使用const声明的对象或数组中的数据,只是不能将标识符重新分配给其他实体。如果你想要一个对象或数组不可变的,你需要使用object.freeze (在ES5中引入)。

</p