|
了解 你会怎么样?JavaScript 封闭包装的概念(如函数、变量等)。)但不了解封闭包装本身的人解释 JavaScript 闭包?0 Q4 i4 Q" V8 {$ B: G
; }5 |! B5 U3 I& h: L3 ~3 H 解决方案: : h8 A+ F, n t; I$ u
闭包是一对:% ^& Z" @" o+ E o
[ol]和
! ^/ }4 `8 @' m) R引用函数外部作用域(词法环境)[/ol]词法环境是每个执行上下文(堆栈帧)的一部分,是标识符(即局部变量名称)和值之间的映射。
$ P. } M: O. s9 u n/ Y4 {JavaScript 中的每个函数都保持引用其外部词法环境。这个引用是在配置调用函数时创建的执行上下文。该引用使函数内部代码能够查看函数外部声明的变量,无论何时何地调用函数。% ^! x6 |" a& F
如果一个函数被一个函数调用,而这个函数被另一个函数调用,它将创建一个引用链,指向外部词法环境。这个链被称为功能域链。
6 y* D) y+ D$ t6 X, i在下面的代码中,inner使用foo调用时创建的执行上下文的词法环境形成一个闭包,关闭变量secret:8 q% U% r, ^; i& [" G* Q: S
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`- R5 I% ]% |- G0 U5 o
换句话说:在 JavaScript 中,只有它们(以及在同一词法环境中声明的任何其他函数)才能访问私有状态框的引用。这个状态框对于函数的调用者来说是看不见的,为数据和包装提供了良好的机制。
) I9 c2 U. h8 {: a1 r7 E$ i请记住:JavaScript 中的函数可以像变量(一等函数)一样传输,这意味着这些功能和状态的匹配可以在你的程序中传输:类似于你在 C 中传实例。' h( c8 ~3 ?. w6 X2 t% @7 ~
如果 JavaScript 函数之间必须没有封包显式传递更多的状态,使参数列表更长,代码更嘈杂。* ]+ r0 x6 w7 \. G* f
因此,如果您希望函数能够始终访问私有状态,则可以使用闭包。
% p2 q. t5 H: ^…我们经常确实希望将状态与函数相关联。例如, Java 或 C 当您向类添加私有实例变量和方法时,您将状态与功能相关联。
9 u! V4 N" F% z; y7 E+ g7 W0 ?* A9 f$ b0 o在 C 和大多数其他常见语言中,由于堆栈帧被破坏,所有局部变量在函数返回后不再可访问。JavaScript 中,如果你在另一个函数中声明一个函数,外部函数的局部变量在返回后仍然可以访问。这样,上面的代码,secret函数对象仍然可用inner,之后它已经从foo。, O# P! \4 G7 R" [7 N: e3 ?, ]/ |
闭包的使用当你需要与函数相关的私有状态时,关闭包是非常有用的。这是一个非常常见的场景 - 请记住:JavaScript 直到2015年 才有类语法,而且它仍然没有私有字段语法。这种需求是封闭的。3 i6 N, X8 \5 t% e: N6 j
私有实例变量函数在以下代码中toString关闭汽车的详细信息。2 Y) I# l7 t# ^2 g
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())! e/ O& m5 m. ~% `8 G
函数式编程函数inner在fn和 上关闭args。
3 U- H* H+ {% L) Q0 O7 q$ X! v$ m: i9 h' L X. X- z" W3 {8 k
- 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
, O0 U0 p/ J8 h+ D3 G ^3 | 模块化所有的实现细节都隐藏在立即执行的函数表达式中。tick和toString关闭他们完成工作所需的私有状态和函数。封闭包可以模块化和包装我们的代码。/ A6 Q) ~% {' Y% r$ m; s
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())$ k9 U% F( e4 [. q
例子示例 1这个例子表明,局部变量没有在闭包中复制原始变量的闭包维护本身引用。就像堆栈帧在外部函数退出后仍存在于内存中一样。/ i' E% d6 i6 `0 q
function foo() { let x = 42 let inner = function() { console.log(x) } x = x 1 return inner}var f = foo()f() // logs 43
0 U4 h7 t, `1 M: \2 v( u5 m; J 示例 2在以下代码中,有三种方法log、increment和update都关闭在同一个词法环境中。
0 `2 x7 Z' R' D7 d& K+ Z8 C: Z4 ^每次createObject调用时,会创建新的执行上下文(堆栈帧),创建新的变量x一组新函数(log等),这些函数会关闭这个新变量。% k) I0 n! ?& b$ @3 w9 s# o1 |
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
E, ^& H2 r0 \% x: ?8 l 示例 3如果正在使用 using 声明的变量var,请注意关闭的变量。使用声明中的变量var被提升。由于引入了let和,这是现代 JavaScript 问题要小得多const。" H2 C7 J. S: k1 W2 g# J
在下面的代码中,每次循环,inner它将创建一个关闭的新函数i. 但是因为var i在循环之外,所有这些内部函数都关闭了相同的变量,这意味着i(3)最终值打印 3 次。$ k: H1 \9 P' L" U+ L) O
[code]function foo() { var result = [] for (var i = 0; i 最后一点:每当在 JavaScript 声明函数时,会创建闭包。
/ C5 `/ X# }+ r ^3 `* B& Wfunction从另一个函数返回 a这是一个经典的闭包示例,因为即使外部函数已经完成执行,外部函数的内部状态也是隐藏的。
) \& u6 d6 u0 M( ?; J+ H5 q( w每当您eval()在函数内部使用时,将使用闭包。eval在非严格模式下甚至可以引用函数的局部变量eval('var foo = …').
1 v ]; i5 C3 H! D4 C当您new Function(…)在函数中使用(Function 构造函数 时,它不会关闭其词法环境:而是关闭整体上下文。新函数不能引用外部函数的局部变量。9 ~$ K: x8 J3 u+ p: H, K# [
JavaScript 中的闭包就像在函数声明点保留作用域的引用(不是副本),后者保留了对其外部作用域的引用,等等,直到顶部的整体对象作用域链。9 H# A# J3 r l4 }4 p1 i3 G
声明函数时,将创建一个封闭包;该封闭包用于在调用函数时配置上下文。
, O1 @! y" {$ P, Y- B7 Y7 b G2 h) U创建一组新的局部变量,每次调用函数。 |
|