我們先看一下運(yùn)行結(jié)果
name 和 age 被響應(yīng)式的渲染出來,在 2s 后我們修改了 name 的值,同樣能在頁面正確更新。愛掏網(wǎng) - it200.com
我們來看一下最小化的 MVVM 的源碼
class Vue{
constructor(opt){
this.opt = opt
this.observe(opt.data)
let root = document.querySelector(opt.el)
this.compile(root)
}
// 為響應(yīng)式對(duì)象 data 里的每一個(gè) key 綁定一個(gè)觀察者對(duì)象
observe(data){
Object.keys(data).forEach(key => {
let obv = new Observer()
data["_"+key] = data[key]
// 通過 getter setter 暴露 for 循環(huán)中作用域下的 obv,閉包產(chǎn)生
Object.defineProperty(data, key, {
get(){
Observer.target && obv.addSubNode(Observer.target);
return data['_'+key]
},
set(newVal){
obv.update(newVal)
data['_'+key] = newVal
}
})
})
}
// 初始化頁面,遍歷 DOM,收集每一個(gè)key變化時(shí),隨之調(diào)整的位置,以觀察者方法存放起來
compile(node){
[].forEach.call(node.childNodes, child =>{
if(!child.firstElementChild && /\{\{(.*)\}\}/.test(child.innerHTML)){
let key = RegExp.$1.trim()
child.innerHTML = child.innerHTML.replace(new RegExp('\\{\\{\\s*'+ key +'\\s*\\}\\}', 'gm'),this.opt.data[key])
Observer.target = child
this.opt.data[key]
Observer.target = null
}
else if (child.firstElementChild)
this.compile(child)
})
}
}
// 常規(guī)觀察者類
class Observer{
constructor(){
this.subNode = []
}
addSubNode(node){
this.subNode.push(node)
}
update(newVal){
this.subNode.forEach(node=>{
node.innerHTML = newVal
})
}
}
復(fù)制代碼
【重點(diǎn)分析】接下來梳理一下這段代碼的思路,順便了解一下 MVVM 閉包的藝術(shù):
- [observe 函數(shù)]:首先我們會(huì)對(duì)需要響應(yīng)式的
data
對(duì)象進(jìn)行 for 循環(huán)遍歷,為data
的每一個(gè)key
映射一個(gè)觀察者對(duì)象- 在 ES6 中,for 循環(huán)每次執(zhí)行,都可以形成閉包,因此這個(gè)觀察者對(duì)象就存放在閉包中
- 閉包形成的本質(zhì)是 內(nèi)層作用域中堆地址暴露,這里我們巧妙的用 getter/setter 函數(shù)暴露了 for 循環(huán)里的觀察者
- [compile 函數(shù)]:我們從根節(jié)點(diǎn)向下遍歷 DOM,遇到 mustache 形式的文本,則映射成 data.key 對(duì)應(yīng)的值,同時(shí)記錄到觀察者中
- 當(dāng)遍歷到 {{xxx}} 形式的文本,我們正則匹配出其中的變量,將它替換成 data 中的值
- 為了滿足后續(xù)響應(yīng)式的更新,將該節(jié)點(diǎn)存儲(chǔ)在 key 對(duì)應(yīng)的觀察者對(duì)象中,我們用 getter 函數(shù)巧妙的操作了閉包
- 在頁面初次渲染之后,后續(xù)的
eventLoop
中,如果修改了key
的值,實(shí)際會(huì)通過 setter 觸發(fā)觀察者的 update 函數(shù),完成響應(yīng)式更新
附上述演示案例的完整代碼 github地址
html lang="en">
head>
meta charset="UTF-8">
meta name="viewport" content="width=device-width, initial-scale=1.0">
head>
body>
div id='app'>
h3>姓名h3>
p>{{name}}p>
h3>年齡h3>
p>{{age}}p>
div>
body>
html>
script>
document.addEventListener('DOMContentLoaded', function(){
let opt = {el:'#app', data:{name:'檢索中...', age:30}}
let vm = new Vue(opt)
setTimeout(() => {
opt.data.name = '王永峰'
}, 2000);
}, false)
class Vue{
constructor(opt){
this.opt = opt
this.observe(opt.data)
let root = document.querySelector(opt.el)
this.compile(root)
}
// 為響應(yīng)式對(duì)象 data 里的每一個(gè) key 綁定一個(gè)觀察者對(duì)象
observe(data){
Object.keys(data).forEach(key => {
let obv = new Observer()
data["_"+key] = data[key]
// 通過 getter setter 暴露 for 循環(huán)中作用域下的 obv,閉包產(chǎn)生
Object.defineProperty(data, key, {
get(){
Observer.target && obv.addSubNode(Observer.target);
return data['_'+key]
},
set(newVal){
obv.update(newVal)
data['_'+key] = newVal
}
})
})
}
// 初始化頁面,遍歷 DOM,收集每一個(gè)key變化時(shí),隨之調(diào)整的位置,以觀察者方法存放起來
compile(node){
[].forEach.call(node.childNodes, child =>{
if(!child.firstElementChild && /\{\{(.*)\}\}/.test(child.innerHTML)){
let key = RegExp.$1.trim()
child.innerHTML = child.innerHTML.replace(new RegExp('\\{\\{\\s*'+ key +'\\s*\\}\\}', 'gm'),this.opt.data[key])
Observer.target = child
this.opt.data[key]
Observer.target = null
}
else if (child.firstElementChild)
this.compile(child)
})
}
}
// 常規(guī)觀察者類
class Observer{
constructor(){
this.subNode = []
}
addSubNode(node){
this.subNode.push(node)
}
update(newVal){
this.subNode.forEach(node=>{
node.innerHTML = newVal
})
}
}
script>
復(fù)制代碼
代碼github地址
作者:maodayeyeye
原文發(fā)布時(shí)間:2024年06月12日 本文來源掘金如需轉(zhuǎn)載請(qǐng)緊急聯(lián)系作者
聲明:所有內(nèi)容來自互聯(lián)網(wǎng)搜索結(jié)果,不保證100%準(zhǔn)確性,僅供參考。如若本站內(nèi)容侵犯了原著者的合法權(quán)益,可聯(lián)系我們進(jìn)行處理。