回答

收藏

JavaScript 闭包是如何工作的?

技术问答 技术问答 357 人阅读 | 0 人回复 | 2023-09-12

了解 你会怎么样?JavaScript 封闭包装的概念(如函数、变量等)。)但不了解封闭包装本身的人解释 JavaScript 闭包?9 w+ l$ m1 b& }0 S' j
                                                                4 b$ U+ G! J9 k( `* c# `
    解决方案:                                                               
  ~4 ]- T  e, u( R, a                                                                闭包是一对:- M% K0 L0 Y8 J7 I! l! J- e$ i
[ol]和2 H: j& ~5 H2 i; o4 N- U3 k
引用函数外部作用域(词法环境)[/ol]词法环境是每个执行上下文(堆栈帧)的一部分,是标识符(即局部变量名称)和值之间的映射。' E$ Y% M( Z. P, H; i
JavaScript 中的每个函数都保持引用其外部词法环境。这个引用是在配置调用函数时创建的执行上下文。该引用使函数内部代码能够查看函数外部声明的变量,无论何时何地调用函数。
& t  ^# g9 t6 l( j1 {如果一个函数被一个函数调用,而这个函数被另一个函数调用,它将创建一个引用链,指向外部词法环境。这个链被称为功能域链。8 u1 t$ A! Y$ B) S: q( E! g
在下面的代码中,inner使用foo调用时创建的执行上下文的词法环境形成一个闭包,关闭变量secret:) E' [# c+ j* S# V* ^) B
    function foo() {  const secret = Math.trunc(Math.random()*100)  return function inner() {    console.log(`The secret number is ${secret}.`)  }}const f = foo() // `secret` is not directly accessible from outside `foo`f() // The only way to retrieve `secret`,is to invoke `f`
    4 w; H, y# v* G
换句话说:在 JavaScript 中,只有它们(以及在同一词法环境中声明的任何其他函数)才能访问私有状态框的引用。这个状态框对于函数的调用者来说是看不见的,为数据和包装提供了良好的机制。! r1 V3 I/ B/ L" @- O! D: A2 U
请记住:JavaScript 中的函数可以像变量(一等函数)一样传输,这意味着这些功能和状态的匹配可以在你的程序中传输:类似于你在 C   中传实例。
; |6 H& [+ w) J  z  u  A5 Y6 l4 k" T如果 JavaScript 函数之间必须没有封包显式传递更多的状态,使参数列表更长,代码更嘈杂。" i3 @6 X( N; T+ t2 |4 D# E
因此,如果您希望函数能够始终访问私有状态,则可以使用闭包。
3 ~$ ^+ R7 _" t/ T- S  V; k- i…我们经常确实希望将状态与函数相关联。例如, Java 或 C   当您向类添加私有实例变量和方法时,您将状态与功能相关联。
" }1 O/ U: Y0 J$ Z6 s在 C 和大多数其他常见语言中,由于堆栈帧被破坏,所有局部变量在函数返回后不再可访问。JavaScript 中,如果你在另一个函数中声明一个函数,外部函数的局部变量在返回后仍然可以访问。这样,上面的代码,secret函数对象仍然可用inner,之后它已经从foo。# u( B( T5 |& j$ ~5 _
闭包的使用当你需要与函数相关的私有状态时,关闭包是非常有用的。这是一个非常常见的场景 - 请记住:JavaScript 直到2015年 才有类语法,而且它仍然没有私有字段语法。这种需求是封闭的。
# x. A; ~; P2 K% ^2 M私有实例变量函数在以下代码中toString关闭汽车的详细信息。
& u" {5 G1 D- H( a- |
    function Car(manufacturer,model,year,color) {  return    toString()      return `${manufacturer} ${model} (${year},${color})`   const car = new Car('Aston Martin','V8 Vantage2012年,Quantum Silver')console.log(car.toString())" I& s, ]9 K9 _9 I( D
函数式编程函数inner在fn和 上关闭args。
* O7 q' A! s; o0 S6 [

    + R6 N! z. p1 H/ l; B3 V
  • function curry(fn) {  const args = []  return function inner(arg)    if(args.length === fn.length) return fn(...args)    args.push(arg)    return inner  }}function add(a,b) {  return a   b}const curriedAdd = curry(add)console.log(curriedAdd(2)(3)code]面向事件的编程函数onClick关闭变量BACKGROUND_COLOR。[code]const $ = document.querySelector.bind(document)const BACKGROUND_COLOR = 'rgba200,200,242,1)function onClick() {  $('body').style.background = BACKGROUND_COLOR}$('button').addEventListener('click',onClick)Set background color
    . h9 o7 ]$ g7 A& S7 J
