这个变量从哪里来?

5
这是来自JavaScript闭包的工作原理?。第一个答案对我来说毫无意义,我也无法评论它。这非常令人沮丧。
function foo(x) {
  var tmp = 3;
  return function(y) {
    alert(x + y + (++tmp));
  }
}
var bar = foo(2); // bar is now a reference to the closure returned by foo
bar(10);

这是什么意思?变量 y 来自哪里?

3
嗯...你是认真的吗? - OzrenTkalcecKrznaric
1
为什么这个问题会有负评?这是一个非常合理的问题,很多人都在探讨这个概念。 - Steven Wexler
1
y只是你在foo函数返回值中创建的一个参数。它在return函数(y)中声明,就像x在函数foo(x)中声明一样。 - Chris Hasiński
1
@steaks 我认为这与原始帖子中的语言有关。 - Dan Teesdale
@DanTeesdale 哦,我现在看了修订历史记录才明白为什么了... - Steven Wexler
3个回答

4

关于变量的来源:

function foo(x) {       // x introduced
  var tmp = 3;          // tmp introduced
  return function (y) { // y introduced
    // Can access all variables in scope, as introduced above.
    // However, ONLY x and tmp are closed-over as y is just a parameter
    // to the inner function.
    alert(x + y + (++tmp));
  }
}

var bar = foo(2);  // 2 is value for x
bar(10);           // 10 is value for y

现在,深入了解一下:

foo(2) 返回一个 新的 函数对象(内部函数),该函数对象绑定到两个变量(x,其当前值为2,和 tmp,其当前值为3)。

然后,bar(10) 运行 该函数对象,并传入10(这是 y 的值)。

重复调用 bar(10) 将导致不同的值,因为闭包变量 tmp 在函数调用期间被重新分配(++tmp)。


好的,谢谢!由于某种原因,我没有理解一个函数返回另一个函数的概念。 - Jessica Shu
@JessicaShu 并非所有语言都有这个特性,但一旦你看到它,就永远不会忘记了 :D JavaScript 中的一个重要概念是函数只是对象,可以像其他值一样传递。这导致了闭包的出现,因为函数可以从定义它的作用域之外调用 - 我不确定闭包是否可以在没有这个属性的情况下存在,尽管我不是一个理论语言专家。 - user2246674
@Jessica:这样的函数被称为高阶函数。http://en.wikipedia.org/wiki/Higher_order_function - Felix Kling

3
你需要区分变量(一个包含信息的内存名称)和参数(传递到函数中的变量的占位符)。 (实际上,在函数原型中称为形式参数,在函数体中使用时称为实际参数。) 所以y不是一个现有的变量,而是一个变量(或值)的占位符。
你需要理解var func = function(){}func转换为匿名函数的引用(没有名称的函数)。 一个简化的例子如下:
var func = function (y) {
   alert(y);
}
func("hello");

你可以从这里开始。其他的都是嵌套应用相同原则的方式罢了。

为什么需要区分呢?(它们在JavaScript中都是变量,并且行为几乎相同 - 如闭包中使用“x”“参数”作为“变量”所示。) - user2246674
没有太大的区别。两者都是内存中的位置名称,一个作为参数列表的一部分声明,另一个在函数体中通过变量声明语句声明。当传递了参数时,参数变量会隐式地被赋值,但从那时起,它们的工作方式完全相同。 "*y是要传递的变量的占位符" 绝对是错误的。 - Bergi
“变量”这个术语在这里真的很模糊。你可能会想到内存中的某个值。但是当这个值作为参数传递时,它的原始名称被别名化(为参数的名称)。实际上,它是完全相同的变量(忽略底层的按值传递机制)。因此,从来没有一个变量y,只是传递进来的东西的名称。 - zany
这是假设实现细节(与JLS不同,ECMAScript规范中没有讨论)的情况。在JavaScript中,不需要使用这种术语来解释行为。变量只是特定值的名称(可能是多个名称之一)。 (另一个行为是通过指出变量既是可变的,变量属于执行作用域,对象也是可变的来解释的。) - user2246674
1
就此而言,变量定义和参数都会在执行环境中创建绑定,因此它们之间最终没有区别。如需更详细的信息,请查看规范:http://es5.github.io/#x10.5。 - Felix Kling

0

foo 返回一个函数,该函数接受一个名为 y 的参数。

因此,当您调用 foo 时,您会得到一个函数,您可以随时使用一个参数来执行该函数,该参数成为 y 的值。

请注意,返回的函数是匿名的,但由于 foo 返回它,因此您可以有效地将该函数绑定到变量。这里,foo 被调用,并且返回值被分配给 bar

现在,您可能会问:“好吧,如果函数返回具有参数 y,并且我调用 bar(10),现在 y 被赋予值 10,但现在 x 是什么?

嗯,x 已经有一个值,在调用 foo 时获得了该值。这是一个“工作流程”表示。

定义了一个名为foo的函数,它接受一个参数x。它返回一个匿名函数,该函数接受一个名为y的参数。
使用值为2的x调用函数foo,并将其返回值绑定到bar。现在,bar是对匿名函数的引用,在其中,所有x的值都等于调用foo时的x的值,即2。
当绑定到bar的匿名函数使用bar(10)调用时,参数y被赋值为10bar执行以下操作:

alert(2 + 10 + (++tmp));

那么你可能会问,“那么tmp是什么呢?”如果你按照上面的步骤进行推导,这应该是相当简单的。当从foo返回匿名函数时,任何局部变量的引用都绑定到foo具有的值。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接