导入JS函数后HTML自定义选择菜单消失且功能失效的原因
我借助他人帮助实现了一个自定义样式的HTML选择菜单,预期用户点击「Next」按钮时,网站向数据库发送SQL请求,返回匹配所选音乐流派的首位艺术家。
我的代码如下:
HTML文件
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Custom Select Menu</title> <link rel="stylesheet" href="main.css"> </head> <body> <header> <h1>Welcome</h1> </header> <div class="genre" id="musicGenre"> <h2>Choose your favorite Music Genre::</h2> <form action=""> <select name="musicGenre" class="musicGenre"> <option value="hiphop">HipHop</option> <option value="classic">Classic Music</option> <option value="pop">Pop</option> <option value="techno">Techno</option> </select> </form> <button onclick="nextStep()">Next</button> </div> <div id="result" style="display: none;"> <h2>Result:</h2> <p id="artistResult"></p> </div> <script src="main.js"></script> </body> </html>
main.js(未添加import时正常)
var genre; class MusicGenre { constructor(originalSelect) { this.originalSelect = originalSelect; this.customSelect = document.createElement("div"); this.customSelect.classList.add("genreSelect"); this.originalSelect.querySelectorAll("option").forEach(optionElement => { const itemElement = document.createElement("div"); itemElement.classList.add("select__item"); itemElement.textContent = optionElement.textContent; this.customSelect.appendChild(itemElement); if (optionElement.selected) { this._select(itemElement); } itemElement.addEventListener("click", () => { if ( this.originalSelect.multiple && itemElement.classList.contains("select__item--selected") ) { this._deselect(itemElement); } else { this._select(itemElement); genre = Array.from(this.customSelect.children).indexOf(itemElement); } }); }); this.originalSelect.insertAdjacentElement("afterend", this.customSelect); this.originalSelect.style.display = "none"; } _select(itemElement) { const index = Array.from(this.customSelect.children).indexOf(itemElement); if (!this.originalSelect.multiple) { this.customSelect.querySelectorAll(".select__item").forEach(element => { element.classList.remove("select__item--selected") }); } this.originalSelect.querySelectorAll("option")[index].selected = true; itemElement.classList.add("select__item--selected"); } _deselect(itemElement) { const index = Array.from(this.customSelect.children).indexOf(itemElement); this.originalSelect.querySelectorAll("option")[index].selected = false; itemElement.classList.remove("select__item--selected"); } } document.querySelectorAll(".musicGenre").forEach(selectElement => { new MusicGenre(selectElement); }) function nextStep() { document.getElementById("result").style.display = "block"; document.getElementById("artTypeCategory").style.display = "none"; const result = await getArtistName(genre+1); const prompText = JSON.stringify(result[0]); document.getElementById("artTypeResult").innerHTML = promptText; }
main.css
.genreSelect{ display: grid; grid-template-columns: repeat(2, 1fr); max-width: 300px; text-align: center; gap: 1px; } .select__item{ padding: 10px; cursor: pointer; font-family: "Heebo", sans-serif; border-radius: 3px; background: #eeeeee; transition: background 0.1s; } .select__item--selected{ background: #009578; color: #ffffff; }
database.js
import mysql from 'mysql2' const pool = mysql.createPool({ host: 'localhost', user: 'root', password: '', database: 'artist' }).promise() export async function getArtistName(id) { const [rows] = await pool.query(` SELECT Name FROM Artists WHERE ID = ? `, id) return rows }
原本菜单显示正常,但在main.js顶部添加import { getArtistName } from "./database.js"语句后,菜单变为原生样式,「Next」按钮也失效,无法获取SQL结果。请问该导入操作为何会破坏代码?
ES模块未被正确识别
添加import语句后,main.js变成了ES模块,但HTML中引用脚本时没有添加type="module"属性,浏览器会把它当作普通脚本解析,import语法直接报错,导致后续所有JS代码(包括自定义选择菜单的逻辑)停止执行,菜单因此回到原生样式。模块作用域隔离全局变量
ES模块有独立作用域,原来的genre变量和nextStep函数会被限制在模块内部,而HTML按钮的onclick="nextStep()"是在全局作用域调用,自然找不到该函数,导致按钮失效。Node.js模块无法在浏览器环境运行
database.js依赖的mysql2是Node.js专属模块,浏览器环境无法识别它,就算解决了模块加载问题,也会因缺少Node.js环境而报错,前端不能直接连接数据库。
正确加载ES模块
修改HTML中的脚本引用,添加type="module"属性:<script type="module" src="main.js"></script>解决全局作用域调用问题
把nextStep函数挂载到全局对象,或者改用事件监听:- 方案一(挂载全局):在
main.js末尾添加window.nextStep = nextStep; - 方案二(事件监听):修改HTML按钮为
<button id="nextBtn">Next</button>,然后在main.js中添加document.getElementById('nextBtn').addEventListener('click', nextStep);
- 方案一(挂载全局):在
改用后端接口实现数据库查询
前端不能直接连接数据库,需通过后端接口中转:- 用Node.js+Express写后端接口(示例):
// server.js import express from 'express'; import { getArtistName } from './database.js'; const app = express(); app.use(express.json()); app.get('/api/artist/:id', async (req, res) => { const id = req.params.id; const artist = await getArtistName(id); res.json(artist); }); app.listen(3000, () => { console.log('Server running on port 3000'); }); - 修改前端
nextStep函数:async function nextStep() { document.getElementById("result").style.display = "block"; document.getElementById("musicGenre").style.display = "none"; // 修正原代码中不存在的artTypeCategory try { const response = await fetch(`http://localhost:3000/api/artist/${genre + 1}`); const result = await response.json(); document.getElementById("artistResult").textContent = result[0]?.Name || '未找到艺术家'; } catch (error) { console.error('获取艺术家失败:', error); document.getElementById("artistResult").textContent = '加载艺术家失败'; } }
- 用Node.js+Express写后端接口(示例):
内容的提问来源于stack exchange,提问作者Tobi

