proxy 可作为 custom elements 的响应式核心,通过拦截状态对象的 set 操作自动触发 render 或生命周期回调;需封装私有状态代理、同步 html 属性、抽象基类,并注意嵌套响应、数组变更及内存泄漏等问题。

Proxy 可以作为 Custom Elements 的响应式核心,让 Web Component 自动响应属性变化、触发更新,而无需手动调用 render() 或依赖外部框架。关键在于:用 Proxy 包裹组件的内部状态对象,拦截 set 操作,在变更时自动触发 DOM 同步或生命周期回调。
1. 在自定义元素中创建可响应的状态代理
不要直接把数据挂到 this 上,而是封装一个私有状态对象,并用 Proxy 监听其变更:
class MyCard extends HTMLElement {
constructor() {
super();
const state = { title: '', content: '', loading: false };
this._state = new Proxy(state, {
set(target, key, value) {
target[key] = value;
// 变更后触发更新(可节流)
requestAnimationFrame(() => {
this.render();
});
return true;
}
});
}
render() {
this.innerHTML = `
<div class="card">
<h3>${this._state.title}</h3>
<p>${this._state.content}</p><div class="aritcle_card flexRow artxards">
<div class="artcardd flexRow">
<a class="aritcle_card_img" rel="nofollow" href="/xiazai/skill9263" title="Html2md"><img
src="https://img.php.cn/upload/skill/000/000/081/179249945220127.jpg" alt="Html2md" onerror="this.onerror='';this.src='/static/lhimages/moren/morentu.png'" ></a>
<div class="aritcle_card_info flexColumn">
<a rel="nofollow" href="/xiazai/skill9263" title="Html2md" class="overflowclass">Html2md</a>
<p class="overflowclass">使用 Readability + Turndown 将 HTML 页面转换为干净的、Agent 友好的 Markdown。去除导航栏、广告、页脚、Cookie 横幅、社交按钮等干扰元素。支持 URL...</p>
</div>
<a rel="nofollow" href="/xiazai/skill9263" title="Html2md" class="aritcle_card_btn flexRow flexcenter"><b></b><span>下载</span>
</a>
</div>
</div>
${this._state.loading ? '<span>Loading...</span>' : ''}
</div>
`;
}
}
customElements.define('my-card', MyCard);
2. 支持 HTML 属性与内部状态双向同步
Custom Elements 原生支持 attributeChangedCallback,但需和 Proxy 协同:外部通过 HTML 属性设置时,同步更新 Proxy 状态;Proxy 更新时,也可选择性地 reflect 回 attribute(如布尔值、字符串):
static get observedAttributes() {
return ['title', 'content', 'loading'];
}
attributeChangedCallback(name, oldValue, newValue) {
// 转换类型并写入 Proxy(避免字符串化副作用)
const value = name === 'loading' ? newValue !== null : newValue;
this._state[name] = value;
}
// 在 Proxy 的 set 中,可选地同步回 attribute
set(target, key, value) {
target[key] = value;
// 仅对简单可序列化的属性 reflect
if (['title', 'content'].includes(key)) {
this.setAttribute(key, value);
} else if (key === 'loading') {
if (value) this.setAttribute('loading', '');
else this.removeAttribute('loading');
}
requestAnimationFrame(() => this.render());
return true;
}
3. 封装通用响应式基类提升复用性
避免每个组件重复写 Proxy 逻辑,可抽象出 ReactiveElement 基类:
class ReactiveElement extends HTMLElement {
constructor() {
super();
this._state = this.initState();
this._state = new Proxy(this._state, {
set: (target, key, value) => {
const old = target[key];
target[key] = value;
if (old !== value) this.onStateChange?.(key, old, value);
this.update();
return true;
}
});
}
initState() { return {}; }
update() { this.render?.(); }
render() {}
}
// 使用
class Counter extends ReactiveElement {
initState() {
return { count: 0 };
}
render() {
this.innerHTML = `
<button onclick="this._state.count++">Count: ${this._state.count}</button>
`;
}
}
customElements.define('x-counter', Counter);
4. 注意事项与边界处理
Proxy 不会自动监听嵌套对象变更 —— 若状态含深层结构(如 {user: {name: ''}}),需递归代理或改用 structuredClone + 全量 diff;生产环境建议配合 MutationObserver 或轻量 diff 库做最小更新,而非全量 innerHTML 替换:
- 避免在 Proxy
set中执行耗时操作,用requestIdleCallback或节流控制 - 数组变更(push、splice)无法被默认 Proxy 捕获,需用包装方法或转为 setter 形式(如
addItem(item)) - Shadow DOM 下推荐用
this.shadowRoot.innerHTML替代this.innerHTML,保持封装性 - 组件销毁前应清理 Proxy 引用(尤其涉及定时器或事件监听时),防止内存泄漏
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!










