JavaScript闭包与匿名函数

2020/09/24 04:21 · javascript ·  · 0评论

我的一个朋友和我目前正在讨论JS中的闭包和不闭包。我们只想确保我们确实正确理解它。

让我们来看这个例子。我们有一个计数循环,并希望延迟在控制台上打印计数器变量。因此,我们使用setTimeout闭包捕获计数器变量的值,以确保其不会打印N倍于值N的值。

没有闭包或接近闭包的错误解决方案是:

for(var i = 0; i < 10; i++) {
    setTimeout(function() {
        console.log(i);
    }, 1000);
}

当然,它将i在循环后打印10倍的值,即10。

因此,他的尝试是:

for(var i = 0; i < 10; i++) {
    (function(){
        var i2 = i;
        setTimeout(function(){
            console.log(i2);
        }, 1000)
    })();
}

按预期打印0到9。

我告诉他,他并没有使用闭包捕获i,但他坚持认为自己是。通过将for循环体放在另一个循环中(将他的匿名函数传递给),再次打印10次10 证明了他没有使用闭包如果我将他的函数存储在a中在循环执行它,并打印10次10​​,则同样适用。所以我的观点是,他并没有真正捕捉到的值,因此他的版本不是闭包。setTimeoutsetTimeoutvari

我的尝试是:

for(var i = 0; i < 10; i++) {
    setTimeout((function(i2){
        return function() {
            console.log(i2);
        }
    })(i), 1000);
}

所以我捕获了ii2在闭包中命名),但是现在我返回了另一个函数并传递了它。就我而言,传递给setTimeout的函数实际上捕获了i

现在谁在使用闭包,谁没有?

请注意,这两个解决方案在控制台上都延迟输出0到9,因此它们可以解决原始问题,但是我们想了解这两个解决方案中的哪个使用闭包来完成此任务。

编者按:在JavaScript中所有的功能都关闭在这个解释但是,我们只对确定这些功能的子集感兴趣,而这些子集从理论上讲有趣的。此后,除非另有说明,否则对闭包一词的任何引用都将指代此功能子集。

闭包的简单说明:

  1. 发挥作用。我们称它为F。
  2. 列出F的所有变量。
  3. 变量可以有两种类型:

    1. 局部变量(绑定变量)
    2. 非局部变量(自由变量)
  4. 如果F没有自由变量,则它不能是闭包。
  5. 如果F具有任何自由变量(在F 父范围中定义),则:

    1. 必须有其中的F只有一个父范围一个自由变量绑定。
    2. 如果父作用域之外引用 F ,则它将成为自由变量的闭包
    3. 自由变量称为闭包F的高值。

现在让我们用它来找出谁使用闭包,谁不使用闭包(为便于说明,我将函数命名为):

情况1:您朋友的程序

for (var i = 0; i < 10; i++) {
    (function f() {
        var i2 = i;
        setTimeout(function g() {
            console.log(i2);
        }, 1000);
    })();
}

在以上程序中,有两个功能:fg让我们看看它们是否为闭包:

对于f

  1. 列出变量:

    1. i2局部变量。
    2. i是一个自由变量。
    3. setTimeout是一个自由变量。
    4. g局部变量。
    5. console是一个自由变量。
  2. 找到每个自由变量绑定到的父作用域:

    1. i绑定到了全球范围。
    2. setTimeout绑定到了全球范围。
    3. console绑定到了全球范围。
  3. 该功能在哪个范围内引用全球范围内

    1. 因此i没有关闭了通过f
    2. 因此setTimeout没有关闭了通过f
    3. 因此console没有关闭了通过f

因此,该功能f不是闭包。

对于g

  1. 列出变量:

    1. console是一个自由变量。
    2. i2是一个自由变量。
  2. 找到每个自由变量绑定到的父作用域:

    1. console绑定到了全球范围。
    2. i2绑定到的范围f
  3. 该功能在哪个范围内引用范围setTimeout

    1. 因此console没有关闭了通过g
    2. 因此i2封闭在通过g

