闭包


作用域闭包

  1. 闭包就好像从 JavaScript 中分离出来的一个充满神秘色彩的未开化世界,只有最勇敢的人 才能够到达那里。但实际上它只是一个标准,显然就是关于如何在函数作为值按需传递的 词法环境中书写代码的。
  2. 当函数可以记住并访问所在的词法作用域,即使函数是在当前词法作用域之外执行,这时 就产生了闭包。
  3. 如果没能认出闭包,也不了解它的工作原理,在使用它的过程中就很容易犯错,比如在循 环中。但同时闭包也是一个非常强大的工具,可以用多种形式来实现模块等模式。
  4. 模块有两个主要特征: (1)为创建内部作用域而调用了一个包装函数; (2)包装函数的返回 值必须至少包括一个对内部函数的引用,这样就会创建涵盖整个包装函数内部作用域的闭 包。

定义:当函数可以记住并访问所在的词法作用域时,就产生了闭包,即使函数是在当前词法作用 域之外执行。

function foo() {   
    var a = 2; 
    function bar() {     
        console.log( a ); // 2    
          } 
    bar(); 
    } 
foo();
  • bar() 对 a 的引用的方法是词法作用域的查找规则,而这些规则只是闭包的一部分。(但却 是非常重要的一部分!)
  • bar() 嵌套在 foo() 内部,所以函数 bar() 具有一个涵盖 foo() 作用域的闭包 (事实上,涵盖了它能访问的所有作用域,比如全局作用域)
function foo() { 
    var a = 2; 
    function bar() {
        console.log( a );  
            } 
    return bar;    
  }  
var baz = foo(); 
baz(); // 2 —— 朋友,这就是闭包的效果。
  • 拜 bar() 所声明的位置所赐,它拥有涵盖 foo() 内部作用域的闭包,使得该作用域能够一 直存活,以供 bar() 在之后任何时间进行引用。bar() 依然持有对该作用域的引用,而这个引用就叫作闭包。
  • 这个函数在定义时的词法作用域以外的地方被调用。闭包使得函数可以继续访问定义时的 词法作用域
    • 无论使用何种方式对函数类型的值进行传递,当函数在别处被调用时都可以观察到闭包。传递函数当然也可以是间接的
    • 无论通过何种手段将内部函数传递到所在的词法作用域以外,它都会持有对原始定义作用 域的引用,无论在何处执行这个函数都会使用闭包。
  • IIFE 本身并不是观察闭包的恰当例子,但它的确创建了闭包,并且也是最常用来创建 可以被封闭起来的闭包的工具。因此 IIFE 的确同闭包息息相关,即使本身并不会真的使用闭包。

循环域闭包

问题

for (var i=1; i<=5; i++) {   
    setTimeout( function timer() {     
        console.log( i );  //66666
            }, i*1000 ); } 
  • 缺陷是我们试图假设循环中的每个迭代在运行时都会给自己“捕获”一个 i 的副本。但是 根据作用域的工作原理,实际情况是尽管循环中的五个函数是在各个迭代中分别定义的, 但是它们都被封闭在一个共享的全局作用域中,因此实际上只有一个 i

解决方案

for (var i=1; i<=5; i++) {  
    (function(j) {       
        setTimeout( function timer() {           
            console.log( j );  //12345
          }, j*1000 );   
    })( i ); 
 }
  • 在迭代内使用 IIFE 会为每个迭代都生成一个新的作用域,使得延迟函数的回调可以将新的 作用域封闭在每个迭代内部,每个迭代中都会含有一个具有正确值的变量供我们访问。
for (let i=1; i<=5; i++) {   
    setTimeout( function timer() {    
        console.log( i );  //12345
        }, i*1000 );
 }
  • 本质上这是将一个块转换成一个可以被关闭的作用域,并且在这个块作用域中声明一个变量。

模块

模块模式需要具备两个必要条件:

  1. 必须有外部的封闭函数,该函数必须至少被调用一次(每次调用都会创建一个新的模块 实例)。
  2. 封闭函数必须返回至少一个内部函数,这样内部函数才能在私有作用域中形成闭包,并且可以访问或者修改私有的状态。
bar.js
function hello(who) {   
    return "Let me introduce: " + who; } 
export hello;

foo.js
// 仅从 "bar" 模块导入 hello() 
import hello from "bar";     
var hungry = "hippo"; 
function awesome() {    
    console.log(hello( hungry )); //Let me introduce:hippo
    } 
  • import 可以将一个模块中的一个或多个 API 导入到当前作用域中,并分别绑定在一个变量 上(在我们的例子里是 hello)。module 会将整个模块的 API 导入并绑定到一个变量上(在 我们的例子里是 foo 和 bar)。
  • export 会将当前模块的一个标识符(变量、函数)导出为公 共 API。这些操作可以在模块定义中根据需要使用任意多次。

文章作者: XiaoQi
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 XiaoQi !
 上一篇
对象和类 对象和类
对象 JavaScript 中的对象有字面形式(比如 var a = { .. })和构造形式(比如 var a = new Array(..))。字面形式更常用,不过有时候构造形式可以提供更多选项。 许多人都以为“JavaScript
2020-08-01
下一篇 
作用域 作用域
1.作用域 作用域是一套规则,用于确定在何处以及如何查找变量(标识符)。 如果查找的目的是对 变量进行赋值,那么就会使用 LHS 查询;如果目的是获取变量的值,就会使用 RHS 查询。 赋值操作符会导致 LHS 查询。=操作符或调用函数时
2020-08-01
  目录