循环内的JavaScript闭合–简单的实际示例

2020/09/14 07:31 · javascript ·  · 0评论
var funcs = [];
// let's create 3 functions
for (var i = 0; i < 3; i++) {
  // and store them in funcs
  funcs[i] = function() {
    // each should log its value.
    console.log("My value: " + i);
  };
}
for (var j = 0; j < 3; j++) {
  // and now let's run each one to see
  funcs[j]();
}

它输出以下内容:

我的价值:3

我的价值:3


我的价值:3

而我希望它输出:

我的价值:0

我的价值:1


我的价值:2


当由于使用事件侦听器而导致功能运行延迟时,会发生相同的问题:

var buttons = document.getElementsByTagName("button");
// let's create 3 functions
for (var i = 0; i < buttons.length; i++) {
  // as event listeners
  buttons[i].addEventListener("click", function() {
    // each should log its value.
    console.log("My value: " + i);
  });
}
<button>0</button>
<br />
<button>1</button>
<br />
<button>2</button>

…或异步代码,例如使用Promises:

// Some async wait function
const wait = (ms) => new Promise((resolve, reject) => setTimeout(resolve, ms));

for (var i = 0; i < 3; i++) {
  // Log `i` as soon as each promise resolves.
  wait(i * 100).then(() => console.log(i));
}

for inand for of循环中也很明显

const arr = [1,2,3];
const fns = [];

for(var i in arr){
  fns.push(() => console.log(`index: ${i}`));
}

for(var v of arr){
  fns.push(() => console.log(`value: ${v}`));
}

for(var f of fns){
  f();
}

这个基本问题有什么解决方案?

好吧,问题在于i每个匿名函数中的变量都绑定到函数外部的相同变量。

ES6解决方案: let

ECMAScript 6(ES6)引入了新的letconst关键字,其作用域与var基于变量的作用域不同例如,在let基于索引的循环中,循环中的每次迭代都将具有一个i具有循环范围的新变量,因此您的代码将按预期工作。有很多资源,但是我建议2ality的区块范围发布作为重要信息来源。

for (let i = 0; i < 3; i++) {
  funcs[i] = function() {
    console.log("My value: " + i);
  };
}

但是请注意,IE9-IE11和Edge 14之前的Edge支持,let但是出现了上述错误(它们不会i每次都创建一个新的,因此上述所有功能都将记录3,就像我们使用一样var)。Edge 14终于正确了。


ES5.1解决方案:forEach

由于该Array.prototype.forEach功能的可用性相对广泛(在2015年),因此值得注意的是,在那些主要涉及对值数组进行迭代的情况下,.forEach()该方法提供了一种干净自然的方法来为每次迭代获取不同的闭包。也就是说,假设您有某种包含值的数组(DOM引用,对象等),并且出现了设置针对每个元素的回调的问题,则可以执行以下操作:

var someArray = [ /* whatever */ ];
// ...
someArray.forEach(function(arrayElement) {
  // ... code code code for this one element
  someAsynchronousFunction(arrayElement, function() {
    arrayElement.doSomething();
  });
});

这个想法是,与.forEach循环一起使用的回调函数的每次调用都将是其自己的闭包。传递给该处理程序的参数是特定于迭代的特定步骤的数组元素。如果在异步回调中使用它,它将不会与在迭代其他步骤中建立的任何其他回调发生冲突。

如果您碰巧在jQuery中工作,该$.each()函数将为您提供类似的功能。


经典解决方案:封闭

您要做的是将每个函数中的变量绑定到函数外部的一个不变的值:

var funcs = [];

function createfunc(i) {
  return function() {
    console.log("My value: " + i);
  };
}

for (var i = 0; i < 3; i++) {
  funcs[i] = createfunc(i);
}

for (var j = 0; j < 3; j++) {
  // and now let's run each one to see
  funcs[j]();
}

由于在JavaScript中没有块作用域-只有函数作用域-通过将函数创建包装在新函数中,因此请确保“ i”的值保持预期。

尝试:

var funcs = [];
    
for (var i = 0; i < 3; i++) {
    funcs[i] = (function(index) {
        return function() {
            console.log("My value: " + index);
        };
    }(i));
}

for (var j = 0; j < 3; j++) {
    funcs[j]();
}

编辑(2014年):

