希望有人找到时间来解释函数和作用域中的函数。 我试图更多地了解变量的函数和范围,并找到了一个非常好的教程,但这部分我只是没有得到。
任务:
创建一个函数和,它将起作用: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.toString
是f
当前返回的function(b)
下一个return f
返回到函数sum(a)
之外的范围。
问题:
我无法弄明白,我需要以不同的方式思考,因为就像我上面所描述的那样,函数不会被再次调用,那么代码的哪一部分会使“几个括号”成为可能?
此外,我是否正确地假设f
被返回的位置?如果有人会给出一些解释,那将会很棒。
答案 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
充当plus
,toString
充当sum
,用于检索值。
答案 2 :(得分:1)
请注意:
f
的4次出现是指相同的功能。 sum
,而其他括号调用f
。sum
返回f
,f
会返回自己,换句话说,无论何时编写()
,您都会返回相同的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。简而言之,它是关于f
和sum
变量之间的关系。实际上,从它们生命的那一刻起,由于第一个函数调用,它们将在其余的存在中共享相同的私有上下文。因此,每次后续调用都包括通过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)
sum
。sum
并将范围更改为函数内部。
a
和sum
以及闭包函数f
都在函数的作用域中定义 - sum
变量将函数定义隐藏在全局作用域中。 toString
位,因为它不重要,直到以后(当它变得非常重要时)。sum
最终将对闭包函数f
的引用作为匿名函数返回到全局作用域(因为f
只能通过其包含函数范围内的名称引用)。sum(1)(2)
与(sum(1))(2)
相同,自sum(1)
返回f
后,这可以缩减为(f)(2)
,或者更简单,f(2)
。
f(2)
将2
添加到sum
- sum
定义在两个范围内:作为全局范围内的函数;并且作为该函数中的变量(当前分配了1
的值),该变量隐藏了全局范围中的定义 - 因此,在f
中,sum
变量设置为{{1 }}。1+2=3
中,函数返回自身。f
返回f(2)
(尽管命名函数f
无法在全局范围内引用,并且再次被视为匿名函数) f
并将匿名函数(称为alert()
)转换为要提醒的字符串;通常在函数上调用f
时,它将显示函数的来源(尝试注释第10行以查看此内容)。但是,由于alert()
具有f
方法(第10行),因此会调用此方法并返回toString
的值(在包含sum
的函数中定义)并且{ {1}}已收到提醒。答案 4 :(得分:0)
这里有 3 概念
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