模块化所有的实现细节都隐藏在立即执行的函数表达式中。tick和toString关闭他们完成工作所需的私有状态和函数。封闭包可以模块化和包装我们的代码。+ E9 D6 D) p9 P9 @* U; k$ N9 T) g  a
    let namespace = {};(function foo(n) {  let numbers = []  function format(n)    return Math.trunc(n)  }  function tick()    numbers.push(Math.random() *  function toString()    return numbers.map(format)  }  n.counter =    tick,   toString  }}(namespace))const counter = namespace.countercounter.tick()counter.tick()console.log(counter.toString())
      @1 N* ?- o# B& A
例子示例 1这个例子表明,局部变量没有在闭包中复制原始变量的闭包维护本身引用。就像堆栈帧在外部函数退出后仍存在于内存中一样。
2 q/ {7 l) \1 u" Y0 ]$ u5 Q( P
    function foo() {  let x = 42  let inner  = function() { console.log(x) }  x = x 1  return inner}var f = foo()f() // logs 43+ Z) u* x, [5 S* {
示例 2在以下代码中,有三种方法log、increment和update都关闭在同一个词法环境中。
5 k4 F# i3 K" A每次createObject调用时,会创建新的执行上下文(堆栈帧),创建新的变量x一组新函数(log等),这些函数会关闭这个新变量。* G' L+ P$ t! c
    function createObject() {  let x = 42;  return    log() { console.log(x) ,  increment() { x   ,  update(value) { x = value   }}}const o = createObject()o.increment()o.log() // 43o.update(5)o.log() // 5const p = createObject()p.log() / 42
    5 f& a  v( s! o- `) {5 {2 {* {
示例 3如果正在使用 using 声明的变量var,请注意关闭的变量。使用声明中的变量var被提升。由于引入了let和,这是现代 JavaScript 问题要小得多const。
  F3 V+ n- D4 I3 V# C: n/ z在下面的代码中,每次循环,inner它将创建一个关闭的新函数i. 但是因为var i在循环之外,所有这些内部函数都关闭了相同的变量,这意味着i(3)最终值打印 3 次。4 t: a, U3 ]: v- }/ m5 m" j
[code]function foo() {  var result = []  for (var i = 0; i 最后一点:每当在 JavaScript 声明函数时,会创建闭包。; u4 v" b9 {: G6 R! }& l
function从另一个函数返回 a这是一个经典的闭包示例,因为即使外部函数已经完成执行,外部函数的内部状态也是隐藏的。7 ^! y) C' d/ U7 v- q
每当您eval()在函数内部使用时,将使用闭包。eval在非严格模式下甚至可以引用函数的局部变量eval('var foo = …').7 m1 g7 d/ v2 ^, {
当您new Function(…)在函数中使用(Function 构造函数 时,它不会关闭其词法环境:而是关闭整体上下文。新函数不能引用外部函数的局部变量。
/ x5 p* z- r1 D: wJavaScript 中的闭包就像在函数声明点保留作用域的引用(不是副本),后者保留了对其外部作用域的引用,等等,直到顶部的整体对象作用域链。
4 ~0 M( B, S1 D声明函数时,将创建一个封闭包;该封闭包用于在调用函数时配置上下文。
. I5 Q! u) ]" J0 i创建一组新的局部变量,每次调用函数。
分享到:
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则