【阅文笔记】让你轻松了解何为 作用域闭包【你不知道的JavaScript(上)】

作用域闭包

词法作用域就是定义在词法阶段的作用域。 换句话说, 词法作用域是由你在写代码时将变量和块作用域写在哪里来决定的, 因此当词法分析器处理代码时会保持作用域不变(大部分情况下是这样的)


词法作用域 有 查找

JavaScript中闭包无处不在, 你只需要能够识别并拥抱它

闭包是基于词法作用域书写代码时所产生的自然结果, 不需要为了利用它们而有意识地创建闭包。 闭包的创建和使用在你的代码中随处可见缺少的是根据自己的意愿来识别、 拥抱和影响闭包的思维环境

一、实质问题

闭包的定义,你需要掌握才能理解和识别👉函数可以记住并访问所在的词法作用域时, 就产生了闭包即使函数是在当前词法作用域之外执行

代码解释定义:

function foo() {
  var a = 2;
  
  function bar() {
    console.log( a ); // 2
  }

  bar();
}

foo();

这段代码看起来和嵌套作用域中的示例代码很相似。 基于词法作用域的查找规则, 函数bar() 可以访问外部作用域中的变量 a(RHS 引用查询

这是闭包吗?

技术上讲,也许是。但根据前面的定义,确切的说并不是。最准确的用来解释 bar() 对 a 的引用的方法是词法作用域的查找规则,而这些规则只是闭包的一部分(非常重要的部分)

从纯学术角度说,上面的代码,函数 bar() 具有一个涵盖 foo() 作用域的闭包(事实上, 涵盖了它能访问的所有作用域, 比如全局作用域)。 也可以认为 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 所引用的函数对象本身当作返回值

foo() 执行后,其返回值(也就是内部的 bar() 函数)赋值给变量 baz 并调用 baz(),实际上只是通过不同的标识符引用内部的函数bar()

bar() 显然可以被正常执行,但此例中,它在自己定义的词法作用域以外的地方执行

在 foo() 执行后, 通常会期待 foo() 的整个内部作用域都被销毁, 因为我们知道引擎有垃圾回收器用来释放不再使用的内存空间。 由于看上去 foo() 的内容不会再被使用, 所以很自然地会考虑对其进行回收

闭包的“神奇”之处正是可以阻止这件事情的发生。事实上内部作用域依然存在,因此没有被回收。谁在使用这个内部作用域?原来是 bar() 本身在使用

【拜 bar() 所声明的位置所赐,它拥有涵盖 foo() 内部作用域的闭包,使得该作用域能够一直存活,以供 bar() 在之后任何时间进行引用】

bar() 依然持有对该作用域的引用,而这个引用就叫做闭包

因此,在几微秒之后,变量 baz 被实际调用(调用内部函数 bar),不出意料它可以访问定义时的词法作用域,因此它也可以如预期般访问变量 a

【这个函数在定义时的词法作用域以外的地方被调用。闭包使得函数可以继续访问定义时的词法作用域】

无论使用何种方式对函数类型的值进行传递, 当函数在别处被调用时都可以观察到闭包

function foo() {
  var a = 2;

  function baz() {
    console.log( a ); // 2
  }

  bar( baz );
}

function bar(fn) {
  fn(); // 快看呀,这就是闭包!
}

把内部函数 baz 传递给 bar, 当调用这个内部函数时(现在叫作 fn), 它涵盖的 foo() 内部作用域的闭包就可以观察到了, 因为它能够访问 a

传递函数也可以是间接的。

var fn;

function foo() {
  var a = 2;
  
  function baz() {
    console.log( a );
  }

  fn = baz; // 将baz分配给全局变量
}
  
  foo();
  bar(); // 2

无论通过何种手段内部函数传递到所在的词法作用域以外, 它都会持有对原始定义作用域的引用, 无论在何处执行这个函数都会使用闭包

二、现在我懂了

前面的代码片段有点死板, 并且为了解释如何使用闭包而人为地在结构上进行了修饰。我们来看其他代码中包含的闭包身影👇

function wait(message) {
	setTimeout( function timer() {
	  console.log( message );
	}, 1000 );
} 
wait( "Hello, closure!" );

将一个内部函数( 名为 timer) 传递给 setTimeout(…)。 timer 具有涵盖 wait(…) 作用域的闭包, 因此还保有对变量 message 的引用

wait(…) 执行 1000 毫秒后, 它的内部作用域并不会消失, timer 函数依然保有 wait(…)作用域的闭包

深入到引擎的内部原理中, 内置的工具函数 setTimeout(…) 持有对一个参数的引用, 这个参数也许叫作 fn 或者 func, 或者其他类似的名字。 引擎会调用这个函数, 在例子中就是内部的 timer 函数, 而词法作用域在这个过程中保持完整

这就是闭包。

【无论何时何地, 如果将函数(访问它们各自的词法作用域) 当作第一级的值类型并到处传递, 你就会看到闭包在这些函数中的应用。 在定时器、 事件监听器、Ajax 请求、 跨窗口通信、 Web Workers 或者任何其他的异步(或者同步) 任务中, 只要使用了回调函数实际上就是在使用闭包!】

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值