我个人认为,@ Aust 最近关于使用的答案.bind是现在执行此类操作的最佳方法。还有LO-破折号/下划线是_.partial当你不需要或不想要惹做bindthisArg

尚未提及的另一种方法是使用 Function.prototype.bind

var funcs = {};
for (var i = 0; i < 3; i++) {
  funcs[i] = function(x) {
    console.log('My value: ' + x);
  }.bind(this, i);
}
for (var j = 0; j < 3; j++) {
  funcs[j]();
}

更新

如@squint和@mekdev所指出的,通过先在循环外部创建函数,然后在循环内绑定结果,可以提高性能。

function log(x) {
  console.log('My value: ' + x);
}

var funcs = [];

for (var i = 0; i < 3; i++) {
  funcs[i] = log.bind(this, i);
}

for (var j = 0; j < 3; j++) {
  funcs[j]();
}

使用立即调用函数表达式,这是封装索引变量的最简单,最易读的方法:

for (var i = 0; i < 3; i++) {

    (function(index) {

        console.log('iterator: ' + index);
        //now you can also loop an ajax call here 
        //without losing track of the iterator value:   $.ajax({});
    
    })(i);

}

这会将迭代器发送i到我们定义为的匿名函数中index这将创建一个闭包,在该闭包中将i保存该变量以供以后在IIFE中的任何异步功能中使用。

参加聚会的时候有点晚,但是我今天正在探讨这个问题,并且注意到许多答案并没有完全解决Javascript如何对待范围的问题,这本质上可以归结为这一点。

因此,正如许多其他提到的那样,问题在于内部函数正在引用相同的i变量。那么,为什么不每次迭代都创建一个新的局部变量,而让内部函数引用呢?

//overwrite console.log() so you can see the console output
console.log = function(msg) {document.body.innerHTML += '<p>' + msg + '</p>';};

var funcs = {};
for (var i = 0; i < 3; i++) {
    var ilocal = i; //create a new local variable
    funcs[i] = function() {
        console.log("My value: " + ilocal); //each should reference its own local variable
    };
}
for (var j = 0; j < 3; j++) {
    funcs[j]();
}

就像之前一样,每个内部函数都输出分配给的最后一个值i,现在每个内部函数都输出分配给的最后一个值ilocal但是每个迭代不应该有它自己的ilocal吗?

原来,这就是问题所在。每次迭代都共享相同的作用域,因此第一次迭代之后的每次迭代都只是覆盖ilocalMDN

重要提示:JavaScript没有阻止范围。随块引入的变量的作用域为包含的函数或脚本,并且设置它们的效果在块本身之外仍然存在。换句话说,block语句不会引入作用域。尽管“独立”块是有效的语法,但是您不希望在JavaScript中使用独立块,因为如果您认为独立块在C或Java中的作用类似于此类块,则它们不会像您想的那样工作。

重申重点:

JavaScript没有阻止范围。块引入的变量的作用域为包含的函数或脚本

我们可以通过ilocal在每次迭代中声明之前进行检查来查看

//overwrite console.log() so you can see the console output
console.log = function(msg) {document.body.innerHTML += '<p>' + msg + '</p>';};

var funcs = {};
for (var i = 0; i < 3; i++) {
  console.log(ilocal);
  var ilocal = i;
}

这就是为什么此bug如此棘手的原因。即使您重新声明变量,Javascript也不会引发错误,JSLint甚至不会发出警告。这也是为什么解决此问题的最佳方法是利用闭包的原因,闭包本质上是这样的想法:在Javascript中,内部函数可以访问外部变量,因为内部作用域“包围”了外部作用域。

关闭

这也意味着内部函数“保持”外部变量并使它们保持活动状态,即使外部函数返回也是如此。为了利用这一点,我们纯粹创建并调用包装函数来创建一个新的作用域,ilocal在新的作用域中声明,并返回一个使用该内部函数ilocal(下面有更多解释):

//overwrite console.log() so you can see the console output
console.log = function(msg) {document.body.innerHTML += '<p>' + msg + '</p>';};

var funcs = {};
for (var i = 0; i < 3; i++) {
    funcs[i] = (function() { //create a new scope using a wrapper function
        var ilocal = i; //capture i into a local var
        return function() { //return the inner function
            console.log("My value: " + ilocal);
        };
    })(); //remember to run the wrapper function
}
for (var j = 0; j < 3; j++) {
    funcs[j]();
}

