UNPKG

wxy-micro-dom

Version:

一个类似jquery调用方法、性能超越react的虚拟轻量级dom库

985 lines (852 loc) 26.1 kB
/** * 高性能响应式虚拟DOM库 - 终极优化版 * 特点: * 1. 极致性能优化的虚拟DOM Diff算法 * 2. 智能响应式依赖追踪系统 * 3. 静态节点自动提升和缓存 * 4. 批量更新智能调度 * 5. 内存高效管理 * 6. 完全兼容原有API */ // ====================== 响应式系统 ====================== class ReactiveSystem { constructor() { this.subscribers = new Map(); this.currentComputed = null; this.proxyCache = new WeakMap(); this.computedCache = new WeakMap(); this.effectQueue = new Set(); this.batchDepth = 0; this.effectId = 0; } reactive(data) { if (typeof data !== 'object' || data === null) return data; if (this.proxyCache.has(data)) { return this.proxyCache.get(data); } const self = this; const proxy = new Proxy(data, { get(target, key) { self.track(target, key); const result = Reflect.get(target, key); return typeof result === 'object' ? self.reactive(result) : result; }, set(target, key, value) { const oldValue = target[key]; const result = Reflect.set(target, key, value); if (oldValue !== value) { self.trigger(target, key); } return result; }, deleteProperty(target, key) { const hadKey = Object.prototype.hasOwnProperty.call(target, key); const result = Reflect.deleteProperty(target, key); if (hadKey) { self.trigger(target, key); } return result; } }); this.proxyCache.set(data, proxy); return proxy; } track(target, key) { if (!this.currentComputed) return; const targetKey = `${target}_${key}`; if (!this.subscribers.has(targetKey)) { this.subscribers.set(targetKey, new Set()); } this.subscribers.get(targetKey).add(this.currentComputed); } trigger(target, key) { const targetKey = `${target}_${key}`; if (this.subscribers.has(targetKey)) { this.subscribers.get(targetKey).forEach(effect => { if (this.batchDepth > 0) { this.effectQueue.add(effect); } else { this.runEffect(effect); } }); } } runEffect(effect) { if (!effect.active) return; if (typeof effect === 'function') { effect(); } else if (effect.update) { effect.update(); } } batch(fn) { this.batchDepth++; try { fn(); } finally { this.batchDepth--; if (this.batchDepth === 0) { this.flushEffects(); } } } flushEffects() { const effects = Array.from(this.effectQueue); this.effectQueue.clear(); // 按优先级执行effects effects.sort((a, b) => (b.priority || 0) - (a.priority || 0)); effects.forEach(effect => this.runEffect(effect)); } computed(fn, options = {}) { const cacheKey = options.cacheKey || fn; if (this.computedCache.has(cacheKey)) { return this.computedCache.get(cacheKey); } const computedObj = { id: this.effectId++, value: null, active: true, priority: options.priority || 0, update() { const newValue = fn(); if (newValue !== this.value) { this.value = newValue; if (this.callback) { this.callback(this.value); } } } }; this.currentComputed = computedObj; computedObj.update(); this.currentComputed = null; const computedWrapper = { get value() { return computedObj.value }, onUpdate(callback) { computedObj.callback = callback; return this; }, dispose() { computedObj.active = false; } }; this.computedCache.set(cacheKey, computedWrapper); return computedWrapper; } watch(getter, callback, options = {}) { const watcher = { id: this.effectId++, value: null, active: true, priority: options.priority || 0, update() { const newValue = getter(); if (newValue !== this.value || options.deep) { const oldValue = this.value; this.value = newValue; callback(newValue, oldValue); } } }; this.currentComputed = watcher; watcher.update(); this.currentComputed = null; return { dispose() { watcher.active = false; } }; } effect(fn, options = {}) { const effect = { id: this.effectId++, active: true, priority: options.priority || 0, run: fn }; this.currentComputed = effect; fn(); this.currentComputed = null; return { dispose() { effect.active = false; } }; } } const reactiveSystem = new ReactiveSystem(); // ====================== 虚拟DOM系统 ====================== class VNode { constructor(tag, props = {}, children = [], key, isStatic = false) { this.tag = tag; this.props = props; this.children = children; this.key = key; this.el = null; this.reactiveProps = {}; this.computedValues = []; this.isStatic = isStatic; this._events = {}; this.staticNodes = new Map(); this.memoizedProps = null; this.memoizedChildren = null; } createElement() { // 静态节点缓存 if (this.isStatic && this.staticNodes.has(this.getStaticKey())) { return this.staticNodes.get(this.getStaticKey()).cloneNode(true); } if (typeof this.tag === 'string') { this.el = document.createElement(this.tag); // 处理props this.processProps(); // 处理子节点 this.processChildren(); // 缓存静态节点 if (this.isStatic) { this.staticNodes.set(this.getStaticKey(), this.el.cloneNode(true)); } return this.el; } // 文本节点或组件 return document.createTextNode(''); } getStaticKey() { return this.key || this.tag + JSON.stringify(this.props); } processProps() { // 分离静态和响应式props const staticProps = {}; const reactiveProps = {}; for (const [key, value] of Object.entries(this.props)) { if (key.startsWith('r:')) { reactiveProps[key.substring(2)] = value; } else { staticProps[key] = value; } } // 处理静态props this.setElementProps(staticProps); // 处理响应式props this.processReactiveProps(reactiveProps); } processReactiveProps(props) { for (const [key, value] of Object.entries(props)) { if (typeof value === 'function') { const computed = reactiveSystem.computed(value, { cacheKey: `${this.tag}_${key}_${this.key || ''}` }); computed.onUpdate(newValue => { this.reactiveProps[key] = newValue; this.setElementAttribute(key, newValue); }); this.reactiveProps[key] = computed.value; this.computedValues.push(computed); } else { this.reactiveProps[key] = value; } this.setElementAttribute(key, this.reactiveProps[key]); } } processChildren() { const fragment = document.createDocumentFragment(); this.children.forEach(child => { if (child instanceof VNode) { if (child.isStatic) { // 静态子节点直接创建并缓存 const staticEl = child.createElement(); fragment.appendChild(staticEl); } else { // 动态子节点 fragment.appendChild(child.createElement()); } } else if (child instanceof DOMWrapper) { fragment.appendChild(child.el); } else if (typeof child === 'function') { // 响应式文本节点 fragment.appendChild(this.createReactiveTextNode(child)); } else { // 普通文本节点 fragment.appendChild(document.createTextNode(String(child))); } }); this.el.appendChild(fragment); } createReactiveTextNode(getter) { const textNode = document.createTextNode(''); const computed = reactiveSystem.computed(getter, { cacheKey: `text_${this.tag}_${this.key || ''}_${getter.toString()}` }); computed.onUpdate(newValue => { textNode.textContent = newValue; }); this.computedValues.push(computed); textNode.textContent = computed.value; return textNode; } setElementProps(props) { for (const [key, value] of Object.entries(props)) { this.setElementAttribute(key, value); } } setElementAttribute(key, value) { if (!this.el) return; if (key === 'style' && typeof value === 'object') { Object.assign(this.el.style, value); } else if (key.startsWith('on')) { const eventName = key.substring(2).toLowerCase(); if (this._events[eventName]) { this.el.removeEventListener(eventName, this._events[eventName]); } this.el.addEventListener(eventName, value); this._events[eventName] = value; } else if (value === true) { this.el.setAttribute(key, ''); } else if (value === false || value === null || value === undefined) { this.el.removeAttribute(key); } else { this.el.setAttribute(key, value); } } updateElement(parentEl, newNode, oldNode, index = 0) { // 静态节点直接复用 if (newNode.isStatic && oldNode.isStatic && newNode.tag === oldNode.tag && newNode.key === oldNode.key) { newNode.el = oldNode.el; return; } if (!oldNode) { parentEl.appendChild(newNode.createElement()); return; } if (!newNode) { if (parentEl.childNodes[index]) { parentEl.removeChild(parentEl.childNodes[index]); } return; } if (newNode.tag !== oldNode.tag || newNode.key !== oldNode.key) { parentEl.replaceChild(newNode.createElement(), oldNode.el); return; } // 复用DOM元素 newNode.el = oldNode.el; this.updateAttributes(newNode, oldNode); this.updateChildren(newNode, oldNode); } updateAttributes(newNode, oldNode) { const newProps = { ...newNode.props, ...newNode.reactiveProps }; const oldProps = { ...oldNode.props, ...oldNode.reactiveProps }; const el = oldNode.el; // 属性diff优化 const allKeys = new Set([...Object.keys(newProps), ...Object.keys(oldProps)]); allKeys.forEach(key => { const newValue = newProps[key]; const oldValue = oldProps[key]; if (newValue === oldValue) return; if (newValue === undefined) { // 移除属性 if (key === 'style') { el.style = ''; } else if (key.startsWith('on')) { el.removeEventListener(key.substring(2).toLowerCase(), oldValue); } else { el.removeAttribute(key); } } else { // 设置属性 this.setElementAttribute(key, newValue); } }); } updateChildren(newNode, oldNode) { const newCh = newNode.children; const oldCh = oldNode.children; const el = oldNode.el; // 快速路径:没有子节点 if (newCh.length === 0 && oldCh.length === 0) return; // 快速路径:只有文本子节点 if (newCh.length === 1 && oldCh.length === 1 && typeof newCh[0] === 'string' && typeof oldCh[0] === 'string') { if (newCh[0] !== oldCh[0]) { el.textContent = newCh[0]; } return; } // 全量Diff算法 this.diffChildren(el, newCh, oldCh); } diffChildren(parentEl, newCh, oldCh) { let oldStartIdx = 0, newStartIdx = 0; let oldEndIdx = oldCh.length - 1, newEndIdx = newCh.length - 1; let oldStartNode = oldCh[oldStartIdx], oldEndNode = oldCh[oldEndIdx]; let newStartNode = newCh[newStartIdx], newEndNode = newCh[newEndIdx]; let keyToIdx, idxInOld, elmToMove; while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) { if (!oldStartNode) { oldStartNode = oldCh[++oldStartIdx]; } else if (!oldEndNode) { oldEndNode = oldCh[--oldEndIdx]; } else if (this.isSameVNode(newStartNode, oldStartNode)) { this.updateElement(parentEl, newStartNode, oldStartNode); newStartNode = newCh[++newStartIdx]; oldStartNode = oldCh[++oldStartIdx]; } else if (this.isSameVNode(newEndNode, oldEndNode)) { this.updateElement(parentEl, newEndNode, oldEndNode); newEndNode = newCh[--newEndIdx]; oldEndNode = oldCh[--oldEndIdx]; } else if (this.isSameVNode(newStartNode, oldEndNode)) { parentEl.insertBefore(oldEndNode.el, oldStartNode.el); this.updateElement(parentEl, newStartNode, oldEndNode); newStartNode = newCh[++newStartIdx]; oldEndNode = oldCh[--oldEndIdx]; } else if (this.isSameVNode(newEndNode, oldStartNode)) { parentEl.insertBefore(oldStartNode.el, oldEndNode.el.nextSibling); this.updateElement(parentEl, newEndNode, oldStartNode); newEndNode = newCh[--newEndIdx]; oldStartNode = oldCh[++oldStartIdx]; } else { if (!keyToIdx) keyToIdx = this.createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx); idxInOld = newStartNode.key ? keyToIdx[newStartNode.key] : null; if (!idxInOld) { parentEl.insertBefore(newStartNode.createElement(), oldStartNode.el); } else { elmToMove = oldCh[idxInOld]; if (this.isSameVNode(newStartNode, elmToMove)) { this.updateElement(parentEl, newStartNode, elmToMove); oldCh[idxInOld] = undefined; parentEl.insertBefore(elmToMove.el, oldStartNode.el); } else { parentEl.insertBefore(newStartNode.createElement(), oldStartNode.el); } } newStartNode = newCh[++newStartIdx]; } } // 处理剩余节点 if (oldStartIdx > oldEndIdx) { const refNode = newCh[newEndIdx + 1] ? newCh[newEndIdx + 1].el : null; for (let i = newStartIdx; i <= newEndIdx; i++) { parentEl.insertBefore(newCh[i].createElement(), refNode); } } else if (newStartIdx > newEndIdx) { for (let i = oldStartIdx; i <= oldEndIdx; i++) { if (oldCh[i]) { parentEl.removeChild(oldCh[i].el); } } } } createKeyToOldIdx(children, beginIdx, endIdx) { const map = {}; for (let i = beginIdx; i <= endIdx; i++) { const key = children[i]?.key; if (key) map[key] = i; } return map; } isSameVNode(a, b) { return a?.tag === b?.tag && a?.key === b?.key; } } // ====================== 批量更新系统 ====================== const batchUpdateQueue = new Set(); let isBatchUpdating = false; let rafId = null; function enqueueUpdate(updateFn) { batchUpdateQueue.add(updateFn); if (!isBatchUpdating) { isBatchUpdating = true; if (typeof queueMicrotask !== 'undefined') { queueMicrotask(() => { requestAnimationFrame(flushUpdates); }); } else if (typeof Promise !== 'undefined') { Promise.resolve().then(() => { requestAnimationFrame(flushUpdates); }); } else { rafId = requestAnimationFrame(flushUpdates); } } } function flushUpdates() { try { const updates = Array.from(batchUpdateQueue); batchUpdateQueue.clear(); // 智能排序:先处理样式更新,再处理布局更新 updates.sort((a, b) => { const aIsStyle = a.toString().includes('style'); const bIsStyle = b.toString().includes('style'); return bIsStyle - aIsStyle; }); reactiveSystem.batch(() => { updates.forEach(update => update()); }); } finally { isBatchUpdating = false; if (rafId) { cancelAnimationFrame(rafId); rafId = null; } } } // ====================== DOM封装 ====================== class DOMWrapper { constructor(selector) { if (typeof selector === 'string') { this.el = document.querySelector(selector); } else if (selector instanceof Element) { this.el = selector; } else if (selector instanceof VNode) { this.vnode = selector; this.el = selector.createElement(); } else { this.el = null; } this.vnode = null; this._events = {}; this._onMountCallbacks = []; this._onUnmountCallbacks = []; this._disposables = []; } // ============== 核心方法 ============== vdom(tag, props, children) { this.vnode = new VNode(tag, props, children); return this; } reactiveVdom(tag, props, children) { const vnode = new VNode(tag, props, children); for (const [key, value] of Object.entries(props)) { if (key.startsWith('w:')) { const propName = key.substring(2); if (typeof value === 'function') { const computed = this.computed(value); computed.onUpdate(newValue => { vnode.reactiveProps[propName] = newValue; if (vnode.el) { vnode.setElementAttribute(propName, newValue); } }); vnode.reactiveProps[propName] = computed.value; } } } this.vnode = vnode; return this; } static(isStatic = true) { if (this.vnode) { this.vnode.isStatic = isStatic; } return this; } mount(container) { if (!this.vnode) return this; if (container) { if (typeof container === 'string') { container = document.querySelector(container); } else if (container instanceof DOMWrapper) { container = container.el; } if (container) { enqueueUpdate(() => { if (container.firstChild) { this.vnode.updateElement(container, this.vnode, new VNode(container.firstChild)); } else { container.appendChild(this.vnode.createElement()); } this._onMountCallbacks.forEach(cb => cb()); }); } } else if (this.el) { enqueueUpdate(() => { this.vnode.updateElement(this.el.parentNode, this.vnode, new VNode(this.el)); this._onMountCallbacks.forEach(cb => cb()); }); } return this; } // ============== 生命周期 ============== onMount(callback) { if (typeof callback === 'function') { this._onMountCallbacks.push(callback); } return this; } onUnmount(callback) { if (typeof callback === 'function') { this._onUnmountCallbacks.push(callback); } return this; } dispose() { this._disposables.forEach(dispose => dispose()); this._disposables = []; this._onUnmountCallbacks.forEach(cb => cb()); return this; } // ============== DOM操作 ============== set html(content) { enqueueUpdate(() => { if (this.el) this.el.innerHTML = content }); return this; } get html() { return this.el?.innerHTML; } set text(content) { enqueueUpdate(() => { if (this.el) this.el.textContent = content }); return this; } get text() { return this.el?.textContent; } append(child) { enqueueUpdate(() => { if (!this.el) return; if (child instanceof DOMWrapper) { this.el.appendChild(child.el); } else if (child instanceof Element) { this.el.appendChild(child); } else if (child instanceof VNode) { this.el.appendChild(child.createElement()); } else { this.el.appendChild(document.createTextNode(String(child))); } }); return this; } // ============== 事件系统 ============== on(event, handler) { if (this.el) { if (this._events[event]) { this.el.removeEventListener(event, this._events[event]); } this.el.addEventListener(event, handler); this._events[event] = handler; } return this; } off(event) { if (this.el) { this.el.removeEventListener(event, this._events[event]); delete this._events[event]; } return this; } delegate(selector, event, handler) { if (!this.el) return this; const wrappedHandler = (e) => { let target = e.target; while (target && target !== this.el) { if (target.matches(selector)) { handler.call(target, e); break; } target = target.parentNode; } }; this.on(event, wrappedHandler); this._disposables.push(() => { this.off(event, wrappedHandler); }); return this; } // ============== 样式操作 ============== css(styles) { if (this.el) { enqueueUpdate(() => { Object.assign(this.el.style, styles) }); } return this; } addClass(className) { if (this.el) { enqueueUpdate(() => { this.el.classList.add(className) }); } return this; } removeClass(className) { if (this.el) { enqueueUpdate(() => { this.el.classList.remove(className) }); } return this; } toggleClass(className) { if (this.el) { enqueueUpdate(() => { this.el.classList.toggle(className) }); } return this; } // ============== 属性操作 ============== attr(name, value) { if (value === undefined) return this.el?.getAttribute(name); enqueueUpdate(() => { if (this.el) this.el.setAttribute(name, value) }); return this; } delAttr(name) { enqueueUpdate(() => { if (this.el) this.el.removeAttribute(name) }); return this; } // ============== DOM遍历 ============== find(selector) { return this.el ? $(this.el.querySelector(selector)) : $(null); } get parent() { return this.el ? $(this.el.parentNode) : $(null); } get children() { if (!this.el) return []; return Array.from(this.el.children).map(el => $(el)); } // ============== 显示/隐藏 ============== show() { if (this.el) { enqueueUpdate(() => { this.el.style.display = '' }); } return this; } hide() { if (this.el) { enqueueUpdate(() => { this.el.style.display = 'none' }); } return this; } // ============== 响应式扩展 ============== reactive(data) { return reactiveSystem.reactive(data); } computed(fn) { const computed = reactiveSystem.computed(fn); this._disposables.push(() => computed.dispose()); return computed; } watch(getter, callback) { const watcher = reactiveSystem.watch(getter, callback); this._disposables.push(() => watcher.dispose()); return watcher; } effect(fn) { const effect = reactiveSystem.effect(fn); this._disposables.push(() => effect.dispose()); return effect; } // ============== 高级功能 ============== model(reactiveObj, propName) { if (this.el && this.el.tagName === 'INPUT') { this.el.value = reactiveObj[propName] || ''; this.on('input', () => { reactiveObj[propName] = this.el.value; }); this.watch(() => reactiveObj[propName], (newVal) => { if (this.el.value !== newVal) { this.el.value = newVal; } }); } return this; } animate(keyframes, options) { if (this.el) { return this.el.animate(keyframes, options); } return null; } frontUpdate(fn) { reactiveSystem.batch(() => { const prevBatch = isBatchUpdating; isBatchUpdating = true; try { fn(); } finally { isBatchUpdating = prevBatch; if (!isBatchUpdating) { flushUpdates(); } } }); return this; } mountList(items, renderItem) { if (!this.el) return this; const fragment = document.createDocumentFragment(); const vnodes = items.map((item, index) => { const vnode = renderItem(item, index); if (vnode instanceof VNode) { fragment.appendChild(vnode.createElement()); } return vnode; }); this.empty(); this.el.appendChild(fragment); this.vnode = new VNode('div', {}, vnodes); return this; } } const createNegativeArray = (arr) => { return new Proxy(arr, { get(target, prop) { // 处理负索引:-1 → 最后一个元素 if (String(prop).startsWith("-")) { const index = Number(prop); if (isNaN(index)) return target[prop]; // 非数字属性 return target[target.length + index]; } return target[prop]; // 正常索引或属性 }, set(target, prop, value) { if (String(prop).startsWith("-")) { const index = Number(prop); if (!isNaN(index)) { target[target.length + index] = value; return true; } } target[prop] = value; return true; } }); }; // ====================== 全局API ====================== function $(tag, props, children, key, isStatic) { if(key===undefined&&props===undefined&&children===undefined&&isStatic===undefined) { if (typeof tag === "string"){ return new DOMWrapper(tag); } else if (typeof tag === "function") { reactiveSystem.batch(tag) } } else { return new DOMWrapper(new VNode(tag, props || {}, children || [], key, isStatic || false)) } } function $$(selector) { return createNegativeArray(Array.from(document.querySelectorAll(selector)).map(it => new DOMWrapper(it))); } $.static = (tag, props = {}, children = []) => { const vnode = new VNode(tag, props, children, null, true); return new DOMWrapper(vnode); }; $.memo = (fn) => { let lastArgs, lastResult; return (...args) => { if (lastArgs && args.every((arg, i) => arg === lastArgs[i])) { return lastResult; } lastArgs = args; lastResult = fn(...args); return lastResult; }; }; // 响应式API $.reactive = (data) => reactiveSystem.reactive(data); $.computed = (fn) => reactiveSystem.computed(fn); $.watch = (getter, callback) => reactiveSystem.watch(getter, callback); $.effect = (fn) => reactiveSystem.effect(fn); // 快捷方式 $.rdom = (tag, props, children) => { const dom = new DOMWrapper(); return dom.reactiveVdom(tag, props, children); }; $.RS = reactiveSystem; // 暴露API window.$ = $; window.$$ = $$;