3

我有一个名为 Range 的简单函数,它根据开始、步长和结束值创建一个整数数组...

function Range (start, end, step) {

  // default step is 1..
  if (step === undefined ) step = 1;

  // creating an array...
  var arr = [], index = 0;

  while(start <= end) {
  arr[index]  = start ;
  index += 1;
  start += step; 
  }

  // simple function expressions
  var getAll = function () {
    return arr  ;  
  };

  var getOne = function(n) {
    return arr[n] ;
  };


  // returns a unnamed function ..
  return function(i) { 
    if (i === undefined) { return getAll() ;}
    else {return getOne(i); } 
  };  // not an iife

} 

所以基本上Range是一个返回未命名函数的函数,该函数再次返回在函数Range中声明的命名函数表达式.. err .. 我不知道.. 类似的东西......

现在下面的代码...

var first10 = Range (1,10) ; // no new ..() here, so no instance should be created.. only Range is called..
var first10Odd = Range(1,20,2) ; // and Range is called again..

alert(first10); // alerts - function(i) { ... }
alert(first10Odd); // alerts- function(i) { ... } 

alert(first10()) ; // alerts - 1,2,3,...10
alert(first10Odd()); // alerts - 1,3,5,...19

alert(first10(0)); // alerts - 1 
alert(first10Odd(9)); // alerts- 19

为什么评论中指定的警报警报??...我认为Range只是一个函数,而不是对象构造函数,也没有创建实例...不应该尽快销毁函数的局部变量功能完成??还是我的逻辑错了??上面的代码发生了什么?谁能解释一下....

我在这里修改了我的代码..

抱歉问这个愚蠢的问题..

4

2 回答 2

4

欢迎来到 Javascript 中的闭包之地。一旦你理解了它们,它们就会非常强大并且非常有用。但是,如果您之前的经验是使用没有它们的语言,那么它们一开始可能会觉得有点陌生。

一些答案/解释:

  1. 调用Range(x, y)会返回一个函数,以后可以调用该函数。
  2. 因为返回的函数在另一个具有变量的函数范围内,所以创建了一个闭包。
  3. 该闭包保持活动状态(即使外部函数已完成执行),因为存在对保存在变量中的内部函数的持久引用,并且该内部函数具有对外部函数中局部变量的引用。这些引用使闭包不会被垃圾收集(因此它保持活动状态)。
  4. 然后,该内部函数可以引用外部函数中的变量,包括最初传递给它的参数。
  5. 此构造允许您创建这些自定义函数,这些函数具有预先内置的参数。
  6. 这种闭包的概念只存在于某些语言中。例如,它在 C++ 中不存在。
  7. 当调用 Range(x,y) 返回的函数本身稍后执行时,它可以使用最初在其范围内的任何变量。
  8. 每次调用Range(x,y)都会创建一个新的闭包。
  9. getAll并且getOne是分配给函数的外部函数中的局部变量。它们访问外部函数中的其他局部变量。所有这些都在前面提到的每次Range()调用时创建的闭包中。

有很多关于闭包是什么的文章(您可以在 Google 上阅读并阅读),但我喜欢将其视为一个执行上下文,其中包含调用函数时范围内的所有内容(包括所有变量)。每次调用一个函数时,它都会创建一个这样的执行上下文。由于 javascript 中的所有内容都是垃圾收集的,并且只有在没有对它的引用时才会被释放/销毁,因此对于这个执行上下文也是如此(例如闭包)。只要某物有对它的引用或其中的某物,那么执行上下文将保持活动状态,并且可以被任何可能运行到该执行上下文中的代码使用。


逐行注释:

// first10 is assigned the anonymous function that the call to Range()
// returned.  That anonymous function has access to the original arguments
// passed to the Range(1,10) call and other local variables in that function.
var first10 = Range (1,10) ; // no new ..() here, so no instance should be created.. only Range is called..

// same as the call before, except this also includes the step argument
var first10Odd = Range(1,20,2) ; // and Range is called again..