在包装函数内部创建内部函数会为内部函数提供一个只有其才能访问的私有环境,即“闭包”。因此,每次调用包装器函数时,我们都会使用其自己的独立环境创建一个新的内部函数,以确保ilocal变量不会发生碰撞和彼此覆盖。进行一些次要的优化可以得出许多其他SO用户给出的最终答案:

//overwrite console.log() so you can see the console output
console.log = function(msg) {document.body.innerHTML += '<p>' + msg + '</p>';};

var funcs = {};
for (var i = 0; i < 3; i++) {
    funcs[i] = wrapper(i);
}
for (var j = 0; j < 3; j++) {
    funcs[j]();
}
//creates a separate environment for the inner function
function wrapper(ilocal) {
    return function() { //return the inner function
        console.log("My value: " + ilocal);
    };
}

更新资料

随着ES6现在成为主流,我们现在可以使用new let关键字创建块作用域变量:

//overwrite console.log() so you can see the console output
console.log = function(msg) {document.body.innerHTML += '<p>' + msg + '</p>';};

var funcs = {};
for (let i = 0; i < 3; i++) { // use "let" to declare "i"
    funcs[i] = function() {
        console.log("My value: " + i); //each should reference its own local variable
    };
}
for (var j = 0; j < 3; j++) { // we can use "var" here without issue
    funcs[j]();
}

看看现在有多容易!有关更多信息,请参见此答案,这是我的信息所基于的。

如今,ES6得到了广泛的支持,对此问题的最佳答案已经改变。ES6 为此情况提供了letconst关键字。不用弄乱闭包,我们可以只使用let这样设置一个循环作用域变量:

var funcs = [];

for (let i = 0; i < 3; i++) {          
    funcs[i] = function() {            
      console.log("My value: " + i); 
    };
}

val然后将指向特定于该循环特定循环的对象,并且将返回正确的值,而无需附加的闭合符号。这显然大大简化了这个问题。

const类似于let附加的限制,即变量名称在初始分配后不能反弹到新引用。

现在,针对那些针对最新版本浏览器的浏览器提供了支持。最新的Firefox,Safari,Edge和Chrome当前支持const/ let它在Node中也受支持,您可以利用Babel等构建工具在任何地方使用它。您可以在这里看到一个有效的示例:http : //jsfiddle.net/ben336/rbU4t/2/

此处的文档:

但是请注意,IE9-IE11和Edge 14之前的Edge支持,let但是出现了上述错误(它们不会i每次都创建一个新的,因此上述所有功能都将记录3,就像我们使用一样var)。Edge 14终于正确了。

换句话说,i函数中的in是在执行函数时绑定的,而不是在创建函数时绑定的。

创建闭包时,它i是对外部作用域中定义的变量的引用,而不是创建闭包时的副本。执行时将对其进行评估。

其他大多数答案都提供了解决方法,方法是创建另一个不会改变您价值的变量。

