手把手教你实现MVVM架构
引言
现在的前端真可谓是百花齐放,百家争鸣,各种框架层出不穷,但是主要目前用的最多的还是要数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('')) {
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框架,虽然简易,但是基本的核心思想差不多都已经表达出来了,最后还是希望大家不要丢在收藏文件夹里吃灰,还是要多多动手练习一下,所谓眼过千遍,不如手过一遍。
作者:前端第一深情阿斌
来源:juejin.cn/post/7202431872968851517