我问了一个关于咖喱和封闭的问题。什么是封包?它与咖喱有何关系?
我问了一个关于咖喱和封闭的问题。什么是封包?它与咖喱有何关系?
Answers:
声明局部变量时,该变量具有作用域。通常,局部变量仅存在于声明它们的块或函数中。
function() {
var a = 1;
console.log(a); // works
}
console.log(a); // fails
如果我尝试访问局部变量,则大多数语言都会在当前作用域中查找它,然后在父作用域中向上查找,直到它们到达根作用域为止。
var a = 1;
function() {
console.log(a); // works
}
console.log(a); // works
完成块或功能后,不再需要其局部变量,通常会将其吹出内存。
这就是我们通常期望事情正常进行的方式。
闭包是一个持久作用域,即使代码执行移出了该块,它也会保留局部变量。支持闭包的语言(例如JavaScript,Swift和Ruby)将允许您保留对范围(包括其父范围)的引用,即使在声明了这些变量的块执行完之后,只要您保留引用即可到某个地方的那个块或功能。
作用域对象及其所有局部变量都绑定到该函数,并且只要该函数持续存在就将持续存在。
这为我们提供了功能的可移植性。我们可以期望,当我们在以后调用该函数时,即使在完全不同的上下文中调用该函数,在第一次定义该函数时在作用域内的所有变量仍然在作用域内。
这是JavaScript中一个非常简单的示例,它说明了这一点:
outer = function() {
var a = 1;
var inner = function() {
console.log(a);
}
return inner; // this returns a function
}
var fnc = outer(); // execute outer to get inner
fnc();
在这里,我在函数中定义了一个函数。内部函数可以访问所有外部函数的局部变量,包括a
。该变量a
在内部函数的范围内。
通常,当函数退出时,其所有局部变量都会被删除。但是,如果我们返回内部函数并将其分配给变量fnc
,以使其在outer
退出后仍然存在,则定义时作用域内的所有变量也都inner
将保留。变量a
已被关闭-位于封闭符内。
请注意,该变量a
是完全私有的fnc
。这是一种使用功能编程语言(例如JavaScript)创建私有变量的方法。
您可能会猜到,当我调用fnc()
它时,它会打印的值a
,即“ 1”。
在没有闭包的语言中,变量a
将被垃圾回收并在函数outer
退出时被丢弃。调用fnc会引发错误,因为a
不再存在。
在JavaScript中,变量会a
持续存在,因为变量范围是在首次声明该函数时创建的,并且在该函数继续存在之前会持续存在。
a
属于的范围outer
。的作用域inner
具有指向的作用域的父指针outer
。fnc
是一个指向的变量inner
。a
只要fnc
坚持下去就坚持下去。a
在封口内。
我将举一个示例(使用JavaScript):
function makeCounter () {
var count = 0;
return function () {
count += 1;
return count;
}
}
var x = makeCounter();
x(); returns 1
x(); returns 2
...etc...
该函数makeCounter的作用是返回一个函数,我们称其为x,每次调用该函数时,其计数将加一。由于我们没有为x提供任何参数,因此必须以某种方式记住该计数。它知道根据所谓的词法作用域在哪里找到它-它必须寻找定义它的地方才能找到该值。此“隐藏”值称为闭包。
这又是我的例子:
function add (a) {
return function (b) {
return a + b;
}
}
var add3 = add(3);
add3(4); returns 7
您会看到,当使用参数a(即3)调用add时,该值包含在我们定义为add3的返回函数的闭包中。这样,当我们调用add3时,它知道在哪里可以找到一个值来执行加法。
首先,与大多数人告诉您的相反,闭包不是函数!那是什么
它是在函数的“周围环境”(称为环境)中定义的一组符号),这使它成为一个CLOSED表达(即,在其中每一个符号定义和具有值的表达式,所以可以评价)。
例如,当您具有JavaScript函数时:
function closed(x) {
return x + 3;
}
它是一个封闭表达式,因为其中定义的所有符号均已定义(它们的含义很清楚),因此您可以对其进行求值。换句话说,它是独立的。
但是,如果您具有以下功能:
function open(x) {
return x*y + 3;
}
它是一个开放表达式,因为其中有未定义的符号。即,y
。在查看此函数时,我们无法分辨出什么y
是什么,意味着什么,我们不知道它的值,因此无法评估该表达式。即,直到我们告诉我们该函数的y
含义之前,我们才能调用此函数。这y
称为自由变量。
这y
是一个定义,但是这个定义不是功能的一部分-它是在其“周围环境”(也称为环境)中的其他地方定义的。至少那是我们希望的:P
例如,可以全局定义:
var y = 7;
function open(x) {
return x*y + 3;
}
或者可以在包装它的函数中定义它:
var global = 2;
function wrapper(y) {
var w = "unused";
return function(x) {
return x*y + 3;
}
}
在表达式中赋予自由变量含义的环境部分是闭包。之所以这样称呼,是因为它通过为所有自由变量提供这些缺失的定义,从而将一个开放表达式变成一个封闭表达式。,从而,以便我们对其求值。
在上面的示例中,内部函数(因为不需要它而没有给出名称)是一个开放表达式,因为其中的变量y
是自由的 –其定义在函数外部,在将其包装的函数中。该匿名函数的环境是一组变量:
{
global: 2,
w: "unused",
y: [whatever has been passed to that wrapper function as its parameter `y`]
}
现在,闭包就是该环境的一部分,它通过为其所有自由变量提供定义来关闭内部函数。在我们的例子中,内部函数中唯一的自由变量是,因此该函数的关闭是其环境的该子集:y
{
y: [whatever has been passed to that wrapper function as its parameter `y`]
}
环境中定义的其他两个符号不属于该函数的关闭部分,因为它不需要运行它们。他们不需要关闭它。
有关其背后理论的更多信息,请访问:https: //stackoverflow.com/a/36878651/434562
值得注意的是,在上面的示例中,包装函数将其内部函数作为值返回。从定义(或创建)函数的那一刻起,我们称之为函数的那一刻可能是遥远的。特别是,它的包装函数不再运行,并且它在调用堆栈中的参数也不再存在:P这是一个问题,因为内部函数y
在被调用时必须在那儿!换句话说,它要求变量从其关闭到某种程度上超过包装函数,并在需要时存在。因此,内部函数必须对这些变量进行快照,以使其关闭并将其存储在安全的位置以供以后使用。(在调用堆栈之外的某个地方。)
这就是为什么人们经常将术语“ 闭包”混淆为一种特殊的函数类型,它可以对他们使用的外部变量或用于存储这些变量以供以后使用的数据结构进行快照。但是,我希望您现在了解它们不是闭包本身,它们只是以编程语言或语言机制实现闭包的方式,这些机制允许在需要时使用函数闭包中的变量。关于闭包有很多误解,这(不必要地)使该主题实际上更加混乱和复杂。
为了帮助促进对闭包的理解,研究如何以过程语言实现闭包可能会很有用。该解释将遵循Scheme中对闭包的简化实现。
首先,我必须介绍名称空间的概念。在Scheme解释器中输入命令时,它必须计算表达式中的各种符号并获取其值。例:
(define x 3)
(define y 4)
(+ x y) returns 7
定义表达式将x的值3存储在点中,将y的值4存储在x中。然后,当我们调用(+ xy)时,解释器将在名称空间中查找值,并能够执行该操作并返回7。
但是,在Scheme中,有一些表达式可以让您临时覆盖符号的值。这是一个例子:
(define x 3)
(define y 4)
(let ((x 5))
(+ x y)) returns 9
x returns 3
let关键字的作用是引入一个新的名称空间,其中x为值5。您将注意到它仍然可以看到y为4,使总和返回为9。您还可以看到,一旦表达式结束x回到3。从这个意义上讲,x被本地值临时掩盖了。
程序和面向对象的语言具有类似的概念。每当在函数中声明与全局变量同名的变量时,都会得到相同的效果。
我们将如何实施呢?一种简单的方法是使用链表-头包含新值,而尾包含旧名称空间。当您需要查找符号时,可以从头开始,然后一直到尾部。
现在,我们暂时跳过一流功能的实现。函数或多或少是在调用该函数最终达到返回值时要执行的一组指令。当我们读入一个函数时,我们可以将这些指令存储在幕后,并在调用该函数时运行它们。
(define x 3)
(define (plus-x y)
(+ x y))
(let ((x 5))
(plus-x 4)) returns ?
我们将x定义为3,将plus-x定义为其参数y,加上x的值。最终,在x被新x掩盖的环境中调用plus-x,该值等于5。如果我们只为函数plus-x存储操作(+ xy),因为我们在上下文中x等于5时,返回的结果将为9。这就是所谓的动态作用域。
但是,Scheme,Common Lisp和许多其他语言都有所谓的词法作用域-除了存储操作(+ xy)之外,我们还在该特定点存储名称空间。这样,当我们查找值时,可以看到x在这种情况下实际上是3。这是一个闭包。
(define x 3)
(define (plus-x y)
(+ x y))
(let ((x 5))
(plus-x 4)) returns 7
总而言之,我们可以使用链表在函数定义时存储名称空间的状态,从而使我们能够从封闭范围中访问变量,并提供在不影响其余变量的情况下本地屏蔽变量的能力。程序。
Why do we want to access variables that are out of scope? when we say let x = 5, we want x to be 5 and not 3. What is happening?
这是一个为什么Closures会踢屁股的真实示例...这完全是我的Javascript代码。让我举例说明。
Function.prototype.delay = function(ms /*[, arg...]*/) {
var fn = this,
args = Array.prototype.slice.call(arguments, 1);
return window.setTimeout(function() {
return fn.apply(fn, args);
}, ms);
};
这是您将如何使用它:
var startPlayback = function(track) {
Player.play(track);
};
startPlayback(someTrack);
现在,假设您希望延迟开始播放,例如在此代码段运行5秒钟后。好吧,这很容易,delay
而且它是闭包的:
startPlayback.delay(5000, someTrack);
// Keep going, do other things
delay
使用5000
ms 调用时,第一个代码段将运行,并将传入的参数存储在其闭包中。然后5秒钟后,当setTimeout
发生回调时,闭包仍将保留这些变量,因此它可以使用原始参数来调用原始函数。
这是一种装饰或功能装饰。
如果没有闭包,您将不得不以某种方式保持这些变量在函数外部的状态,从而在函数外部散乱地填充逻辑上属于其内部的代码。使用闭包可以大大提高代码的质量和可读性。
var pure = function pure(x){
return x
// only own environment is used
}
var foo = "bar"
var closure = function closure(){
return foo
// foo is a free variable from the outer environment
}
src:https://leanpub.com/javascriptallongesix/read#leanpub-auto-if-functions-without-free-variables-are-pure-are-closures-impure
闭包是一个函数,其作用域分配给(或用作)变量。因此,名称闭包:作用域和函数被封装起来,就像其他任何实体一样使用。
在具有一流功能的语言中实现词法作用域名称绑定的技术。
这意味着什么?让我们研究一些定义。
我将通过以下示例解释闭包和其他相关定义:
function startAt(x) {
return function (y) {
return x + y;
}
}
var closure1 = startAt(1);
var closure2 = startAt(5);
console.log(closure1(3)); // 4 (x == 1, y == 3)
console.log(closure2(3)); // 8 (x == 5, y == 3)
基本上,这意味着我们可以像使用其他实体一样使用函数。我们可以修改它们,将它们作为参数传递,从函数中返回它们或为变量分配它们。从技术上讲,他们是一等公民,因此得名:一等职能。
在上面的示例中,startAt
返回一个(匿名)函数,该函数被分配给closure1
和closure2
。因此,正如您所见,JavaScript像对待其他实体(一等公民)一样对待函数。
名称绑定是关于找出变量(标识符)所引用的数据。范围在这里非常重要,因为这将决定如何解析绑定。
在上面的示例中:
y
绑定到3
。startAt
的范围内,x
绑定到1
或5
(取决于闭包)。在匿名函数的作用域内,x
没有任何值绑定,因此需要在上层startAt
作用域内进行解析。
如Wikipedia所述,范围:
是绑定有效的计算机程序区域:该名称可用于引用实体。
有两种技术:
有关更多说明,请查看此问题,然后查看Wikipedia。
在上面的示例中,我们可以看到JavaScript在词法范围内,因为x
解析后,将startAt
根据源代码(在内部定义x的匿名函数startAt
)并在上层范围内搜索绑定,并且不是基于调用堆栈,而是函数的调用方式(范围)。
在我们的例子中,当我们调用startAt
,它会返回将被分配到一个(第一类)功能closure1
,并closure2
因此关闭被创建,因为传递的变量1
和5
将内保存startAt
的范围,将与返回的封闭匿名功能。当我们通过closure1
并closure2
使用相同的参数(3
)调用该匿名函数时,y
将立即找到的值(因为该参数是该函数的参数),但x
不受匿名函数范围的限制,因此解析继续(在词法上)上部函数范围(保存在闭包中)x
被发现绑定到其中一个1
或5
。现在我们知道了求和的所有内容,因此可以返回结果,然后进行打印。
现在,您应该了解闭包及其行为方式,这是JavaScript的基本组成部分。
哦,而且您还了解了什么用武之地:您使用函数(闭包)传递操作的每个参数,而不是使用一个具有多个参数的函数。
闭包是JavaScript中的一项功能,其中一个函数可以访问自己的作用域变量,访问外部函数变量以及访问全局变量。
即使在外部函数返回之后,闭包也可以访问其外部函数范围。这意味着闭包即使在函数完成后也可以记住并访问其外部函数的变量和参数。
内部函数可以访问在其自身作用域,外部函数的作用域和全局作用域中定义的变量。并且外部函数可以访问在其自身范围和全局范围中定义的变量。
关闭示例:
var globalValue = 5;
function functOuter() {
var outerFunctionValue = 10;
//Inner function has access to the outer function value
//and the global variables
function functInner() {
var innerFunctionValue = 5;
alert(globalValue + outerFunctionValue + innerFunctionValue);
}
functInner();
}
functOuter();
输出为20,这是其内部函数自变量,外部函数变量和全局变量值的总和。
•闭包是子程序,也是定义它的引用环境。
–如果可以从程序中的任意位置调用子程序,则需要引用环境
–不允许嵌套子程序的静态作用域语言不需要闭包
–仅当子程序可以访问嵌套作用域中的变量并且可以从任何地方调用它时,才需要闭包
–为了支持闭包,实现可能需要无限制地提供某些变量的范围(因为子程序可以访问通常不再存在的非局部变量)
例
function makeAdder(x) {
return function(y) {return x + y;}
}
var add10 = makeAdder(10);
var add5 = makeAdder(5);
document.write(″add 10 to 20: ″ + add10(20) +
″<br />″);
document.write(″add 5 to 20: ″ + add5(20) +
″<br />″);
这是另一个真实的示例,并使用游戏中流行的脚本语言-Lua。我需要稍微更改库函数的工作方式,以避免标准输入不可用的问题。
local old_dofile = dofile
function dofile( filename )
if filename == nil then
error( 'Can not use default of stdin.' )
end
old_dofile( filename )
end
当此代码块完成作用域时,old_dofile的值会消失(因为它是本地的),但是该值已包含在闭包中,因此新的重新定义的dofile函数可以访问它,或者与函数一起存储的副本“升值”。
闭包每当我们在另一个函数中定义一个函数时,内部函数就可以访问在外部函数中声明的变量。最好用示例来说明闭包。在清单2-18中,您可以看到内部函数可以从外部范围访问变量(variableInOuterFunction)。外部函数中的变量已由内部函数关闭(或绑定到内部函数中)。因此,术语“封闭”。这个概念本身很简单并且相当直观。
Listing 2-18:
function outerFunction(arg) {
var variableInOuterFunction = arg;
function bar() {
console.log(variableInOuterFunction); // Access a variable from the outer scope
}
// Call the local function to demonstrate that it has access to arg
bar();
}
outerFunction('hello closure!'); // logs hello closure!
来源:http : //index-of.es/Varios/Basarat%20Ali%20Syed%20(auth.-Beginning%20Node.js-Apress%20(2014).pdf
请看下面的代码以更深入地了解闭包:
for(var i=0; i< 5; i++){
setTimeout(function(){
console.log(i);
}, 1000);
}
这里将输出什么?0,1,2,3,4
不是5,5,5,5,5
因为关闭
那么它将如何解决?答案如下:
for(var i=0; i< 5; i++){
(function(j){ //using IIFE
setTimeout(function(){
console.log(j);
},1000);
})(i);
}
让我简单地解释一下,当一个函数什么都没有创建之前,直到它在调用5的第一个代码中调用for循环但没有立即调用时才发生,所以当它调用时,即1秒钟之后,并且这是异步的,因此在此for循环结束之前并存储值5在var i中,最后执行setTimeout
功能五次并打印5,5,5,5,5
在这里,它如何使用IIFE(即立即调用函数表达式)求解
(function(j){ //i is passed here
setTimeout(function(){
console.log(j);
},1000);
})(i); //look here it called immediate that is store i=0 for 1st loop, i=1 for 2nd loop, and so on and print 0,1,2,3,4
有关更多信息,请了解执行上下文以了解闭包。
还有一个使用let(ES6功能)解决此问题的解决方案,但在上述功能的作用下可以工作
for(let i=0; i< 5; i++){
setTimeout(function(){
console.log(i);
},1000);
}
Output: 0,1,2,3,4
=>更多说明:
在内存中,当for循环执行图片时,如下所示:
循环1)
setTimeout(function(){
console.log(i);
},1000);
循环2)
setTimeout(function(){
console.log(i);
},1000);
循环3)
setTimeout(function(){
console.log(i);
},1000);
循环4)
setTimeout(function(){
console.log(i);
},1000);
循环5)
setTimeout(function(){
console.log(i);
},1000);
在这里,我不执行,然后在完成循环后,变量i在内存中存储了值5,但是它的范围在子函数中始终可见,因此当函数执行由内setTimeout
而外执行五次时,会打印5,5,5,5,5
因此,如上文所述,可以使用IIFE解决此问题。
Currying:它允许您仅传递函数的子集来部分评估函数。考虑一下:
function multiply (x, y) {
return x * y;
}
const double = multiply.bind(null, 2);
const eight = double(4);
eight == 8;
闭包:闭包无非就是访问函数范围之外的变量。重要的是要记住,函数内部或嵌套函数不是闭包。当需要访问函数范围之外的变量时,总是使用闭包。
function apple(x){
function google(y,z) {
console.log(x*y);
}
google(7,2);
}
apple(3);
// the answer here will be 21
关闭非常容易。我们可以这样考虑:闭包=函数+它的词法环境
考虑以下功能:
function init() {
var name = “Mozilla”;
}
在上述情况下将关闭什么?在其词法环境(即名称)中使用init()函数和变量。 闭包 = init()+名称
考虑另一个功能:
function init() {
var name = “Mozilla”;
function displayName(){
alert(name);
}
displayName();
}
这里的关闭会是什么?内部函数可以访问外部函数的变量。displayName()可以访问在父函数init()中声明的变量名。但是,如果存在,则将使用displayName()中相同的局部变量。
闭包1:初始化函数+(名称变量+ displayName()函数)->词法范围
闭包2: displayName函数+(name变量)->词法作用域
编程中的状态只是意味着记住事情。
例
var a = 0;
a = a + 1; // => 1
a = a + 1; // => 2
a = a + 1; // => 3
在上述情况下,状态存储在变量“ a”中。接下来,我们将“ a”加1数次。我们只能这样做是因为我们能够“记住”价值。状态持有者“ a”将该值保存在内存中。
通常,在编程语言中,您想跟踪事物,记住信息并在以后访问。
这一点,在其他语言中,通常通过使用类来完成。与变量一样,类也跟踪其状态。而该类的实例又在其中具有状态。状态仅表示您可以在以后存储和检索的信息。
例
class Bread {
constructor (weight) {
this.weight = weight;
}
render () {
return `My weight is ${this.weight}!`;
}
}
我们如何从“渲染”方法中访问“权重”?好吧,感谢国家。面包类的每个实例都可以通过从“状态”中读取重量来呈现自己的重量,“状态”是我们可以在其中存储该信息的位置。
现在,JavaScript是一种非常独特的语言,从历史上讲它没有类(它现在有,但是在幕后只有函数和变量),因此Closures为JavaScript提供了一种记住事情并在以后访问它们的方法。
例
var n = 0;
var count = function () {
n = n + 1;
return n;
};
count(); // # 1
count(); // # 2
count(); // # 3
上面的示例通过变量实现了“保持状态”的目标。这很棒!但是,这样做的缺点是现在公开了变量(“状态”持有人)。我们可以做得更好。我们可以使用闭包。
例
var countGenerator = function () {
var n = 0;
var count = function () {
n = n + 1;
return n;
};
return count;
};
var count = countGenerator();
count(); // # 1
count(); // # 2
count(); // # 3
现在我们的“计数”功能可以计数了。它之所以只能这样做是因为它可以“保持”状态。在这种情况下,状态为变量“ n”。现在已关闭此变量。时空封闭。因为您将无法及时恢复,更改,为其分配值或直接与它进行交互,所以会及时发布。在空间中,因为它在地理上嵌套在“ countGenerator”函数中。
为什么这太奇妙了?因为无需使用任何其他复杂的复杂工具(例如类,方法,实例等),我们就可以1.隐藏2.远距离控制
我们隐藏状态,即变量“ n”,这使其成为私有变量!我们还创建了一个可以以预定义方式控制此变量的API。特别是,我们可以像这样调用API:“ count()”,然后将“距离”加1到“ n”。除非通过API,否则任何人都不能以任何形式访问“ n”。
封闭是为什么这样做的很大一部分。