只是以为我会添加一个说明以便清楚。对于解决方案,就我个人而言,我会选择Harto,因为这是从此处的答案中最不言自明的方式。发布的任何代码都可以使用,但是我选择了一个关闭工厂,而不是不得不编写一堆注释来解释为什么我要声明一个新变量(Freddy和1800's)或具有怪异的嵌入式关闭语法(apphacker)。

您需要了解的是javascript中变量的范围是基于该函数的。这与在拥有块作用域的地方说c#相比是一个重要的区别,只需将变量复制到for内部即可。

将其包装在一个评估返回函数的函数中(例如apphacker的答案)可以解决问题,因为变量现在具有函数作用域。

还有一个let关键字代替var,它将允许使用块范围规则。在那种情况下,在for中定义一个变量就可以解决问题。就是说,由于兼容性,let关键字不是实际的解决方案。

var funcs = {};

for (var i = 0; i < 3; i++) {
  let index = i; //add this
  funcs[i] = function() {
    console.log("My value: " + index); //change to the copy
  };
}

for (var j = 0; j < 3; j++) {
  funcs[j]();
}

这是该技术的另一种变体,类似于Bjorn(apphacker)的技术,它使您可以在函数内部分配变量值,而不是将其作为参数传递,这有时可能更清楚:

var funcs = [];
for (var i = 0; i < 3; i++) {
    funcs[i] = (function() {
        var index = i;
        return function() {
            console.log("My value: " + index);
        }
    })();
}

请注意,无论使用哪种技术,该index变量都将成为一种静态变量,绑定到内部函数的返回副本上。即,在两次调用之间保留对其值的更改。可能非常方便。

这描述了在JavaScript中使用闭包的常见错误。

一个函数定义了一个新的环境

考虑:

function makeCounter()
{
  var obj = {counter: 0};
  return {
    inc: function(){obj.counter ++;},
    get: function(){return obj.counter;}
  };
}

counter1 = makeCounter();
counter2 = makeCounter();

counter1.inc();

alert(counter1.get()); // returns 1
alert(counter2.get()); // returns 0

对于每次makeCounter调用,都会{counter: 0}创建一个新对象。另外,还将obj
创建
的新副本以引用新对象。因此,
counter1counter2彼此独立。

闭包循环

在循环中使用闭包非常棘手。

考虑:

var counters = [];

function makeCounters(num)
{
  for (var i = 0; i < num; i++)
  {
    var obj = {counter: 0};
    counters[i] = {
      inc: function(){obj.counter++;},
      get: function(){return obj.counter;}
    }; 
  }
}

makeCounters(2);

counters[0].inc();

alert(counters[0].get()); // returns 1
alert(counters[1].get()); // returns 1

请注意,counters[0]counters[1]不是独立的。实际上,它们在相同的位置上运行obj

这是因为obj可能出于性能原因,在循环的所有迭代中只有一个共享副本即使{counter: 0}在每次迭代中创建一个新对象,相同的副本也obj将通过引用最新对象来更新。

解决方案是使用另一个辅助函数:

function makeHelper(obj)
{
  return {
    inc: function(){obj.counter++;},
    get: function(){return obj.counter;}
  }; 
}

function makeCounters(num)
{
  for (var i = 0; i < num; i++)
  {
    var obj = {counter: 0};
    counters[i] = makeHelper(obj);
  }
}

之所以可行,是因为直接在函数作用域中的局部变量以及函数自变量在输入时被分配了新副本。

最简单的解决方案是

而不是使用:

var funcs = [];
for(var i =0; i<3; i++){
    funcs[i] = function(){
        alert(i);
    }
}

for(var j =0; j<3; j++){
    funcs[j]();
}

会发出“ 2”警报3次。这是因为在for循环中创建的匿名函数共享相同的闭包,并且在该闭包中,其值i相同。使用这个来防止共享关闭:

var funcs = [];
for(var new_i =0; new_i<3; new_i++){
    (function(i){
        funcs[i] = function(){
            alert(i);
        }
    })(new_i);
}

for(var j =0; j<3; j++){
    funcs[j]();
}

其背后的想法是,使用IIFE(立即调用函数表达式)封装for循环的整个主体,new_i作为参数传递并将其捕获为i由于匿名函数会立即执行,i因此匿名函数内部定义的每个函数值都不同。

该解决方案似乎适合任何此类问题,因为它将需要对受此问题困扰的原始代码进行最少的更改。实际上,这是设计使然,根本不成问题!

这是一个使用简单的解决方案forEach(可回溯到IE9):

var funcs = [];
[0,1,2].forEach(function(i) {          // let's create 3 functions
    funcs[i] = function() {            // and store them in funcs
        console.log("My value: " + i); // each should log its value.
    };
})
for (var j = 0; j < 3; j++) {
    funcs[j]();                        // and now let's run each one to see
}

印刷品:

My value: 0
My value: 1
My value: 2

试试这个较短的

  • 没有数组

  • 没有额外的循环

for (var i = 0; i < 3; i++) {
    createfunc(i)();
}

function createfunc(i) {
    return function(){console.log("My value: " + i);};
}

http://jsfiddle.net/7P6EN/

OP显示的代码的主要问题是,i直到第二个循环才读取。为了演示,假设看到代码内部有错误

funcs[i] = function() {            // and store them in funcs
    throw new Error("test");
    console.log("My value: " + i); // each should log its value.
};

funcs[someIndex]执行之前,实际上不会发生该错误()使用相同的逻辑,很显然,i直到这一点也不会收集的值原始循环完成后,i++i其取值3会导致条件i < 3失败和循环结束。在这一点上,i3等时funcs[someIndex]()使用,并i进行评估,这是3 -每一次。

为了克服这个问题,您必须评估i遇到的情况。请注意,这已经以funcs[i](其中有3个唯一索引)的形式发生有几种获取此值的方法。一种是将其作为参数传递给函数,此处已经以几种方式显示了该函数。

另一种选择是构造一个函数对象,该对象将能够覆盖变量。这样就可以做到

jsFiddle Demo

funcs[i] = new function() {   
    var closedVariable = i;
    return function(){
        console.log("My value: " + closedVariable); 
    };
};

JavaScript函数在声明时“关闭”它们可以访问的范围,并且即使该范围中的变量发生更改,也保留对该范围的访问。

var funcs = []

for (var i = 0; i < 3; i += 1) {
  funcs[i] = function () {
    console.log(i)
  }
}

for (var k = 0; k < 3; k += 1) {
  funcs[k]()
}

上面数组中的每个函数都关闭了全局范围(全局,仅仅是因为那恰好是它们在其中声明的范围)。

之后,调用这些函数,记录i全局范围内的最新值这就是关闭的魔力和挫败感。

“ JavaScript函数在声明它们的范围内关闭,并且即使该范围内的变量值发生更改,也保留对该范围的访问。”

使用let而不是var通过在每次for循环运行时创建一个新的作用域,为每个要关闭的函数创建一个单独的作用域来解决此问题。其他各种技术也可以通过其他功能来完成相同的任务。

var funcs = []

for (let i = 0; i < 3; i += 1) {
  funcs[i] = function () {
    console.log(i)
  }
}

for (var k = 0; k < 3; k += 1) {
  funcs[k]()
}

let使变量成为块作用域。块用花括号表示,但是在for循环的i情况下,初始化变量在本例中被认为是在花括号中声明的。)

