【问题标题】:scope calling functions inside functions作用域调用函数内部的函数
【发布时间】:2013-12-02 21:24:17
【问题描述】:

希望有人能抽出时间来解释一下函数和作用域中的函数。 我试图了解更多关于函数和变量范围的知识,并找到了一个很好的教程,但这部分我只是不明白。

任务:

创建一个类似这样工作的函数 sum:sum(a)(b) = a+b 并接受任意数量的括号。例子:

sum(1)(2) == 3
sum(5)(-1)(2) == 6

解决办法:

function sum(a) {

    var sum = a;

    function f(b){
        sum += b;
        return f;
    }

    f.toString = function() { return sum };

    return f;         //line 12
}

alert( sum(1)(2) );   // 3e

解释:

要使sum(1) 可以作为sum(1)(2) 调用,它必须返回一个函数。 可以使用valueOf 调用该函数或将其转换为数字。 解决方案真的不言自明:

我的解释:

function f(b) 中的 f 返回到范围,即第 02 - 12 行。 f.toString 中的 f 是当前从 function(b) 返回的 f 下一个return f返回到函数sum(a)之外的作用域。

问题:

我无法弄清楚,我需要在哪里考虑不同的想法,因为就像我上面描述的那样,该函数不会再次被调用,那么代码的哪一部分使“几个括号”成为可能?

此外,我是否正确假设了 fs 的返回位置?如果有人能给出一些解释,那就太好了。

【问题讨论】:

  • 使用一个参数调用sum 会返回一个可以使用下一个参数调用的函数。这称为“currying”;使用一个参数调用函数,直到所有参数都被计算,然后返回结果。
  • 下一个return f返回到函数sum(a)之外的作用域”是什么意思?
  • @Bergi 感谢您为我解惑。咖喱是完全不同的野兽。一个奇妙的问题,感谢您提出这个问题,它将创建大量包含多汁信息的答案

标签: javascript function scope


【解决方案1】:

函数sum返回一个函数,我们称之为f

函数f也返回一个函数:其实函数f返回本身

当函数fsum 中定义时,它可以永久访问作用域链中当前可见的所有变量。在这里,它包括本地定义的变量sum(本地运行总和)和f(函数本身)。 (“闭包”就是我们所说的f 的功能代码以及它的所有范围内变量。)

因为f 会返回自身,所以您可以将f 与重复调用链接起来:

var this_is_f = sum(1);
var same_f_again = this_is_f(2);
var f_a_third_time = same_f_again(3);

或者简单地说:

sum(1)(2)(3);

需要注意的是,在我的第一个示例中,我没有创建新函数。相反,我只是用三个不同的标识符引用完全相同的函数对象。

sum 的每次调用都会在其范围内创建一个全新的f,其中包含一个新的本地sum(这里,我的意思是在名为sum 的函数的第一行定义的本地sum) .但是,调用函数sum 不会破坏任何旧的f,因为每次调用sum 都会实例化一个新的f(并且对先前调用@ 时创建的任何其他fs 一无所知987654344@)。这样,您可以运行多个计数:

var first_tally = sum(1)(2);   // first: 3
var second tally = sum(4)(5);  // second: 9
first_tally(3);   // first: 6
second_tally(6);  // second: 15

您能够随时看到有意义的结果的原因是f 刺痛了sum 的值,而不是向您显示其源代码。

【讨论】:

  • 您好,谢谢您的回答。函数 f 返回 f。但是去哪里?什么“包含” f?或不同:返回的 f 去哪里...没有分配它的 var。到。梅鲁
  • @Meru 它目前无处可去......但你知道这个令人不安的现实,对吧?我猜,你只是不想承认 :D
  • @Meru 好的,我在开玩笑。 f 生活在一个平行宇宙中,除了 f,没有人可以去,这是在调用 sum 函数时创建的(这在某种程度上就像大爆炸)。然后,不需要使用var,因为写function f会产生相同的效果。
【解决方案2】:

如果您采用此代码并将其简化到最低限度,我会更容易理解。取一个函数add,它只对两个数字求和:

function add(x,y) {
  return x + y;
}

以上是“正常”功能。如果你没有传递所有的参数,你会得到意想不到的结果。

现在,如果您想要一个对任意数字加 2 的函数,您可以将一个参数部分应用于 add,例如:

function add2(x) {
  return add(2, x);
}

但在 JavaScript 中,我们有一流的函数(可以传递的对象),因此函数可以将函数作为输入并返回其他函数。这就是“currying”派上用场的地方。虽然“部分应用程序”可以让您修复函数参数,但“currying”接受一个包含许多参数的函数并将其分解为单个参数的函数,该函数返回一个单个参数的另一个函数,直到所有参数都按顺序计算,然后返回结果。例如:

function add(x) {
  return function(y) {
    return x + y;
  }
}

现在你可以通过柯里化函数add来创建函数add2

var add2 = add(2);
add2(1); //=> 3

普通函数和柯里化函数具有等效的计算,其中:

add(1, 2) === add(1)(2)

这就是使“几个括号”成为可能的原因。

