返回首页

Web components 问题:性能和 API

文章分析了关键的 web components 问题:属性过多样板代码、由于主机代理导致的低性能、全局注册冲突和 Shadow DOM 不便。提供了基准测试和代码示例。

为什么 web components 落后:基准测试和代码
Advertisement 728x90

Web组件性能与API挑战解析

仅通过一个属性创建一个Web组件,使用Custom Elements API就需要编写大量代码。基础实现仅支持字符串属性,其他数据类型必须进行序列化处理。

最小化组件示例

class MyFoo extends HTMLElement {

	_bar = 0
	get bar() {
		return this._bar
	}
	set bar( next ) {
		this.setAttribute( 'bar', next )
	}
	
	static observedAttributes = [ 'bar' ]
	attributeChangedCallback( name, prev, next ) {
		this[ '_' + name ] = next
	}
	
}

globalThis.customElements.define( 'my-foo', MyFoo )

为支持数字类型,需引入JSON序列化,并添加 _muted_ 标志位以防止递归调用:

class MyFoo extends HTMLElement {

	_bar = 0
	get bar() {
		return this._bar
	}
	set bar( next ) {
		this._bar = next
		this._muted_ = true; try {
			this.setAttribute( 'bar', JSON.stringify( next ) )
		} finally { this._muted_ = false }
	}
	
	_muted_ = false
	static observedAttributes = [ 'bar' ]
	attributeChangedCallback( name, prev, next ) {
		if( this._muted_ ) return
		this[ '_' + name ] = JSON.parse( next )
	}
	
}

这带来了属性字符串转义问题,并且对日期、对象等类型还需额外抽象层支持。

Google AdInline article slot

性能:微秒级开销影响帧率

由于运行时代理机制,Web组件的性能比普通JS对象慢几个数量级。创建时间对比:

  • 普通JS对象:约0.01 μs
  • Web组件:约1–3 μs

创建一千个组件耗时1–3毫秒——几乎占满16毫秒的单帧预算。属性变更同样消耗微秒级时间:

  • 简单JS赋值:纳秒级
  • Web组件setter:微秒级

根本原因在于JS与浏览器C++运行时之间持续的数据交换,阻断了JIT内联优化。内存占用方面,每个简单Web组件消耗124字节,而普通JS对象仅需16字节。

Google AdInline article slot

全局注册与命名冲突

组件注册是全局且不可逆的,直到标签页关闭为止:

customElements.define( 'ya-button', YandexButton )
// ...
customElements.define( 'ya-button', YahooButton ) // 💥 错误:名称已存在

命名冲突破坏了模块化设计。尽管Shadow DOM中的CustomElementRegistry提供部分缓解,但缺乏polyfill支持,且合并注册表需要复杂的命名空间管理。

替代方案

  • 类似DNS的全局分形注册表
  • VerLess —— 支持无兼容性中断的更新

生命周期钩子与Shadow DOM

connectedCallback/disconnectedCallback在任何组件移动时都会触发,即使移动到相同位置也会引发子树级联响应,导致不必要的异步任务。

Google AdInline article slot

Shadow DOM虽提供作用域隔离,却使样式管理复杂化:adoptedStyleSheets需从外部动态导入样式。元素名称必须严格采用连字符命名(kebab-case),与标准DOM元素命名规则冲突。

核心问题总结

  • 性能:因宿主代理机制,Web组件比JS对象慢100–1000倍
  • 样板代码:非字符串属性必须强制序列化/反序列化
  • 注册机制:全局唯一、不可撤销,易发生命名冲突
  • 生命周期:移动即触发钩子,引发级联效应
  • Shadow DOM:样式定制困难,polyfill支持不足

Web组件不支持TypeScript静态类型检查或内置响应式能力,实际应用中仍需依赖框架支撑。

— Editorial Team

Advertisement 728x90

继续阅读