Search K
Appearance
Appearance
📊 SEO元描述:2024年最新JavaScript虚拟DOM实现教程,详解简单虚拟DOM实现、JSX转换原理、完整虚拟DOM库构建。包含React风格虚拟DOM实现,适合前端开发者快速掌握虚拟DOM核心技术。
核心关键词:JavaScript虚拟DOM实现2024、虚拟DOM库开发、JSX转换原理、React虚拟DOM、前端框架开发、虚拟DOM源码
长尾关键词:虚拟DOM怎么实现、JSX如何转换、虚拟DOM库开发、React虚拟DOM原理、前端框架源码学习
通过本节JavaScript虚拟DOM实现教程,你将系统性掌握:
如何实现虚拟DOM?这是理解现代前端框架的关键问题。虚拟DOM的实现需要解决虚拟节点表示、渲染机制、diff算法、事件处理等核心问题,也是构建自定义前端框架的基础技术。
💡 架构设计建议:虚拟DOM库的设计要考虑可扩展性、性能、易用性三个维度,核心是要有清晰的模块分离和统一的接口设计。
// 🎉 完整的虚拟节点实现
class VNode {
constructor(tag, props = {}, children = [], text = null) {
this.tag = tag; // 标签名或组件类型
this.props = props; // 属性对象
this.children = children; // 子节点数组
this.text = text; // 文本内容(文本节点专用)
this.key = props.key; // diff算法使用的key
this.ref = props.ref; // DOM引用
// 内部属性
this.elm = null; // 对应的真实DOM元素
this.parent = null; // 父虚拟节点
this.component = null; // 组件实例(如果是组件节点)
this.context = null; // 上下文对象
// 标记属性
this.isStatic = false; // 是否为静态节点
this.isComment = false; // 是否为注释节点
this.isCloned = false; // 是否为克隆节点
this.isOnce = false; // 是否为v-once节点
}
// 创建元素节点
static createElement(tag, props, ...children) {
// 处理子节点,扁平化并过滤无效节点
const processedChildren = children
.flat(Infinity)
.filter(child => child != null && child !== false)
.map(child => {
if (typeof child === 'string' || typeof child === 'number') {
return VNode.createTextNode(String(child));
}
return child;
});
return new VNode(tag, props, processedChildren);
}
// 创建文本节点
static createTextNode(text) {
return new VNode(null, {}, [], text);
}
// 创建注释节点
static createComment(text) {
const vnode = new VNode(null, {}, [], text);
vnode.isComment = true;
return vnode;
}
// 创建空节点
static createEmpty() {
return VNode.createComment('');
}
// 克隆虚拟节点
clone() {
const cloned = new VNode(
this.tag,
{ ...this.props },
this.children.map(child => child.clone()),
this.text
);
cloned.isStatic = this.isStatic;
cloned.isComment = this.isComment;
cloned.isCloned = true;
return cloned;
}
// 判断是否为文本节点
isTextNode() {
return this.tag === null && this.text !== null && !this.isComment;
}
// 判断是否为元素节点
isElementNode() {
return typeof this.tag === 'string';
}
// 判断是否为组件节点
isComponentNode() {
return typeof this.tag === 'function' || typeof this.tag === 'object';
}
// 设置父节点关系
setParent(parent) {
this.parent = parent;
return this;
}
// 添加子节点
appendChild(child) {
if (child) {
this.children.push(child);
child.setParent(this);
}
return this;
}
// 移除子节点
removeChild(child) {
const index = this.children.indexOf(child);
if (index > -1) {
this.children.splice(index, 1);
child.parent = null;
}
return this;
}
// 插入子节点
insertBefore(newChild, referenceChild) {
const index = this.children.indexOf(referenceChild);
if (index > -1) {
this.children.splice(index, 0, newChild);
newChild.setParent(this);
}
return this;
}
// 替换子节点
replaceChild(newChild, oldChild) {
const index = this.children.indexOf(oldChild);
if (index > -1) {
this.children[index] = newChild;
newChild.setParent(this);
oldChild.parent = null;
}
return this;
}
// 查找子节点
findChild(predicate) {
return this.children.find(predicate);
}
// 遍历所有后代节点
traverse(callback) {
callback(this);
this.children.forEach(child => {
child.traverse(callback);
});
}
// 转换为JSON(调试用)
toJSON() {
return {
tag: this.tag,
props: this.props,
children: this.children.map(child => child.toJSON()),
text: this.text,
isComment: this.isComment
};
}
// 转换为字符串表示
toString() {
if (this.isTextNode()) {
return this.text;
}
if (this.isComment) {
return `<!-- ${this.text} -->`;
}
const attrs = Object.keys(this.props)
.filter(key => key !== 'key' && key !== 'ref')
.map(key => `${key}="${this.props[key]}"`)
.join(' ');
const attrsStr = attrs ? ` ${attrs}` : '';
const childrenStr = this.children.map(child => child.toString()).join('');
if (this.children.length === 0) {
return `<${this.tag}${attrsStr} />`;
}
return `<${this.tag}${attrsStr}>${childrenStr}</${this.tag}>`;
}
}
// 便捷的创建函数(类似React.createElement)
const h = VNode.createElement;
// 使用示例
const vnode = h('div', { className: 'container', id: 'app' },
h('h1', { style: { color: 'blue' } }, 'Hello Virtual DOM'),
h('p', {}, 'This is a paragraph'),
h('ul', {},
h('li', { key: '1' }, 'Item 1'),
h('li', { key: '2' }, 'Item 2'),
h('li', { key: '3' }, 'Item 3')
)
);
console.log('Virtual DOM structure:', JSON.stringify(vnode.toJSON(), null, 2));// 🎉 完整的渲染器实现
class Renderer {
constructor(options = {}) {
this.options = {
// 是否启用事件委托
eventDelegation: true,
// 根容器
container: null,
// 自定义属性处理器
attributeHandlers: {},
// 自定义事件处理器
eventHandlers: {},
...options
};
// 事件委托管理器
this.eventManager = new EventManager(this.options.container);
// 组件实例缓存
this.componentInstances = new WeakMap();
// 渲染上下文
this.renderContext = {
isHydrating: false,
isSVG: false,
inVPre: false
};
}
// 渲染虚拟节点到真实DOM
render(vnode, container) {
if (!vnode) {
// 清空容器
this.unmount(container._vnode);
container._vnode = null;
return;
}
const prevVNode = container._vnode;
if (prevVNode) {
// 更新现有节点
this.patch(prevVNode, vnode, container);
} else {
// 首次渲染
this.mount(vnode, container);
}
container._vnode = vnode;
}
// 挂载虚拟节点
mount(vnode, container, anchor = null) {
const el = this.createDOMElement(vnode);
vnode.elm = el;
if (anchor) {
container.insertBefore(el, anchor);
} else {
container.appendChild(el);
}
// 触发挂载后的钩子
this.invokeHook(vnode, 'mounted');
return el;
}
// 卸载虚拟节点
unmount(vnode) {
if (!vnode || !vnode.elm) return;
// 触发卸载前的钩子
this.invokeHook(vnode, 'beforeUnmount');
// 递归卸载子节点
if (vnode.children) {
vnode.children.forEach(child => this.unmount(child));
}
// 清理事件监听器
this.eventManager.removeAllListeners(vnode.elm);
// 从DOM中移除
if (vnode.elm.parentNode) {
vnode.elm.parentNode.removeChild(vnode.elm);
}
// 触发卸载后的钩子
this.invokeHook(vnode, 'unmounted');
vnode.elm = null;
}
// 创建真实DOM元素
createDOMElement(vnode) {
if (vnode.isTextNode()) {
return document.createTextNode(vnode.text);
}
if (vnode.isComment) {
return document.createComment(vnode.text);
}
if (vnode.isComponentNode()) {
return this.createComponentElement(vnode);
}
// 创建普通元素
const el = document.createElement(vnode.tag);
// 设置属性
this.setElementProps(el, vnode.props);
// 递归创建子元素
vnode.children.forEach(child => {
const childEl = this.createDOMElement(child);
child.elm = childEl;
el.appendChild(childEl);
});
return el;
}
// 创建组件元素
createComponentElement(vnode) {
const Component = vnode.tag;
const props = vnode.props;
let instance;
if (typeof Component === 'function') {
if (Component.prototype && Component.prototype.render) {
// 类组件
instance = new Component(props);
this.componentInstances.set(vnode, instance);
} else {
// 函数组件
instance = { render: () => Component(props) };
}
}
// 渲染组件
const componentVNode = instance.render();
const el = this.createDOMElement(componentVNode);
// 保存组件实例和渲染结果
vnode.component = instance;
vnode.componentVNode = componentVNode;
return el;
}
// 设置元素属性
setElementProps(el, props) {
Object.keys(props).forEach(key => {
const value = props[key];
if (key === 'key' || key === 'ref') {
// 跳过特殊属性
return;
}
if (key.startsWith('on') && typeof value === 'function') {
// 事件处理器
this.setEventListener(el, key, value);
} else if (key === 'style' && typeof value === 'object') {
// 样式对象
Object.assign(el.style, value);
} else if (key === 'className') {
// 类名
el.className = value;
} else if (key in el) {
// DOM属性
el[key] = value;
} else {
// HTML属性
el.setAttribute(key, value);
}
});
}
// 设置事件监听器
setEventListener(el, eventName, handler) {
const event = eventName.slice(2).toLowerCase();
if (this.options.eventDelegation) {
// 使用事件委托
this.eventManager.addEventListener(el, event, handler);
} else {
// 直接绑定事件
el.addEventListener(event, handler);
}
}
// 补丁更新(简化版diff)
patch(oldVNode, newVNode, container) {
if (oldVNode === newVNode) return;
if (this.shouldReplace(oldVNode, newVNode)) {
// 完全替换
const newEl = this.createDOMElement(newVNode);
const oldEl = oldVNode.elm;
oldEl.parentNode.replaceChild(newEl, oldEl);
newVNode.elm = newEl;
this.unmount(oldVNode);
} else {
// 更新现有节点
newVNode.elm = oldVNode.elm;
this.patchProps(oldVNode, newVNode);
this.patchChildren(oldVNode, newVNode);
}
}
// 判断是否需要替换节点
shouldReplace(oldVNode, newVNode) {
return oldVNode.tag !== newVNode.tag ||
oldVNode.key !== newVNode.key;
}
// 更新属性
patchProps(oldVNode, newVNode) {
const el = newVNode.elm;
const oldProps = oldVNode.props || {};
const newProps = newVNode.props || {};
// 移除旧属性
Object.keys(oldProps).forEach(key => {
if (!(key in newProps)) {
this.removeElementProp(el, key, oldProps[key]);
}
});
// 设置新属性
Object.keys(newProps).forEach(key => {
const oldValue = oldProps[key];
const newValue = newProps[key];
if (oldValue !== newValue) {
this.setElementProp(el, key, newValue, oldValue);
}
});
}
// 设置单个属性
setElementProp(el, key, value, oldValue) {
if (key === 'key' || key === 'ref') return;
if (key.startsWith('on') && typeof value === 'function') {
// 更新事件处理器
if (oldValue) {
this.removeEventListener(el, key, oldValue);
}
this.setEventListener(el, key, value);
} else if (key === 'style' && typeof value === 'object') {
// 更新样式
if (oldValue) {
Object.keys(oldValue).forEach(styleKey => {
if (!(styleKey in value)) {
el.style[styleKey] = '';
}
});
}
Object.assign(el.style, value);
} else if (key === 'className') {
el.className = value;
} else if (key in el) {
el[key] = value;
} else {
el.setAttribute(key, value);
}
}
// 移除属性
removeElementProp(el, key, value) {
if (key.startsWith('on') && typeof value === 'function') {
this.removeEventListener(el, key, value);
} else if (key === 'style') {
el.style.cssText = '';
} else if (key === 'className') {
el.className = '';
} else if (key in el) {
el[key] = '';
} else {
el.removeAttribute(key);
}
}
// 移除事件监听器
removeEventListener(el, eventName, handler) {
const event = eventName.slice(2).toLowerCase();
if (this.options.eventDelegation) {
this.eventManager.removeEventListener(el, event, handler);
} else {
el.removeEventListener(event, handler);
}
}
// 更新子节点(简化版)
patchChildren(oldVNode, newVNode) {
const oldChildren = oldVNode.children || [];
const newChildren = newVNode.children || [];
const el = newVNode.elm;
const maxLength = Math.max(oldChildren.length, newChildren.length);
for (let i = 0; i < maxLength; i++) {
const oldChild = oldChildren[i];
const newChild = newChildren[i];
if (!oldChild && newChild) {
// 新增子节点
const childEl = this.createDOMElement(newChild);
newChild.elm = childEl;
el.appendChild(childEl);
} else if (oldChild && !newChild) {
// 删除子节点
this.unmount(oldChild);
} else if (oldChild && newChild) {
// 更新子节点
this.patch(oldChild, newChild, el);
}
}
}
// 调用生命周期钩子
invokeHook(vnode, hookName) {
if (vnode.component && typeof vnode.component[hookName] === 'function') {
vnode.component[hookName]();
}
}
}// 🎉 事件委托管理器实现
class EventManager {
constructor(container) {
this.container = container;
this.eventMap = new Map(); // 事件类型 -> 处理器映射
this.elementMap = new WeakMap(); // 元素 -> 事件映射
// 支持的事件类型
this.supportedEvents = [
'click', 'dblclick', 'mousedown', 'mouseup', 'mousemove',
'mouseover', 'mouseout', 'mouseenter', 'mouseleave',
'keydown', 'keyup', 'keypress',
'focus', 'blur', 'change', 'input', 'submit',
'touchstart', 'touchmove', 'touchend'
];
this.initEventDelegation();
}
// 初始化事件委托
initEventDelegation() {
this.supportedEvents.forEach(eventType => {
this.container.addEventListener(eventType, (e) => {
this.handleDelegatedEvent(e);
}, true); // 使用捕获阶段
});
}
// 处理委托事件
handleDelegatedEvent(e) {
let target = e.target;
// 向上冒泡查找事件处理器
while (target && target !== this.container) {
const handlers = this.getElementHandlers(target, e.type);
if (handlers && handlers.length > 0) {
// 创建合成事件对象
const syntheticEvent = this.createSyntheticEvent(e);
handlers.forEach(handler => {
try {
handler.call(target, syntheticEvent);
} catch (error) {
console.error('Event handler error:', error);
}
});
// 如果事件被阻止传播,停止向上查找
if (syntheticEvent.isPropagationStopped()) {
break;
}
}
target = target.parentNode;
}
}
// 创建合成事件对象
createSyntheticEvent(nativeEvent) {
const syntheticEvent = {
type: nativeEvent.type,
target: nativeEvent.target,
currentTarget: nativeEvent.currentTarget,
nativeEvent: nativeEvent,
// 阻止默认行为
preventDefault() {
nativeEvent.preventDefault();
this.defaultPrevented = true;
},
// 阻止事件传播
stopPropagation() {
nativeEvent.stopPropagation();
this._propagationStopped = true;
},
// 立即阻止事件传播
stopImmediatePropagation() {
nativeEvent.stopImmediatePropagation();
this._immediatePropagationStopped = true;
},
// 检查是否阻止了传播
isPropagationStopped() {
return this._propagationStopped || this._immediatePropagationStopped;
},
// 检查是否阻止了默认行为
isDefaultPrevented() {
return this.defaultPrevented;
}
};
// 复制原生事件的属性
const eventProps = [
'bubbles', 'cancelable', 'detail', 'eventPhase',
'metaKey', 'altKey', 'ctrlKey', 'shiftKey',
'button', 'buttons', 'clientX', 'clientY',
'pageX', 'pageY', 'screenX', 'screenY',
'key', 'keyCode', 'charCode', 'which'
];
eventProps.forEach(prop => {
if (prop in nativeEvent) {
syntheticEvent[prop] = nativeEvent[prop];
}
});
return syntheticEvent;
}
// 添加事件监听器
addEventListener(element, eventType, handler) {
if (!this.elementMap.has(element)) {
this.elementMap.set(element, new Map());
}
const elementEvents = this.elementMap.get(element);
if (!elementEvents.has(eventType)) {
elementEvents.set(eventType, []);
}
elementEvents.get(eventType).push(handler);
}
// 移除事件监听器
removeEventListener(element, eventType, handler) {
const elementEvents = this.elementMap.get(element);
if (elementEvents && elementEvents.has(eventType)) {
const handlers = elementEvents.get(eventType);
const index = handlers.indexOf(handler);
if (index > -1) {
handlers.splice(index, 1);
// 如果没有处理器了,删除事件类型
if (handlers.length === 0) {
elementEvents.delete(eventType);
}
// 如果元素没有任何事件了,删除元素映射
if (elementEvents.size === 0) {
this.elementMap.delete(element);
}
}
}
}
// 移除元素的所有事件监听器
removeAllListeners(element) {
this.elementMap.delete(element);
}
// 获取元素的事件处理器
getElementHandlers(element, eventType) {
const elementEvents = this.elementMap.get(element);
return elementEvents ? elementEvents.get(eventType) : null;
}
// 销毁事件管理器
destroy() {
this.elementMap = new WeakMap();
this.eventMap.clear();
}
}虚拟DOM实现的核心特点:
💼 实现价值:通过从零实现虚拟DOM,能够深入理解现代前端框架的核心原理,为框架选型和性能优化提供理论基础。
// 🎉 JSX转换器实现
class JSXTransformer {
constructor() {
this.pragmaName = 'h'; // 默认的创建函数名
this.fragmentName = 'Fragment'; // Fragment组件名
}
// 转换JSX代码为JavaScript
transform(jsxCode) {
// 简化的JSX解析器(实际应该使用AST)
return this.parseJSX(jsxCode);
}
// 解析JSX(简化版本)
parseJSX(code) {
// 移除注释
code = this.removeComments(code);
// 转换JSX元素
code = this.transformElements(code);
// 转换JSX表达式
code = this.transformExpressions(code);
return code;
}
// 移除注释
removeComments(code) {
return code
.replace(/\/\*[\s\S]*?\*\//g, '') // 块注释
.replace(/\/\/.*$/gm, ''); // 行注释
}
// 转换JSX元素
transformElements(code) {
// 匹配JSX元素的正则表达式(简化版)
const jsxElementRegex = /<(\w+)([^>]*?)(?:\s*\/\s*>|>([\s\S]*?)<\/\1>)/g;
return code.replace(jsxElementRegex, (match, tagName, attributes, children) => {
const props = this.parseAttributes(attributes);
const childrenCode = children ? this.parseChildren(children) : '';
return `${this.pragmaName}('${tagName}', ${props}${childrenCode ? ', ' + childrenCode : ''})`;
});
}
// 解析属性
parseAttributes(attributesStr) {
if (!attributesStr.trim()) return '{}';
const attributes = {};
const attrRegex = /(\w+)(?:=(?:"([^"]*)"|'([^']*)'|{([^}]*)}))?\s*/g;
let match;
while ((match = attrRegex.exec(attributesStr)) !== null) {
const [, name, doubleQuoted, singleQuoted, expression] = match;
if (expression) {
attributes[name] = expression;
} else if (doubleQuoted !== undefined) {
attributes[name] = `"${doubleQuoted}"`;
} else if (singleQuoted !== undefined) {
attributes[name] = `"${singleQuoted}"`;
} else {
attributes[name] = 'true';
}
}
const propsArray = Object.keys(attributes).map(key => {
return `${key}: ${attributes[key]}`;
});
return `{${propsArray.join(', ')}}`;
}
// 解析子元素
parseChildren(childrenStr) {
if (!childrenStr.trim()) return '';
// 分割子元素(简化处理)
const children = [];
const parts = childrenStr.split(/(<[^>]+>[\s\S]*?<\/[^>]+>|<[^>]+\s*\/>)/);
parts.forEach(part => {
part = part.trim();
if (!part) return;
if (part.startsWith('<')) {
// JSX元素
children.push(this.transformElements(part));
} else if (part.startsWith('{') && part.endsWith('}')) {
// JavaScript表达式
children.push(part.slice(1, -1));
} else {
// 文本内容
children.push(`"${part}"`);
}
});
return children.join(', ');
}
// 转换JSX表达式
transformExpressions(code) {
// 处理JSX中的JavaScript表达式
return code.replace(/{([^}]+)}/g, (match, expression) => {
return expression;
});
}
// 设置编译选项
setPragma(pragmaName) {
this.pragmaName = pragmaName;
return this;
}
setFragment(fragmentName) {
this.fragmentName = fragmentName;
return this;
}
}
// JSX编译示例
const transformer = new JSXTransformer();
// 原始JSX代码
const jsxCode = `
<div className="container" id="app">
<h1 style={{color: 'blue'}}>Hello JSX</h1>
<p>This is a paragraph</p>
<ul>
<li key="1">Item 1</li>
<li key="2">Item 2</li>
<li key="3">Item 3</li>
</ul>
<button onClick={handleClick}>Click Me</button>
</div>
`;
// 编译后的JavaScript代码
const compiledCode = transformer.transform(jsxCode);
console.log('Compiled JSX:', compiledCode);
// 实际的编译结果应该类似于:
// h('div', {className: "container", id: "app"},
// h('h1', {style: {color: 'blue'}}, "Hello JSX"),
// h('p', {}, "This is a paragraph"),
// h('ul', {},
// h('li', {key: "1"}, "Item 1"),
// h('li', {key: "2"}, "Item 2"),
// h('li', {key: "3"}, "Item 3")
// ),
// h('button', {onClick: handleClick}, "Click Me")
// )// 🎉 Babel JSX插件的核心原理实现
class BabelJSXPlugin {
constructor(babel) {
this.babel = babel;
this.types = babel.types;
}
// 插件主函数
plugin() {
return {
visitor: {
// 处理JSX元素
JSXElement: (path) => {
const element = path.node;
const tagName = this.getTagName(element.openingElement.name);
const props = this.transformProps(element.openingElement.attributes);
const children = this.transformChildren(element.children);
// 创建h函数调用
const callExpression = this.types.callExpression(
this.types.identifier('h'),
[tagName, props, ...children]
);
path.replaceWith(callExpression);
},
// 处理JSX片段
JSXFragment: (path) => {
const children = this.transformChildren(path.node.children);
const callExpression = this.types.callExpression(
this.types.identifier('h'),
[
this.types.identifier('Fragment'),
this.types.objectExpression([]),
...children
]
);
path.replaceWith(callExpression);
},
// 处理JSX表达式容器
JSXExpressionContainer: (path) => {
path.replaceWith(path.node.expression);
}
}
};
}
// 获取标签名
getTagName(name) {
if (this.types.isJSXIdentifier(name)) {
const tagName = name.name;
// 判断是否为组件(首字母大写)
if (tagName[0] === tagName[0].toUpperCase()) {
return this.types.identifier(tagName);
} else {
return this.types.stringLiteral(tagName);
}
}
return this.types.stringLiteral('div');
}
// 转换属性
transformProps(attributes) {
if (attributes.length === 0) {
return this.types.objectExpression([]);
}
const properties = attributes.map(attr => {
if (this.types.isJSXAttribute(attr)) {
const key = attr.name.name;
let value = attr.value;
if (!value) {
// 布尔属性
value = this.types.booleanLiteral(true);
} else if (this.types.isStringLiteral(value)) {
// 字符串值
value = value;
} else if (this.types.isJSXExpressionContainer(value)) {
// 表达式值
value = value.expression;
}
return this.types.objectProperty(
this.types.identifier(key),
value
);
}
return null;
}).filter(Boolean);
return this.types.objectExpression(properties);
}
// 转换子元素
transformChildren(children) {
return children
.map(child => {
if (this.types.isJSXText(child)) {
const text = child.value.trim();
return text ? this.types.stringLiteral(text) : null;
} else if (this.types.isJSXElement(child) || this.types.isJSXFragment(child)) {
// 递归处理JSX元素
return child;
} else if (this.types.isJSXExpressionContainer(child)) {
return child.expression;
}
return null;
})
.filter(Boolean);
}
}// 🎉 完整的虚拟DOM应用示例
// 创建应用类
class VirtualDOMApp {
constructor(container) {
this.container = container;
this.renderer = new Renderer({ container });
this.state = {};
this.currentVNode = null;
// 绑定方法
this.setState = this.setState.bind(this);
this.render = this.render.bind(this);
}
// 设置状态
setState(newState) {
this.state = { ...this.state, ...newState };
this.update();
}
// 更新视图
update() {
const newVNode = this.render();
this.renderer.render(newVNode, this.container);
this.currentVNode = newVNode;
}
// 渲染方法(需要子类实现)
render() {
throw new Error('render method must be implemented');
}
// 挂载应用
mount() {
this.update();
}
// 卸载应用
unmount() {
this.renderer.render(null, this.container);
}
}
// 待办事项应用示例
class TodoApp extends VirtualDOMApp {
constructor(container) {
super(container);
this.state = {
todos: [
{ id: 1, text: '学习虚拟DOM', completed: false },
{ id: 2, text: '实现JSX转换', completed: true },
{ id: 3, text: '构建完整应用', completed: false }
],
newTodoText: '',
filter: 'all' // all, active, completed
};
// 绑定事件处理器
this.addTodo = this.addTodo.bind(this);
this.toggleTodo = this.toggleTodo.bind(this);
this.deleteTodo = this.deleteTodo.bind(this);
this.updateNewTodoText = this.updateNewTodoText.bind(this);
this.setFilter = this.setFilter.bind(this);
this.clearCompleted = this.clearCompleted.bind(this);
}
// 添加待办事项
addTodo() {
const text = this.state.newTodoText.trim();
if (!text) return;
const newTodo = {
id: Date.now(),
text: text,
completed: false
};
this.setState({
todos: [...this.state.todos, newTodo],
newTodoText: ''
});
}
// 切换待办事项状态
toggleTodo(id) {
this.setState({
todos: this.state.todos.map(todo =>
todo.id === id ? { ...todo, completed: !todo.completed } : todo
)
});
}
// 删除待办事项
deleteTodo(id) {
this.setState({
todos: this.state.todos.filter(todo => todo.id !== id)
});
}
// 更新输入框文本
updateNewTodoText(e) {
this.setState({ newTodoText: e.target.value });
}
// 设置过滤器
setFilter(filter) {
this.setState({ filter });
}
// 清除已完成的待办事项
clearCompleted() {
this.setState({
todos: this.state.todos.filter(todo => !todo.completed)
});
}
// 获取过滤后的待办事项
getFilteredTodos() {
const { todos, filter } = this.state;
switch (filter) {
case 'active':
return todos.filter(todo => !todo.completed);
case 'completed':
return todos.filter(todo => todo.completed);
default:
return todos;
}
}
// 渲染应用
render() {
const { newTodoText, filter } = this.state;
const filteredTodos = this.getFilteredTodos();
const activeCount = this.state.todos.filter(todo => !todo.completed).length;
const completedCount = this.state.todos.length - activeCount;
return h('div', { className: 'todo-app' },
// 标题
h('h1', {}, 'Virtual DOM Todo App'),
// 输入区域
h('div', { className: 'todo-input' },
h('input', {
type: 'text',
placeholder: 'What needs to be done?',
value: newTodoText,
onInput: this.updateNewTodoText,
onKeyPress: (e) => {
if (e.key === 'Enter') {
this.addTodo();
}
}
}),
h('button', { onClick: this.addTodo }, 'Add')
),
// 待办事项列表
h('ul', { className: 'todo-list' },
...filteredTodos.map(todo =>
h('li', {
key: todo.id,
className: `todo-item ${todo.completed ? 'completed' : ''}`
},
h('input', {
type: 'checkbox',
checked: todo.completed,
onChange: () => this.toggleTodo(todo.id)
}),
h('span', { className: 'todo-text' }, todo.text),
h('button', {
className: 'delete-btn',
onClick: () => this.deleteTodo(todo.id)
}, '×')
)
)
),
// 底部工具栏
h('div', { className: 'todo-footer' },
h('span', { className: 'todo-count' },
`${activeCount} item${activeCount !== 1 ? 's' : ''} left`
),
h('div', { className: 'todo-filters' },
h('button', {
className: filter === 'all' ? 'active' : '',
onClick: () => this.setFilter('all')
}, 'All'),
h('button', {
className: filter === 'active' ? 'active' : '',
onClick: () => this.setFilter('active')
}, 'Active'),
h('button', {
className: filter === 'completed' ? 'active' : '',
onClick: () => this.setFilter('completed')
}, 'Completed')
),
completedCount > 0 && h('button', {
className: 'clear-completed',
onClick: this.clearCompleted
}, 'Clear completed')
)
);
}
}
// 使用示例
const container = document.getElementById('app');
const todoApp = new TodoApp(container);
todoApp.mount();
// 添加样式
const style = document.createElement('style');
style.textContent = `
.todo-app {
max-width: 600px;
margin: 0 auto;
padding: 20px;
font-family: Arial, sans-serif;
}
.todo-input {
display: flex;
margin-bottom: 20px;
}
.todo-input input {
flex: 1;
padding: 10px;
border: 1px solid #ddd;
border-radius: 4px 0 0 4px;
}
.todo-input button {
padding: 10px 20px;
background: #007bff;
color: white;
border: none;
border-radius: 0 4px 4px 0;
cursor: pointer;
}
.todo-list {
list-style: none;
padding: 0;
}
.todo-item {
display: flex;
align-items: center;
padding: 10px;
border-bottom: 1px solid #eee;
}
.todo-item.completed .todo-text {
text-decoration: line-through;
color: #888;
}
.todo-text {
flex: 1;
margin: 0 10px;
}
.delete-btn {
background: #dc3545;
color: white;
border: none;
border-radius: 50%;
width: 24px;
height: 24px;
cursor: pointer;
}
.todo-footer {
display: flex;
justify-content: space-between;
align-items: center;
margin-top: 20px;
padding-top: 20px;
border-top: 1px solid #eee;
}
.todo-filters button {
margin: 0 5px;
padding: 5px 10px;
border: 1px solid #ddd;
background: white;
cursor: pointer;
}
.todo-filters button.active {
background: #007bff;
color: white;
}
.clear-completed {
padding: 5px 10px;
background: #dc3545;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
}
`;
document.head.appendChild(style);完整虚拟DOM实现的特点:
通过本节JavaScript虚拟DOM实现的学习,你已经掌握:
A: 主要区别在于功能完整性和性能优化程度。React有Fiber架构、Suspense、并发模式等高级特性,而自实现的库通常只包含核心功能。但理解自实现有助于深入理解React的工作原理。
A: JSX不是标准的JavaScript语法,浏览器无法直接解析。Babel通过AST解析将JSX转换为标准的JavaScript函数调用,使得浏览器能够正确执行代码。
A: 事件委托减少了事件监听器的数量,降低内存使用;支持动态添加的元素自动获得事件处理;提供了统一的事件处理入口,便于实现合成事件等高级特性。
A: 可以通过对象池复用VNode对象、及时清理不需要的引用、使用WeakMap存储临时数据、合理设计数据结构减少内存占用等方式控制内存开销。
A: 可以通过添加日志输出、实现虚拟DOM树的可视化、开发专用的调试工具、使用浏览器开发者工具的Performance面板分析性能等方式进行调试。
// VNode对象池实现
class VNodePool {
constructor(maxSize = 1000) {
this.pool = [];
this.maxSize = maxSize;
}
get() {
return this.pool.length > 0 ? this.pool.pop() : {};
}
release(vnode) {
if (this.pool.length < this.maxSize) {
// 清理对象属性
Object.keys(vnode).forEach(key => delete vnode[key]);
this.pool.push(vnode);
}
}
}
const vnodePool = new VNodePool();
// 优化的VNode创建
function createOptimizedVNode(tag, props, children) {
const vnode = vnodePool.get();
vnode.tag = tag;
vnode.props = props;
vnode.children = children;
return vnode;
}// 批量DOM更新优化
class BatchUpdater {
constructor() {
this.pendingUpdates = [];
this.isUpdating = false;
}
schedule(updateFn) {
this.pendingUpdates.push(updateFn);
if (!this.isUpdating) {
this.isUpdating = true;
requestAnimationFrame(() => {
this.flush();
});
}
}
flush() {
const updates = this.pendingUpdates.splice(0);
updates.forEach(update => {
try {
update();
} catch (error) {
console.error('Update error:', error);
}
});
this.isUpdating = false;
}
}// 静态节点标记和复用
function markStatic(vnode) {
if (vnode.isTextNode()) {
vnode.isStatic = true;
return;
}
if (vnode.children && vnode.children.length > 0) {
let allChildrenStatic = true;
vnode.children.forEach(child => {
markStatic(child);
if (!child.isStatic) {
allChildrenStatic = false;
}
});
vnode.isStatic = allChildrenStatic && !hasDynamicProps(vnode.props);
}
}
function hasDynamicProps(props) {
return Object.keys(props).some(key =>
key.startsWith('on') || key === 'ref' || key === 'key'
);
}// 虚拟DOM调试工具
class VDOMDebugger {
constructor() {
this.history = [];
this.maxHistory = 50;
}
logRender(vnode, patches) {
const snapshot = {
timestamp: Date.now(),
vnode: this.serializeVNode(vnode),
patches: patches,
performance: performance.now()
};
this.history.push(snapshot);
if (this.history.length > this.maxHistory) {
this.history.shift();
}
console.group('Virtual DOM Render');
console.log('VNode:', vnode);
console.log('Patches:', patches);
console.log('History:', this.history);
console.groupEnd();
}
serializeVNode(vnode) {
return {
tag: vnode.tag,
props: vnode.props,
childrenCount: vnode.children ? vnode.children.length : 0,
isStatic: vnode.isStatic
};
}
exportHistory() {
return JSON.stringify(this.history, null, 2);
}
}// 性能监控工具
class VDOMProfiler {
constructor() {
this.metrics = {
renderTime: [],
diffTime: [],
patchTime: [],
nodeCount: []
};
}
startRender() {
this.renderStart = performance.now();
}
endRender(nodeCount) {
const renderTime = performance.now() - this.renderStart;
this.metrics.renderTime.push(renderTime);
this.metrics.nodeCount.push(nodeCount);
console.log(`Render completed in ${renderTime.toFixed(2)}ms for ${nodeCount} nodes`);
}
getAverageRenderTime() {
const times = this.metrics.renderTime;
return times.reduce((sum, time) => sum + time, 0) / times.length;
}
getReport() {
return {
averageRenderTime: this.getAverageRenderTime(),
totalRenders: this.metrics.renderTime.length,
metrics: this.metrics
};
}
}"实现虚拟DOM不仅仅是技术练习,更是深入理解现代前端框架设计思想的最佳途径。通过从零构建虚拟DOM库,你将获得对React、Vue等框架更深层次的理解,这种理解将指导你在实际开发中做出更好的技术决策。"