引言
DOM(文档对象模型)是Web开发的核心,它代表了HTML和XML文档的结构化表示。高效的DOM操作对于构建高性能Web应用至关重要。本文将全面解析DOM操作相关的API、性能优化策略和现代最佳实践。
一、DOM基础与性能考量
1.1 DOM操作的成本分析
DOM操作是浏览器中成本最高的操作之一,理解其性能影响是优化的第一步。
javascript
class DOMPerformanceAnalyzer {
// 重排(Reflow)测试
static testReflow() {
const container = document.createElement('div');
document.body.appendChild(container);
// 连续修改样式导致多次重排
console.time('多次重排');
for (let i = 0; i < 1000; i++) {
container.style.width = i + 'px';
container.style.height = i + 'px';
container.style.padding = i + 'px';
}
console.timeEnd('多次重排');
// 使用cssText批量修改
console.time('批量修改');
let styles = '';
for (let i = 0; i < 1000; i++) {
styles = `width: ${i}px; height: ${i}px; padding: ${i}px;`;
}
container.style.cssText = styles;
console.timeEnd('批量修改');
document.body.removeChild(container);
}
// 重绘(Repaint)优化
static testRepaint() {
const elements = [];
const container = document.createElement('div');
document.body.appendChild(container);
// 创建1000个元素
for (let i = 0; i < 1000; i++) {
const el = document.createElement('div');
el.textContent = 'Item ' + i;
elements.push(el);
}
// 一次性添加到DOM
console.time('一次性添加');
const fragment = document.createDocumentFragment();
elements.forEach(el => fragment.appendChild(el));
container.appendChild(fragment);
console.timeEnd('一次性添加');
document.body.removeChild(container);
}
}
// 性能测试
DOMPerformanceAnalyzer.testReflow();
DOMPerformanceAnalyzer.testRepaint();
1.2 选择器性能优化
不同的DOM选择方法在性能上有显著差异。
javascript
class SelectorPerformance {
static compareSelectors() {
const container = document.createElement('div');
container.id = 'test-container';
for (let i = 0; i < 1000; i++) {
const div = document.createElement('div');
div.className = i % 2 === 0 ? 'even item' : 'odd item';
div.setAttribute('data-index', i);
container.appendChild(div);
}
document.body.appendChild(container);
// 各种选择器的性能比较
const selectors = [
{
name: 'getElementById',
test: () => document.getElementById('test-container')
},
{
name: 'querySelector',
test: () => document.querySelector('#test-container')
},
{
name: 'getElementsByClassName',
test: () => document.getElementsByClassName('even')
},
{
name: 'querySelectorAll (class)',
test: () => document.querySelectorAll('.even')
},
{
name: 'querySelectorAll (attribute)',
test: () => document.querySelectorAll('[data-index]')
}
];
selectors.forEach(selector => {
console.time(selector.name);
for (let i = 0; i < 1000; i++) {
selector.test();
}
console.timeEnd(selector.name);
});
document.body.removeChild(container);
}
// 选择器最佳实践
static selectorBestPractices() {
// 1. 缓存选择器结果
const cachedElements = {
container: document.getElementById('container'),
buttons: document.querySelectorAll('.btn'),
form: document.forms['user-form']
};
// 2. 使用最具体的父元素
const parent = document.getElementById('specific-parent');
const children = parent.querySelectorAll('.child-element');
// 3. 避免过度使用通用选择器
// 不好: document.querySelectorAll('div .item span')
// 好: container.querySelectorAll('.item > span')
// 4. 使用ID选择器最快
const fastest = document.getElementById('unique-id');
return cachedElements;
}
}
二、虚拟DOM实现原理
2.1 基础虚拟DOM实现
javascript
class VNode {
constructor(tag, props = {}, children = []) {
this.tag = tag;
this.props = props;
this.children = children;
this.key = props.key || null;
this.el = null; // 对应的真实DOM
}
// 创建虚拟DOM
static create(tag, props, ...children) {
// 扁平化children数组
const flatChildren = children.reduce((acc, child) => {
if (Array.isArray(child)) {
return acc.concat(child);
} else if (child == null || child === false) {
return acc;
} else {
return acc.concat(child);
}
}, []);
// 处理文本节点
const processedChildren = flatChildren.map(child => {
if (typeof child === 'string' || typeof child === 'number') {
return VNode.createText(child);
}
return child;
});
return new VNode(tag, props || {}, processedChildren);
}
// 创建文本节点
static createText(text) {
return new VNode('#text', { nodeValue: text }, []);
}
// 渲染为真实DOM
render() {
// 文本节点
if (this.tag === '#text') {
return document.createTextNode(this.props.nodeValue);
}
// 创建元素
const el = document.createElement(this.tag);
// 设置属性
for (const [key, value] of Object.entries(this.props)) {
if (key.startsWith('on') && typeof value === 'function') {
// 事件处理
const eventName = key.slice(2).toLowerCase();
el.addEventListener(eventName, value);
} else if (key === 'className') {
// class属性
el.className = value;
} else if (key === 'style' && typeof value === 'object') {
// style对象
Object.assign(el.style, value);
} else if (key !== 'key') {
// 其他属性
el.setAttribute(key, value);
}
}
// 递归渲染子节点
this.children.forEach(child => {
if (child) {
const childEl = child.render();
el.appendChild(childEl);
child.el = childEl; // 保存DOM引用
}
});
this.el = el;
return el;
}
}
// 虚拟DOM使用示例
class VirtualDOMExample {
static createVirtualDOM() {
const vdom = VNode.create(
'div',
{ id: 'app', className: 'container' },
VNode.create('h1', null, 'Hello Virtual DOM'),
VNode.create(
'ul',
{ style: { color: 'blue', padding: '10px' } },
VNode.create('li', { key: '1' }, 'Item 1'),
VNode.create('li', { key: '2' }, 'Item 2'),
VNode.create('li', { key: '3' }, 'Item 3')
),
VNode.create(
'button',
{
onClick: () => alert('Clicked!'),
className: 'btn btn-primary'
},
'Click Me'
)
);
return vdom;
}
static mount(containerId) {
const container = document.getElementById(containerId);
const vdom = this.createVirtualDOM();
const realDOM = vdom.render();
container.appendChild(realDOM);
return vdom;
}
}
2.2 组件化虚拟DOM
javascript
// 组件基类
class Component {
constructor(props = {}) {
this.props = props;
this.state = {};
this.vnode = null;
}
setState(partialState) {
this.state = { ...this.state, ...partialState };
this.update();
}
update() {
const oldVNode = this.vnode;
const newVNode = this.render();
// 执行diff和patch
const patches = VirtualDOM.diff(oldVNode, newVNode);
VirtualDOM.patch(this.vnode.el.parentNode, patches);
this.vnode = newVNode;
}
render() {
throw new Error('render() must be implemented');
}
// 生命周期方法
componentDidMount() {}
componentDidUpdate() {}
componentWillUnmount() {}
}
// 函数式组件支持
class FunctionalComponent extends Component {
constructor(props, renderFn) {
super(props);
this.renderFn = renderFn;
}
render() {
return this.renderFn(this.props);
}
}
// 组件示例
class TodoList extends Component {
constructor(props) {
super(props);
this.state = {
todos: [],
inputValue: ''
};
}
handleInputChange = (e) => {
this.setState({ inputValue: e.target.value });
};
handleAddTodo = () => {
if (this.state.inputValue.trim()) {
this.setState({
todos: [...this.state.todos, this.state.inputValue],
inputValue: ''
});
}
};
handleRemoveTodo = (index) => {
const newTodos = [...this.state.todos];
newTodos.splice(index, 1);
this.setState({ todos: newTodos });
};
render() {
return VNode.create(
'div',
{ className: 'todo-list' },
VNode.create('h2', null, 'Todo List'),
VNode.create(
'div',
{ className: 'input-group' },
VNode.create('input', {
type: 'text',
value: this.state.inputValue,
onInput: this.handleInputChange,
placeholder: 'Add new todo'
}),
VNode.create(
'button',
{ onClick: this.handleAddTodo },
'Add'
)
),
VNode.create(
'ul',
null,
...this.state.todos.map((todo, index) =>
VNode.create(
'li',
{ key: index, className: 'todo-item' },
todo,
VNode.create(
'button',
{
onClick: () => this.handleRemoveTodo(index),
className: 'remove-btn'
},
'×'
)
)
)
)
);
}
}
三、DOM Diff算法深度解析
3.1 完整的Diff算法实现
javascript
class VirtualDOM {
// 比较两个虚拟节点的差异
static diff(oldVNode, newVNode) {
// 如果旧节点不存在, 直接创建新节点
if (!oldVNode) {
return { type: "CREATE", vnode: newVNode };
}
// 如果新节点不存在, 删除旧节点
if (!newVNode) {
return { type: "REMOVE" };
}
// 如果节点类型不同, 直接替换
if (this.isDifferentType(oldVNode, newVNode)) {
return {
type: "REPLACE",
oldVNode,
newVNode,
};
}
// 如果是文本节点, 比较文本内容
if (oldVNode.tag === "#text" && newVNode.tag === "#text") {
if (oldVNode.props.nodeValue !== newVNode.props.nodeValue) {
return {
type: "UPDATE_TEXT",
oldVNode,
newVNode,
};
}
return null;
}
// 比较属性差异
const propsPatches = this.diffProps(oldVNode, newVNode);
// 比较子节点差异
const childrenPatches = this.diffChildren(oldVNode, newVNode);
// 如果有差异, 返回补丁
if (propsPatches.length > 0 || childrenPatches.length > 0) {
return {
type: "UPDATE_ELEMENT",
props: propsPatches,
children: childrenPatches,
vnode: newVNode,
};
}
return null;
}
// 判断节点类型是否不同
static isDifferentType(vnode1, vnode2) {
return vnode1.tag !== vnode2.tag || vnode1.key !== vnode2.key;
}
// 比较属性差异
static diffProps(oldVNode, newVNode) {
const patches = [];
const oldProps = oldVNode.props;
const newProps = newVNode.props;
// 检查新增或修改的属性
for (const [key, newValue] of Object.entries(newProps)) {
const oldValue = oldProps[key];
// 事件处理函数特殊处理
if (key.startsWith("on")) {
if (oldValue !== newValue) {
patches.push({
type: "UPDATE_EVENT",
key,
oldValue,
newValue,
});
}
continue;
}
// 其他属性比较
if (oldValue !== newValue) {
patches.push({
type: "UPDATE_PROP",
key,
value: newValue,
});
}
}
// 检查被删除的属性
for (const key of Object.keys(oldProps)) {
if (!(key in newProps)) {
patches.push({
type: "REMOVE_PROP",
key,
});
}
}
return patches;
}
// 比较子节点差异(使用key优化)
static diffChildren(oldVNode, newVNode) {
const patches = [];
const oldChildren = oldVNode.children;
const newChildren = newVNode.children;
// 使用key映射优化查找
const oldKeyMap = new Map();
oldChildren.forEach((child, index) => {
if (child.key != null) {
oldKeyMap.set(child.key, { child, index });
}
});
let lastIndex = 0;
const newChildrenPatches = [];
// 遍历新子节点
for (let i = 0; i < newChildren.length; i++) {
const newChild = newChildren[i];
const newKey = newChild.key;
let oldChildIndex = -1;
let oldChild = null;
// 通过key查找旧节点
if (newKey != null && oldKeyMap.has(newKey)) {
const item = oldKeyMap.get(newKey);
oldChildIndex = item.index;
oldChild = item.child;
oldKeyMap.delete(newKey); // 从map中移除, 后续剩下的就是需要删除的
} else {
// 如果没有key, 尝试通过位置查找(效率较低)
oldChildIndex = i < oldChildren.length ? i : -1;
oldChild = oldChildIndex !== -1 ? oldChildren[oldChildIndex] : null;
// 检查类型是否匹配
if (oldChild && this.isDifferentType(oldChild, newChild)) {
oldChild = null;
oldChildIndex = -1;
}
}
// 递归比较子节点
const childPath = this.diff(oldChild, newChild);
if (childPath) {
newChildrenPatches.push({
index: i,
oldIndex: oldChildIndex,
patch: childPath,
});
}
// 判断是否需要移动
if (oldChildIndex !== -1) {
if (oldChildIndex < lastIndex) {
// 需要移动
patches.push({
type: "MOVE",
fromIndex: oldChildIndex,
toIndex: i,
});
}
lastIndex = Math.max(lastIndex, oldChildIndex);
}
}
// 处理需要删除的旧节点
for (const { child, index } of oldKeyMap.values()) {
patches.push({
type: "REMOVE_CHILD",
index,
});
}
// 添加子节点补丁
patches.push(...newChildrenPatches);
return patches;
}
// 应用补丁到真实DOM
static patch(parent, patches) {
if (!patches) return;
switch (patches.type) {
case "CREATE":
const newEl = patches.vnode.render();
parent.appendChild(newEl);
patches.vnode.el = newEl;
break;
case "REMOVE":
if (parent.parentNode) {
parent.parentNode.removeChild(parent);
}
break;
case "REPLACE":
const oldEl = patches.oldVNode.el;
const newEl2 = patches.newVNode.render();
oldEl.parentNode.replaceChild(newEl2, oldEl);
patches.newVNode.el = newEl2;
break;
case "UPDATE_TEXT":
patches.oldVNode.textContent = patches.newVNode.props.nodeValue;
patches.newVNode.el = patches.oldVNode.el;
break;
case "UPDATE_ELEMENT":
this.patchElement(patches.oldVNode.el, patches);
patches.vnode.el = patches.oldVNode.el;
break;
}
}
// 应用元素级别的补丁
static patchElement(el, patches) {
// 应用属性补丁
if (patches.props) {
patches.props.forEach((patch) => {
switch (path.type) {
case "UPDATE_PROP":
if (patch.key === "className") {
el.className = patch.value;
} else if (
patch.key === "style" &&
typeof patch.value === "object"
) {
Object.assign(el.style, patch.value);
} else {
el.setAttribute(patch.key, patch.value);
}
break;
case "REMOVE_PROP":
el.removeAttribute(patch.key);
break;
case "UPDATE_EVENT":
const eventName = patch.key.slice(2).toLowetCase();
if (patch.oldValue) {
el.removeEventListener(eventName, patch.oldValue);
}
if (patch.newValue) {
el.addEventListener(eventName, patch.newValue);
}
break;
}
});
}
// 应用子节点补丁
if (patches.children) {
this.patchChildren(el, patches.children);
}
}
// 应用子节点补丁
static patchChildren(parent, patches) {
const childNodes = Array.from(parent.childNodes);
patches.forEach((patch) => {
switch (patch.type) {
case "MOVE":
const childToMove = childNodes[patch.fromIndex];
const referenceNode = childNodes[patch.toIndex] || null;
parent.insertBefore(childToMove, referenceNode);
break;
case "REMOVE_CHILD":
const childToRemove = childNodes[patch.index];
if (childToRemove) {
parent.removeChild(childToRemove);
}
break;
default:
if (patch.patch) {
const childEl =
childNodes[patch.oldIndex] || parent.childNodes[patch.index];
if (childEl) {
this.patch(childEl, patch.patch);
} else if (patch.patch.type === "CREATE") {
const newEl = patch.patch.vnode.render();
const referenceNode = parent.childNodes[patch.index] || null;
parent.insertBefore(newEl, referenceNode);
}
}
break;
}
});
}
}
3.2 Diff算法优化策略
javascript
class OptimizedDiff {
// 双指针算法优化
static optimizedDiff(oldChilren, newChildren) {
let oldStartIdx = 0;
let oldEndIdx = oldChilren.length - 1;
let newStartIdx = 0;
let newEndIdx = newChildren.length - 1;
let oldStartVNode = oldChilren[oldStartIdx];
let oldEndVNode = oldChilren[oldEndIdx];
let newStartVNode = newChildren[newStartIdx];
let newEndVNode = newChildren[newEndIdx];
const patches = [];
const oldKeyToIdx = this.createKeyMap(oldChilren);
while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {
// 跳过已处理的节点
if (!oldStartVNode) {
oldStartVNode = oldChilren[++oldStartIdx];
continue;
}
if (!oldEndVNode) {
oldEndVNode = oldChilren[--oldEndIdx];
continue;
}
// 四种比较情况
if (this.sameVNode(oldStartVNode, newStartVNode)) {
// 情况1: 头头比较
patches.push({
type: "UPDATE",
oldIdx: oldStartIdx,
newIdx: newStartIdx,
});
oldStartVNode = oldChilren[++oldStartIdx];
newStartVNode = newChildren[++newStartIdx];
} else if (this.sameVNode(oldEndVNode, newEndVNode)) {
// 情况2: 尾尾比较
patches.push({
type: "UPDATE",
oldIdx: oldEndIdx,
newIdx: newEndIdx,
});
oldEndVNode = oldChilren[--oldEndIdx];
newEndVNode = newChildren[--newEndIdx];
} else if (this.sameVNode(oldStartVNode, newEndVNode)) {
// 情况3: 头尾比较(需要移动)
patches.push({
type: "MOVE",
fromIdx: oldStartIdx,
toIdx: oldEndIdx,
});
oldStartVNode = oldChilren[++oldStartIdx];
newEndVNode = newChildren[--newEndIdx];
} else if (this.sameVNode(oldEndVNode, newStartVNode)) {
// 情况4: 头尾比较(需要移动)
patches.push({
type: "MOVE",
fromIdx: oldEndIdx,
toIdx: oldStartIdx,
});
oldEndVNode = oldChilren[--oldEndIdx];
newStartVNode = newChildren[++newStartIdx];
} else {
// 情况5: 通过key查找
const idxInOld = oldKeyToIdx.get(newStartVNode.key);
if (idxInOld == null) {
// 新节点, 需要创建
patches.push({
type: "CREATE",
vnode: newStartVNode,
idx: newStartIdx,
});
} else {
// 找到对应节点, 需要移动
const vnodeToMove = oldChilren[idxInOld];
patches.push({
type: "MOVE",
fromIdx: idxInOld,
toIdx: newStartIdx,
});
// 标记为已处理
oldChilren[idxInOld] = undefined;
}
newStartVNode = newChildren[++newStartIdx];
}
}
// 处理剩余节点
if (oldStartIdx > oldEndIdx) {
// 添加新节点
for (let i = newStartIdx; i <= newEndIdx; i++) {
patches.push({
type: "CREATE",
vnode: newChildren[i],
idx: i,
});
}
} else if (newStartIdx > newEndIdx) {
// 删除旧节点
for (let i = oldStartIdx; i <= oldEndIdx; i++) {
if (oldChilren[i]) {
patches.push({
type: "REMOVE",
idx: i,
});
}
}
}
return patches;
}
static createKeyMap(children) {
const map = new Map();
children.forEach((child, idx) => {
if (child && child.key !== null) {
map.set(child.key, idx);
}
});
return map;
}
static sameVNode(vnode1, vnode2) {
return (
vnode1 && vnode2 && vnode1.tag === vnode2.tag && vnode1.key === vnode2.key
);
}
// 事件切片优化
static timeSlicedDiff(oldVNode, newVNode, callback, chunkSize = 10) {
const patches = [];
const queue = [{ oldVNode, newVNode, path: [] }];
function processChunk() {
const startTime = performance.now();
let processed = 0;
while (queue.length > 0 && processed < chunkSize) {
const { oldVNode, newVNode, path } = queue.shift();
const patch = VirtualDOM.diff(oldVNode, newVNode);
if (patch) {
patches.push({ patch, path });
}
// 添加子节点到队列
if (oldVNode && newVNode && oldVNode.children && newVNode.children) {
const maxLen = Math.max(
oldVNode.children.length,
newVNode.children.length
);
for (let i = 0; i < maxLen; i++) {
queue.push({
oldVNode: oldVNode.children[i],
newVNode: newVNode.children[i],
path: [...path, i],
});
}
}
processed++;
// 检查是否超过时间限制
if (performance.now() - startTime > 16) {
// 约一帧的时间
break;
}
}
if (queue.length > 0) {
// 继续处理下一个chunk
requestIdleCallback(processChunk);
} else {
// 完成,执行回调
callback(patches);
}
}
// 开始处理
requestIdleCallback(processChunk);
}
}
四、事件委托高级模式
4.1 完善的事件委托系统
javascript
class EventDelegation {
constructor(rootElement = document) {
this.root = rootElement;
this.handlers = new Map(); // Map<eventType, Map<selector, handler>>
this.eventListeners = new Map(); // Map<eventType, listener>
}
// 注册事件委托
on(eventType, selector, handler, options = {}) {
if (!this.handlers.has(eventType)) {
this.handlers.set(eventType, new Map());
// 为每种事件类型添加一个事件监听器
const listener = (event) => {
this.handleEvent(eventType, event);
};
this.root.addEventListener(eventType, listener, options);
this.eventListeners.set(eventType, listener);
}
const selectorMap = this.handlers.get(eventType);
selectorMap.set(selector, handler);
}
// 移除事件委托
off(eventType, selector) {
const selectorMap = this.handlers.get(eventType);
if (selectorMap) {
selectorMap.delete(selector);
// 如果没有其他选择器,移除事件监听器
if (selectorMap.size === 0) {
const listener = this.eventListeners.get(eventType);
if (listener) {
this.root.removeEventListener(eventType, listener);
this.eventListeners.delete(eventType);
}
this.handlers.delete(eventType);
}
}
}
// 处理事件
handleEvent(eventType, event) {
const selectorMap = this.handlers.get(eventType);
if (!selectorMap) return;
// 从目标元素向上查找匹配的选择器
let element = event.target;
while (element && element !== this.root) {
// 检查所有选择器
for (const [selector, handler] of selectorMap.entries()) {
if (element.matches(selector)) {
// 执行处理器
const result = handler.call(element, event);
// 如果处理器返回false,阻止默认行为和冒泡
if (result === false) {
event.preventDefault();
event.stopPropagation();
}
// 如果处理器返回true,继续检查父元素
if (result !== true) {
return;
}
}
}
element = element.parentElement;
}
}
// 一次性事件
once(eventType, selector, handler) {
const onceHandler = (event) => {
const result = handler.call(event.target, event);
this.off(eventType, selector, onceHandler);
return result;
};
this.on(eventType, selector, onceHandler);
}
// 动态添加支持
static createDynamicDelegation() {
const delegation = new EventDelegation();
// 监听DOM变化,自动处理新元素
const observer = new MutationObserver((mutations) => {
for (const mutation of mutations) {
if (mutation.type === 'childList') {
mutation.addedNodes.forEach(node => {
if (node.nodeType === Node.ELEMENT_NODE) {
delegation.bindNewElement(node);
}
});
}
}
});
observer.observe(document.body, {
childList: true,
subtree: true
});
return delegation;
}
// 绑定新元素
bindNewElement(element) {
// 为元素绑定已注册的事件
for (const [eventType, selectorMap] of this.handlers.entries()) {
for (const [selector, handler] of selectorMap.entries()) {
if (element.matches(selector)) {
element.addEventListener(eventType, handler);
}
}
}
// 递归处理子元素
element.querySelectorAll('*').forEach(child => {
this.bindNewElement(child);
});
}
// 事件节流委托
onThrottled(eventType, selector, handler, delay = 100) {
let lastCall = 0;
let timeoutId = null;
const throttledHandler = (event) => {
const now = Date.now();
const timeSinceLastCall = now - lastCall;
if (timeSinceLastCall >= delay) {
lastCall = now;
handler.call(event.target, event);
} else {
// 取消之前的超时
if (timeoutId) {
clearTimeout(timeoutId);
}
// 设置新的超时
timeoutId = setTimeout(() => {
lastCall = Date.now();
handler.call(event.target, event);
}, delay - timeSinceLastCall);
}
};
this.on(eventType, selector, throttledHandler);
}
// 事件防抖委托
onDebounced(eventType, selector, handler, delay = 100) {
let timeoutId = null;
const debouncedHandler = (event) => {
if (timeoutId) {
clearTimeout(timeoutId);
}
timeoutId = setTimeout(() => {
handler.call(event.target, event);
timeoutId = null;
}, delay);
};
this.on(eventType, selector, debouncedHandler);
}
}
// 使用示例
class EventDelegationExample {
static setupDelegation() {
const delegation = new EventDelegation(document.body);
// 为所有按钮添加点击事件
delegation.on('click', '.btn', function(event) {
console.log('Button clicked:', this.textContent);
});
// 为动态添加的列表项添加事件
delegation.on('click', '.list-item', function(event) {
console.log('List item clicked:', this.dataset.id);
});
// 表单提交防抖
delegation.onDebounced('input', '.search-input', function(event) {
console.log('Search:', this.value);
}, 300);
// 滚动事件节流
delegation.onThrottled('scroll', '.scroll-container', function(event) {
console.log('Scrolling...');
}, 100);
// 一次性事件
delegation.once('click', '.intro-modal .close', function(event) {
this.parentElement.style.display = 'none';
});
return delegation;
}
}
4.2 触摸事件委托
javascript
class TouchEventDelegation extends EventDelegation {
constructor(rootElement = document) {
super(rootElement);
this.touchState = new Map(); // Map<touchId, {element, startX, startY}>
// 初始化触摸事件
this.initTouchEvents();
}
initTouchEvents() {
// 处理触摸开始
this.on('touchstart', '*', (event) => {
for (const touch of event.changedTouches) {
this.touchState.set(touch.identifier, {
element: event.target,
startX: touch.clientX,
startY: touch.clientY,
startTime: Date.now()
});
}
}, { passive: true });
// 处理触摸移动
this.on('touchmove', '*', (event) => {
for (const touch of event.changedTouches) {
const state = this.touchState.get(touch.identifier);
if (state) {
const deltaX = touch.clientX - state.startX;
const deltaY = touch.clientY - state.startY;
// 触发自定义事件
const customEvent = new CustomEvent('touchdrag', {
detail: {
touchId: touch.identifier,
element: state.element,
deltaX,
deltaY,
clientX: touch.clientX,
clientY: touch.clientY
},
bubbles: true
});
state.element.dispatchEvent(customEvent);
}
}
}, { passive: true });
// 处理触摸结束
this.on('touchend', '*', (event) => {
for (const touch of event.changedTouches) {
const state = this.touchState.get(touch.identifier);
if (state) {
const deltaTime = Date.now() - state.startTime;
const deltaX = touch.clientX - state.startX;
const deltaY = touch.clientY - state.startY;
const distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
// 判断是否是点击(快速触摸且移动距离小)
if (deltaTime < 300 && distance < 10) {
const clickEvent = new MouseEvent('click', {
bubbles: true,
cancelable: true,
clientX: touch.clientX,
clientY: touch.clientY
});
state.element.dispatchEvent(clickEvent);
}
this.touchState.delete(touch.identifier);
}
}
}, { passive: true });
}
// 添加滑动手势支持
enableSwipe(selector, options = {}) {
const defaultOptions = {
threshold: 50, // 最小滑动距离
velocity: 0.3, // 最小速度
direction: 'horizontal' // horizontal, vertical, both
};
const config = { ...defaultOptions, ...options };
this.on('touchdrag', selector, (event) => {
const { deltaX, deltaY, element } = event.detail;
// 检查滑动方向
const isHorizontal = Math.abs(deltaX) > Math.abs(deltaY);
const isValidDirection =
config.direction === 'both' ||
(config.direction === 'horizontal' && isHorizontal) ||
(config.direction === 'vertical' && !isHorizontal);
if (!isValidDirection) return;
// 检查滑动距离
const distance = isHorizontal ? Math.abs(deltaX) : Math.abs(deltaY);
if (distance < config.threshold) return;
// 确定滑动方向
let direction = '';
if (isHorizontal) {
direction = deltaX > 0 ? 'right' : 'left';
} else {
direction = deltaY > 0 ? 'down' : 'up';
}
// 触发滑动手势事件
const swipeEvent = new CustomEvent('swipe', {
detail: {
direction,
distance,
element,
deltaX,
deltaY
},
bubbles: true
});
element.dispatchEvent(swipeEvent);
});
}
}
五、自定义事件系统
5.1 高级事件总线
javascript
class EventEmitter {
constructor() {
this.events = new Map();
this.onceEvents = new Map();
this.maxListeners = 10;
this.wildcard = false;
}
// 添加事件监听器
on(eventName, listener) {
if (!this.events.has(eventName)) {
this.events.set(eventName, []);
}
const listeners = this.events.get(eventName);
listeners.push(listener);
// 检查监听器数量限制
if (listeners.length > this.maxListeners) {
console.warn(`Event "${eventName}" has more than ${this.maxListeners} listeners`);
}
return this;
}
// 一次性事件监听器
once(eventName, listener) {
const onceWrapper = (...args) => {
listener.apply(this, args);
this.off(eventName, onceWrapper);
};
// 保存原始监听器引用,以便可以正确移除
onceWrapper.listener = listener;
if (!this.onceEvents.has(eventName)) {
this.onceEvents.set(eventName, new Map());
}
this.onceEvents.get(eventName).set(listener, onceWrapper);
return this.on(eventName, onceWrapper);
}
// 移除事件监听器
off(eventName, listener) {
if (!this.events.has(eventName)) return this;
const listeners = this.events.get(eventName);
// 移除特定监听器
if (listener) {
const index = listeners.indexOf(listener);
if (index !== -1) {
listeners.splice(index, 1);
}
// 同时移除once包装器
if (this.onceEvents.has(eventName)) {
const onceMap = this.onceEvents.get(eventName);
if (onceMap.has(listener)) {
const onceWrapper = onceMap.get(listener);
const wrapperIndex = listeners.indexOf(onceWrapper);
if (wrapperIndex !== -1) {
listeners.splice(wrapperIndex, 1);
}
onceMap.delete(listener);
}
}
} else {
// 移除所有监听器
listeners.length = 0;
if (this.onceEvents.has(eventName)) {
this.onceEvents.get(eventName).clear();
}
}
// 如果监听器数组为空,删除事件
if (listeners.length === 0) {
this.events.delete(eventName);
this.onceEvents.delete(eventName);
}
return this;
}
// 触发事件
emit(eventName, ...args) {
// 触发精确匹配的事件
if (this.events.has(eventName)) {
const listeners = this.events.get(eventName).slice();
// 异步触发
Promise.resolve().then(() => {
listeners.forEach(listener => {
try {
listener.apply(this, args);
} catch (error) {
console.error(`Error in event listener for "${eventName}":`, error);
}
});
});
}
// 触发通配符事件
if (this.wildcard) {
this.emit('*', eventName, ...args);
}
// 触发命名空间事件
const namespaceIndex = eventName.indexOf(':');
if (namespaceIndex !== -1) {
const namespace = eventName.substring(0, namespaceIndex + 1);
const eventWithoutNamespace = eventName.substring(namespaceIndex + 1);
if (this.events.has(namespace)) {
const listeners = this.events.get(namespace).slice();
Promise.resolve().then(() => {
listeners.forEach(listener => {
try {
listener.call(this, eventWithoutNamespace, ...args);
} catch (error) {
console.error(`Error in namespace event listener for "${namespace}":`, error);
}
});
});
}
}
return this;
}
// 获取事件监听器数量
listenerCount(eventName) {
if (!this.events.has(eventName)) return 0;
return this.events.get(eventName).length;
}
// 获取所有事件名称
eventNames() {
return Array.from(this.events.keys());
}
// 设置最大监听器数量
setMaxListeners(n) {
this.maxListeners = n;
return this;
}
// 启用通配符监听
enableWildcard() {
this.wildcard = true;
return this;
}
// 链式操作支持
chain(eventName) {
return {
on: (listener) => {
this.on(eventName, listener);
return this;
},
once: (listener) => {
this.once(eventName, listener);
return this;
},
off: (listener) => {
this.off(eventName, listener);
return this;
},
emit: (...args) => {
this.emit(eventName, ...args);
return this;
}
};
}
}
// 异步事件系统
class AsyncEventEmitter extends EventEmitter {
constructor() {
super();
this.asyncListeners = new Map();
}
// 添加异步事件监听器
onAsync(eventName, listener) {
if (!this.asyncListeners.has(eventName)) {
this.asyncListeners.set(eventName, []);
}
this.asyncListeners.get(eventName).push(listener);
return this;
}
// 异步触发事件
async emitAsync(eventName, ...args) {
const promises = [];
// 同步监听器
if (this.events.has(eventName)) {
this.events.get(eventName).forEach(listener => {
try {
const result = listener.apply(this, args);
if (result instanceof Promise) {
promises.push(result);
}
} catch (error) {
console.error(`Error in sync event listener for "${eventName}":`, error);
}
});
}
// 异步监听器
if (this.asyncListeners.has(eventName)) {
this.asyncListeners.get(eventName).forEach(async listener => {
try {
const result = await listener.apply(this, args);
if (result instanceof Promise) {
promises.push(result);
}
} catch (error) {
console.error(`Error in async event listener for "${eventName}":`, error);
}
});
}
// 等待所有异步操作完成
return Promise.all(promises);
}
// 带有超时的事件触发
async emitWithTimeout(eventName, timeout, ...args) {
const timeoutPromise = new Promise((_, reject) => {
setTimeout(() => reject(new Error(`Event "${eventName}" timeout after ${timeout}ms`)), timeout);
});
const eventPromise = this.emitAsync(eventName, ...args);
return Promise.race([eventPromise, timeoutPromise]);
}
}
// 使用示例
class EventSystemExample {
static createEventSystem() {
const emitter = new AsyncEventEmitter();
// 启用通配符
emitter.enableWildcard();
// 监听所有事件
emitter.on('*', (eventName, ...args) => {
console.log(`Event "${eventName}" emitted with args:`, args);
});
// 异步事件处理
emitter.onAsync('user:login', async (userData) => {
console.log('User login event received:', userData);
// 模拟异步操作
await new Promise(resolve => setTimeout(resolve, 1000));
console.log('Login processing completed');
return { success: true };
});
// 带超时的事件触发
emitter.emitWithTimeout('user:login', 2000, { userId: 123, username: 'john' })
.then(result => console.log('Login successful:', result))
.catch(error => console.error('Login failed:', error));
// 链式调用
emitter.chain('app:start')
.on(() => console.log('App starting...'))
.emit();
return emitter;
}
}
5.2 浏览器原生事件扩展
javascript
class NativeEventExtension {
// 扩展EventTarget原型
static extendEventTarget() {
const originalAddEventListener = EventTarget.prototype.addEventListener;
const originalRemoveEventListener = EventTarget.prototype.removeEventListener;
const originalDispatchEvent = EventTarget.prototype.dispatchEvent;
// 增强addEventListener
EventTarget.prototype.addEventListener = function(type, listener, options) {
// 支持once选项
if (options && options.once) {
const originalListener = listener;
listener = function(...args) {
originalListener.apply(this, args);
this.removeEventListener(type, listener, options);
};
}
// 支持signal选项
if (options && options.signal) {
const abortListener = () => {
this.removeEventListener(type, listener, options);
};
options.signal.addEventListener('abort', abortListener);
// 修改listener以清理signal监听器
const originalListener = listener;
listener = function(...args) {
options.signal.removeEventListener('abort', abortListener);
return originalListener.apply(this, args);
};
}
return originalAddEventListener.call(this, type, listener, options);
};
// 保持removeEventListener不变
EventTarget.prototype.removeEventListener = originalRemoveEventListener;
// 增强dispatchEvent
EventTarget.prototype.dispatchEvent = function(event) {
// 支持异步事件处理
if (event.async) {
Promise.resolve().then(() => {
originalDispatchEvent.call(this, event);
});
return true;
}
return originalDispatchEvent.call(this, event);
};
// 添加自定义方法
EventTarget.prototype.on = function(type, listener, options) {
this.addEventListener(type, listener, options);
return this;
};
EventTarget.prototype.off = function(type, listener, options) {
this.removeEventListener(type, listener, options);
return this;
};
EventTarget.prototype.emit = function(type, detail) {
const event = new CustomEvent(type, { detail });
return this.dispatchEvent(event);
};
EventTarget.prototype.once = function(type, listener) {
this.addEventListener(type, listener, { once: true });
return this;
};
}
// 创建可取消的延迟事件
static createDelayedEvent(target, type, delay, detail) {
let timeoutId = null;
let cancelled = false;
const dispatch = () => {
if (!cancelled) {
target.dispatchEvent(new CustomEvent(type, { detail }));
}
};
timeoutId = setTimeout(dispatch, delay);
return {
cancel: () => {
cancelled = true;
if (timeoutId) {
clearTimeout(timeoutId);
}
},
dispatchNow: () => {
if (!cancelled) {
dispatch();
if (timeoutId) {
clearTimeout(timeoutId);
}
}
}
};
}
// 事件节流装饰器
static throttleEvent(type, delay) {
return (target, propertyKey, descriptor) => {
const originalMethod = descriptor.value;
let lastCall = 0;
let timeoutId = null;
descriptor.value = function(event) {
const now = Date.now();
if (now - lastCall >= delay) {
lastCall = now;
originalMethod.call(this, event);
} else if (!timeoutId) {
timeoutId = setTimeout(() => {
lastCall = Date.now();
originalMethod.call(this, event);
timeoutId = null;
}, delay - (now - lastCall));
}
};
return descriptor;
};
}
// 事件防抖装饰器
static debounceEvent(type, delay) {
return (target, propertyKey, descriptor) => {
const originalMethod = descriptor.value;
let timeoutId = null;
descriptor.value = function(event) {
if (timeoutId) {
clearTimeout(timeoutId);
}
timeoutId = setTimeout(() => {
originalMethod.call(this, event);
timeoutId = null;
}, delay);
};
return descriptor;
};
}
}
// 使用示例
class NativeEventExample {
static setupEnhancedEvents() {
// 扩展原生事件系统
NativeEventExtension.extendEventTarget();
const button = document.createElement('button');
button.textContent = 'Click me';
// 使用新API
button
.on('click', () => console.log('Clicked!'))
.once('mouseenter', () => console.log('Mouse entered (once)'))
.emit('custom', { message: 'Hello' });
// 使用装饰器
class Component {
@NativeEventExtension.throttleEvent('scroll', 100)
handleScroll(event) {
console.log('Throttled scroll:', event);
}
@NativeEventExtension.debounceEvent('input', 300)
handleInput(event) {
console.log('Debounced input:', event.target.value);
}
}
return button;
}
}
六、DOM操作性能优化
6.1 批量DOM更新
javascript
class BatchDOMUpdater {
constructor() {
this.updates = new Map(); // Map<element, {properties, children}>
this.rafId = null;
this.batchSize = 50; // 每批处理的最大元素数量
}
// 计划更新
scheduleUpdate(element, updates) {
if (!this.updates.has(element)) {
this.updates.set(element, {
properties: new Map(),
children: null
});
}
const elementUpdates = this.updates.get(element);
// 合并属性更新
if (updates.properties) {
Object.entries(updates.properties).forEach(([key, value]) => {
elementUpdates.properties.set(key, value);
});
}
// 设置子节点更新
if (updates.children !== undefined) {
elementUpdates.children = updates.children;
}
// 请求动画帧进行批量更新
if (!this.rafId) {
this.rafId = requestAnimationFrame(() => this.processBatch());
}
}
// 处理批量更新
processBatch() {
this.rafId = null;
// 创建文档片段用于批量插入
const fragment = document.createDocumentFragment();
const elementsToUpdate = Array.from(this.updates.entries());
const batch = elementsToUpdate.slice(0, this.batchSize);
// 应用样式和属性更新
batch.forEach(([element, updates]) => {
// 批量应用样式
if (updates.properties.size > 0) {
const styleUpdates = {};
const otherUpdates = {};
updates.properties.forEach((value, key) => {
if (key === 'style' && typeof value === 'object') {
Object.assign(element.style, value);
} else if (key.startsWith('style.')) {
const styleProp = key.slice(6);
element.style[styleProp] = value;
} else if (key === 'className') {
element.className = value;
} else {
otherUpdates[key] = value;
}
});
// 一次性设置其他属性
Object.keys(otherUpdates).forEach(key => {
element.setAttribute(key, otherUpdates[key]);
});
}
// 处理子节点更新
if (updates.children !== null) {
// 清除现有子节点
while (element.firstChild) {
element.removeChild(element.firstChild);
}
// 添加新子节点
if (Array.isArray(updates.children)) {
updates.children.forEach(child => {
if (child instanceof Node) {
element.appendChild(child);
} else if (typeof child === 'string') {
element.appendChild(document.createTextNode(child));
}
});
}
}
});
// 移除已处理的更新
batch.forEach(([element]) => {
this.updates.delete(element);
});
// 如果还有剩余更新,继续处理
if (this.updates.size > 0) {
this.rafId = requestAnimationFrame(() => this.processBatch());
}
}
// 取消所有计划中的更新
cancel() {
if (this.rafId) {
cancelAnimationFrame(this.rafId);
this.rafId = null;
}
this.updates.clear();
}
// 静态方法:快速批量创建元素
static createElements(tagName, count, properties = {}) {
const fragment = document.createDocumentFragment();
for (let i = 0; i < count; i++) {
const element = document.createElement(tagName);
// 应用属性
Object.entries(properties).forEach(([key, value]) => {
if (key === 'style' && typeof value === 'object') {
Object.assign(element.style, value);
} else if (key === 'className') {
element.className = value;
} else if (key.startsWith('data-')) {
element.setAttribute(key, value);
} else {
element[key] = value;
}
});
fragment.appendChild(element);
}
return fragment;
}
// 静态方法:使用模板克隆
static cloneFromTemplate(templateId, count, data = []) {
const template = document.getElementById(templateId);
if (!template || template.tagName !== 'TEMPLATE') {
throw new Error('Template not found');
}
const fragment = document.createDocumentFragment();
for (let i = 0; i < count; i++) {
const clone = document.importNode(template.content, true);
// 填充数据
if (data[i]) {
this.fillTemplate(clone, data[i]);
}
fragment.appendChild(clone);
}
return fragment;
}
static fillTemplate(node, data) {
// 处理文本节点
if (node.nodeType === Node.TEXT_NODE) {
const text = node.textContent;
const filledText = text.replace(/\{\{(\w+)\}\}/g, (match, key) => {
return data[key] || match;
});
if (filledText !== text) {
node.textContent = filledText;
}
}
// 处理元素节点
if (node.nodeType === Node.ELEMENT_NODE) {
// 填充属性
Array.from(node.attributes).forEach(attr => {
const value = attr.value;
const filledValue = value.replace(/\{\{(\w+)\}\}/g, (match, key) => {
return data[key] || match;
});
if (filledValue !== value) {
node.setAttribute(attr.name, filledValue);
}
});
// 递归处理子节点
Array.from(node.childNodes).forEach(child => {
this.fillTemplate(child, data);
});
}
}
}
6.2 使用MutationObserver监控DOM变化
javascript
class DOMChangeMonitor {
constructor(options = {}) {
this.options = {
childList: true,
subtree: true,
attributes: true,
attributeOldValue: true,
characterData: true,
characterDataOldValue: true,
...options
};
this.observer = new MutationObserver(this.handleMutations.bind(this));
this.callbacks = {
added: new Set(),
removed: new Set(),
attributeChanged: new Set(),
textChanged: new Set()
};
this.isObserving = false;
}
// 开始监控
observe(target = document.documentElement) {
if (this.isObserving) {
this.disconnect();
}
this.observer.observe(target, this.options);
this.isObserving = true;
return this;
}
// 停止监控
disconnect() {
this.observer.disconnect();
this.isObserving = false;
return this;
}
// 处理变化
handleMutations(mutations) {
mutations.forEach(mutation => {
switch (mutation.type) {
case 'childList':
this.handleChildListMutation(mutation);
break;
case 'attributes':
this.handleAttributeMutation(mutation);
break;
case 'characterData':
this.handleCharacterDataMutation(mutation);
break;
}
});
}
// 处理子节点变化
handleChildListMutation(mutation) {
// 新增节点
mutation.addedNodes.forEach(node => {
this.callbacks.added.forEach(callback => {
callback(node, mutation.target);
});
});
// 移除节点
mutation.removedNodes.forEach(node => {
this.callbacks.removed.forEach(callback => {
callback(node, mutation.target);
});
});
}
// 处理属性变化
handleAttributeMutation(mutation) {
this.callbacks.attributeChanged.forEach(callback => {
callback({
target: mutation.target,
attributeName: mutation.attributeName,
oldValue: mutation.oldValue,
newValue: mutation.target.getAttribute(mutation.attributeName)
});
});
}
// 处理文本变化
handleCharacterDataMutation(mutation) {
this.callbacks.textChanged.forEach(callback => {
callback({
target: mutation.target,
oldValue: mutation.oldValue,
newValue: mutation.target.textContent
});
});
}
// 注册回调
on(event, callback) {
if (this.callbacks[event]) {
this.callbacks[event].add(callback);
}
return this;
}
off(event, callback) {
if (this.callbacks[event]) {
this.callbacks[event].delete(callback);
}
return this;
}
// 一次性监控
once(event, callback) {
const wrapper = (...args) => {
callback(...args);
this.off(event, wrapper);
};
this.on(event, wrapper);
return this;
}
// 获取快照
takeSnapshot(target = document.documentElement) {
const snapshot = {
timestamp: Date.now(),
html: target.outerHTML,
children: Array.from(target.children).map(child => ({
tagName: child.tagName,
attributes: Array.from(child.attributes).reduce((acc, attr) => {
acc[attr.name] = attr.value;
return acc;
}, {}),
childrenCount: child.children.length
}))
};
return snapshot;
}
// 比较快照
static compareSnapshots(snapshot1, snapshot2) {
const differences = [];
// 比较HTML
if (snapshot1.html !== snapshot2.html) {
differences.push({
type: 'html',
before: snapshot1.html,
after: snapshot2.html
});
}
// 比较子节点数量
if (snapshot1.children.length !== snapshot2.children.length) {
differences.push({
type: 'childrenCount',
before: snapshot1.children.length,
after: snapshot2.children.length
});
}
// 比较子节点属性
const maxLength = Math.max(snapshot1.children.length, snapshot2.children.length);
for (let i = 0; i < maxLength; i++) {
const child1 = snapshot1.children[i];
const child2 = snapshot2.children[i];
if (!child1 && child2) {
differences.push({
type: 'childAdded',
index: i,
child: child2
});
} else if (child1 && !child2) {
differences.push({
type: 'childRemoved',
index: i,
child: child1
});
} else if (child1 && child2) {
// 比较标签名
if (child1.tagName !== child2.tagName) {
differences.push({
type: 'tagNameChanged',
index: i,
before: child1.tagName,
after: child2.tagName
});
}
// 比较属性
const allAttrs = new Set([
...Object.keys(child1.attributes),
...Object.keys(child2.attributes)
]);
allAttrs.forEach(attr => {
const val1 = child1.attributes[attr];
const val2 = child2.attributes[attr];
if (val1 !== val2) {
differences.push({
type: 'attributeChanged',
index: i,
attribute: attr,
before: val1,
after: val2
});
}
});
}
}
return differences;
}
}
// 使用示例
class DOMMonitorExample {
static setupMonitoring() {
const monitor = new DOMChangeMonitor();
// 监控元素添加
monitor.on('added', (node, parent) => {
console.log('Node added:', node.nodeName, 'to', parent.nodeName);
// 自动为新元素添加事件监听器
if (node.nodeType === Node.ELEMENT_NODE) {
if (node.matches('.dynamic-element')) {
node.addEventListener('click', () => {
console.log('Dynamic element clicked');
});
}
}
});
// 监控属性变化
monitor.on('attributeChanged', ({ target, attributeName, oldValue, newValue }) => {
console.log(`Attribute changed: ${attributeName}`, { oldValue, newValue });
});
// 开始监控整个文档
monitor.observe();
// 获取快照
const snapshot1 = monitor.takeSnapshot();
// 做一些DOM修改
const div = document.createElement('div');
div.className = 'dynamic-element';
div.textContent = 'New Element';
document.body.appendChild(div);
// 获取另一个快照并比较
setTimeout(() => {
const snapshot2 = monitor.takeSnapshot();
const diff = DOMChangeMonitor.compareSnapshots(snapshot1, snapshot2);
console.log('DOM changes:', diff);
}, 100);
return monitor;
}
}
总结
DOM操作是现代Web开发的核心技能,掌握高效的DOM操作技术对于构建高性能应用至关重要。本文涵盖了:
- DOM性能优化: 理解重排重绘、选择器优化
- 虚拟DOM实现: 原理、实现和实际应用
- DOM Diff算法: 核心算法、优化策略
- 事件委托: 高级模式、触摸事件处理
- 自定义事件系统: 事件总线、异步事件处理
- 批量DOM更新: 性能优化技巧
- DOM变化监控: MutationObserver高级用法
关键要点:
- 尽量减少直接DOM操作,使用虚拟DOM或批量更新
- 合理使用事件委托减少内存占用
- 利用浏览器API如MutationObserver监控DOM变化
- 掌握自定义事件系统实现组件通信
- 始终考虑性能影响,避免不必要的重排重绘
通过本文的学习,你应该能够构建高效、可维护的DOM操作代码,为开发复杂Web应用打下坚实基础。