在创建异步程序时,你必须密切关注程序的执行流程,并瞪大眼睛盯着程序的状态:事件轮询的条件、程序变量,以及其他随着程序逻辑执行而发生变化的资源。
比如说,Node的事件轮询会跟踪还没有完成的异步逻辑。
只要有异步逻辑未完成,Node进程就不会退出。
一个持续运行的Node进程对Web服务器之类的应用来说很有必要,但对于命令行工具这种经过一段时间后就应该结束的应用却意义不大。事件轮询会跟踪所有数据库连接,直到它们关闭,以防止Node退出。
如果你不小心,程序的变量也可能会出现意想不到的变化。
下面是一段可能因为执行顺序而导致混乱的异步代码。如果例子中的代码能够同步执行,你可以肯定输出应该是"The color is blue”。可这个例子是异步的,在console.log 执行之前color的值还在变化,所以输出是“The color is green”。
【作用域是如何导致bug 的出现的】
function asyncFunction(callback) {
setTimeout(callback, 200);
}
let color = 'blue';
asyncFunction(() => {
console.log(`The color is ${color}`); //这个最后执行(200ms之后)
});
color = 'green';

用JavaScript闭包可以“冻结”color的值。
在下面的代码中对asyncFunction的调用被封装到了一个以color为参数的匿名函数里。
这样你就可以马上执行这个匿名函数,把当前的color的值传给它。而color变成了匿名函数的参数,也就是这个匿名函数内部的本地变量,当匿名函数外面的 color值发生变化时,本地版的color不会受影响。
【用匿名函数保留全局变量的值】
function asyncFunction(callback) {
setTimeout(callback, 200);
}
let color = 'blue';
(color => {
asyncFunction(() => {
console.log('The color is ', color);
});
})(color);
color = 'green';

在 Node开发中,你要用到很多JavaScript编程技巧,这只是其中之一。
官方地址:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Closures

【概念】
闭包(closure)是一个函数以及其捆绑的周边环境状态(lexical environment,词法环境)的引用的组合。换而言之,闭包让开发者可以从内部函数访问外部函数的作用域。
在 JavaScript 中,闭包会随着函数的创建而被同时创建。
【词法作用域】
举个栗子:
function init() {
var name = "Mozilla"; // name 是一个被 init 创建的局部变量
function displayName() { // displayName() 是内部函数,一个闭包
console.log(name);; // 使用了父函数中声明的变量
}
displayName();
}
init();

init() 创建了一个局部变量 name 和一个名为 displayName() 的函数。displayName() 是定义在 init() 里的内部函数,并且仅在 init() 函数体内可用。请注意,displayName() 没有自己的局部变量。
然而,因为它可以访问到外部函数的变量,所以 displayName() 可以使用父函数 init() 中声明的变量 name 。
这个*词法作用域的例子描述了分析器如何在函数嵌套的情况下解析变量名。词法(lexical)一词指的是,词法作用域根据源代码中声明变量的位置来确定该变量在何处可用。嵌套函数可访问声明于它们外部作用域的变量。
【闭包】
举个栗子:
function makeFunc() {
var name = "Mozilla";
function displayName() {
console.log(name);;
}
return displayName;
}
var myFunc = makeFunc();
myFunc();

运行这段代码的效果和之前 init() 函数的示例完全一样。其中不同的地方(也是有意思的地方)在于内部函数 displayName() *在执行前,从外部函数返回。
第一眼看上去,也许不能直观地看出这段代码能够正常运行。在一些编程语言中,一个函数中的局部变量仅存在于此函数的执行期间。一旦 makeFunc() 执行完毕,你可能会认为 name 变量将不能再被访问。然而,因为代码仍按预期运行,所以在 JavaScript 中情况显然与此不同。
原因在于,JavaScript 中的函数会形成了闭包。
*闭包是由函数以及声明该函数的词法环境组合而成的。该环境包含了这个闭包创建时作用域内的任何局部变量。在本例子中,myFunc 是执行 makeFunc 时创建的 displayName 函数实例的引用。displayName 的实例维持了一个对它的词法环境(变量 name 存在于其中)的引用。因此,当 myFunc 被调用时,变量 name 仍然可用,其值 Mozilla 就被传递到console.log()中。
下面是一个更有意思的示例 — 一个 makeAdder 函数:
function makeAdder(x) {
return function(y) {
return x + y;
};
}
var add5 = makeAdder(5);
var add10 = makeAdder(10);
console.log(add5(2)); // 7
console.log(add10(2)); // 12

在这个示例中,我们定义了 makeAdder(x) 函数,它接受一个参数 x ,并返回一个新的函数。返回的函数接受一个参数 y,并返回x+y的值。
从本质上讲,makeAdder 是一个函数工厂 — 他创建了将指定的值和它的参数相加求和的函数。在上面的示例中,我们使用函数工厂创建了两个新函数 — 一个将其参数和 5 求和,另一个和 10 求和。
add5 和 add10 都是闭包。
它们共享相同的函数定义,但是保存了不同的词法环境。在 add5 的环境中,x 为 5。而在 add10 中,x 则为 10。