【问题标题】:Javascript closures function parameters?Javascript闭包函数参数?
【发布时间】:2013-09-06 10:12:15
【问题描述】:

代码属于 javascriptissexy.com 我的问题是为什么调用 mjName ("Jackson") 会返回“这个名人是迈克尔杰克逊”? 是否总是在任何外部函数中给出的第二个参数对 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 ("Jackson"); // This celebrity is Michael Jackson

【问题讨论】:

  • 每个 JavaScript 函数都可以访问在相同或更高范围内定义的变量。 “是否总是在任何外部函数中给出第二个参数,对 js = 内部函数参数说?” 我不知道这应该是什么意思。

标签: javascript closures


【解决方案1】:

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

我认为与来自 javascriptissexy 的示例的混淆部分在于,这些函数的名称并不能清楚地代表它们应该做什么,尤其是对于不熟悉 javascript 或一般新编码的人来说。

让我们从范围开始:

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

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

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

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

    function parent() {

        var parentAsset = 'The Minivan'

        function child() {

            //child has access to parent asset

            console.log(parentAsset);

        }

        // call child function

        child();
    }

    parent();  // 'The Minivan'

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

    function parent() {

        function child() {

            var childAsset = 'Mp3 Player'

        }

        //parent can't get childAsset

        console.log(childAsset);

    }

    parent();   // ReferenceError childAsset not defined

同级作用域不能访问彼此的作用域变量

    function childOne() {

        var childOneAsset = 'Iphone'

    }

    function childTwo() {

        console.log(childOneAsset);

    }

    childTwo();  // ReferenceError childOneAsset not defined

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

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

  1. 在此函数中,变量firstName 设置为 “迈克尔”
  2. var nameIntro 将其设置为值“此名人 是“
  3. var unusedString 设置为值“此字符串将被垃圾回收”
  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');
    

你可能已经知道了。

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

  • getFirstName 函数对firstNamenameIntro 除了设置它们的值之外没有任何作用。所以那里没有魔法。
  • 子函数introduceCelebrity 引用这两个变量。如前所述,它可以做到这一点,因为子作用域可以访问父作用域变量。这是关闭的第一个重要步骤。
  • 然后返回introduceCelebrity 函数(但不执行),大概是为了我们可以在以后调用它。这是关闭的第二步。
  • 因为introduceCelebrity 引用父范围变量,并且我们返回整个函数,所以即使在getFirstName 函数返回之后,javascript 运行时也会维护指向这些变量的指针。
  • 由于存在该指针,垃圾收集器将不理会这些变量。如果这些指针不存在,垃圾收集器就会通过并清理这些内存地址,而这些值将无法访问。
  • unusedString 变量在子函数中没有被引用,因此它被垃圾回收并且不再可用。

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

function getFirstName (firstName) {

    var nameIntro = "This celebrity is ";

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

var mjName = getFirstName('Michael');

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

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

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

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

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

var nameIntro = "This celebrity is ";

var firstName = "Michael"

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

了解我们现在如何使用nameIntrofirstName那是因为我们创造了闭包

所以现在我们调用mjName

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

我们得到了预期的结果。

等等,最后一件事!

为了真正深入人心,让我们将我们的示例与稍作修改的示例进行比较。

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

function getFirstName (firstName) {

    var nameIntro = "This celebrity is ";

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

var mjName = getFirstName('Michael');

让我们尝试移除那个嵌套:

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

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

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

// ReferenceError: nameIntro is not defined

这行得通吗?

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

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

  1. getFirstName 必须返回带有我们变量的对象或数组
  2. 我们必须将getFirstName('Michael')设置为一个全局变量mjName
  3. 调用introduceCelebrity('Jackon'),传入我们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('Jackson', mjName.firstName, mjName.nameIntro);  
    
    // 'This celebrity is Michael Jackson'
    

【讨论】:

  • 如果 var nameIntro = "这个名人是 ";是一个全局对象(外部函数范围之外),如 var nameIntro = ["This名人是"],我们在调用 mjName('Jackson') 之前将此数组更改为 ["Celebrity is "],为什么控制台仍然打印 这个名人是而不是“名人是”??对象是通过引用传递的,所以函数参数应该反映变化。你能解释一下吗???
【解决方案2】:

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

步骤:

  1. celebrityName ("Michael") 返回函数 lastName(theLastName)
  2. ("Jackson") 被传递给函数 lastName
  3. function lastName(theLastName) 在执行时打印字符串

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

【讨论】:

    【解决方案3】:

    有了这个电话

    var mjName = celebrityName ("Michael");
    

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

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

    如果你想绑定另一个名字,你必须再次调用celebrityName()

    var michaelName = celebrityName( "Michael" );
    var davidName =   celebrityName( "David" );
    
    michaelName( "Jackson" ); // yields "This celebrity is Michael Jackson"
    davidName( "Duchovny" );  // yields "This celebrity is David Duchovny"
    

    【讨论】:

    • 所以@Sirko:第一次输入的参数与外部功能有关。第二次输入的参数是内部函数参数。调用名人姓名()两次绑定外部和内部函数(按此顺序)?
    • @user2067358 调用celebrityName() 只是将firstName 绑定到返回的函数。第二个参数是绑定的,当这个返回的函数被调用时。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-07-06
    • 2011-06-12
    • 1970-01-01
    • 2018-05-23
    • 2017-01-11
    相关资源
    最近更新 更多