范围调用函数内部的函数

时间:2013-12-02 21:24:17

标签: javascript function scope

希望有人找到时间来解释函数和作用域中的函数。 我试图更多地了解变量的函数和范围,并找到了一个非常好的教程,但这部分我只是没有得到。

任务:

创建一个函数和,它将起作用: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的数字。 解决方案实际上是不言自明的:

我的解释:

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

问题:

我无法弄明白,我需要以不同的方式思考,因为就像我上面所描述的那样,函数不会被再次调用,那么代码的哪一部分会使“几个括号”成为可能?

此外,我是否正确地假设f被返回的位置?如果有人会给出一些解释,那将会很棒。

6 个答案:

答案 0 :(得分:7)

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

函数f也返回一个函数:实际上,函数f返回本身

当在f内定义函数sum时,它可以永久访问范围链中当前可见的所有变量。这里,它包括本地定义的变量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(并且对任何其他f一无所知先前调用sum时创建的s。这样,您可以运行多个标签:

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标记为{{1}},而不是向您显示其源代码。

答案 1 :(得分:3)

如果您使用此代码并将其简化为最低限度,我会更容易理解。取一个只有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;
  }
}

现在,您可以通过调整函数add2

来创建函数add
var add2 = add(2);
add2(1); //=> 3

正常函数和curried函数具有等效计算,其中:

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

这使“几个括号”成为可能。

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

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

与“currying”类似,有“发电机”的概念;一种模型,用于模拟延迟计算的序列,在这种情况下,只需在前一个总和上添加一个数字。

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,用于检索值。

答案 2 :(得分:1)

链接

请注意:

  • f的4次出现是指相同的功能。
  • 第一对括号调用sum,而其他括号调用f
  • sum返回ff会返回自己,换句话说,无论何时编写(),您都会返回相同的f函数。< / LI>

总结:

sum(0)       // sum(0) -> f
sum(0)(1)    // sum(0), f(1) -> f
sum(0)(1)(2) // sum(0), f(1), f(2) -> f

持久性

话虽如此,主要问题是:总和的状态如何能够在这些函数调用中持续存在?这就是这个着名的“封闭”事物的用武之地。我不打算详细讨论这个概念,Google can help far better。简而言之,它是关于fsum变量之间的关系。实际上,从它们生命的那一刻起,由于第一个函数调用,它们将在其余的存在中共享相同的私有上下文。因此,每次后续调用都包括通过sum修改现有的f变量:

sum(0)       // var sum = 0
sum(0)(1)    // var sum = 0, sum += 1
sum(0)(1)(2) // var sum = 0, sum += 1, sum += 2

结果

最后,当预期字符串代替toString时会自动调用f

typeof(sum(1)(1))      // typeof(f) -> "function"
typeof(sum(1)(1) + '') // typeof(f + '') -> typeof('2' + '') -> "string"
                                                    ^
                                                    toString was called

请注意,您也可以使用valueOf。它可能会更加一致,因为Function已经拥有toString方法的一个版本,以便获得函数声明本身。

加成

就是这样,希望这个解释足够清楚。作为结论,这里有一个类似的方法(为了获得最终结果,只需在队列末尾放置一对空括号):

function sum(a) {
    return function (b) {
        return arguments.length ? sum(a+b) : a;
    };
};

sum(1)(1)(1)(); // 3

答案 3 :(得分:1)

  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. 最后,在1+2=3中,函数返回自身。
  4. 再次返回第15行 - f返回f(2)(尽管命名函数f无法在全局范围内引用,并且再次被视为匿名函数)
  5. 处理
  6. f并将匿名函数(称为alert())转换为要提醒的字符串;通常在函数上调用f时,它将显示函数的来源(尝试注释第10行以查看此内容)。但是,由于alert()具有f方法(第10行),因此会调用此方法并返回toString的值(在包含sum的函数中定义)并且{ {1}}已收到提醒。

答案 4 :(得分:0)

这里有 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 并询问所有出现的问题

答案 5 :(得分:0)

让我们逐行逐步完成功能:

function sum(a) {

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

    var sum = a

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

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

这是一个名为f内部函数,它接受一个名为b的参数。然后将此b添加到外部范围内的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的值将是您传递给函数a的初始调用的sum的值。但是,由于toString的{​​{1}}已重新定义,因此您只能看到值f。然后让我们说你做3

你像以前一样回来了:

sum(3)(4)

但是然后你实际上是以function f(b) { sum += b; //value of sum is 3 return f; } (基本上是f)的参数调用4。由于f(4)是内部函数,因此它可以完全访问其父函数的范围。此父函数(f)维护名为sum的变量中的运行总计,sum可以访问该变量。因此,当您现在致电f时,您将f(4)设置为b,将4设置为sum

3

因此,对于每个后续的对括号,您将重复调用相同的 function f(b) { //b is 4 sum += b; //value of sum is 3 + 4, which is 7 return f; } ,这样可以保持运行记录。

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

f