MVVM框架,实现双向数据绑定。
核心:编译compile、数据劫持observer、观察者watcher 观察者模式
思路:
1.model影响视图:编译时注册watcher,在注册watcher,调用get,通过observer数据劫持get方法,将多个观察者统一管理起来。当改变数据时,调用set方法,将收拢的对应观察者的upadte方法更新。
2.视图影响model:编译时注册wather,node节点绑定对应的事件,事件触发时,更改数据模型。
js模拟实现MVVM
MVVM.js,集中统一;供外调用;
class MVVM{
constructor(options){
this.$data = options.data;
this.$el = options.el;
if(this.$el){
new Observer(this.$data);
this.proxyData(this.$data);
new Compile(this.$el,this);
}
}
proxyData(data){
Object.keys(data).forEach(key=>{
Object.defineProperty(this,key,{
get(){
return data[key];
},
set(newVal){
data[key] = newValue;
}
})
})
}
}
Observe.js,数据劫持;收拢watcher统一管理;通知watcher更新;
class Observer(){
constructor(data){
this.observe(data);
}
observe(data){
if(Object.prototype.toString.call(data) !='[object Array]'){
}
if(!data || Object.prototype.toString.call(data) !='[object Object]'){
return;
};
Object.keys(data).forEach(key=>{
this.defineReactive(data,key,data[key]);
this.observe(data[key]);
})
}
defineReactive(obj,key,value){
let that = this;
let dep = new Dep();
Object.defineProperty(obj,key,{
enumerable:true,
configurable:true,
get(){
Dep.target && dep.addSub(Dep.target);
return value;
},
set(newValue){
if(newValue!=value){
value = newValue;
that.observe(newvalue)
dep.notify();
}
}
})
}
}
class Dep(){
constructor(){
this.subs = []
}
addSub(watcher){
this.subs.push(watcher)
}
notify(){
this.subs.forEach(watcher=>{
watcher.update();
})
}
}
Compile.js,元素和数据进行编译,首次渲染视图;创建watcher,定义数据变化后如何更改视图;绑定事件,定义视图变化如何更改model
class Compile{
constructor(el,vm){
this.el = this.isElementNode(el) ? el : document.querySelector(el);
this.vm = vm;
if(this.el){
let fragment =this.node2Fragment(this.el)
this.complile();
this.el.appendChild(fragment)
}
}
isElementNode(){
return node.nodeType == 1;
}
idDirective(name){
return name.includes('v-')
}
node2Fragment(el){
let fragment = document.createDocumentFragment();
let firstChild = el.firstChild;
while(firstChild){
fragment.appenChild(firstChild);
firstChild = el.firstChild;
}
return fragment;
}
compile(fragment){
let childrens = fragment.childNodes;
Array.from(childrens).forEach(node=>{
if(this.isElementNode(node)){
this.compileElementNode(node);
this.compile(node)
}else{
this.compileText(node)
}
})
}
compileElement(node){
let attrs = node.attributes;
Array.from(attrs).forEach(attr=>{
let attrName = attr.name;
if(this.isDirective(attrName)){
let expr = attr.value;
let [,type] = attrName.split('-');
CompileUtil[type](node,this.vm,expr)
}
})
}
compileText(node){
let expr = node.textContent;
let reg = /\{\{\([^}]+)}\}/g;
if(reg.test(expr)){
CompileUtil['text'](node,this.vm,expr)
}
}
}
CompileUtil = {
getVal(vm,expr){
expr = expr.split('.');
return expr.reduce((prev,next)=>{
return prev[next]
},vm.$data)
}
getTextVal(vm,expr){
let reg = /\{\{\([^}]+)}\}/g;
return expr.replace(reg,(...args)=>{
retrun this.getVal(vm,args[1])
})
}
setVal(vm,expr,value){
expr = expr.split('.');
return expr.reduce((prev,next,index)=>{
if(index == expr.length-1){
prev[next] = value;
}
return prev[next]
},vm.$data)
}
model(node,vm,expr){
let updateFn = this.updater['modelUpdater'];
updateFn && updateFn(node,this.getVal(vm,expr));
new Watcher(vm,expr,(newValue)=>{
updateFn && updateFn(node,newValue)
})
node.addEventListener('input',(e)=>{
let newValue = e.target.value;
this.setVal(vm,expr,newValue)
})
}
text(node,vm,expr){
let updateFn = this.updater['textUpdater'];
let value = this.getTextVal(vm,expr);
updateFn && updateFn(node,value);
let reg = /\{\{\([^}]+)}\}/g;
expr.replace(reg,(...args)=>{
new Watcher(vm,args[1],(newValue)=>{
updateFn && updateFn(node,this.getTextVal(vm,expr));
})
})
}
updater:{
textUpdater(node,value){
node.textContent = value
}
modelUpdater(node,value){
node.value = value;
}
}
}
Watcher.js ,创建观察者,编译过程中每遇到一个表达式或者v-model指令,就创建一个观察者。 每一个数据 对应多个观察者
calss Watcher{
constructor(vm,expr,cb){
this.vm = vm;
this.expr = expr;
this.cb = cb;
this.value = this.get();
}
getVal(vm,expr){
expr = expr.split('.');
return expr.reduce((prev,next) =>{
return prev[next]
},vm.$data)
}
get(){
Dep.targer = this;
let value = this.getVal(this.vm,this.expr)
Dep.target = null;
return value;
}
update(){
let newValue = this.getVal(this.vm,this.expr);
let oldValue = this.value;
if(newValue != oldValue){
this.cb(newValue);
}
}
}