手把手教你实现MVVM架构
.markdown-body p { color: rgba(89, 89, 89, 1); font-size: 15px; line-height: 2; font-weight: 400 }
.markdown-body p+p { margin-top: 16px }
.markdown-body h1, .markdown-body h2, .markdown-body h3, .markdown-body h4, .markdown-body h5, .markdown-body h6 { padding: 30px 0; margin: 0; color: rgba(19, 92, 224, 1) }
.markdown-body h1 { position: relative; text-align: center; font-size: 22px; margin: 50px 0 }
.markdown-body h1:before { position: absolute; content: ""; top: -10px; left: 50%; width: 32px; height: 32px; transform: translateX(-50%); background: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAMAAABEpIrGAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAABfVBMVEX///8Ad/8AgP8AgP8AgP8Aff8AgP8Af/8AgP8AVf8Af/8Af/8AgP8AgP8Af/8Afv8AAP8Afv8Afv8Aef8AgP8AdP8Afv8AgP8AgP8Acf8Ae/8AgP8Af/8AgP8Af/8Af/8AfP8Afv8AgP8Af/8Af/8Afv8Afv8AgP8Afv8AgP8Af/8Af/8AgP8AgP8Afv8AgP8Af/8AgP8AgP8AgP8Ae/8Afv8Af/8AgP8Af/8AgP8Af/8Af/8Aff8Af/8Abf8AgP8Af/8AgP8Af/8Af/8Afv8AgP8AgP8Afv8Afv8AgP8Af/8Aff8AgP8Afv8AgP8Aff8AgP8AfP8AgP8Ae/8AgP8Af/8AgP8AgP8AgP8Afv8AgP8AgP8AgP8Afv8AgP8AgP8AgP8AgP8AgP8Af/8AgP8Af/8Af/8Aev8Af/8AgP8Aff8Afv8AgP8AgP8AgP8Af/8AgP8Af/8Af/8AgP8Afv8AgP8AgP8AgP8AgP8Af/8AeP8Af/8Af/8Af//////rzEHnAAAAfXRSTlMAD7CCAivatxIDx5EMrP19AXdLEwgLR+6iCR/M0yLRzyFF7JupSXn8cw6v60Q0QeqzKtgeG237HMne850/6Qeq7QaZ+WdydHtj+OM3qENCMRYl1B3K2U7wnlWE/mhlirjkODa9FN/BF7/iNV/2kASNZpX1Wlf03C4stRGxgUPclqoAAAABYktHRACIBR1IAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH4gEaBzgZ4yeM3AAAAT9JREFUOMvNUldbwkAQvCAqsSBoABE7asSOBRUVVBQNNuy9996789+9cMFAMHnVebmdm+/bmdtbQv4dOFOW2UjPzgFyLfo6nweKfIMOBYWwFtmMPGz2Yj2pJI0JDq3udJW6VVbmKa9I192VQFV1ktXUAl5NB0cd4KpnORqsEO2ZIRpF9gJfE9Dckqq0KuZt7UAH5+8EPF3spjsRpCeQNO/tA/qDwIDA+OCQbBoKA8NOdjMySgcZGVM6jwcgRuUiSs0nlPFNSrEpJfU0jTLD6llqbvKxei7OzvkFNQohi0vAsj81+MoqsCaoPOQFgus/1LyxichW+hS2JWCHZ7VlF9jb187pIAYcHiViHAMnp5mTjJ8B5xeEXF4B1ze/fTh/C0h398DDI9HB07O8ci+vRBdvdGnfP4gBuM8vw7X/G3wDmFhFZEdxzjMAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTgtMDEtMjZUMDc6NTY6MjUrMDE6MDA67pVWAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDE4LTAxLTI2VDA3OjU2OjI1KzAxOjAwS7Mt6gAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAAAWdEVYdFRpdGxlAGp1ZWppbl9sb2dvIGNvcHlxapmKAAAAV3pUWHRSYXcgcHJvZmlsZSB0eXBlIGlwdGMAAHic4/IMCHFWKCjKT8vMSeVSAAMjCy5jCxMjE0uTFAMTIESANMNkAyOzVCDL2NTIxMzEHMQHy4BIoEouAOoXEXTyQjWVAAAAAElFTkSuQmCC"); opacity: 0.36 }
.markdown-body h2 { position: relative; font-size: 20px; border-left: 4px solid; padding: 0 0 0 10px; margin: 30px 0 }
.markdown-body h3 { font-size: 16px }
.markdown-body ul { list-style: disc outside; margin-left: 2em; margin-top: 1em }
.markdown-body li { line-height: 2; color: rgba(89, 89, 89, 1) }
.markdown-body img.loaded { margin: 0 auto; display: block }
.markdown-body blockquote { background: rgba(255, 249, 249, 1); margin: 2em 0; padding: 2px 20px; border-left: 4px solid rgba(178, 174, 197, 1) }
.markdown-body blockquote p { color: rgba(102, 102, 102, 1); line-height: 2 }
.markdown-body a { color: rgba(3, 106, 202, 1); border-bottom: 1px solid rgba(3, 106, 202, 0.8); font-weight: 400; text-decoration: none }
.markdown-body em strong, .markdown-body strong { color: rgba(3, 106, 202, 1) }
.markdown-body hr { border-top: 1px solid rgba(19, 92, 224, 1) }
.markdown-body pre { overflow: auto }
.markdown-body code, .markdown-body pre { overflow: auto; position: relative; line-height: 1.75; font-family: Menlo, Monaco, Consolas, Courier New, monospace }
.markdown-body pre>code { font-size: 12px; padding: 15px 12px; margin: 0; word-break: normal; display: block; overflow-x: auto; color: rgba(51, 51, 51, 1); background: rgba(248, 248, 248, 1) }
.markdown-body code { border-radius: 2px; overflow-x: auto; background-color: rgba(255, 245, 245, 1); color: rgba(255, 80, 44, 1); font-size: 0.87em; padding: 0.065em 0.4em }
.markdown-body table { border-collapse: collapse; margin: 1rem 0; overflow-x: auto }
.markdown-body table td, .markdown-body table th { border: 1px solid rgba(223, 226, 229, 1); padding: 0.6em 1em }
.markdown-body table tr { border-top: 1px solid rgba(223, 226, 229, 1) }
.markdown-body table tr:nth-child(2n) { background-color: rgba(246, 248, 250, 1) }.markdown-body pre, .markdown-body pre>code.hljs { color: rgba(51, 51, 51, 1); background: rgba(248, 248, 248, 1) }
.hljs-comment, .hljs-quote { color: rgba(153, 153, 136, 1); font-style: italic }
.hljs-keyword, .hljs-selector-tag, .hljs-subst { color: rgba(51, 51, 51, 1); font-weight: 700 }
.hljs-literal, .hljs-number, .hljs-tag .hljs-attr, .hljs-template-variable, .hljs-variable { color: rgba(0, 128, 128, 1) }
.hljs-doctag, .hljs-string { color: rgba(221, 17, 68, 1) }
.hljs-section, .hljs-selector-id, .hljs-title { color: rgba(153, 0, 0, 1); font-weight: 700 }
.hljs-subst { font-weight: 400 }
.hljs-class .hljs-title, .hljs-type { color: rgba(68, 85, 136, 1); font-weight: 700 }
.hljs-attribute, .hljs-name, .hljs-tag { color: rgba(0, 0, 128, 1); font-weight: 400 }
.hljs-link, .hljs-regexp { color: rgba(0, 153, 38, 1) }
.hljs-bullet, .hljs-symbol { color: rgba(153, 0, 115, 1) }
.hljs-built_in, .hljs-builtin-name { color: rgba(0, 134, 179, 1) }
.hljs-meta { color: rgba(153, 153, 153, 1); font-weight: 700 }
.hljs-deletion { background: rgba(255, 221, 221, 1) }
.hljs-addition { background: rgba(221, 255, 221, 1) }
.hljs-emphasis { font-style: italic }
.hljs-strong { font-weight: 700 }
引言
現在的前端真可謂是百花齊放,百家爭鳴,各種框架層出不窮,但是主要目前用的最多的還是要數Vue、React、以及Angular,這三種,當然不乏近期新出的一些其他框架,但是她們都有一個顯著的特點,那就是使用了MVVM的架構。
首先我們要搞清楚什么是
MVVM?
MVVM就是Model-View-ViewModel的縮寫,MVVM最早由微軟提出來,它借鑒了桌面應用程序的MVC思想,在前端頁面中,把Model用純JavaScript對象表示,View負責顯示,兩者做到了最大限度的分離。把Model和View關聯起來的就是ViewModel。ViewModel負責把Model的數據同步到View顯示出來,還負責把View的修改同步回Model。改變
JavaScript對象的狀態,會導致DOM結構作出對應的變化!這讓我們的關注點從如何操作DOM變成了如何更新JavaScript對象的狀態,而操作JavaScript對象比DOM簡單多了。這就是
MVVM的設計思想:關注Model的變化,讓MVVM框架去自動更新DOM的狀態,從而把開發者從操作DOM的繁瑣步驟中解脫出來!
接下來我會帶著你們如何去實現一個簡易的MVVM架構。
一、構建MVVM構造函數
創建一個MVVM構造函數,用于接收參數,如:data、methods、computed等:
function MVVM(options) {
this.$options = options;
let data = this._data = this.$options.data;
observe(data);
for (let key in data) {
Object.defineProperty(this, key, {
enumerable: true,
get() {
return this._data[key];
},
set(newVal) {
this._data[key] = newVal;
}
});
};
initComputed.call(this);
new Compile(options.el, this);
options.mounted.call(this);
}
二、構建Observe構造函數
創建一個Observe構造函數,用于監聽數據變化:
function Observe(data) {
let dep = new Dep();
for (let key in data) {
let val = data[key];
observe(val);
Object.defineProperty(data, key, {
enumerable: true,
get() {
Dep.target && dep.addSub(Dep.target);
return val;
},
set(newVal) {
if (val === newVal) {
return;
}
val = newVal;
observe(newVal);
dep.notify();
}
});
};
};
三、構建Compile構造函數
創建一個Compile構造函數,用于解析模板指令:
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(frag) {
Array.from(frag.childNodes).forEach(node => {
let txt = node.textContent;
let reg = /\{\{(.*?)\}\}/g;
if (node.nodeType === 3 && reg.test(txt)) {
let arr = RegExp.$1.split('.');
let val = vm;
arr.forEach(key => { val = val[key]; });
node.textContent = txt.replace(reg, val).trim();
new Watcher(vm, RegExp.$1, newVal => {
node.textContent = txt.replace(reg, newVal).trim();
});
}
if (node.nodeType === 1) {
let nodeAttr = node.attributes;
Array.from(nodeAttr).forEach(attr => {
let name = attr.name;
let exp = attr.value;
if (name.includes('v-')) {
node.value = vm[exp];
}
new Watcher(vm, exp, newVal => {
node.value = newVal;
});
node.addEventListener('input', e => {
let newVal = e.target.value;
vm[exp] = newVal;
});
});
};
if (node.childNodes && node.childNodes.length) {
replace(node);
}
});
}
vm.$el.appendChild(fragment);
}
四、構建Watcher構造函數
創建一個Watcher構造函數,用于更新視圖:
function Watcher(vm, exp, fn) {
this.fn = fn;
this.vm = vm;
this.exp = exp;
Dep.target = this;
let arr = exp.split('.');
let val = vm;
arr.forEach(key => {
val = val[key];
});
Dep.target = null;
}
Watcher.prototype.update = function() {
let arr = this.exp.split('.');
let val = this.vm;
arr.forEach(key => {
val = val[key];
});
this.fn(val);
}
五、構建Dep構造函數
創建一個Dep構造函數,用于管理Watcher:
function Dep() {
this.subs = [];
}
Dep.prototype.addSub = function(sub) {
this.subs.push(sub);
}
Dep.prototype.notify = function() {
this.subs.forEach(sub => {
sub.update();
});
}
六、構建initComputed構造函數
創建一個initComputed構造函數,用于初始化計算屬性:
function initComputed() {
let vm = this;
let computed = this.$options.computed;
Object.keys(computed).forEach(key => {
Object.defineProperty(vm, key, {
get: typeof computed[key] === 'function' ? computed[key] : computed[key].get,
set() {}
});
});
}
總結:
至此我們就完成了一個簡易的MVVM框架,雖然簡易,但是基本的核心思想差不多都已經表達出來了,最后還是希望大家不要丟在收藏文件夾里吃灰,還是要多多動手練習一下,所謂眼過千遍,不如手過一遍。
總結
以上是生活随笔為你收集整理的手把手教你实现MVVM架构的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: codeup之冒泡排序
- 下一篇: Flutter集成微信小程序技术教程