调试:ESLint 警告 "Function declared in a loop contains unsafe references to variable(s)...no-loop-func"

Debugging: ESLint Warning "Function declared in a loop contains unsafe references to variable(s)...no-loop-func"

使用 Create-React-App 在 React 中构建一个 Sort-Visualizer [https://roy-05.github.io/sort-visualizer/ ]

我正在使用 setTimeouts 为循环的每次迭代设置动画。在开发控制台上,我收到以下警告:

Line 156:32: Function declared in a loop contains unsafe references to variable(s) 'minimum', 'minimum', 'minimum', 'minimum' no-loop-func

这是代码片段:

for(let i=0; i<arr.length-1; i++){
            let minimum = i; //Declare minimum here
            setTimeout(()=>{
                for(let j = i+1; j<arr.length; j++){
                    setTimeout(()=>{
                        //Getting a warning for these references:
                        array_bar[j].style.backgroundColor = 'red';
                        array_bar[minimum].style.backgroundColor = 'blue';
                        setTimeout(()=>{
                            if(arr[j] < arr[minimum]){
                            array_bar[minimum].style.backgroundColor = 'lightblue';
                            minimum = j; 
                            }  
                            else{
                                array_bar[j].style.backgroundColor = 'lightblue';
                            }  
                        }, 4);
                    }, (j-1)*4);    
                }

通过 ESLint Docs,我认为问题可能是我正在修改 setTimeout 内的值,但变量声明在其范围之外。

我不确定如何修复该警告,我们将不胜感激!

注意:如果您需要,这里是完整的函数 -

selectionSort(){
        const arr = this.state.array,
            array_bar = document.getElementsByClassName("array-elem");

        this.setState({startedSelectionSort: true});

        for(let i=0; i<arr.length-1; i++){
            let minimum = i; //Declare minimum here
            setTimeout(()=>{
                for(let j = i+1; j<arr.length; j++){
                    setTimeout(()=>{
                        //Getting a warning for these references:
                        array_bar[j].style.backgroundColor = 'red';
                        array_bar[minimum].style.backgroundColor = 'blue';
                        setTimeout(()=>{
                            if(arr[j] < arr[minimum]){
                            array_bar[minimum].style.backgroundColor = 'lightblue';
                            minimum = j; 
                            }  
                            else{
                                array_bar[j].style.backgroundColor = 'lightblue';
                            }  
                        }, 4);
                    }, (j-1)*4);    
                }
                setTimeout(()=>{
                    let temp = arr[i],
                    arr1_height = arr[minimum],
                    arr2_height = arr[i];

                    arr[i] = arr[minimum];
                    arr[minimum] = temp;

                    array_bar[i].style.height = `${arr1_height}px`;
                    array_bar[minimum].style.height = `${arr2_height}px`;

                    array_bar[i].style.backgroundColor = "green";
                    if(i !== minimum){
                        array_bar[minimum].style.backgroundColor = 'lightblue';
                    }
                }, 400);


                if(i === arr.length-2){
                    setTimeout(()=>{
                        array_bar[i+1].style.backgroundColor = "green";
                    },800);
                }

            }, i*400);
        }

        setTimeout(()=>{
            this.setState({sorted: true})
        }, arr.length*400+1750);

    }

我也遇到了同样的警告。在我的例子中,我在迭代之外声明了变量,但在 forEach 方法中修改了变量。

类似于:

// some code above
let validInputs = true;

someInputs.forEach( input => {
  validInputs = input.value && validInputs;
})

在我做了一些研究之后,我发现在这个 post, JSHint error : Functions declared within loops referencing an outer scoped variable may lead to confusing semantics, 提到 JSHint 不喜欢那里的匿名函数是如何被重新创建的一遍又一遍.

我将 forEach 箭头函数更改为 for (let index i = 0; index < someInputs.length; index++),警告消失了。

也许在你的情况下,将setTimeout更改为传统的非箭头函数可以消除警告。

2021 年 4 月 7 日更新

当我阅读 Professional JavaScript for Web Developers,第 4 版 时,我可能发现了为什么在 ESLint 中实现了这个警告。

从第 4.3 节垃圾收集部分,书中提到闭包也可能导致内存泄漏。

forEacharrow function 的目的是限制变量的范围,如下 MDN 所述:

Arrow functions establish "this" based on the scope the Arrow function is defined within. from Arrow function expressions

Creating closures in loops: A common mistake 部分中,MDN 提到:

Another alternative could be to use forEach() to iterate over the helpText array and attach a listener to each , as shown:

function showHelp(help) {
  document.getElementById('help').textContent = help;
}

function setupHelp() {
  var helpText = [
      {'id': 'email', 'help': 'Your e-mail address'},
      {'id': 'name', 'help': 'Your full name'},
      {'id': 'age', 'help': 'Your age (you must be over 16)'}
    ];

  helpText.forEach(function(text) {
    document.getElementById(text.id).onfocus = function() {
      showHelp(text.help);
    }
  });
}

setupHelp();

在我们的实现中,在 forEach 中调用 arrow functions 是创建闭包的闭包,这显然会给垃圾收集带来一些混乱的语义。

您是对的,修改 setTimeout 中的变量是导致问题的原因。您可以通过将 setTimeout 包装在一个承诺中并在修改您的变量之前等待它解决来解决这个问题。使用 async/await:

更简洁
for (let i = 0; i < arr.length - 1; i++) {
    let minimum = i;
    await new Promise(resolve => setTimeout(resolve, i * 400));
    for (let j = i + 1; j < arr.length; j++) {
        array_bar[j].style.backgroundColor = "red";
        array_bar[minimum].style.backgroundColor = "blue";

        await new Promise(resolve => setTimeout(resolve, (j - 1) * 400));
        if (arr[j] < arr[minimum]) {
            array_bar[minimum].style.backgroundColor = "lightblue";
            minimum = j;
        }
    }
}

对于每个循环,您都在创建一个承诺,该承诺会在超时到期后解决。使用 await 将暂停您的函数的执行,直到承诺解决。然后,您可以修改 minimum 之类的变量,因为它们不再在您最初传递给 setTimeout.

的回调函数的范围内

使用 typescript 和 React,我能够在 for 循环调用中初始化 minimum,然后在进入内部后重新初始化:

for (let i, minimum = 0; i < arr.length - 1; i++) {
  minimum = i; //reinitialize minimum here
  setTimeout(() => {
    for (let j = i + 1; j < arr.length; j++) {
      setTimeout(() => {
        //Getting a warning for these references:
        array_bar[j].style.backgroundColor = "red";
        array_bar[minimum].style.backgroundColor = "blue";
        setTimeout(() => {
          if (arr[j] < arr[minimum]) {
            array_bar[minimum].style.backgroundColor = "lightblue";
            minimum = j;
          } else {
            array_bar[j].style.backgroundColor = "lightblue";
          }
        }, 4);
      }, (j - 1) * 4);
    }
  });
}

对我来说,在超时函数中重新声明变量确实消除了我在 FirebaseFunctions 中的警告。

 setTimeout(async ()=> {
                       var NumberInst = await admin
                        .firestore()
                        .collection("CollName")
                        .doc('DocName')
                        .get();
                       var Numbers = NumberInst.data().postponeX;
                    }, 1000 * 60 * 11 );