1.MVVM是什么市栗?
響應(yīng)式,雙向數(shù)據(jù)綁定咳短,即MVVM肃廓。是指數(shù)據(jù)層(Model)-視圖層(View)-數(shù)據(jù)視圖(ViewModel)的響應(yīng)式框架。它包括:
1.修改View層诲泌,Model對應(yīng)數(shù)據(jù)發(fā)生變化盲赊。
2.Model數(shù)據(jù)變化,不需要查找DOM敷扫,直接更新View哀蘑。
2.實(shí)現(xiàn)方式
Vue是采用數(shù)據(jù)劫持結(jié)合發(fā)布者-訂閱者模式的方式,通過Object.defineProperty()來劫持各個(gè)屬性的setter葵第,getter绘迁,在數(shù)據(jù)變動時(shí)發(fā)布消息給訂閱者,觸發(fā)相應(yīng)的監(jiān)聽回調(diào)卒密。
3.實(shí)現(xiàn)
1)實(shí)現(xiàn)Observe
使用Object.definProperty()實(shí)現(xiàn)Observe()方法對_data數(shù)據(jù)進(jìn)行劫持
// 數(shù)據(jù)劫持
function Observe(data) {
for (let key in data) { // 循環(huán)
let val = data[key];
observe(val); // 屬性值有可能是對象缀台,遞歸調(diào)用
Object.defineProperty(data, key, { // 數(shù)據(jù)劫持
configurable: true,
enumerable: true,
get() {
return val;
},
set(newVal) {
if (val === newVal) return;
observe(newVal); // 設(shè)置新的屬性值時(shí)也要進(jìn)行數(shù)據(jù)劫持
val = newVal;
},
});
}
}
function observe(data) {
if (typeof data !== "object") return; // 不是對象不劫持
return new Observe(data);
}
2)實(shí)現(xiàn)數(shù)據(jù)代理
!O妗膛腐!用vm實(shí)例對象來代替_data, 對_data中的數(shù)據(jù)進(jìn)行操作
function Vue(options = {}) {
this.$options = options;
let data = (this._data = this.$options.data());
// 數(shù)據(jù)劫持
observe(data);
// 數(shù)據(jù)代理
Object.keys(data).forEach((key) => {
Object.defineProperty(this, key, {
configurable: true,
enumerable: true,
get() {
return this._data[key]; // 代理
},
set(newVal) {
this._data[key] = newVal; // 代理
},
});
});
// 數(shù)據(jù)編譯
new Compile(options.el, this);
}
3) 實(shí)現(xiàn)模板編譯Compile
解析模板指令 即:{{data數(shù)據(jù)}}睛约,將模板中的變量解析成數(shù)據(jù),并渲染到視圖上
// 編譯
function Compile(el, vm) {
vm.$el = document.querySelector(el);
let fragment = document.createDocumentFragment(); // 文檔碎片
while ((child = vm.$el.firstChild)) { // 將dom元素放入文檔碎片中
fragment.appendChild(child);
}
replace(fragment);
function replace(fragment) { // 替換data數(shù)據(jù)
Array.from(fragment.childNodes).forEach((node) => {
let text = node.textContent; // 獲取{{}}
let reg = /\{\{(.*)\}\}/; // 匹配正則
if (node.nodeType === 3 && reg.test(text)) { //正則匹配且dom元素為文本
let val = vm;
let arr = RegExp.$1.split(".");
arr.forEach((k) => {
val = val[k];
});
if (node.childNodes) replace(node);
});
}
vm.$el.appendChild(fragment);
}
4) 實(shí)現(xiàn)發(fā)布訂閱
維護(hù)一個(gè)數(shù)組哲身,用于收集訂閱者(Watcher)辩涝;當(dāng)數(shù)據(jù)發(fā)生改變時(shí),調(diào)用 notify勘天,從而觸發(fā)訂閱者的 update()怔揩,完成視圖跟新。
// 發(fā)布訂閱
function Dep() {
this.subs = [];
}
Dep.prototype.addSub = function (sub) {
this.subs.push(sub); // 收集訂閱
};
Dep.prototype.notify = function () {
this.subs.forEach((sub) => sub.update()); // 通知watcher脯丝,調(diào)用update()
};
5)實(shí)現(xiàn)Watcher
當(dāng)數(shù)據(jù)改變時(shí)觸發(fā)watcher的update()商膊,讓視圖跟新。
// watcher
function Watcher(vm, exp, fn) { // vm:vue實(shí)例宠进,exp:匹配正則{{}}翘狱,fn為回調(diào)函數(shù)即update方法
this.fn = fn;
this.vm = vm;
this.exp = exp;
Dep.target = this
let val = vm;
let arr = exp.split(".");
arr.forEach((k) => {
val = val[k];
});
Dep.target = null
}
// update
Watcher.prototype.update = function () {
let newVal = this.vm;
let arr = this.exp.split(".");
arr.forEach((k) => {
newVal = newVal[k];
});
this.fn(newVal);
};
!E椴浴潦匈!在compile中添加Watcher
// 編譯
function Compile(el, vm) {
vm.$el = document.querySelector(el);
let fragment = document.createDocumentFragment();
while ((child = vm.$el.firstChild)) {
fragment.appendChild(child);
}
replace(fragment);
function replace(fragment) {
Array.from(fragment.childNodes).forEach((node) => {
let text = node.textContent;
let reg = /\{\{(.*)\}\}/;
if (node.nodeType === 3 && reg.test(text)) {
let val = vm;
let arr = RegExp.$1.split(".");
arr.forEach((k) => {
val = val[k];
});
// -----------------------------------------------添加觀察者
new Watcher(vm, RegExp.$1, function (newVal) {
node.textContent = text.replace(reg, newVal); // 跟新視圖
});
//------------------------------------------------添加觀察者
node.textContent = text.replace(reg, val);
}
if (node.childNodes) replace(node);
});
}
vm.$el.appendChild(fragment);
}
!W肌茬缩!在observe中收集訂閱者
function Observe(data) {
let dep = new Dep() // ----------收集訂閱者
for (let key in data) {
let val = data[key];
observe(val);
Object.defineProperty(data, key, {
configurable: true,
enumerable: true,
get() {
Dep.target && dep.addSub(Dep.target) // --------獲取數(shù)據(jù)時(shí)收集訂閱者
return val;
},
set(newVal) {
if (val === newVal) return;
observe(newVal);
val = newVal;
dep.notify() // --------------設(shè)置數(shù)據(jù)時(shí)通知訂閱者跟新視圖
},
});
}
}
以上完成簡易的mvvm模型
總結(jié)
MVVM原理就是利用observe對數(shù)據(jù)進(jìn)行劫持(Object.definpropety),然后通過compile編譯模板指令( ''{{ }}'' )吼旧;并使用發(fā)布訂閱模型進(jìn)行數(shù)據(jù)和視圖的雙向綁定凰锡。
參考文章: