// jwon.js - 带差异更新的响应式库
var $ = {};
// 存储上一次的数据快照
let lastDataSnapshot = null;
// 获取所有节点(包含文本节点)
function getNodes(root = document.body) {
const allElements = root.querySelectorAll("*");
const nodes = Array.from(allElements);
// 获取文本节点
const walker = document.createTreeWalker(
root,
NodeFilter.SHOW_TEXT,
null,
false
);
let node;
while (node = walker.nextNode()) {
if (node.nodeValue.trim()) { // 使用 nodeValue 而不是 textContent
nodes.push(node);
}
}
return nodes;
}
// 安全的模板解析(防止XSS)
function parseTemplate(template, data) {
try {
const keys = Object.keys(data);
const values = keys.map(key => data[key]);
const func = new Function(...keys, `
try {
return ${template};
} catch(e) {
console.error('Template error:', e);
return '';
}
`);
return func(...values) ?? '';
} catch (e) {
console.error('Template parsing failed:', e);
return '';
}
}
// 比较新旧数据,返回变化的key
function getChangedKeys(newData, oldData) {
const changedKeys = new Set();
const allKeys = new Set([...Object.keys(newData), ...Object.keys(oldData || {})]);
for (const key of allKeys) {
if (!oldData || newData[key] !== oldData[key]) {
changedKeys.add(key);
}
}
return changedKeys;
}
// 处理文本节点转换
function processTextNode(node) {
const text = node.nodeValue;
if (/\{\{.*?\}\}/.test(text)) {
const template = document.createElement('template');
template.innerHTML = text.replace(
/\{\{(.*?)\}\}/g,
(_, exp) => `<span $text="${exp.trim()}"></span>`
);
node.replaceWith(template.content);
return true; // 表示已替换
}
return false;
}
// 核心更新函数(带差异更新)
function update() {
const currentData = { ...window.$ };
const changedKeys = getChangedKeys(currentData, lastDataSnapshot);
// 如果没有变化,直接返回
if (changedKeys.size === 0) return;
// 遍历所有节点
getNodes(document.body).forEach(element => {
// 处理文本节点({{ }}语法)
if (element.nodeType === Node.TEXT_NODE) {
processTextNode(element);
}
// 处理元素节点
else if (element.nodeType === Node.ELEMENT_NODE) {
// 先收集所有属性,避免动态修改影响遍历
const attrs = Array.from(element.attributes);
// 处理指令属性
attrs.forEach(attr => {
if (attr.name.startsWith('$')) {
const directive = attr.name.slice(1);
const attrValue = attr.value;
// 只有当绑定的数据发生变化时才更新
if (changedKeys.has(attrValue.split('.')[0])) {
try {
if (directive === "html") {
element.innerHTML = parseTemplate(attrValue, window.$);
}
else if (directive === "text") {
element.textContent = parseTemplate(attrValue, window.$);
}
else if (directive === "show") {
element.style.display = parseTemplate(attrValue, window.$) ? "" : "none";
}
else if (directive === "value") {
if (element.value !== parseTemplate(attrValue, window.$)) {
element.value = parseTemplate(attrValue, window.$);
}
// 确保只绑定一次事件
if (!element._hasValueListener) {
element.addEventListener("input", function() {
window.$[attrValue] = this.value;
});
element._hasValueListener = true;
}
}
else {
element.setAttribute(directive, parseTemplate(attrValue, window.$));
}
} catch(e) {
console.error(`Error processing ${attr.name}:`, e);
}
}
}
});
// 处理事件属性
attrs.forEach(attr => {
if (attr.name.startsWith('@')) {
const eventName = attr.name.slice(1);
const handlerCode = attr.value;
// 确保只绑定一次事件
if (!element._eventListeners) {
element._eventListeners = {};
}
if (!element._eventListeners[eventName]) {
element.removeAttribute(attr.name);
element._eventListeners[eventName] = function() {
try {
new Function('data', `with(data){ ${handlerCode} }`)(window.$);
} catch(e) {
console.error('Event error:', e);
}
};
element.addEventListener(eventName, element._eventListeners[eventName]);
}
}
});
}
});
// 更新数据快照
lastDataSnapshot = currentData;
}
// 初始化数据代理
window.$ = new Proxy($, {
set(target, key, value) {
target[key] = value;
update(); // 数据变化时触发差异更新
return true;
}
});
// 初始渲染
document.addEventListener('DOMContentLoaded', update);
请问为什么只能解析第一个$xx属性