Javascript 闭包函数参数?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/18655275/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-27 12:46:36  来源:igfitidea点击:

Javascript closures function parameters?

javascriptclosures

提问by user2067358

Code belongs to javascriptissexy.com My question is why invoking mjName ("Hymanson") returns "This celebrity is Michael Hymanson"? Is it that second parameter given in ANY outer function always, says to js = inner function parameter? Could someone explain the whole concept in great detail?

代码属于 javascriptissexy.com 我的问题是为什么调用 mjName ("Hymanson") 会返回“这个名人是迈克尔Hyman逊”?是否总是在任何外部函数中给出的第二个参数表示 js = 内部函数参数?有人可以详细解释整个概念吗?

function celebrityName (firstName) {
    var nameIntro = "This celebrity is ";
// this inner function has access to the outer function's variables, including the parameter
    function lastName (theLastName) {
        return nameIntro + firstName + " " + theLastName;
    }
    return lastName;
}

var mjName = celebrityName ("Michael");
    // At this juncture, the celebrityName outer function has returned.

// The closure (lastName) is called here after the outer function has returned above
// Yet, the closure still has access to the outer function's variables and parameter
mjName ("Hymanson"); // This celebrity is Michael Hymanson

回答by kurtcorbett

The OP asked for an explanation of the Whole concept in detail. The attempt here is to describe the core elements that are necessary for closures to occur.

OP 要求详细解释整个概念。这里的尝试是描述发生闭包所必需的核心元素。

I think that part of the confusion with examples like the one from javascriptissexy is that the names of these functions do not clearly represent what they are supposed to do, especially to someone who is new to javascript or new coding in general.

我认为与来自 javascriptissexy 的示例混淆的部分原因是这些函数的名称没有清楚地表示它们应该做什么,特别是对于 javascript 新手或一般新编码的人。

Lets start by talking about scope:

让我们从讨论范围开始:

In Javascript, every function creates its own local scope or memory space. This is called Lexical Scoping. This memory space stores all of the variables from the functions's parameters as well as the declared variables and expressions within the function body (inside the curly braces).

在 Javascript 中,每个函数都会创建自己的局部作用域或内存空间。这称为词法范围。该内存空间存储来自函数参数的所有变量以及函数体内(花括号内)声明的变量和表达式。

As seen in the example from javascriptissexy, we can nest functions. Since each function creates its own local scope, we need to understand how these scopes relate and interact with each other. There are three different types of relationships that scopes can have.

从 javascriptissexy 的示例中可以看出,我们可以嵌套函数。由于每个函数都创建了自己的局部作用域,我们需要了解这些作用域如何相互关联和交互。作用域可以具有三种不同类型的关系。

I would encourage you to test all of these code snippets inside your browser dev console:

我鼓励您在浏览器开发控制台中测试所有这些代码片段:

Child scopes have access to their parent's (and grandparent's, great grandparent's etc...) scope variables

子作用域可以访问其父(以及祖父母、曾祖父母等)的作用域变量

    function parent() {

        var parentAsset = 'The Minivan'

        function child() {

            //child has access to parent asset

            console.log(parentAsset);

        }

        // call child function

        child();
    }

    parent();  // 'The Minivan'

Parent scopes DO NOT have access to their children's scope variables

父作用域无权访问其子作用域变量

    function parent() {

        function child() {

            var childAsset = 'Mp3 Player'

        }

        //parent can't get childAsset

        console.log(childAsset);

    }

    parent();   // ReferenceError childAsset not defined

Sibling scopes DO NOT have access to each other's scope variables

兄弟作用域不能访问彼此的作用域变量

    function childOne() {

        var childOneAsset = 'Iphone'

    }

    function childTwo() {

        console.log(childOneAsset);

    }

    childTwo();  // ReferenceError childOneAsset not defined

Okay, so back to the function mentioned by OP. Let's try to remake this function with better names. I am adding one more variable to this first example function to show a point.

好的,那么回到 OP 提到的功能。让我们尝试用更好的名称重新制作这个函数。我在第一个示例函数中再添加一个变量以显示一个点。

Here are 4 things that happen when you call getFirstName('Michael')in the example below:

