为何preventDefault无法阻止自定义元素聊天应用页面重载?
问题
使用原生JavaScript开发自定义元素形式的聊天应用,每次提交消息(尚未实现实际发送逻辑)时页面都会重载,根本看不到控制台日志。
消息表单HTML代码如下:
<template id="chatapptemplate"> <form hidden id="messageForm"> <textarea></textarea> <input type="submit" value="Send message"> </form> </template>
自定义元素最小可复现示例代码:
customElements.define('chat-app', class chatApp extends HTMLElement { constructor () { super() this.attachShadow({ mode: 'open' }) const chatAppTemplate = document.querySelector('#chatapptemplate') this.shadowRoot.appendChild(chatAppTemplate.content.cloneNode(true)) this.messageForm = this.shadowRoot.querySelector('#messageForm') this.messageDetails = { 'data': 'This object contains more fields, but this is enough for my example.' } } connectedCallback () { this.messageForm.addEventListener('submit', this.messageWasSubmitted) } disconnectedCallback () { this.messageForm.removeEventListener('submit', this.messageWasSubmitted) } messageWasSubmitted = (e) => { e.preventDefault() e.stopImmediatePropagation() const message = e.target.firstElementChild.value this.messageDetails['data'] = message console.log(this.messageDetails) if (message.length > 0) { e.target.firstElementChild.value = '' } } })
解决方案
- 页面重载的直接原因:表单的默认提交行为没被阻止,因为你的
messageWasSubmitted事件处理函数根本没执行。 - 为什么事件函数没执行:你用了类字段语法(箭头函数)定义
messageWasSubmitted,这种写法在自定义元素的生命周期中存在兼容性问题——部分浏览器中,connectedCallback执行时类字段还未完成初始化,导致绑定的是undefined;另外,类字段每次实例化都会生成新的函数引用,后续removeEventListener也无法正确移除事件,但这不是当前页面重载的核心原因。
修复方法(二选一即可)
方法一:普通类方法 + 手动绑定this
将事件处理函数改为普通类方法,在constructor中绑定this,确保上下文正确且函数引用唯一:
customElements.define('chat-app', class chatApp extends HTMLElement { constructor () { super() this.attachShadow({ mode: 'open' }) const chatAppTemplate = document.querySelector('#chatapptemplate') this.shadowRoot.appendChild(chatAppTemplate.content.cloneNode(true)) this.messageForm = this.shadowRoot.querySelector('#messageForm') // 绑定this到当前实例 this.messageWasSubmitted = this.messageWasSubmitted.bind(this) this.messageDetails = { 'data': 'This object contains more fields, but this is enough for my example.' } } connectedCallback () { this.messageForm.addEventListener('submit', this.messageWasSubmitted) } disconnectedCallback () { this.messageForm.removeEventListener('submit', this.messageWasSubmitted) } // 改为普通类方法 messageWasSubmitted(e) { e.preventDefault() e.stopImmediatePropagation() // 用querySelector更可靠,避免DOM结构变化出错 const message = e.target.querySelector('textarea').value this.messageDetails['data'] = message console.log(this.messageDetails) if (message.length > 0) { e.target.querySelector('textarea').value = '' } } })
方法二:箭头函数包裹 + 保存引用
在connectedCallback中用箭头函数包裹事件处理,同时保存这个函数引用,方便后续移除:
customElements.define('chat-app', class chatApp extends HTMLElement { constructor () { super() this.attachShadow({ mode: 'open' }) const chatAppTemplate = document.querySelector('#chatapptemplate') this.shadowRoot.appendChild(chatAppTemplate.content.cloneNode(true)) this.messageForm = this.shadowRoot.querySelector('#messageForm') // 保存箭头函数引用 this.submitHandler = (e) => this.messageWasSubmitted(e) this.messageDetails = { 'data': 'This object contains more fields, but this is enough for my example.' } } connectedCallback () { this.messageForm.addEventListener('submit', this.submitHandler) } disconnectedCallback () { this.messageForm.removeEventListener('submit', this.submitHandler) } messageWasSubmitted(e) { e.preventDefault() e.stopImmediatePropagation() const message = e.target.querySelector('textarea').value this.messageDetails['data'] = message console.log(this.messageDetails) if (message.length > 0) { e.target.querySelector('textarea').value = '' } } })
额外建议
不要用firstElementChild定位文本框,改用querySelector('textarea'),这样即使后续DOM结构调整,代码也不会轻易出错。
内容的提问来源于stack exchange,提问作者Dominus
相关产品推荐
相关产品推荐

