如何解决如何在 JavaScript 的循环中添加 eventListeners 而不触发 JSHint 的混淆语义错误? 更好的解决方案展开警告
我正在使用 DOM 在 JavaScipt 中创建动态项目列表。每个项目都有一个链接(一个 HTML 元素),链接到页面上的另一个元素;允许用户编辑团队的表单。链接工作正常,但我试图通过向链接添加一个 eventListener 来使表单工作,因为表单需要获取我们正在编辑的团队作为参考。
这是有问题的代码:
for (let i = 0; i < teams.length; i++) {
let row = table.insertRow(-1);
//removed code that made cells under the row
let link = document.createElement("a");
link.setAttribute("href","#form");
link.team = teams[i];
link.addEventListener("click",function (e) {
updateForm(link.team);
});
link.textContent = teams[i].name;
//code that adds the link and other elements into the cells of the table
}
为了清晰起见,我删除了部分代码。重要的是 addEventListener,它功能完美。当用户单击按钮时,当前的方式正确地发送有问题的团队。但是,我从 JSHint 收到以下消息:
(error) Functions declared within loops referencing an outer scoped variable may lead to confusing semantics.
如果我理解正确,这是因为变量 table
存在于循环范围之外。但是,我不明白为什么将表放入循环会改变任何事情。我到底应该在代码中更改什么以确保 JSHint 会满意?如果看起来是更好的解决方案,我也可以使用除添加 eventListener 之外的其他方法。
干杯!
解决方法
如果我理解正确,这是因为变量 table 存在于循环范围之外。
link
,而不是 table
。
我到底应该在代码中更改哪些内容以确保 JSHint 会满意?
我从另一面来看:您需要在 JSHint/linting 设置中更改什么,以便在回调中使用 link
的完全有效的代码不会触发 linting 错误?如果您的代码使用 var
,警告将非常有用,但您的代码使用的是 let
。
IIRC,JSHint 比它最初基于的 JSLint 的可配置性要高得多(几年前的现在),您可以查看是否有选项可以在您使用 let
时关闭此特定警告link
。
或者,您可能会考虑另一种 linting 解决方案(ESLint 非常流行)。
但是您可能会更改 team
所指对象的 link
属性的可能性很小。如果你想消除这种可能性并摆脱显式函数,你可以使用 bind
:
link.addEventListener("click",updateForm.bind(null,link.team));
这也将消除 JSHint 警告。
或者由于您在元素本身上使用 expando 属性,您可以使用 this
:
link.addEventListener("click",function() {
updateForm(this.team);
});
...但我建议您不要在 DOM 元素上使用 expando 属性。例如:
for (const team of teams) {
const row = table.insertRow(-1);
//removed code that made cells under the row
const link = document.createElement("a");
link.setAttribute("href","#form");
link.addEventListener("click",() => { // Or:
updateForm(team); // link.addEventListener("click",team));
}); //
link.textContent = team.name;
//code that adds the link and other elements into the cells of the table
}
,
当你在循环 b/c 中定义一个函数时,Linters 曾经抱怨你不是 DRY。 (我有点惊讶 JSHint 没有,但 JSLint 似乎也不再认为这是一个错误!)
与旧版本的 linter 一样,我认为在循环的每次迭代中 [重新] 定义相同的函数在任何时候都是不好的做法。因为函数声明发生在循环中,所以每个单独函数的逻辑与其他函数的逻辑相同。这意味着代码不能是 DRY 的。它还允许像您这样的复杂范围设置!
link.addEventListener("click",function (e) {
updateForm(link.team);
});
通常,您应该将该函数从循环中拉出,并在循环外定义对其的单个引用。
但是...您通过后门将变量传递到您的函数中,并且使用了一些创造性的闭包,这是一个更细微的错误 [linters显然还是在乎]。
这是一种修复 lint 的方法,它使初始代码中范围界定的重要性变得明确(尽管继续阅读!)。
/*jshint esversion: 6 */
/*global updateForm,teams,table */
// Note that this function returns a function containing a closure
// that maintains one `link` per call.
function myListenerFactory(link) {
return function (e) {
updateForm(link.team);
};
}
for (let i = 0; i < teams.length; i++) {
let row = table.insertRow(-1);
//removed code that made cells under the row
let link = document.createElement("a");
link.setAttribute("href","#form");
link.team = teams[i];
// Use the same function each time. Note that it's called IMMEDIATELY,// returning a new function for each iteration.
link.addEventListener("click",myListenerFactory(link)()); // <<< Same function reused in the loop
link.textContent = teams[i].name;
//code that adds the link and other elements into the cells of the table
}
在 jshint.com 上的那些 lint。
此代码使用工厂维护原始闭包,并在每次循环迭代中返回一个作用域包装函数。
很好,它清楚地表明受保护的范围是有意的——你希望每次迭代都有一个新函数,因为范围很重要——并且还限制了其中的内容受保护的范围为 link
而没有别的,但除此之外没有任何好处。
这就是为什么 TJ 说“你在回调中使用 link
的完全有效的代码”——你的代码取决于闭包,所以每次重新定义在技术上都是不同的,尽管只是它的范围。从一开始就是干燥的。它只是看起来不像。它看起来不像它的作用,imo。
(我们指望 variable hoisting 将 link
传递给我们的函数应该是您需要知道的所有 code smell 可能有更好的解决方案。)
更好的解决方案
所以我想稍微强调一下“完全有效”,我认为 TJ 也是如此,说要避免 expando 属性。这不是最容易理解的代码(也不是上面的 lint-passing 替代方案),这就是 linter 所识别的。
让我们尝试寻找更好的选择。
我很确定您可以从 e.target
获得您想要的参考(除非您支持 IE 8 or lower)。然后我们根本不需要传递 link
。我们可以从我们的事件中推导出它。
/*jshint esversion: 6 */
/*global updateForm,table */
function myEventHandler(e) {
// No need to backdoor `link` when we can derive it from `e`!
updateForm(e.target.team);
}
for (let i = 0; i < teams.length; i++) {
let row = table.insertRow(-1);
//removed code that made cells under the row
let link = document.createElement("a");
link.setAttribute("href","#form");
link.team = teams[i];
// Use the same function each time
link.addEventListener("click",myEventHandler);
link.textContent = teams[i].name;
//code that adds the link and other elements into the cells of the table
}
还有繁荣。没有古怪的作用域,没有重复的函数声明。完美。
展开警告
同样,TJ 关于避免“扩展属性”的警告是一个很好的警告。 Expando properties 是在对象初始化后砸到 DOM 元素上的非标准元素。
这些属性可以在没有警告的情况下被其他库相当普遍地编辑和/或删除。您可能最好使用 data
attributes...
... 或者,我的偏好是,通过向您的每个 link
添加唯一元素 ID 来访问查找表。
也就是说,我不喜欢 TJ 的解决方案,即保留您最初的范围依赖的时髦;^D,但它也有效。
有意义吗?
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。