wxy-micro-dom
Version:
一个类似jquery调用方法、性能超越react的虚拟轻量级dom库
985 lines (852 loc) • 26.1 kB
JavaScript
/**
* 高性能响应式虚拟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.$$ = $$;