在阅读了各种解决方案之后,我想补充一点,那些解决方案起作用的原因是依赖于范围链的概念这是JavaScript在执行期间解析变量的方式。

  • 每个函数定义形式,包括所有的局部变量的范围内声明的通过var和它的arguments
  • 如果我们在另一个(外部)函数中定义了内部函数,则会形成一个链,并将在执行期间使用
  • 执行函数时,运行时将通过搜索作用域链来评估变量如果可以在链的某个点找到变量,它将停止搜索并使用它,否则它将一直持续到达到属于的全局范围为止window

在初始代码中:

funcs = {};
for (var i = 0; i < 3; i++) {         
  funcs[i] = function inner() {        // function inner's scope contains nothing
    console.log("My value: " + i);    
  };
}
console.log(window.i)                  // test value 'i', print 3

funcs被执行时,作用域链会function inner -> global由于i无法在中找到变量function inner(既未使用声明,var也未作为参数传递),因此它将继续搜索,直到i最终在的全局范围中找到的值window.i

通过将其包装在外部函数中,或者像harto那样显式定义一个辅助函数,或者像Bjorn那样使用匿名函数

funcs = {};
function outer(i) {              // function outer's scope contains 'i'
  return function inner() {      // function inner, closure created
   console.log("My value: " + i);
  };
}
for (var i = 0; i < 3; i++) {
  funcs[i] = outer(i);
}
console.log(window.i)          // print 3 still

如果funcs得到执行,现在的作用域链会function inner -> function outer这个时间i可以在外部函数的作用域中找到,该作用域在for循环中执行了3次,每次都有i正确绑定的window.i内部执行时将不使用值

可以在此处找到更多详细信息

其中包括在循环中创建闭合的常见错误以及我们为什么需要闭合和性能考虑。

借助ES6的新功能,可以管理块级范围:

var funcs = [];
for (let i = 0; i < 3; i++) {          // let's create 3 functions
    funcs[i] = function() {            // and store them in funcs
        console.log("My value: " + i); // each should log its value.
    };
}
for (let j = 0; j < 3; j++) {
    funcs[j]();                        // and now let's run each one to see
}

OP的问题中的代码被替换letvar

令我惊讶的是,还没有人建议使用该forEach函数来更好地避免(重新)使用局部变量。实际上,for(var i ...)由于这个原因,我不再使用了。

[0,2,3].forEach(function(i){ console.log('My value:', i); });
// My value: 0
// My value: 2
// My value: 3

//编辑以使用forEach而不是地图。

