调试:ESLint 警告“循环中声明的函数包含对变量的不安全引用...no-loop-func”

IT技术 javascript reactjs settimeout create-react-app eslint
2021-04-19 04:05:49

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

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

第 156:32 行:在循环中声明的函数包含对变量 '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);

    }
4个回答

我也遇到了同样的警告。就我而言,我在迭代外部声明了变量,但在forEach方法内部修改了变量

就像是:

// some code above
let validInputs = true;

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

在我做了一些研究之后,我在这篇文章中发现了JSHint 错误:在引用外部作用域变量的循环中声明的函数可能会导致语义混乱,提到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描述:

箭头函数根据定义箭头函数的范围建立“this”。来自箭头函数表达式

在循环中创建闭包:一个常见错误一节中,MDN 提到:

另一种替代方法是使用 forEach() 遍历 helpText 数组并将侦听器附加到 each ,如下所示:

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();

在我们的实现中,调用arrow functionsinsideforEach是创建闭包的闭包,这显然会给垃圾收集带来一些令人困惑的语义。

eslint 也不喜欢 index++
2021-05-31 04:05:49
can create some confusing semantics for garbage collection-请不要黄鼠狼无意冒犯,但这句话听起来像是有人在谈论他们知之甚少的事情。我建议要么就 GC 如何被“混淆”做出准确的陈述,要么完全删除该陈述。在 MDN 示例中,可能会发生内存泄漏,因为该函数被分配给了一个 DOM 节点,这与您的情况非常不同。
2021-06-06 04:05:49

您是正确的,修改内部的变量setTimeout会导致问题。您可以通过包装setTimeout在一个 Promise 中并在修改变量之前等待它解决来解决这个问题。使用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;
        }
    }
}

对于每个循环,您都会创建一个Promise,一旦超时到期就会解决。使用await将暂停您的函数的执行,直到Promise解决。然后,您可以修改变量,minimum因为它们不再在您最初传入的回调函数的范围内setTimeout

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

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

使用 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);
    }
  });
}