咱們先看一下運行結果javascript
name 和 age 被響應式的渲染出來,在 2s 後咱們修改了 name 的值,一樣能在頁面正確更新。html
咱們來看一下最小化的 MVVM 的源碼java
class Vue{
constructor(opt){
this.opt = opt
this.observe(opt.data)
let root = document.querySelector(opt.el)
this.compile(root)
}
// 爲響應式對象 data 裏的每個 key 綁定一個觀察者對象
observe(data){
Object.keys(data).forEach(key => {
let obv = new Observer()
data["_"+key] = data[key]
// 經過 getter setter 暴露 for 循環中做用域下的 obv,閉包產生
Object.defineProperty(data, key, {
get(){
Observer.target && obv.addSubNode(Observer.target);
return data['_'+key]
},
set(newVal){
obv.update(newVal)
data['_'+key] = newVal
}
})
})
}
// 初始化頁面,遍歷 DOM,收集每個key變化時,隨之調整的位置,以觀察者方法存放起來
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)
})
}
}
// 常規觀察者類
class Observer{
constructor(){
this.subNode = []
}
addSubNode(node){
this.subNode.push(node)
}
update(newVal){
this.subNode.forEach(node=>{
node.innerHTML = newVal
})
}
}
複製代碼
data
對象進行 for 循環遍歷,爲 data
的每個 key
映射一個觀察者對象
eventLoop
中,若是修改了 key
的值,實際會經過 setter 觸發觀察者的 update 函數,完成響應式更新<!DOCTYPE html>
<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) } // 爲響應式對象 data 裏的每個 key 綁定一個觀察者對象 observe(data){ Object.keys(data).forEach(key => { let obv = new Observer() data["_"+key] = data[key] // 經過 getter setter 暴露 for 循環中做用域下的 obv,閉包產生 Object.defineProperty(data, key, { get(){ Observer.target && obv.addSubNode(Observer.target); return data['_'+key] }, set(newVal){ obv.update(newVal) data['_'+key] = newVal } }) }) } // 初始化頁面,遍歷 DOM,收集每個key變化時,隨之調整的位置,以觀察者方法存放起來 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) }) } } // 常規觀察者類 class Observer{ constructor(){ this.subNode = [] } addSubNode(node){ this.subNode.push(node) } update(newVal){ this.subNode.forEach(node=>{ node.innerHTML = newVal }) } } </script>
複製代碼
代碼github地址node