在 JavaScript 中,“作用域”和“闭包”指的是函数,但它们是不同的概念。 “范围”确定变量的范围/隐私,而“闭包”允许您封装代码并随身携带。在上面的柯里化函数中,变量x 通过闭包保存在内存中,因为它在返回的函数对象中被引用。

“currying”的一个特殊限制是你不能有动态的函数;参数的数量必须是固定的。在您发布的代码中不是这种情况,您的 sum 函数必须能够无限期地向先前的总和添加一个新数字。

与“currying”类似,还有“generators”的概念;一种对惰性计算序列进行建模的模式,在这种情况下,只需根据需要将一个数字添加到先前的总和中。

function sum(a) { // begin closure
  var sum = a; // kept in memory...
  function f(b) {
    sum += b; //...because you use it here
    return f;
  }
  f.toString = function() { return sum };
  return f;
} // end closure

表达代码的另一种(可能更清晰)方式是返回一个对象以链接计算,您可以在其中将新数字添加到先前的总和,或获取到目前为止的总和:

function add(a) {
  var sum = a;
  return {
    plus: function(b) {
      sum += b;
      return this;
    },
    sum: function() {
      return sum;
    }
  }
}

add(1).plus(2).plus(3).sum(); //=> 6

在您的代码中,返回的函数f 充当plustoString 充当检索值的sum

【讨论】:

  • 感谢您的回答。在您的调用链中,“sum()”仅被调用一次。你是什​​么意思:这是“你”编写的函数调用,还是执行它的代码?如果是执行顺序,考虑调用“sum(1)(2)(3)”,那么我有一个问题:为什么 sum() 只调用一次?
【解决方案3】:
  1. 01-13 行定义了全局范围内的函数sum
  2. 第 15 行调用 sum 并将作用域更改为函数内部。
    1. 变量asum 以及闭包函数f 都定义在函数的作用域中——sum 变量在全局作用域中隐藏了函数定义。
    2. 我将跳过这里的 toString 位,因为它直到以后才重要(当它变得非常重要时)。
    3. 函数sum 最终将闭包函数f 的引用作为匿名函数返回到全局范围(因为f 只能在其包含函数的范围内按名称引用)。
  3. 回到第 15 行 - sum(1)(2)(sum(1))(2) 相同,并且由于 sum(1) 返回 f,因此可以将其简化为 (f)(2),或者更简单地说,f(2)
    1. 调用f(2)2 添加到sum - sum 定义在两个范围内:作为全局范围内的函数;并且作为该函数中的一个变量(当前分配了1 的值),它在全局范围内隐藏了定义 - 因此,在f 中,sum 变量设置为1+2=3
    2. 最后,在f 中,函数返回自身。
  4. 再次回到第 15 行 - f(2) 返回 f(尽管命名函数 f 不能在全局范围内引用,并且再次被视为匿名函数)
  5. 处理alert(),将匿名函数(简称f)转为待告警字符串;通常当在函数上调用alert() 时,它将显示函数的源代码(尝试注释掉第 10 行以查看此内容)。但是,由于 f 有一个 toString 方法(第 10 行),因此会调用它并返回 sum 的值(在包含 f 的函数中定义)并提醒 3

【讨论】:

  • 那么,“f.toString”只被调用了一次,而“函数 f(b)”被调用了两次(如果调用 sum 中有两个参数)? “f”的参数,即“b”,始终是该调用的参数:sum (1)(2)?
  • 否 - alert( sum(1)(2) )alert( ( ( sum(1) )(2) ).toString() ) 相同,可以按以下顺序计算:sum(1) 称为返回 f 然后 f(2) 称为返回 f,最后是 f .toString() 被调用;所以sum()f()f.toString() 都只被调用一次。
  • @MTO,谢谢。因此 - 调用 'sum(1)(2)(3)' 调用将是:sum(1) > 返回 f > f(2) > 返回 f > f(3) > 返回 f,因为没有其他括号不再存在 > f.toString() 被调用。这是正确的吗?
  • sum(1) 返回f > f(2) 返回f > alert(f) 隐式转换为alert( f.toString() )f.toString() 返回3
  • @MT0 这是我容易理解的唯一答案。我不知道为什么没有人不赞成你的回答。谢谢!
【解决方案4】:

让我们逐行遍历函数:

function sum(a) {

这部分是不言自明的;我们有一个名为sum 的函数,它接受一个参数a

    var sum = a

这里我们有一个名为 sum 的局部变量,它被设置为传入参数的值。

    function f(b) {
        sum += b
        return f
    }

这是一个名为f内部 函数,它接受一个名为b 的参数。这个b 然后从outer 范围(即,也称为sum 的函数范围内)添加到sum 的值。之后,函数返回自身

    f.toString = function() { return sum }

这是有趣的部分!当你通常console.log 一个函数时,它只会吐出函数的源代码。在这里,我们重新定义了toString 方法,使其成为一个输出sum 值的函数。 这就是为什么您最终会看到显示的运行总计而不是函数的源代码即使您返回的仍然是一个函数。

终于有了:

    return f

所以你基本上有一个函数sum,返回一个返回自身的函数sum 函数基本上设置了所有内容,但是在调用 sum(3) 之后,您将使用您反复调用的 f

所以当你第一次调用sum 时,你实质上是取回了这个函数:

function f(b) {
   sum += b; //value of sum is 3
   return f;
}

在这种情况下,sum 的值将是您传递给函数sum 的初始调用的a 的值。但是,由于ftoString 已被重新定义,您只能看到3 的值。然后假设你做sum(3)(4)

你回来了,和以前一样:

function f(b) {
   sum += b; //value of sum is 3
   return f;
}

但是那么你实际上是用4(基本上是f(4))的参数调用f。由于f 是一个内部函数,它可以完全访问其父函数的范围。这个父函数 (sum) 在一个名为 sum 的变量中维护运行总计,f 可以访问该变量。因此,当您现在调用f(4) 时,您将b 设置为4sum,其值为3

function f(b) { //b is 4
   sum += b; //value of sum is 3 + 4, which is 7
   return f;
}

因此,对于每个后续的括号,您都在重复调用 same f,它正在保持运行计数。

另一种方法是将sum 视为一种工厂,它可以为您提供不同的f,所有这些都保留它们自己的运行记录(基本上表现得像累加器):

var firstSum = sum(4);
var secondSum = sum(2);

firstSum(5); //equivalent to sum(4)(5) returns 9
secondSum(2); //equivalent to sum(2)(2) returns 4

【讨论】:

    【解决方案5】:

    这里有 3 个概念

    • 闭包(无范围)。
    • 函数是javascript中的第一类OBJECTS(允许链接f(a)(b)(c))。无需保存该函数的句柄以便稍后调用它。

    我会给你一个破败,然后添加一些额外的解释

    function sum(a) {
      // when sum is called, sumHolder is created
      var sumHolder = a;
      // the function f is created and holds sumHolder (a closure on the parent environment)
      function f(b) {
        // do the addition
        sumHolder += b;
        // return a FUNCTION
        return f;
      }
      // change the functions default toString method (another closure)
      f.toString = function() {return sumHolder;}
      // return a FUNCTION
      return f
    }
    /*
     * ok let's explain this piece by piece
     * 
     * you call sum with a parameter
     *  - parameter is saved into sumHolder
     *  - a function is returned
     * 
     * you call the returned function f with another parameter
     * EXPLANATION: { sum(a) } returns f, so let's call f as this => {...}()
     *  - this private (priviledged) function adds whatever it's been passed
     *  - returns itself for re-execution, like a chain
     * 
     * when it all ends {{{{}(a)}(b)}(c)} the remainder is a FUNCTION OBJECT
     * this remainder is special in that it's toString() method has been changed
     *  so we can attempt to cast (juggle) it to string for (loose) comparison
     * 
     */
    

    闭包背后的概念很容易理解,但它的应用会让你头晕目眩,直到你习惯 javascript 中没有函数范围,只有 闭包强>这些确实是强大的小动物

    // this anonymous function has access to the global environment and window object
    (function()
      {// start this context
        var manyVars, anotherFunc;
        function someFunc() {
          // has access to manyVars and anotherFunc
          // creates its own context
        };
        anotherFunc = function () {
          // has access to the same ENVIRONMENT
          // creates its own context
        };
      }// whatever is in these keys is context that is not destroyed and
      //  will exist within other functions declared inside
      //  those functions have closure on their parent's environment
      //  and each one generates a new context
    )();
    

    函数是第一类对象。这是什么意思?我不确定自己,但让我用一些进一步的例子来解释:

    // how about calling an anonymous function just as its created
    // *cant do the next line due to a language constraint
    // function(){}()
    // how about a set of parens, this way the word "function" is not the first expression
    (function(){}());
    // the function was created, called and forgotten
    // but the closure inside MAY STILL EXIST
    function whatDoIReturn() {
      return function (){alert('this is legal');return 'somevalue';}();// and executed
    }// returns 'somevalue'
    

    不要逐字逐句。去寻找其他人的代码,检查 Crockford 并提出任何和所有出现的问题

    【讨论】:

    • 当然有函数作用域!你那是什么意思?另外,如何“检查Crockford”?
    • 顺便说一句,函数返回函数不称为柯里化,这只是函数式编程。检查what currying actually it is,注意它实际上只为具有定义数量参数的函数定义。
    • @Bergi 关于柯里化你是绝对正确的。我将删除有关该主题的所有内容
    • @Bergi 当人们来自其他语言并在函数定义中看到 {} (块)时,他们错误地认为范围与 C、PHPspace 范围,该范围将被保存并传递给其中定义的其他函数。有些人喜欢通过在 Java 中创建看起来像私有成员的东西来使用这些 隐藏 范围。至于克罗克福德:javascript.crockford.com。他的视频让一切都清晰明了
    • @hanzo2001:所以你的意思是:“只有函数作用域,没有块作用域(和其他语言一样)”
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-01-12
    • 2017-03-05
    • 1970-01-01
    • 2017-12-06
    相关资源
    最近更新 更多