// this makes sense because Range(1,10) returns a function so
// when you alert it's value, it tells you it's a function
alert(first10); // alerts - function(i) { ... }
alert(first10Odd); // alerts- function(i) { ... } 

// When you execute the function in first10, it runs that function 
// and the alert shows the return value from that function
// This particular function is set to return the entire array if nothing is passed
// to it
alert(first10()) ; // alerts - 1,2,3,...10
alert(first10Odd()); // alerts - 1,3,5,...19


// This particular function is set to return a specific index from the array
// if an argument is passed to it
alert(first10(0)); // alerts - 1 
alert(first10Odd(9)); // alerts- 19

如果您知道如何使用 javascript 调试器,则可以if (i === undefined) { return getAll() ;}在内部函数的这一行设置断点,您将能够检查范围内的所有变量,包括start,endstep来自外部函数的变量。


您可能会发现这篇文章很有用,因为它封装了一些可以将闭包与对象声明一起使用的方式:http: //javascript.crockford.com/private.html(不完全是这里所做的,但可能会帮助您理解他们)。

于 2013-10-13T07:48:35.997 回答
1

欢迎使用 javascript 闭包。让我们一行一行。

var first10 = Range(1,10);
var first10Odd = Range(1,20,2);

我们知道这Range只是一个函数。因此,在这两行中,我们只是Range分别使用 2 个和 3 个参数调用函数。

现在,当你调用一个函数时会发生什么。显而易见的答案是,函数的主体被执行。我们在函数体中有什么。

if (step === undefined ) step = 1;

var arr = [], index = 0;

while(start <= end) {
  arr[index]  = start ;
  index += 1;
  start += step; 
}

我希望上面看到的线条非常明显,你对它们没有任何问题。

var getAll = function () {
   return arr;
};

这条线有什么作用?它在运行时创建一个函数。为什么是运行时?让我们看一个例子。

<script>
   func1();
   var func1 = function() {
      alert("Hi");
   }
</script>

<script>
   func1();
   function func1() {
      alert("Hi");
   }
</script>

如果你使用第一个脚本块,它会抛出错误。为什么?您正在调用尚未定义的函数。第二种情况,您在 javascript 解析时间本身期间定义函数。在第一种情况下创建的函数类型称为匿名函数。让我们回到getAll. 现在我们知道这getAll只是一个指向匿名函数的变量,让我们看看它做了什么。它返回arr。它如何访问arr?它是在函数外部声明的,因此它仍然可以访问它。同样的情况

var getOne = function(n) {
  return arr[n] ;
};

现在非常重要的部分,

  return function(i) { 
    if (i === undefined) {
       return getAll();
    } else {
       return getOne(i);
    }
  };

它有什么作用?它返回一个函数。准确地说,它返回一个匿名函数。每当Range被调用时,它都会创建一个新的匿名函数,该函数接受一个参数并返回它。那么,现在做什么first10first10Odd拥有什么?是的。你是对的,它们有功能。我希望能解释

alert(first10);    // alerts - function(i) { ... }
alert(first10Odd); // alerts - function(i) { ... } 

让我们检查这两个功能。当first10什么都没有调用时,我的意思是,first10()参数i取值undefined。所以,我们实际上是在不带参数地调用匿名函数,它应该返回getAll()。如果你还记得的话,first10它是用Range(1,10);. 所以,arr遗嘱现在有[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

你可能会问,当我们从函数返回时,函数内部声明的变量会不会超出范围。答案是“是”和“否”。是的,当您只返回一个值时。不,当你返回一个函数时。当您返回一个函数时,变量的状态将保持不变。此属性称为closures。这就是它返回的原因

[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]     for alert(first10())
[1, 3, 5, 7, 9, 11, 13, 15, 17, 19] for alert(first10Odd())
1                                   for alert(first10(0))
19                                  for alert(first10Odd(9))

请在此处阅读有关 Closure 的更多信息https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Closures

于 2013-10-13T07:58:12.357 回答