这个问题确实显示了JavaScript的历史!现在我们可以避免使用箭头功能进行块作用域定义,而可以使用Object方法直接从DOM节点处理循环。

const funcs = [1, 2, 3].map(i => () => console.log(i));
funcs.map(fn => fn())

const buttons = document.getElementsByTagName("button");
Object
  .keys(buttons)
  .map(i => buttons[i].addEventListener('click', () => console.log(i)));
<button>0</button><br>
<button>1</button><br>
<button>2</button>

您的原始示例不起作用的原因是,您在循环中创建的所有闭包都引用了同一框架。实际上,对一个对象具有3个方法而只有一个i变量。它们都打印出相同的值。

首先,了解此代码有什么问题:

var funcs = [];
for (var i = 0; i < 3; i++) {          // let's create 3 functions
    funcs[i] = function() {            // and store them in funcs
        console.log("My value: " + i); // each should log its value.
    };
}
for (var j = 0; j < 3; j++) {
    funcs[j]();                        // and now let's run each one to see
}

在这里,当funcs[]数组初始化时,i要递增,funcs数组初始化,func数组的大小变为3,所以i = 3,现在,当funcs[j]()调用时,它再次使用变量i,该变量已经增加到3。

现在解决这个问题,我们有很多选择。以下是其中两个:

  1. 我们可以初始化ilet或初始化一个新的变量indexlet和使其等于i因此,在进行调用时,index将使用它并且其范围将在初始化后结束。并进行调用,index将再次初始化:

    var funcs = [];
    for (var i = 0; i < 3; i++) {          
        let index = i;
        funcs[i] = function() {            
            console.log("My value: " + index); 
        };
    }
    for (var j = 0; j < 3; j++) {
        funcs[j]();                        
    }
  2. 其他选项可以是引入一个tempFunc返回实际功能的方法:

    var funcs = [];
    function tempFunc(i){
        return function(){
            console.log("My value: " + i);
        };
    }
    for (var i = 0; i < 3; i++) {  
        funcs[i] = tempFunc(i);                                     
    }
    for (var j = 0; j < 3; j++) {
        funcs[j]();                        
    }

使用闭包结构,这样可以减少多余的for循环。您可以在单个for循环中完成此操作:

var funcs = [];
for (var i = 0; i < 3; i++) {     
  (funcs[i] = function() {         
    console.log("My value: " + i); 
  })(i);
}

我们将检查,varlet
一一
声明时实际发生的情况

案例1使用var

<script>
   var funcs = [];
   for (var i = 0; i < 3; i++) {
     funcs[i] = function () {
        debugger;
        console.log("My value: " + i);
     };
   }
   console.log(funcs);
</script>

现在,按F12键打开chrome控制台窗口然后刷新页面。扩展数组中的每3个函数。您将看到一个名为.Expand 的属性您将看到一个名为的数组对象,将其扩展。您会发现在对象中声明了一个值为3 的属性[[Scopes]]"Global"'i'

在此处输入图片说明

在此处输入图片说明

结论:

  1. 当您'var'在函数外部使用变量声明时,它将变为全局变量(您可以通过在控制台窗口中键入i
    window.i进行检查,它将返回3)。
  2. 除非您调用函数,否则声明的匿名函数将不会调用并检查函数内部的值。
  3. 调用函数时,console.log("My value: " + i)从其Global对象获取值并显示结果。

案例2:使用let

现在更换'var''let'

<script>
    var funcs = [];
    for (let i = 0; i < 3; i++) {
        funcs[i] = function () {
           debugger;
           console.log("My value: " + i);
        };
    }
    console.log(funcs);
</script>

做同样的事情,转到范围。现在您将看到两个对象"Block""Global"现在展开Block对象,您将看到在其中定义了“ i”,奇怪的是,对于每个函数,其值if i是不同的(0,1,2)。

在此处输入图片说明

结论:

当您'let'甚至在函数外部但在循环内部使用变量声明时,此变量将不是全局变量,它将变为Block仅适用于同一函数级别变量。这就是为什么我们得到i不同的对于每个函数,当我们调用函数时。

有关更近距离工作原理的更多详细信息,请浏览精彩的视频教程https://youtu.be/71AtaJpJHw0

您可以对数据列表使用声明性模块,例如query-js(*)。在这些情况下,我个人发现声明式方法不足为奇

var funcs = Query.range(0,3).each(function(i){
     return  function() {
        console.log("My value: " + i);
    };
});

