(未完成👃)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. 在写代码期间被定义的作用域。

 

案例:

见初始章节Module的案例。

 

 


 

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

  1. 必须有一个外部enclosing function, 它必须被运行至少一次(每次创建一个新的模块实例)
  2. 这个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的两个关键特征:

  1. 有一个外部包裹函数被运行,创建一个enclosing scope
  2. 这个包裹函数的返回值必须至少引用一个内部函数,这个内部函数会有closure。
posted @ 2018-10-03 13:58  Mr-chen  阅读(244)  评论(0编辑  收藏  举报