在HTML脚本中嵌入的ES6模块中定义的函数对该脚本不可用。因此,如果您有这样的语句:
<button onclick="doSomething();">Do something</button>
中的doSomething()函数存在于HTML脚本中嵌入的ES6模块中,你会得到一个"doSomething() is undefined"运行脚本时出错。
直接在html中使用ES6模块中定义的函数对当前问题提出了一个很好的解决方案,建议您使用"bind">
<button id="dosomethingbutton">Do something</button>
和使用模块本身创建一个链接,这样:
document.getElementById('dosomethingbutton').addEventListener('click', doSomething);
这工作得很好,但如果你原来的按钮有点复杂,是参数化的呢?例如:
<button onclick="doSomething('withThisString');">Do Something with String</button>
最能说明"binding"所能提供的似乎仅限于与事件相关的情况-我找不到将其与数据联系起来的方法。我完全卡住了,试图找到一个解决这个问题的方法,如果能得到帮助,我将非常感激。
我想补充一点,虽然这个问题看起来有点晦涩,但我认为任何迁移到Firebase 9的人都会对它感兴趣。在其他更改中,迁移要求您将javascript代码移动到ES6模块中(其中名称空间不能直接用于HTML DOM),因此最简单的HTML可能会立即遇到这些问题。欢迎多多指教。
这很好,但是如果您的原始按钮更复杂一些并且是参数化的呢?
有两个解决方案:
-
data-*
属性:<button id="the-button" data-string="withThisString">Do Something with String</button>
document.getElementById("the-button").addEventListener("click", function() { doSomething(this.getAttribute("data-string")); });
(后面会详细介绍)
或
-
绑定事件时绑定字符串
<button id="the-button">Do Something with String</button>
document.getElementById("the-button").addEventListener("click", () => { doSomething("withThisString"); });
上面有很多变化,如果你使用doSomething
和不同字符串的多个按钮,你可以用一个类和一个循环来做#1,而不是用id
,但这是一般的想法。
关于data-*
属性的事情:如果您愿意,您可以通过data-*
属性和一个连接事物的单一函数使这个过程完全由html驱动。例如,假设您有这些按钮:
<button data-click="doThisx@module1">Do This</button>
<button data-click="doThat@module2">Do That</button>
<button data-click="doTheOther@module3">Do The Other</button>
你可以用一个可重用的函数把它们连接起来:
class EventSetupError extends Error {
constructor(element, msg) {
if (typeof element === "string") {
[element, msg] = [msg, element];
}
super(msg);
this.element = element;
}
}
export async function setupModuleEventHandlers(eventName) {
try {
const attrName = `data-${eventName}`;
const elements = [...document.querySelectorAll(`[${attrName}]`)];
await Promise.all(elements.map(async element => {
const attrValue = element.getAttribute(`data-${eventName}`);
const [fname, modname] = attrValue ? attrValue.split("@", 2) : [];
if (!fname || !modname) {
throw new EventSetupError(
element,
`Invalid '${attrName}' attribute "${attrValue}"`
);
}
// It's fine if we do import() more than once for the same module,
// the module loader will return the same module
const module = await import(`./${modname}.js`);
const fn = module[fname];
if (typeof fn !== "function") {
throw new EventSetupError(
element,
`Invalid '${attrName}': no '${fname}' on module '${modname}' or it isn't a function`
);
}
element.addEventListener(eventName, fn);
}));
} catch (error) {
console.error(error.message, error.element);
}
}
使用它来查找和连接点击处理程序:
import { setupModuleEventHandlers } from "./data-attr-event-setup.js";
setupModuleEventHandlers("click")
.catch(error => {
console.error(error.message, error.element);
});
这是一次性管道,但在HTML中为您提供相同的基于属性的体验(事件处理程序仍然可以从另一个data-*
属性获取参数信息,或者您可以将其放入设置函数中)。(该示例依赖于动态import
,但所有主流浏览器的最新版本都支持它,并且在不同程度上支持捆绑器。
有几十种方法来旋转它,我不是在推广它,只是举一个例子,如果你想的话,你可以很容易地这样做。
但实际上,这是React, Vue, Ember, Angular, Lit等库发挥作用的地方。
虽然t.j. Crowder已经回答了这个问题,但我想我可能会补充一些难以作为评论的观点。
当我进一步研究Firebase V9转换时,我开始发现"模块名称空间"的一些后果问题非常深刻。我在最初的问题中引用的例子很容易处理,如上所述,但我发现我还需要弄清楚如何处理"动态"。响应来自数据库的可变情况的HTML。在这种情况下,我的javascript最初会创建一个包含HTML块的字符串,如:
realDiv = `
<div>
<button onclick = "function fn1 (param1a, param1b,...);">button1</button>
<button onclick = "function fn2 (param2a, param2b,...);">button2</button>
etc
</div>
`
然后把它扔进"real"在应用程序的HTML框架中使用
定义的realdivdocument.getElementById("realdiv") = realDiv;
现在,由于上面描述的原因,一旦javascript在模块中,这种安排就不再有效了。
我学会采用的模式(再次感谢t.j. Crowder)是这样的:
realDiv = `
<div>
<button id = "button1" data-param1a="param1a" data-param1b="param1b";">button1</button>
<button id = "button2" data-param2a="param2a" data-param2b="param2b";">button2</button>
etc
</div>
`
然后我将生成的代码像以前一样用
扔到我的HTML框架中document.getElementById("realdiv") = readlDiv;
,现在您已经将代码嵌入到DOM中(并假设我已经保留了您生成的按钮数量),我将使用最后一点javascript为它们创建绑定,如:
for (let i = 0; i>buttonCount; i++) {
document.getElementById('button' + i).onclick = function() { fn1 (this.getAttribute('data-param1a'), this.getAttribute('data-param1b') };
etc
}
我发现,当我需要让onclick启动多个不同的功能时,用这种模式创建onclick在保持清晰度方面特别有帮助。