因此,该功能g是用于自由变量的封闭i2(这是用于的upvalue g它的引用从内setTimeout

对您不利:您的朋友正在使用闭包。内部函数是一个闭包。

情况2:您的程序

for (var i = 0; i < 10; i++) {
    setTimeout((function f(i2) {
        return function g() {
            console.log(i2);
        };
    })(i), 1000);
}

在以上程序中,有两个功能:fg让我们看看它们是否为闭包:

对于f

  1. 列出变量:

    1. i2局部变量。
    2. g局部变量。
    3. console是一个自由变量。
  2. 找到每个自由变量绑定到的父作用域:

    1. console绑定到了全球范围。
  3. 该功能在哪个范围内引用全球范围内

    1. 因此console没有关闭了通过f

因此,该功能f不是闭包。

对于g

  1. 列出变量:

    1. console是一个自由变量。
    2. i2是一个自由变量。
  2. 找到每个自由变量绑定到的父作用域:

    1. console绑定到了全球范围。
    2. i2绑定到的范围f
  3. 该功能在哪个范围内引用范围setTimeout

    1. 因此console没有关闭了通过g
    2. 因此i2封闭在通过g

因此,该功能g是用于自由变量的封闭i2(这是用于的upvalue g它的引用从内setTimeout

对您有好处:您正在使用闭包。内部函数是一个闭包。

因此,您和您的朋友都在使用闭包。别吵了 我希望我清除了闭包的概念以及如何为你们两个人识别它们。

编辑:关于为什么所有函数都关闭的简单说明(点数@Peter):

首先让我们考虑以下程序(它是控件):

lexicalScope();

function lexicalScope() {
    var message = "This is the control. You should be able to see this message being alerted.";

    regularFunction();

    function regularFunction() {
        alert(eval("message"));
    }
}
  1. 我们知道,无论lexicalScoperegularFunction不封闭,从上面的定义
  2. 当我们执行程序时,我们希望 message收到警告,因为 regularFunction它不是闭包的(即它可以访问其父作用域中的所有变量,包括message)。
  3. 当我们执行程序时,我们观察message确实警告。

接下来,让我们考虑以下程序(它是替代程序):

var closureFunction = lexicalScope();

closureFunction();

function lexicalScope() {
    var message = "This is the alternative. If you see this message being alerted then in means that every function in JavaScript is a closure.";

    return function closureFunction() {
        alert(eval("message"));
    };
}
  1. 我们知道,这只是上述定义closureFunction的闭包
  2. 当我们执行程序时,我们希望 message不会因为 closureFunction闭包而收到警告(即,创建函数时,它只能访问其所有非局部变量请参阅此答案)-不包括)。message
  3. 当我们执行程序时,我们观察message实际上正在被警告。

我们从中得出什么呢?

  1. JavaScript解释器对待闭包的方式与对待其他函数的方式没有区别。
  2. 每个功能都带有其作用域链闭包没有单独的引用环境。
  3. 闭包就像其他函数一样。它们所属的范围之外的范围引用它们时,我们只称它们为闭包,因为这是一个有趣的情况。

根据closure定义:

“闭包”是一个表达式(通常是一个函数),可以将自由变量绑定这些变量环境(“关闭”表达式)结合在一起

您正在使用closure是否定义了一个函数,该函数使用在函数外部定义的变量。(我们将该变量称为自由变量)。

它们都使用
closure(即使在第一个示例中也是如此)。

一言以蔽之的Javascript闭包允许函数访问的变量在词法父函数声明

让我们看一个更详细的解释。要了解闭包,重要的是要了解JavaScript如何作用域变量。

范围

在JavaScript中,作用域是用函数定义的。每个函数定义一个新的范围。

考虑以下示例;

function f()
{//begin of scope f
  var foo='hello'; //foo is declared in scope f
  for(var i=0;i<2;i++){//i is declared in scope f
     //the for loop is not a function, therefore we are still in scope f
     var bar = 'Am I accessible?';//bar is declared in scope f
     console.log(foo);
  }
  console.log(i);
  console.log(bar);
}//end of scope f

调用f打印

hello
hello
2
Am I Accessible?

现在让我们考虑g在另一个函数中定义一个函数的情况f

function f()
{//begin of scope f
  function g()
  {//being of scope g
    /*...*/
  }//end of scope g
  /*...*/
}//end of scope f

我们将调用f词汇父g如前所述,我们现在有2个作用域;范围f和范围g

但是一个作用域在另一个作用域“内”,那么子功能的作用域是父功能作用域的一部分吗?在父函数范围内声明的变量会发生什么;我可以从子功能的范围访问它们吗?这正是闭包介入的地方。

关闭

在JavaScript中,该函数g不仅可以访问在scope中声明的任何变量,g而且还可以访问在父函数的scope中声明的任何变量f

考虑以下;

function f()//lexical parent function
{//begin of scope f
  var foo='hello'; //foo declared in scope f
  function g()
  {//being of scope g
    var bar='bla'; //bar declared in scope g
    console.log(foo);
  }//end of scope g
  g();
  console.log(bar);
}//end of scope f

调用f打印

hello
undefined

让我们看看这条线console.log(foo);至此,我们进入了作用域g,我们尝试访问foo在作用域中声明的变量f但是如前所述,我们可以访问在词法父函数中声明的任何变量。g是。的词汇父f因此hello被打印。

现在让我们看一下这条线
console.log(bar);至此,我们进入了作用域f,我们尝试访问bar在作用域中声明的变量gbar未在当前作用域中声明,并且该函数g不是的父项f,因此bar未定义

实际上,我们还可以访问在词法“祖父母”函数范围内声明的变量。因此,如果在函数中h定义了一个函数g

function f()
{//begin of scope f
  function g()
  {//being of scope g
    function h()
    {//being of scope h
      /*...*/
    }//end of scope h
    /*...*/
  }//end of scope g
  /*...*/
}//end of scope f

然后h将能够访问所有的功能范围内声明的变量hgf这是通过闭包完成的在JavaScript中,闭包允许我们访问在词汇父函数,词汇大父函数,词汇大祖父函数等中声明的任何变量。这可以看作是作用域链 scope of current function -> scope of lexical parent function -> scope of lexical grand parent function -> ... 直到没有词法父级的最后一个父级函数。

窗口对象

实际上,链条不会在最后一个父函数处停止。还有一个特殊的范围;全球范围内未在函数中声明的每个变量都被视为在全局范围内声明。全局范围有两个专长;

  • 全局范围内声明的每个变量都可以在任何地方访问
  • 在全局范围内声明的变量对应于window对象的属性

因此,有两种foo在全局范围内声明变量的方法通过不在函数中声明它或通过设置foo窗口对象的属性来实现。

两种尝试都使用闭包

现在,您已经阅读了更详细的说明,现在很明显两个解决方案都使用了闭包。但是可以肯定的是,让我们做个证明。

让我们创建一种新的编程语言;JavaScript不公开。顾名思义,JavaScript-No-Closure与JavaScript相同,只是不支持Closures。

换一种说法;

var foo = 'hello';
function f(){console.log(foo)};
f();
//JavaScript-No-Closure prints undefined
//JavaSript prints hello

好吧,让我们看看第一个使用JavaScript-No-Closure的解决方案会发生什么;

for(var i = 0; i < 10; i++) {
  (function(){
    var i2 = i;
    setTimeout(function(){
        console.log(i2); //i2 is undefined in JavaScript-No-Closure 
    }, 1000)
  })();
}

因此,这将undefined在JavaScript不关闭中打印10次​​。

因此,第一个解决方案使用闭包。

让我们看第二种解决方案;

for(var i = 0; i < 10; i++) {
  setTimeout((function(i2){
    return function() {
        console.log(i2); //i2 is undefined in JavaScript-No-Closure
    }
  })(i), 1000);
}

因此,这将undefined在JavaScript不关闭中打印10次​​。

两种解决方案都使用闭包。

编辑:假定这3个代码段未在全局范围内定义。否则,变量fooi将被绑定到window对象,因此可以window在JavaScript和JavaScript-No-Closure中通过对象进行访问

我从来不满意任何人对此进行解释的方式。

理解闭包的关键是了解没有闭包的JS的样子。

没有闭包,这将引发错误

function outerFunc(){
    var outerVar = 'an outerFunc var';
    return function(){
        alert(outerVar);
    }
}

outerFunc()(); //returns inner function and fires it

一旦externalFunc返回了一个假想的禁用禁用的JavaScript版本,对外部变量的引用将被垃圾回收并且不留任何内容供内部函数引用。

闭包本质上是特殊的规则,当内部函数引用外部函数的变量时,闭包可以使这些var存在。使用闭包,即使在完成外部函数之后也将保留引用的变量,如果可以帮助您记住要点,则可以“关闭”。

即使使用闭包,没有内部函数引用其局部变量的函数中局部变量的生命周期也与无闭合版本中的相同。该功能完成后,本地人将收集垃圾。

一旦在内部函数中对外部var进行了引用,那么就好比对于那些引用的var来说,门禁将被垃圾回收。

查看闭包的一种可能更准确的方法是,内部函数基本上将内部范围用作其自身的范围基础。

但是引用的上下文实际上是持久的,而不是快照。重复触发返回的内部函数,该内部函数将不断递增并记录外部函数的本地var,将继续提醒更高的值。

function outerFunc(){
    var incrementMe = 0;
    return function(){ incrementMe++; console.log(incrementMe); }
}
var inc = outerFunc();
inc(); //logs 1
inc(); //logs 2

你们都在使用闭包。

在这里使用Wikipedia定义

在计算机科学中,闭包(也称为词法闭包或函数闭包)是一个函数或对该函数的引用以及一个引用环境-一个表,该表存储对该函数的每个非局部变量(也称为自由变量)的引用。与普通函数指针不同,闭包允许函数访问那些非局部变量,即使在其直接词法范围之外调用该函数也是如此。

您朋友的尝试i通过获取值并将其副本存储到local中,显然使用了非local 变量i2

您自己的尝试i(在调用站点的范围内)传递给匿名函数作为参数。到目前为止,这不是一个闭包,但是该函数返回另一个引用了same的函数i2由于内部匿名函数内部i2不是本地函数,因此将创建一个闭包。

您和您的朋友都使用闭包:

闭包是一种特殊的对象,它结合了两件事:一个函数以及创建该函数的环境。环境由创建闭包时在范围内的任何局部变量组成。

MDN:https//developer.mozilla.org/zh-CN/docs/JavaScript/Guide/Closures

在您朋友的代码函数function(){ console.log(i2); }内部定义的闭包匿名函数function(){ var i2 = i; ... ,可以读写局部变量i2

在您的代码函数中,在函数function(){ console.log(i2); }闭包内定义function(i2){ return ...并且可以读/写本地有价值的东西i2(在这种情况下声明为参数)。

在这两种情况下,函数都function(){ console.log(i2); }传递给setTimeout

另一个等效项(但内存利用率较低)是:

function fGenerator(i2){
    return function(){
        console.log(i2);
    }
}
for(var i = 0; i < 10; i++) {
    setTimeout(fGenerator(i), 1000);
}

让我们看一下两种方式:

(function(){
    var i2 = i;
    setTimeout(function(){
        console.log(i2);
    }, 1000)
})();

声明并立即执行setTimeout()在其自身上下文中运行的匿名函数的当前值i是通过将副本复制为i2first 来保留的它之所以有效是因为立即执行。

setTimeout((function(i2){
    return function() {
        console.log(i2);
    }
})(i), 1000);

声明内部函数的执行上下文,从而将的当前值i保存到其中i2这种方法还使用立即执行来保留值。

重要

应该提到的是,两种方法之间的运行语义是不同的。您的内部函数会传递给setTimeout()而他的内部函数会调用setTimeout()自己。

将两个代码包装在另一个代码中setTimeout()并不能证明只有第二种方法使用了闭包,开始的东西并不相同。

结论

两种方法都使用闭包,所以这取决于个人喜好。第二种方法更容易“移动”或概括。

关闭

闭包不是函数,也不是表达式。必须将其视为函数范围之外和函数内部使用的变量的“快照”。在语法上,一个人应该说:'采取变量的封闭'。

同样,换句话说:闭包是函数所依赖的变量的相关上下文的副本。

再一次(naïf):闭包可以访问未作为参数传递的变量。

请记住,这些功能概念在很大程度上取决于您使用的编程语言/环境。在JavaScript中,闭包取决于词法作用域(在大多数C语言中都是如此)。

因此,返回函数主要是返回匿名/未命名函数。当函数访问变量(未作为参数传递)且在其(词法)范围内时,将采取闭包的方式。

因此,关于您的示例:

// 1
for(var i = 0; i < 10; i++) {
    setTimeout(function() {
        console.log(i); // closure, only when loop finishes within 1000 ms,
    }, 1000);           // i = 10 for all functions
}
// 2
for(var i = 0; i < 10; i++) {
    (function(){
        var i2 = i; // closure of i (lexical scope: for-loop)
        setTimeout(function(){
            console.log(i2); // closure of i2 (lexical scope:outer function)
        }, 1000)
    })();
}
// 3
for(var i = 0; i < 10; i++) {
    setTimeout((function(i2){
        return function() {
            console.log(i2); // closure of i2 (outer scope)

        }
    })(i), 1000); // param access i (no closure)
}

所有都使用闭包。不要将执行点与闭包混淆。如果在错误的时间获取了闭包的“快照”,则这些值可能是意外的,但肯定会进行闭包!

我前一段时间写这是为了提醒自己什么是闭包以及它在JS中的工作方式。

闭包是一个函数,在调用该函数时,它使用声明的范围而不是调用的范围。在javaScript中,所有函数的行为都像这样。只要存在仍指向它们的函数,作用域中的变量值就会保留。规则的例外是“ this”,它是指函数被调用时位于其内部的对象。

var z = 1;
function x(){
    var z = 2; 
    y(function(){
      alert(z);
    });
}
function y(f){
    var z = 3;
    f();
}
x(); //alerts '2' 

仔细检查后,看起来你们俩都在使用闭包。

在您的朋友的情况下,i可在匿名函数1内部i2进行访问,在存在的匿名函数2中进行访问console.log

在您的情况下,您正在访问i2内部console.log存在的匿名函数在chrome开发人员工具的“作用域变量”下方和debugger;之前添加一条语句console.log,它将告诉变量该变量在什么作用域下。

考虑以下。这会创建并重新创建一个f在上关闭i但不同的函数!:

i=100;

f=function(i){return function(){return ++i}}(0);
alert([f,f(),f(),f(),f(),f(),f(),f(),f(),f(),f()].join('\n\n'));

f=function(i){return new Function('return ++i')}(0);        /*  function declarations ~= expressions! */
alert([f,f(),f(),f(),f(),f(),f(),f(),f(),f(),f()].join('\n\n'));

而以下代码关闭“ a”函数“自身”

(自己!此后的代码段使用单个引用对象
f

for(var i = 0; i < 10; i++) {
    setTimeout( new Function('console.log('+i+')'),  1000 );
}

或更明确地说:

for(var i = 0; i < 10; i++) {
    console.log(    f = new Function( 'console.log('+i+')' )    );
    setTimeout( f,  1000 );
}

注意 的最后定义ffunction(){ console.log(9) } 在之前 0

警告!闭包概念可能会强制干扰基本编程的本质:

for(var i = 0; i < 10; i++) {     setTimeout( 'console.log('+i+')',  1000 );      }

外部参考:
JavaScript闭包如何工作?
Javascript闭包说明
(JS)闭是否需要函数内部的函数
如何理解JavaScript中的闭包?
Javascript局部和全局变量混淆

我想分享我的例子和关于闭包的解释。我做了一个python示例,并用两个数字演示了堆栈状态。

def maker(a, b, n):
    margin_top = 2
    padding = 4
    def message(msg):
        print('\n * margin_top, a * n, 
            ' ‘ * padding, msg, '  * padding, b * n)
    return message

f = maker('*', '#', 5)
g = maker('', '♥’, 3)

f('hello')
g(‘good bye!')

此代码的输出如下:

*****      hello      #####

      good bye!    ♥♥♥

这是两个图,显示了堆栈和连接到函数对象的闭包。

从制造商返回功能时

稍后调用该函数时

通过参数或非局部变量调用该函数时,代码需要局部变量绑定,例如margin_top,padding以及a,b,n。为了确保功能代码正常工作,应该访问很早就消失的maker函数的堆栈框架,该框架在我们可以与函数消息对象一起找到的闭包中进行了备份。

本文地址:http://javascript.askforanswer.com/javascriptbibaoyuniminghanshu.html
文章标签: ,   ,  
版权声明:本文为原创文章,版权归 javascript 所有,欢迎分享本文,转载请保留出处!

文件下载

老薛主机终身7折优惠码boke112

上一篇:
下一篇:

评论已关闭!