ThinkChat2.0新版上线,更智能更精彩,支持会话、画图、阅读、搜索等,送10W Token,即刻开启你的AI之旅 广告
## 1 mvvm意义 >[info] 以数据双向绑定为核心,实现UI层与数据层的互动mvvm效果 >[info] mvvm中的m指的是服务器后端的数据层封装 >[info] mvvm中的v指的是客户端视图层的封装 >[info] mvvm中的vm指的是数据层与视图层的数据绑定核心 >[info] 正如前面数据绑定介绍mvvm实现了视图层与数据层的同步刷新 ## 2 思路整理 >[info] mvvm在概念上将视图层与数据层逻辑分离,ViewModel也就是vm是整个模式的核心,实现Vm需要将模型与视图关联起来。总共分为5点 >[info] (1) 实现一个Compiler对元素的每个节点进行指令的扫描和提取。这些指令实现View与Vm的互动 >[info] (2) 实现一个Parser解析元素中的指令,将指令的意图通过某个刷新函数更新到dom上。比如解析节点<p v-show = "isShow" > </p>后,根据Model中的isShow调用node.style.display控制元素的显示和隐藏 >[info] (3) 实现一个Watcher注册Parser对指令解析的刷新函数和对应Model的字段关联起来 >[info] (4) 实现一个Observer能够对象所有字段进行set检测,一旦发生变化可以对所有的消息订阅者Watcher进行刷新操作 >[info] (5) 通过Observer在Watcher中建立对Model的监听。当Model在的值发生变化时,监听者Watcher被触发刷新,调用步骤2中生成的刷新函数。实现视图的刷新操作 ## 3 模块划分 >[info] 这里将mvvm框架实现为5个模块。 >[info] (1)编译模块Compiler。编译好指令将指令信息传递给解析器Parser处理 >[info] (2)解析模块Parser。解析指令初始化,生成对应Watcher刷新操作 >[info] (3)订阅模块Watcher。管理所有的消息接受与刷新操作的关联。 >[info] (4)监视模块Observer。监视数据的变化反馈给Watcher >[info] (5)刷新模块Update。接受Watcher的刷新消息,进行视图刷新 ![](https://box.kancloud.cn/2016-05-06_572c4de51a9a8.png) ## 4 编译模块Compiler >[info] 编译模块实现对视图中元素的节点指令的扫描和提取。 >[info] 编译和解析的过程会多次遍历节点树,为了提高编译效率,在mvvm中将节点元素转换成文档碎片fragment编译对象。 >[info]待全部节点编译完成后将文档碎片添加回到原来的真实节点中 `vm.complieElement`实现对元素所有节点的扫描和指令提取 ~~~ vm.complieElement = function(fragment,root) { ;获取孩子节点 var node , childNodes = fragment.childNodes; ;子节点扫描处理 for (var i = 0; i< childNodes.length; i++){ node = childNodes[i]; ;包含指令 if(this.hasDirective(node)){ this.$unCompileNodes.push(node); } ;扫描子节点 if(node.childeNodes.length){ this.compileElement(node,false); } } ; if(root){ this.compileAllNodes(); } } ~~~ `vm.compileAllNode()`实现对`this.$unCompileNodes`中每个节点的的编译(将指令信息交给解析模块Parser),编译完一个节点从缓存队列删除,检`this.$unCompileNodes`直到0。然后将文档碎片添加到真实节点上 ## 5 解析模块 Parser >[info] Compiler完成指令的提取,解析模块Parser对每个指令实现不同的解析方法 >[info] 指令的解析一方面将数据值更新到视图上, >[info] 指令的解析一方面将刷新函数订阅到Model的变化监测中 ~~~ ;以v-text指令的解析为例 parser.parseVText = function(node,model){ ;获取Model中定义的初始值 var text = this.$model[model]; ;更新节点的文本 node.textContent = text; ;或者使用刷新函数 //updater.updateNodeTextContent(node,text); ;在watcher中订阅model的变化 watcher.watch(model,function(last,old){ node.textContent = last; //刷新函数 //updater.updateNodeTextContent(node,text); }) } ~~~ ## 6 数据订阅模块Watcher >[info] Watcher提供watch方法对数据变化进行订阅, >[info] 一个参数是模型字段model >[info] 另一个是回调函数,实现Observer来触发的,参数传入新值last和旧值old。 >[info] Watcher拿到新值后就可以找到model对应的回调进行更新视图。 >[info] 这里可以知道model和刷新函数是一对多的庴,一个model可以包含多个回调函数(刷新函数)。 ~~~ ;Watcher.watch()方法实现如下 watcher.watch = function(field,callback,context){ var callbacks = this.$watchCallbacks; ;检查vm.$model中属性的更新 if(!Object.hasOwnProperty.call(this.$model,field)){ console.warn('The field:'+field+'does not exist in model!'); return; } ;缓存回调函数的数组 if(!callbacks[field]){ callbacks[field] = []; } ;添加回调函数到数组 callbacks[field].push([callback,context]); } ~~~ 数据模型的field字段发生变化时,watcher缓存数组中订阅field的所有回调 ## 7 数据监听模块Observer >[info] Observer是整个mvvm实现的核心基础。 >[info] 其实现原理与机制见上面两节。 >[info] 这里使用Object.defineProperty实现对属性的get/set拦截 ~~~ ;实现object的prop属性的get和set方法 Object.defineProperty(object,prop,{ get:function(){ return this.getValue(object,prop); } set:function(newValue){ var oldValue = this.getValue(object,prop); if(newValue !== oldValue){ this.setValue(object,newValue,prop); this.triggerChange(prop,newValue,oldValue); } } }) ;实现对数组push和shift的监听 observer.rewriteArrayMethods = function(array){ var self = this; var arrayProto = Array.prototype; var arrayMethods = Object.create(arrayProto); var methods = 'push|pop|shift|unshift|splice|sort|reverse'.split('|'); methods.forEach(function(method){ Object.defineProperty( arrayMethods, method, function(){ var i = arguments.length; var original = arrayProto[method]; var args = new Array(i); while(i--){ args[i] = arguments[i]; } var result = original.apply(this,args); self.triggerChange(this,method); return result; }); }); array.__proto__ = arrayMethods; } ~~~ ## 8 视图刷新模块Updater >[info] 刷新模块Updater是最简单,负责每个指令对应的刷新函数。 >[info] 这里接受其他模块的解析结果。调用Update实现对视图或者事件的更新 ~~~ ;v-text的刷新函数 updater.updateNodeTextContent = function(node,text){ node.textContent = text; } ;v-bind的刷新函数 updater.updateNodeStyle = function(node,propperty,value){ node.style[propperty] = value; } ~~~ ## 9 双向绑定实现 >[info] 双向绑定在数据变化时更新视图层,或者视图层变化时数据层也进行同步 >[info] 数据变化更新表单值可以使用Watcher模块 >[info] 表单变化更新数据只需要监听表单的事件 ~~~ ;数据到视图层 watcher.watch(model,function(last,old){ input.value = last; }); ;视图到数据层 var model = this.$model; input.addEventListener('change',function(){ model[field] = this.value; }); ~~~ ## 10 参考 [mvvm的简单实现](https://segmentfault.com/a/1190000004847657) [mvvm的完整代码](https://github.com/tangbc/sugar)