以下是getFirstName('Michael')在以下示例中调用时会发生的 4 件事:

  1. Within this function, the variable firstNameis set to 'Michael'
  2. var nameIntrois set it to the value "This celebrity is "
  3. var unusedStringis set to the value "This string will be garbage collected"
  4. The function introduceCelebrityis declared
  5. The function introduceCelebrityis returned

    function getFirstName (firstName) {
    
        var nameIntro = "This celebrity is ";
        var unusedString = "This string will be garbage collected";
    
        function introduceCelebrity (lastName) {
            return nameIntro + firstName + " " + lastName;
        }
    
        return introduceCelebrity;
    }
    
    var mjName = getFirstName('Michael');
    
  1. 在此函数中,变量firstName设置为“Michael”
  2. varnameIntro将其设置为值“这个名人是”
  3. varunusedString设置为值“此字符串将被垃圾收集”
  4. 函数introduceCelebrity被声明
  5. 函数introduceCelebrity返回

    function getFirstName (firstName) {
    
        var nameIntro = "This celebrity is ";
        var unusedString = "This string will be garbage collected";
    
        function introduceCelebrity (lastName) {
            return nameIntro + firstName + " " + lastName;
        }
    
        return introduceCelebrity;
    }
    
    var mjName = getFirstName('Michael');
    

You probably already knew that.

你可能已经知道了。

Here are some interesting things to note:

这里有一些有趣的事情需要注意:

  • The getFirstNamefunction does nothing with firstNameor nameIntroother than set their values. So there is no magic there.
  • The child function introduceCelebrityreferences those two variables. As mentioned before, it can do that because children scopes can access parent scope variables. This is the first important step to a closure.
  • The introduceCelebrityfunction is then returned (but not executed), presumably so we can call it at a later time. This is the second step to a closure.
  • Because introduceCelebrityreferences parent scope variables, and we return the whole function, the javascript runtime maintains a pointer to those variables, even after the getFirstNamefunction returns.
  • Because that pointer exists, the garbage collector leaves those variables alone. If those pointers didn't exist, the garbage collector would come through and clean those memory addresses and those values would be inaccessible.
  • The unusedStringvariable is not referenced in the child function, therefore it is garbage collected and is no longer available.
  • getFirstName函数不做任何处理firstNamenameIntro其他不是设置它们的值。所以那里没有魔法。
  • 子函数introduceCelebrity引用这两个变量。如前所述,它可以做到这一点,因为子作用域可以访问父作用域变量。这是关闭的第一个重要步骤。
  • introduceCelebrity然后返回该函数(但不执行),大概是为了我们可以稍后调用它。这是关闭的第二步。
  • 因为introduceCelebrity引用父作用域变量,并且我们返回整个函数,即使在getFirstName函数返回之后,javascript 运行时也会维护一个指向这些变量的指针。
  • 由于该指针存在,垃圾收集器将单独保留这些变量。如果这些指针不存在,垃圾收集器将通过并清理这些内存地址,而这些值将无法访问。
  • unusedString变量在子函数中没有被引用,因此它被垃圾收集并且不再可用。

So let's look at the code again:

那么让我们再看一遍代码:

function getFirstName (firstName) {

    var nameIntro = "This celebrity is ";

    function introduceCelebrity (theLastName) {
        return nameIntro + firstName + " " + theLastName;
    }
    return introduceCelebrity;
}

var mjName = getFirstName('Michael');

When this code executes, we are basically doing this:

当这段代码执行时,我们基本上是这样做的:

var mjName = function(theLastName) {
    return nameIntro + firstName + " " + theLastName;
}

What is special about this? Where is the closure?

这有什么特别之处?闭包在哪里?

Because our getFirstNamefunction has been executed, we might think that the whole thing has gone away along with its local variables or assets. THIS IS INCORRECT.

因为我们的getFirstName函数已经被执行,我们可能会认为整个事情连同它的局部变量或资产一起消失了。 这是不正确的

We created a closure by referencing parent scope variables inside a child function and returning the child function. So really, the new scope of the code right above actually looks more like this:

我们通过在子函数中引用父作用域变量并返回子函数来创建一个闭包。所以实际上,上面代码的新范围实际上看起来更像这样:

var nameIntro = "This celebrity is ";

var firstName = "Michael"

var mjName = function(theLastName) {
    return nameIntro + firstName + " " + theLastName;
}

See how nameIntroand firstNameare now available to us? THAT IS BECAUSE WE CREATED CLOSURE.

看看我们如何nameIntro以及firstName现在可以使用吗? 那是因为我们创建了闭包

So now we call mjName:

所以现在我们调用mjName

mjName('Hymanson');  // 'This celebrity is Michael Hymanson'

And we get the result expected.

我们得到了预期的结果。

Wait, One last thing!

等等,最后一件事!

To really drive the point home, lets compare our example to a slightly modified one.

为了真正说明这一点,让我们将我们的示例与稍作修改的示例进行比较。

Notice the original function is nested. Closures only happen with nested functions.

注意原始函数是嵌套的。 闭包只发生在嵌套函数中

function getFirstName (firstName) {

    var nameIntro = "This celebrity is ";

    function introduceCelebrity (theLastName) {
        return nameIntro + firstName + " " + theLastName;
    }
    return introduceCelebrity;
}

var mjName = getFirstName('Michael');

Let's try removing that nesting:

让我们尝试删除该嵌套:

function getFirstName (firstName) { 
    var nameIntro = "This celebrity is ";
}

function introduceCelebrity (theLastName) {
    return nameIntro + firstName + " " + theLastName;
}

var mjName = getFirstName('Michael');
introduceCelebrity('Hymanson');  

// ReferenceError: nameIntro is not defined

Would this work?

这行得通吗?

No, it wouldn't. Because sibling scopes can't access each other's variables.

不,不会。因为同级作用域不能访问彼此的变量。

How could we get this to work without a closure then?

那么我们如何在没有关闭的情况下让它工作呢?

  1. getFirstNamemust return an object or array with our variables
  2. we must set getFirstName('Michael')to a global variable mjName
  3. Call introduceCelebrity('Hymanon'), passing in the values we mjName

    function getFirstName (firstName) { 
    
        var nameIntro = "This celebrity is ";
    
        return {
            firstName: firstName,
            nameIntro: nameIntro
        }
    }
    
    var mjName = getFirstName('Michael');  // returns our object
    
    function introduceCelebrity (theLastName, firstName, nameIntro) {
        return nameIntro + firstName + " " + theLastName;
    }
    
    introduceCelebrity('Hymanson', mjName.firstName, mjName.nameIntro);  
    
    // 'This celebrity is Michael Hymanson'
    
  1. getFirstName必须用我们的变量返回一个对象或数组
  2. 我们必须设置getFirstName('Michael')为一个全局变量mjName
  3. 调用introduceCelebrity('Hymanon'),传入我们的值mjName

    function getFirstName (firstName) { 
    
        var nameIntro = "This celebrity is ";
    
        return {
            firstName: firstName,
            nameIntro: nameIntro
        }
    }
    
    var mjName = getFirstName('Michael');  // returns our object
    
    function introduceCelebrity (theLastName, firstName, nameIntro) {
        return nameIntro + firstName + " " + theLastName;
    }
    
    introduceCelebrity('Hymanson', mjName.firstName, mjName.nameIntro);  
    
    // 'This celebrity is Michael Hymanson'
    

回答by user568109

The function is evaluated to be celebrityName ("Michael")("Hymanson");

该函数被评估为 celebrityName ("Michael")("Hymanson");

Steps :

脚步 :

  1. celebrityName ("Michael") returns function lastName(theLastName)
  2. ("Hymanson") is passed to function lastName
  3. function lastName(theLastName) prints the string when executed
  1. 名人名 ("迈克尔") 返回函数 lastName(theLastName)
  2. ("Hymanson") 传递给函数 lastName
  3. 函数 lastName(theLastName) 在执行时打印字符串

Arguments from left to right go from outer to inner called methods.

从左到右的参数从外部到内部调用方法。

回答by Sirko

With this call

有了这个电话

var mjName = celebrityName ("Michael");

you create a custom function, which has the firstNamevariable bound to "Michael". This function is returned to you by celebrityName().

您创建了一个自定义函数,该函数将firstName变量绑定到"Michael". 此函数由 返回给您celebrityName()

When you call that returned function again, you bind lastNameas well, which results in your output.

当您再次调用返回的函数时,您lastName也会绑定,这会导致您的输出。

If you want to bind another first name, you have to call celebrityName()again.

如果要绑定另一个名字,则必须celebrityName()再次调用。

var michaelName = celebrityName( "Michael" );
var davidName =   celebrityName( "David" );

michaelName( "Hymanson" ); // yields "This celebrity is Michael Hymanson"
davidName( "Duchovny" );  // yields "This celebrity is David Duchovny"