- 这次的改动大一些,目标是希望在数据更改时不必重新遍历 dom,只更新对应的文本。
- 思路是首先遍历 dom,将所有文本元素 ( nodeName === '#text' ) 和对应的数据绑定起来,即根据数据可以直接对应到 dom 节点。
- 接下来的工作就是监控数据变化了,数据变化时需要知道被改的数据是哪个,但 defineProperty 不能做到,因为它的 set 中参数只有新的 value。所以使用了proxy,proxy 可以直接对整个 json 进行代理,使用起来更加方便。
- 总的流程:
先用 proxy 对 data 代理,在 set 中设置 update( ),数据更新时,直接更新对应文本节点
遍历 dom,将数据和对应文本节点绑定起来
遍历 data,将所有的 {{ }} 替换
class Vue { // Vue 类
constructor(obj) {
let {el, data} = obj
const node = document.querySelector(el)
const dataBindNode = {}
data = new Proxy(data, {
get(tar, key){
return tar[key]
},
set(tar, key, val){
tar[key] = val
updateText(dataBindNode[key], data)
}
})
this.data = data
traverseNode(node, data, dataBindNode)
for(let key in dataBindNode) { // 第一次更新
updateText(dataBindNode[key], data)
}
}
}
const updateText = (node, data) => { // 更新数据对应的文本节点
const reg = /{{\w+}}/g
const arr = node.text.match(reg) || []
let text = node.text
arr.forEach((item)=>{
const name = item.slice(2, -2)
if(data[name]) {
node.node.textContent = text = text.replace(item, data[name])
}
})
}
const traverseNode = (node, data, dataBindNode) => { // 遍历
node.childNodes.forEach(item => {
if(item.nodeName === '#text') {
bindNode(item, data, dataBindNode)
}
else {
traverseNode(item, data, dataBindNode)
}
});
}
const bindNode = (node, data, dataBindNode) => { // 绑定
const reg = /{{\w+}}/g
const arr = node.textContent.match(reg) || []
arr.forEach((item)=>{
const key = item.slice(2, -2)
if(data[key]) {
dataBindNode[key] = {}
dataBindNode[key].node = node
dataBindNode[key].text = node.textContent
}
else { } // 数据未定义却使用
})
}