然后,您可以使用第二个循环并获得预期的结果,或者您可以执行

funcs.iterate(function(f){ f(); });

(*)我是query-js的作者,因此偏向于使用它,因此不要只将我的话作为对上述库的建议:)

我更喜欢使用forEach函数,该函数在创建伪范围时具有自己的闭包:

var funcs = [];

new Array(3).fill(0).forEach(function (_, i) { // creating a range
    funcs[i] = function() {            
        // now i is safely incapsulated 
        console.log("My value: " + i);
    };
});

for (var j = 0; j < 3; j++) {
    funcs[j](); // 0, 1, 2
}

这看起来比其他语言的范围丑陋,但是恕我直言,它不如其他解决方案那么可怕。

还有另一种解决方案:无需创建另一个循环,只需将绑定this到return函数即可。

var funcs = [];

function createFunc(i) {
  return function() {
    console.log('My value: ' + i); //log value of i.
  }.call(this);
}

for (var i = 1; i <= 5; i++) {  //5 functions
  funcs[i] = createFunc(i);     // call createFunc() i=5 times
}

通过绑定,也解决了问题。

直到ES5,只能使用Closure解决此问题

但是现在在ES6中,我们有了块级范围变量。更改VAR在第一个for循环会解决这个问题。

var funcs = [];
for (let i = 0; i < 3; i++) {      // let's create 3 functions
  funcs[i] = function() {          // and store them in funcs
    console.log("My value: " + i); // each should log its value.
  };
}
for (var j = 0; j < 3; j++) {
  funcs[j]();                      // and now let's run each one to see
}

许多解决方案似乎都是正确的,但他们没有提及它的名称Currying,这是针对类似情况的功能编程设计模式。比绑定速度快3-10倍,具体取决于浏览器。

var funcs = [];
for (var i = 0; i < 3; i++) {      // let's create 3 functions
  funcs[i] = curryShowValue(i);
}
for (var j = 0; j < 3; j++) {
  funcs[j]();                      // and now let's run each one to see
}

function curryShowValue(i) {
  return function showValue() {
    console.log("My value: " + i);
  }
}

查看不同浏览器中的性能提升

您的代码不起作用,因为它的作用是:

Create variable `funcs` and assign it an empty array;  
Loop from 0 up until it is less than 3 and assign it to variable `i`;
    Push to variable `funcs` next function:  
        // Only push (save), but don't execute
        **Write to console current value of variable `i`;**

// First loop has ended, i = 3;

Loop from 0 up until it is less than 3 and assign it to variable `j`;
    Call `j`-th function from variable `funcs`:  
        **Write to console current value of variable `i`;**  
        // Ask yourself NOW! What is the value of i?

现在的问题是,i调用函数时变量的值是多少?由于第一个循环是使用条件创建的i < 3,因此当条件为false时,它将立即停止i = 3

您需要了解,在创建函数时,不会执行任何代码,只会将其保存以供以后使用。因此,当稍后调用它们时,解释器将执行它们并询问:“的当​​前值是i多少?”

因此,您的目标是先保存ito函数的值,然后再将函数保存到funcs例如,可以通过以下方式完成此操作:

var funcs = [];
for (var i = 0; i < 3; i++) {          // let's create 3 functions
    funcs[i] = function(x) {            // and store them in funcs
        console.log("My value: " + x); // each should log its value.
    }.bind(null, i);
}
for (var j = 0; j < 3; j++) {
    funcs[j]();                        // and now let's run each one to see
}

这样,每个函数将拥有自己的变量x,我们将其设置xi每次迭代中的值

这只是解决此问题的多种方法之一。

var funcs = [];
for (var i = 0; i < 3; i++) {      // let's create 3 functions
  funcs[i] = function(param) {          // and store them in funcs
    console.log("My value: " + param); // each should log its value.
  };
}
for (var j = 0; j < 3; j++) {
  funcs[j](j);                      // and now let's run each one to see with j
}
本文地址:http://javascript.askforanswer.com/xunhuanneidejavascriptbihejiandandeshijishili.html
文章标签: ,   ,  
版权声明:本文为原创文章,版权归 javascript 所有,欢迎分享本文,转载请保留出处!

文件下载

老薛主机终身7折优惠码boke112

上一篇:
下一篇:

评论已关闭!