<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>自定義MVVM架構,這是比較牛逼的v-text,v-model和資料綁定原理介紹</title>
</head>
<body>
<div id="app">
<p v-text="message"></p>
<input type="text" v-model="message"/>
<p>{{message}}</p>
</div>
<script src="observer.js"></script>
<script src="TemepCompiler.js"></script>
<script src="watcher.js"></script>
<script src="mvvm.js"></script>
<script>
var vm = new MVVM({
el: "#app", //挂載視圖
data: {
msg:'這是自己寫的mvvm架構',
message:'mvvm出來吧'
} //定義資料
})
</script>
</body>
</html>
//建立一個MVVM架構類
class MVVM { //類構造器(創造執行個體模闆代碼)
constructor(options) { //相當于函數的參數 {el:...,data:...}
//緩存重要屬性
this.$vm = this;
this.$el = options.el;
this.$data = options.data;
//判斷視圖是否存在
if(this.$el) {
//添加屬性觀察對象(實作屬性挾持)
new Observer(this.$data);
//建立模闆編譯器,來解析視圖
this.$compiler = new TemepCompiler(this.$el, this.$vm);
}
}
}
class TemepCompiler { //類構造器(創造執行個體模闆代碼)
constructor(el, vm) { //相當于函數的參數 (this.$el,this.$vm)
//緩存重要屬性
this.vm = vm;
this.el = this.isElemetNode(el) ? el : document.querySelector(el);
//判斷視圖是否存在
if(this.el) {
//1.把模闆内容放入記憶體(片段)
var fragment = this.node2fragment(this.el);
//2.解析模闆
this.compile(fragment);
//3.把記憶體的結果傳回頁面
this.el.appendChild(fragment);
}
}
//工具方法
isElemetNode(node) {
return node.nodeType === 1 //1.元素節點 2.屬性節點 3.文本節點
}
isTextNode(node) {
return node.nodeType === 3 //1.元素節點 2.屬性節點 3.文本節點
}
toArray(arr) {
return [].slice.call(arr); //假數組轉為數組;
}
isDerective(attrName) {
return attrName.indexOf("v-") >= 0; //判斷屬性名中是否有v-開頭的屬性
}
//核心方法(節省記憶體)把模闆放入記憶體,等待解析
node2fragment(node) {
//建立記憶體片段
var fragment = document.createDocumentFragment(),
child;
//模闆内容丢到記憶體
while(child = node.firstChild) {
fragment.appendChild(child);
}
//傳回
return fragment;
}
compile(parentNode) {
//擷取子節點
var childNodes = parentNode.childNodes, //類數組
compiler = this;
//周遊每一個節點
this.toArray(childNodes).forEach((node) => {
//判斷節點類型
if(compiler.isElemetNode(node)) {
//1.屬性節點(解析指令)
compiler.compileElement(node);
} else {
//2.文本節點(解析指令)
var textReg = /\{\{(.+)\}\}/; //(\轉義)文本表達式驗證規則
var expr = node.textContent;
//var expr = node.innerText; //谷歌支援
//按照規則驗證内容
if(textReg.test(expr)) {
expr = RegExp.$1 //緩存最近一次的正則裡面的值;
//調用方法編譯
compiler.compileText(node, expr); //如果還有子節點,繼續解析;
}
}
});
}
//解析元素節點的指令的方法
compileElement(node) {
//擷取目前元素節點的所有屬性
var arrs = node.attributes;
self = this;
//周遊目前元素所有屬性
this.toArray(arrs).forEach(attr => {
var attrName = attr.name;
//判斷屬性是否是指令
if(self.isDerective(attrName)) {
var type = attrName.substr(2); //v-text,v-model...
var expr = attr.value; //指令的值就是表達式
//找幫手
CompilerUntils[type](node, self.vm, expr);
}
})
}
//解析表達式的方法
compileText(node, expr) {
CompilerUntils.text(node, this.vm, expr);
}
}
//解析指令幫手
CompilerUntils = {
//解析text指令
text(node, vm, expr) {
//第一次觀察
//1.找到更新規則對象的更新方法
var updaterFn = this.updater["textUpdater"];
//2.執行方法
updaterFn && updaterFn(node, vm.$data[expr]) //等價if(updaterFn){updaterFn(node,vm.$data[expr])}
//第n+1次觀察
new Watcher(vm, expr, (newVaule) => {
//觸發訂閱時按之前的規則對節點進行更新;v-model的也一樣;
updaterFn && updaterFn(node, newVaule);
})
},
//解析model指令
model(node, vm, expr) {
//1.找到更新規則對象的更新方法
var updaterFn = this.updater["modelUpdater"];
//2.執行方法
updaterFn && updaterFn(node, vm.$data[expr]) //等價if(updaterFn){updaterFn(node,vm.$data[expr])}
//第n+1次觀察
new Watcher(vm, expr, (newVaule) => {
//觸發訂閱時按之前的規則對節點進行更新;v-model的也一樣;
updaterFn && updaterFn(node, newVaule);
})
//視圖到模型變化
node.addEventListener("input", (e) => {
var newValue = e.target.value;
//把值放進資料
console.log(newValue+'新值');
vm.$data[expr] = newValue;
})
},
updater: {
//文本更新方法
textUpdater(node, value) {
node.textContent;
//node.innerText; //谷歌支援
},
//輸入框值更新方法
modelUpdater(node, value) {
node.value = value;
}
}
}
class Observer {
//構造函數
constructor(data) {
//提供一個解析方法,完成屬性的分析,和挾持
this.observe(data);
}
observe(data) {
//判斷資料的有效性 必須是對象
if(!data || typeof data !== "object") {
return;
}
var keys = Object.keys(data); //拿到所有的屬性(key)轉為數組
keys.forEach((key) => {
//重新定義key
this.defineReactive(data, key, data[key]); //動态屬性需要中括号不能.
})
}
//針對目前對象屬性的重新定義(挾持)
defineReactive(obj, key, val) {
var dep = new Dep();
//重新定義
Object.defineProperty(obj, key, {
enumerable: true, //是否可以周遊
configurable: false, //是否可以重新配置
get() { //getter取值
Dep.target && dep.addSub(Dep.target); //拿到訂閱者
//傳回屬性
return val;
},
set(newValue) { //setter修改值
val = newValue; //新值覆寫舊值
dep.notify(); //通知,觸發update操作;
}
})
}
}
//建立釋出者
//1.管理訂閱者
//2.通知
class Dep {
constructor() {
this.subs = [];
}
//添加訂閱
addSub(sub) { //其實就是Watcher執行個體
this.subs.push(sub);
}
//集體通知
notify() {
this.subs.forEach((sub) => {
sub.update();
})
}
}
//定義一個訂閱者
class Watcher {
//構造函數
//1.需要使用訂閱功能的節點
//2.全局vm對象,用于擷取資料
//3.需要使用訂閱功能的節點
constructor(vm, expr, cb) {
//緩存重要屬性
this.vm = vm;
this.expr = expr;
this.cb = cb;
//緩存目前值
this.value = this.get();
}
//擷取目前值
get() {
//把目前訂閱者添加到全局
Dep.target = this; //watcher執行個體
//擷取目前值
var value = this.vm.$data[this.expr];
//清空全局
Dep.target = null;
//傳回
return value;
}
//提供一個更新
update() {
//擷取新值
var newValue = this.vm.$data[this.expr];
//擷取老值
var oldValue = this.value;
//執行回調
if(newValue !== oldValue) {
this.cb(newValue); //效果一樣關鍵需不需要改變this指向this.cb.call(this.vm,newValue)
}
}
}