警告不要在循环中执行函数

Posted

技术标签:

【中文标题】警告不要在循环中执行函数【英文标题】:Warning not to make function within a loop 【发布时间】:2018-09-26 12:41:22 【问题描述】:

我编写了一个代码来为 div 容器创建模态窗口。单击按钮后,我会获取按钮的编号并显示相关的模式窗口。经过测试,适用于所有浏览器。

myModalContent = new tingle.modal();
var myBtn = document.querySelectorAll("button.project__btn");
for (var i = 0; i < myBtn.length; i++) 
myBtn[i].addEventListener("click", function () 
    myModalContent.open();
    if (this.hasAttribute("data-btn")) 
        myModalContent.setContent(document.querySelector(".project" + this.getAttribute("data-btn") + "-modal").innerhtml);
     else 
        myModalContent.setContent(document.querySelector(".project1-modal").innerHTML);
    
);

一个 js 验证器会给出一个警告“不要在循环中创建函数”。 看了一些与这个主题相关的帖子,特别是函数必须在循环之外创建,我创建了一个函数:

 function handler(modalDiv, trigBtn, index)
    modalDiv.open();
    if (trigBtn[index].hasAttribute("data-btn")) 
        modalDiv.setContent(document.querySelector(".project" + trigBtn[index].getAttribute("data-btn") + "-modal").innerHTML);
     else 
        modalDiv.setContent(document.querySelector(".project1-modal").innerHTML);
    

然后在循环中调用它:

for (var i = 0; i < myBtn.length; i++) 
    myBtn[i].onclick = handler(myModalContent, myBtn, i);

它似乎无法正常工作,它会在网页加载后立即显示最后一个模式窗口。我的理解是该函数必须与单击事件监听器连接,即单击按钮时,应弹出模态窗口。现在,模式窗口弹出,没有任何点击事件。你能给我一个如何正确编写函数的想法吗?或者我是否应该简单地忽略这个 js 验证警告。

【问题讨论】:

为什么要更改函数的内容,为什么现在尝试调用它而不是像以前那样将其安装为处理程序? 【参考方案1】:

保持简单!您无需更改任何代码,只需将函数表达式移动到循环体外部的命名函数声明:

var myModalContent = new tingle.modal();
var myBtn = document.querySelectorAll("button.project__btn");
function myHandler() 
    myModalContent.open();
    if (this.hasAttribute("data-btn")) 
        myModalContent.setContent(document.querySelector(".project" + this.getAttribute("data-btn") + "-modal").innerHTML);
     else 
        myModalContent.setContent(document.querySelector(".project1-modal").innerHTML);
    

for (var i = 0; i < myBtn.length; i++) 
    myBtn[i].addEventListener("click", myHandler);

【讨论】:

我明白了。实际上,所有答案都是有用的,有助于将解决方案分解成碎片。我明白你的意思了。将代码块移动到函数表达式后,将其作为函数引用添加到事件侦听器中。或者,当处理函数返回自身时,它被用作事件侦听器内部的函数调用。那么,避免污染全局作用域并将所有代码放入 IIFE (function () // code )() 是否有意义? @holler 我不明白您所说的“处理函数返回自身”是什么意思? @holler 是的,将myModalContentmyBtnmyHandleri 保留在本地范围内是有意义的。【参考方案2】:

警告试图防止“修改后的闭包”出现问题。如果您的函数对变量i 做了任何操作,那么您会发现在用户单击按钮时变量i 的值始终为myBtn.length,因为这是它最后的值循环。

这个:

for (var i = 0; i < myBtn.length; i++) 
...

被这样对待:

var i;
for (i = 0; i < myBtn.length; i++) 
...

由于您没有在函数中的任何地方使用i,因此您在技术上是安全的,但未来其他开发人员可能会更改代码并最终遇到此问题。

为了以您尝试修复它的方式修复此代码,您需要拥有 handler 函数返回一个函数本身。

myBtn[i].addEventListener("click", createHandler());

function createHandler() 
    return function() 
        myModalContent.open();
        if (this.hasAttribute("data-btn")) 
            myModalContent.setContent(document.querySelector(".project" + this.getAttribute("data-btn") + "-modal").innerHTML);
         else 
            myModalContent.setContent(document.querySelector(".project1-modal").innerHTML);
        
    ;

这与您的工作代码具有相同的效果,但可以防止有人尝试在闭包内使用i。如果有人在那里需要i,他们可以将其添加到createHandler 的参数列表中,它不会为每次循环重复使用相同的变量。

或者,如果您可以使用现代版本的 javascript,您可以使用 let 关键字而不是 var

这个:

for (let i = 0; i < myBtn.length; i++) 
...

更像是这段代码在 C# 等语言中的工作方式:

for (var _ = 0; _ < myBtn.length; _++) 
    var i = _;
...

换句话说,i 变量的范围是 for 循环内部的,而不是你所在函数的全局范围。

【讨论】:

出于好奇,为什么你必须在createHandler() 函数中返回一个函数。过去,我只编写了函数function openModal() myModalContent.open().../* rest of code here */.. ,然后在将函数添加到事件时,只需编写myBtn[i].addEventListener('click', openModal)。请注意,在 addEventListener 中它没有使用括号,因为它会调用它,在这种情况下您需要返回一个函数。由于我们没有调用它,只是说在点击事件中使用该函数 如果createHandler不带任何参数,则根本不需要该函数。 警告不是因为闭包可能存在问题,而是因为创建了多个函数,单个对象就足够了。这是性能优化,仅此而已。 (如果有闭包,则需要多个函数,并且不会出现警告)。 @Tyler 和@Bergi,如果不需要根据for 循环中的位置传递参数,您可以只使用一个声明的函数作为处理程序。我正在查看 OP 似乎是如何尝试解决问题的,并解释了为什么他的方法不起作用。

以上是关于警告不要在循环中执行函数的主要内容,如果未能解决你的问题,请参考以下文章

Vue.js 警告您可能在组件渲染函数中有无限更新循环

如何修复或抑制误报“您可能在组件渲染函数中有无限更新循环”Vue 警告

用 keil编译的时候有两个警告怎么回事

Vue 组件中的“组件渲染函数中可能存在无限更新循环”警告

不要在我无法修复的循环错误中创建函数

警告:不要多次处理对象[重复]