(未完成👃)You Don't Know JS: Scope & Closures (第5章: Scope & Closures)
Chapter 5: Scope Closure
我们到达这里时,已经对作用域如何工作有了非常健康稳固的理解。
下面,我们转移注意力到一个及其重要,但长期难以理解,几乎是神话中的部分语言:Closure!
Enlightenment💡启发
那些有使用JS的经验,但没有完全抓住closures的概念的人,要理解closure就好像是一场特别的涅槃。一个需要抗争和牺牲才能对closure理解。
Closure was the other side to JavaScript, one which promised even more capability than I already possessed, teased and tauned me.
promise to be sth: to show signs of sth预示某事要发生
possess: 拥有,⚠️和process:进程前进有, (写法区别)
我记得通过读早期框架的源代码,试图理解它是如何工作的。我记得第一次关于module模式的东西开始从我的思想中冒出来。我清楚地记得the a-ha!(顿悟/灵光一现)时刻!
我曾经不知道的 花费了我多年去理解的,我希望现在impart to you的,是这个secret:
closure is all around you in JavaScript, you just have to recognize and embrace it!
Closure不是一个让你学习新句法和模式的可选工具。不,closure甚至不是一把你必须学会wield并掌握的武器。
Closure作为一个写代码的结果,发生在lexical scope。just happen。你无需主动去创建closures来利用它们。Closure被创建和利用在所有的code中。
你所缺失的是mental context, 需要你去理解,拥抱。 leverage closures激活它是你自己的意愿。
灵感点亮时刻应该是: 哈! closures 已经正在发生在我的代码中了,现在我终于可以看到它们了。
Nitty Gritty(the important detail of sth)重要细节
美國人似乎好把兩個押韻的詞連在一起組成習慣用語。类似nuts and bolts(螺母和螺栓,指最基本但不可缺失的东西)
1.闭包就是 一个函数能够记住和存取它的lexical作用域,即使这个函数是在它的lexical作用域的外面执行!
理解:
lexical scope是一个函数/变量被定义时,这个函数/变量所在的作用域。
lexical scope is scope that is defined at lexing time. 在写代码期间被定义的作用域。
案例:
Now I Can See
例子之一:
function wait(message) { setTimeout( function timer(){ console.log( message ); }, 1000 ); } wait( "Hello, closure!" );
Closure:
Be that timers, event handlers, Ajax requests, cross-window messaging, web workers, or any of the other asynchronous (or synchronous!) tasks, when you pass in a callback function, get ready to sling some closure around!
一个函数一定有它的闭包Closure,闭包的用途就是得到Lexcial scope中的变量。
Loops + Closure
问题案例:
这段代码的目的是: 打印:1 2 3 4 5,并且每次打印的间隔时间增长1秒。形成每过一秒打印一个数字。
for (var i=1; i<=5; i++) { setTimeout( function timer(){ console.log( i ); }, i*1000 ); }
但是,实际上它会打印5次数字6。
The timeout function callbacks are all running well after the completion of the loop.
setTimeout()函数中的回调函数timer(),会在循环loop结束后执行。
我们试图暗示每次迭代的loop会capture它自身i的副本。但是,作用域工作的方式,所有5个函数,尽管它们是分别在每个循环迭代中定义的,但共享一个全局作用域,这个全局作用域中只有一个i变量。
所以,循环结束后的i等于6,所有5个timer(),都会从Closure,中得到i的值6,并打印。
要想实现之前的目标:需要在循环内新建立一个作用域,并定义一个变量j, 用j来接收i的值。这样每个timer函数会有各自的Closure。
使用let j, 声明一个块作用域:
for (var i = 1; i <= 5; i++) { let j = i; setTimeout( function timer() { console.log(j); }, j*1000); } 1 2 3 4 5
或者直接写:
for (let i = 1; i <= 5; i++ ) {..略..}
或者使用立即执行函数()();
for (var i=1; i<=5; i++) { (function(){ var j = i; setTimeout( function timer() { console.log(j); }, j*1000); })(); }
再次理解,闭包就是函数声明时候所在的作用域(非全局作用域),函数通过闭包可以得到其中的变量的值。
意思就是闭包储存一个变量的name名单,这个名单包括所有函数定义时所在scope中的声明的变量的identifier。
只要需要,函数就通过闭包中的identifier使用这些变量。
一个函数的[[scopes]]有Closure和Global2类。
Block Scoping Revisited
块作用域和closure合作非常好!
每次循环都会定义一个新的块变量let i, 并同步生成一个新的block scope 。
回调函数Timer()在这个block scope内声明。
for (let i=1; i<=5; i++) {
setTimeout( function timer(){
console.log( i );
}, i*1000 );
}
Modules
执行模块模式的最普遍的方式被称为“Revealing Module”。
function CoolModule() { var something = "cool"; var another = [1, 2, 3]; function doSomething() { console.log( something ); } function doAnother() { console.log( another.join( " ! " ) ); } return { doSomething: doSomething, doAnother: doAnother }; } var foo = CoolModule(); foo.doSomething(); // cool foo.doAnother(); // 1 ! 2 ! 3
- 一个函数内定义了内部变量,内部函数,并返回一个对象,对象引用了内部函数。
- 可以认为这个对象返回值可以作为一个公共API。
- 这个对象返回值被分配给一个外部变量foo, 然后通过foo.doSomething(),在API上就可以存取那些属性方法。
注意⚠️:
- 从一个module返回一个实际的对象(字面量)不是必须的。
- 我们可以直接地返回一个内部函数。
- 例如jQuery和$识别符号就是jQuery模块的公共API, 但是它们本身是函数
- 因为所有函数都是对象,所有它们可以有属性properties。
Module pattern 的2个必需条件requirements
- 必须有一个外部enclosing function, 它必须被运行至少一次(每次创建一个新的模块实例)
- 这个enclosing function必须return至少一个内部function, 以便这个inner function 有closure over the private scope, 这个内部函数能存取/修改 那个private state。
什么不是model
- 一个对象只有一个函数属性不是一个真的module。(可以理解为,那个函数不是一个内部函数。)
- 一个对象从一个函数运行中返回,它只有数据属性,却没有closured函数,也不是一个真正的module。
Modern Modules
Future Modules
ES6可以把一个文件当作一个独立的module。
每个模块都可以import其他modules或者指定的API members。
ES6 modules没有一个inline format! 他们必须被定义在单独的文件内(每个文件一个module)
(Vue.js的单文件组件*.vue就是这样用的!)
- import 函数名 from "文件名/路径" //引进某个函数,进入当前作用域,分配给一个绑定的变量。
- export 函数/{ .. }
- module xx from "文件名/路径" //使用module是引进整个文件API给一个绑定的变量。
Review
Closure is when a function can remember and access its lexical scope even when it's invoked outside its lexical scope.
闭包就是一个函数能够记住和存取它的lexical scope,哪怕这个函数在它的lexical scope外部使用。
如果不小心识别闭包在循环和实例,弄懂它们如何工作,我们就会被trip up绊倒。
Module的两个关键特征:
- 有一个外部包裹函数被运行,创建一个enclosing scope
- 这个包裹函数的返回值必须至少引用一个内部函数,这个内部函数会有closure。