
本文详解为何在 document.createElement() 中无法通过构造函数读取属性,以及如何利用 connectedCallback 在元素挂载到 DOM 时安全初始化属性与 Shadow DOM。
本文详解为何在 `document.createelement()` 中无法通过构造函数读取属性,以及如何利用 `connectedcallback` 在元素挂载到 dom 时安全初始化属性与 shadow dom。
在 Web Components 开发中,一个常见误区是:试图在自定义元素的 constructor 中读取 HTML 属性(如 src、href)并立即操作 Shadow DOM。但事实上,当调用 document.createElement('div-item') 时,该元素尚未插入 DOM,其属性虽已存在,但此时执行 this.getAttribute() 可能返回 null(尤其在 SSR 或快速创建场景下),且更关键的是——constructor 阶段禁止执行任何依赖 DOM 连接状态的操作(如 attachShadow 后立即写入 innerHTML),因为此时元素尚未“连接”(connected),部分浏览器会静默失败或延迟渲染。
正确的做法是将属性读取与 Shadow DOM 初始化逻辑移至 connectedCallback 生命周期钩子中。该回调在元素首次被添加到文档(DOM)时触发,此时属性已稳定可读,且可安全调用 attachShadow 并渲染内容:
customElements.define('div-item', class extends HTMLElement {
connectedCallback() {
// ✅ 安全读取属性:此时元素已挂载,属性值确定
const src = this.getAttribute('src') || '';
const href = this.getAttribute('href') || '#';
const text = this.textContent.trim();
// ✅ 安全创建 Shadow DOM
const shadow = this.attachShadow({ mode: 'open' });
// ✅ 渲染模板(注意:避免直接拼接未转义内容,生产环境建议使用 innerHTML + sanitizer 或 template)
shadow.innerHTML = `
<style>
:host { display: inline-block; }
.bg { width: 100px; height: 100px; background: #f0f0f0; }
img { width: 100%; height: 100%; object-fit: cover; }
p { margin: 4px 0; font-size: 14px; }
</style><div>
<a href="%24%7Bhref%7D">
<div class="bg">
@@##@@
</div>
<p>${text}</p>
</a>
</div>
`;
}
});
同时,JavaScript 动态创建逻辑也需优化:
- ❌ 避免在循环中多次调用
row.appendChild(elem)—— 每次都会触发重排(reflow)与重绘(repaint),性能低下; - ✅ 改用
row.append(...items)批量插入,仅触发一次布局更新; - ✅ 使用扩展运算符(
...)和数组批量构建,提升可读性与效率。
示例优化后的创建函数:
function createDivItems() {
const rows = [...document.querySelectorAll('.row')]; // 转为真数组,便于 map
const itemCount = Math.max(1, Math.ceil((window.innerWidth - 60) / 120));
rows.forEach(row => {
row.innerHTML = ''; // 清空旧内容(也可用 row.replaceChildren())
const items = Array.from({ length: itemCount }, (_, i) => {
const elem = document.createElement('div-item');
elem.setAttribute('src', 'assets/not-found.png');
elem.setAttribute('href', '/newPage.php');
elem.textContent = `Item ${i + 1}`; // 设置 light DOM 文本内容
return elem;
});
row.append(...items); // ⚡ 一次性插入全部元素
});
}
// 建议:监听窗口大小变化时重新生成(防抖处理)
let resizeTimer;
window.addEventListener('resize', () => {
clearTimeout(resizeTimer);
resizeTimer = setTimeout(createDivItems, 100);
});
// 初始调用
createDivItems();
⚠️ 注意事项:
- 不要在
constructor中调用this.textContent或this.innerHTML,因其可能为空(light DOM 尚未解析完毕); -
connectedCallback可能被多次调用(如元素被移出再插入 DOM),若需仅执行一次初始化,请配合this._initialized标志位; - 属性值应做空值校验与 HTML 转义(尤其是
href和alt),防止 XSS; - 若需响应属性变更(如
src动态修改),应实现attributeChangedCallback并声明observedAttributes。
总结:自定义元素的初始化必须遵循 Web Components 生命周期规范。将属性读取与 Shadow DOM 渲染统一交由 connectedCallback 处理,配合批量 DOM 操作,即可彻底解决“空方块”问题,并保障响应式渲染的稳定性与性能。











