10分鐘了解MVVM,實現簡易MVVM
MVVM 是 Model-View-ViewModel 縮寫,也就是把 MVC 中的 Controller 演變成 ViewModel。Model 層代表數據模型,View 代表 UI 組件,ViewModel 是 View 和 Model 層的橋梁,數據會綁定到 viewModel 層并自動將數據渲染到頁面中,視圖變化的時候會通知 viewModel 層更新數據。
- Model: 代表數據模型,也可以在 Model 中定義數據修改和操作的業務邏輯。我們可以把 Model 稱為數據層,因為它僅僅關注數據本身,不關心任何行為
- View: 用戶操作界面。當 ViewModel 對 Model 進行更新的時候,會通過數據綁定更新到 View
- ViewModel: 業務邏輯層,View 需要什么數據,ViewModel 要提供這個數據;View 有某些操作,ViewModel 就要響應這些操作,所以可以說它是 Model for View.
總結: MVVM 模式簡化了界面與業務的依賴,解決了數據頻繁更新。MVVM 在使用當中,利用雙向綁定技術,使得 Model 變化時,ViewModel 會自動更新,而 ViewModel 變化時,View 也會自動變化。
實現一個簡易的 MVVM 分為這么幾步來
1.類 Vue:這個類接收的是一個 options。
el屬性:根元素的id
data屬性:雙向綁定的數據。
2.Dep 類:
subNode數組:存放所依賴這個屬性 的依賴,
addSub方法:添加依賴的
removeSub方法:刪除,
updata方法:遍歷更新它subs中的所有依賴
target靜態屬性:用來表示 '當前的觀察者' ,依賴收集的時候可以將它添加到dep. subNode中
靜態的是指向類自身,而不是指向實例對象,主要是歸屬不同,這是靜態屬性的核心
3.observe 方法:監聽數據變化(參數:data,也就是 options.data)
遍歷data:使用Object.defineProperty()來重寫它的get和set,
使用new Dep():實例化一個dep對象,
get時:addSub 添加 '當前的觀察者Dep.target' 完成依賴收集,
set時:dep.updata 通知每一個依賴它的觀察者進行更新
4.compile 方法:來將 HTML 模版和數據結合起來(參數:node 節點)。
遍歷它的所有子級,判斷是否有firstElmentChild
有:進行遞歸調用compile方法,
沒有:child.innderHTML用判斷是否有(/\{\{(.*)\}\}/)需要雙向綁定的數據,
有:new Reg('\\{\\{\\s*' + key + '\\s*\\}\\}', 'gm')替換msg變量。
5.將 Dep.target 指向當前的這個 child,
調用this.opt.data[key]:觸發這個數據的get來對當前的child進行依賴收集,
目的:下次數據變化通知child進行視圖更新,
將Dep.target指為null(其實在Vue中是有一個targetStack棧用來存放target的指向的)
6.監聽 document 的 DOMContentLoaded
回調函數中實例化這個Vue對象就可以了
需要注意的點:
childNodes 會獲取到所有的子節點以及文本節點(包括元素標簽中的空白節點)
firstElementChild 表示獲取元素的第一個字元素節點,以此來區分是不是元素節點,如果是的話則調用 compile 進行遞歸調用,否則用正則匹配
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>MVVM</title>
</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: 20 } };
let vm = new Vue(opt);
setTimeout(() => {
opt.data.name = "jing";
}, 2000);
},
false
);
class Vue {
constructor(opt) {
this.opt = opt;
this.observer(opt.data);
let root = document.querySelector(opt.el);
this.compile(root);
}
observer(data) {
Object.keys(data).forEach((key) => {
let obv = new Dep();
data["_" + key] = data[key];
Object.defineProperty(data, key, {
get() {
Dep.target && obv.addSubNode(Dep.target);
return data["_" + key];
},
set(newVal) {
obv.update(newVal);
data["_" + key] = newVal;
},
});
});
}
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]
);
Dep.target = child;
this.opt.data[key];
Dep.target = null;
} else if (child.firstElementChild) this.compile(child);
});
}
}
class Dep {
constructor() {
this.subNode = [];
}
addSubNode(node) {
this.subNode.push(node);
}
update(newVal) {
this.subNode.forEach((node) => {
node.innerHTML = newVal;
});
}
}
</script>

浙公網安備 33010602011771號