根据vue的官网介绍,可以得知vue是一个mvvm框架,且是响应式的。为了更深入了理解其内涵,本人以及理解实现了一个简单的mvvm学习的demo。下面分享给大家,欢迎大家一起讨论。
一、mvvm至少包含的内容
- 指令集合,如:text、model等
- 数据模型,与视图交互的数据
- 组件的支持:也就是部分html代码的动态更新
二、我的实现
1. 变量的定义与watch的实现
var directives = {}; //指令集合 var vNodes = new Array(); //解析的Dom集合 var dataModel = { name:"name", title: "title" }; //数据Model var Watch = { isInit: false, watchs: new Array(), run: function(newValue, expOrfn){ var self = this; if(!self.isInit){ expOrfn.call(vModel); } this.watchs.map(function(data,index){ data.nodes.map(function(d,i){ if(self.isInit){ d.directive.init(newValue, d, data); //绑定初始化值, 以及初始化一些事件 }else{ d.directive.update(newValue, d, data); //只更新值,此时run的调用来值value-set } }); }); self.watchs = []; }, push:function(watch){ this.watchs.push(watch); } } //任务管理
说明:
- Watch的push方法,用于依赖的添加,然后run来执行所以依赖,执行完成后,需要清理当前依赖的集合。在vue中依赖的收集是在dep中完成的,而watch提供的任务管理(不知道理解是否正确)
2. 指令的定义
directives.text = { init: function(value, vNode){ vNode.elm.textContent = value; }, update: function(value, vNode){ vNode.elm.textContent = value; } } //需要响应事件的怎么办 directives.model ={ init: function(value, vNode, _watch){ vNode.elm.value = value; //判断自己发生的改变,不应该再改变自己 vNode.elm.addEventListener('keyup',function(evt){ vNode.model[_watch.key] = vNode.elm.value; }); }, update:function(){ } }
说明:
- 由于是demo学习示例,所以只定义了简单的text和model两个指定,text:用于数据的显示,而model用于input(输入框)的响应
3. vModel的生成
//转换vModel,暂支持一级 var properties = Object.getOwnPropertyNames(dataModel); var vModel = {}, formSetting = false; for( var index in properties){ (function refreshData(_index){ var key = properties[_index]; var property = Object.getOwnPropertyDescriptor(dataModel, key); var setter = property.set; var getter = property.get; var _val = property.value; var _getter = function(){ var val = getter ? getter.call(vModel) : _val; //收集依赖,与watch要分开 Watch.push({ key: key, nodes: vNodes.filter(function(data,index){ return data.modelKey == key ? true : false; }), getter: _getter }); return val; }; Object.defineProperty(vModel, key, { configurable: true, enumerable: true, set: function(value){ if(setter){ setter.call(vModel, value); } //处理依赖 Watch.run(value, _getter); //this.value = value; }, get: _getter }) })(index); }
说明:
- vModel是根据dataModel生成的,也就是自定义了每个属性的get和set方法,在es6中也可以用proxy实现(是否说对了)。
- 在属性set的时候,会先调用get方法来收集依赖。方便值改变后,能将所影响的内容都修改掉。
4. 解析dom为vNode
//解析vNodes var app = document.getElementById('app'); app.childNodes.forEach(function(data,index){ if(data.nodeType != 1) return; var hv = data.getAttribute('data-hv'); var hvs = hv.split(','); hvs.forEach(function(item,row){ var keyValue = item.split(':'); //vNode对象上一定要有model,这是方便vNode相应时候的找vModel vNodes.push({ directive: directives[keyValue[0]], modelKey: keyValue[1], model: vModel, elm: data }); }); });
说明:
- 这里说解析为vNode很是牵强,因为此只是收集了dom上data-hv指定的指令,并将对就的指令、元素、vModel等组成一个对象存储在vNodes中,以供vModel各属性的get方法收集依赖时引用。
5. 第一次初始化
//调用所有的get一次 Watch.isInit = true; var _keys = Object.getOwnPropertyNames(vModel); _keys.map(function(key,data){ var data = vModel[key]; Watch.run(data); }); Watch.isInit = false;
说明:
- 将初始化的vModel的值渲染到Dom上,这里是主动执行每个的get,然后运行watch.run方法。
- 此处设计和实现本人感觉与vue的思路不对,如有高人看见,麻烦提点与指引。
6. 被解析的dom
<div id="app"> <span data-hv="text:title"></span> <span data-hv="text:title"></span> <input data-hv="model:title" /> </div>