转载

如何实现一个MVVM框架

MVVM(Model View ViewModel)最初由微软在Windows Presentation Foundation(WPF)和Silverlight中引入,近年来、它作为MVC的一种替代方案在前端也如日中天。像其他MV*一样,MVVM中的Model代表着我们应用的数据;而View代表着用户界面;最重要的是ViewModel,可以将其看作一个拥有双向数据处理能力的转换器,它将模型数据传递到视图,并将视图指令传递到模型。MVVM框架将前端工程师从繁琐的DOM操作中彻底地解放出来,让我们可以更专注于自己的业务。

接下来我们探讨一种实现双向绑定的方案,本文适合实际使用过MVVM框架的人阅读,包括AngularJS、Avalon等。最终效果如下:

JS Bin on jsbin.com

详细源代码在请到github下载 。

1.基本功能

双向绑定作为MVVM框架的最大特点,是如何实现的呢?MVVM数据流示意图如下:

如何实现一个MVVM框架

示意图中可以看出双向数据流:

View将变动通知到ViewModel,然后ViewModel对Model进行更新。 Model将变动通知到ViewModel,然后ViewModel对View进行更新。 

其中最核心的功能是对视图(View)和模型(Model)变动的监听。

(1).视图变动的监听

MVVM框架都是通过相应的指令,在HTML中声明式的标记出需要监听的DOM节点。本文实现中,我们主要涉及到两个指令: foio-controllerfoio-model 以及一个表达式{{}}。 比如:

<input type="text" foio-model="nickname"> 

上述指令foio-model,声明将View中的input的变动通知到Model中的nickname。通过对的视图节点(input)注册监听函数就可以得到视图(input)的变动了。

//对视图中的input节点注册input事件监听函数 var elem = document.querySelector('input'); if (elem.addEventListener) {         elem.addEventListener('input', callback, false);     } else {         elem.attachEvent('oninput', callback); } 

(2).模型变动的监听

对模型变动的监听可以通过ECMAScript5中的API实现。

Object.defineProperty(obj, prop, descriptor) 

可以通过该API为对象添加一个属性,并设置该属性的gett函数和set函数,在访问属性时会触发相应的get函数和set函数。

var air = {}; Object.defineProperty(air, 'temperature', {     get: function() {         console.log('get!');     },     set: function(value) {         console.log('set!');     } });  air.temperature = 15; //output: set! air.temperature;    //outpu: get! 

我们可以在set函数中得到模型的变动,并将相关变动通知到ViewModel。

2.总体实现

MVVM的主要流程包括(View)视图扫描、(Model)模型构建、以及关联视图和模型(ViewModel)

(1)View(视图)扫描

处理View(视图)必然涉及到对DOM结构的扫描,通过扫描抽取指令(本文只有三种指令,foio-controller、foio-model、);并对相应的节点进行如下处理:

绑定通知函数,用于在视图更新时通知ViewModel 绑定更新函数,用于在模型更新时通过该函数更新视图 

针对不同的节点类型,这些通知函数和更新函数都是预先定义好的,存储在 directives 结构中。在节点扫描过程中,当遇到指令时,就通过executeBindings函数对相应的节点进行绑定处理。流程图如下:

如何实现一个MVVM框架

(2)Model(模型)构建

而对Model的处理也主要是注册监听函数,用于在Model变化时得到通知,如上图所示。controller中的每一个变量都通过 Object.defineProperty(obj, prop, descriptor) 定义到Model上,其中descriptor上的get函数可以用于搜集依赖,而set函数则用于通知依赖于该Model的视图进行更新。

var descriptor = {     var dependencyList = [];     get: function() {             //搜集依赖             dependencyList.push(this);             return value;         },     set: function(newVal) {             if (oldVal === newVal) {                 return;             }             oldVal = newVal;             //通知依赖于该Model的视图进行更新             for (var dependIdx in dependencyList) {                 dependencyList[dependIdx].updateView(newVal);             }         } } 

(3)关联模型和视图

View(视图)扫描的结果是一个元素集合

bindings = [                 {                     type: type, //指令类型                     element: elem, //DOM节点                     expr: value, //绑定的变量名称                 },                 {...}             ] 

而Model(模型)构建的结果也是一个集合:

vmodels = {             controller1: {                 expr1: value1,                 expr2: value2,                 binder: {expr1: function(){},expr2:function(){}}             },             controller1: {...}         } 

通过executeBindings函数,将视图和模型关联起来。

function executeBindings(bindings, vmodels) {     for (var i = 0, binding; (binding = bindings[i++]);) {         binding.vmodels = vmodels;         directives[binding.type](binding);     }; } 

每一种指令都有不同的初始化函数,比如针对 foio-model 指令,当DOM节点为input类型时,初始化函数做了三件事:

监听input和DOMAutoComplete事件 注册对模型的依赖 提供更新该DOM节点的方法 

详细代码如下:

directives['model']={         switch (binding.xtype) {             case "input":                 //绑定input事件                 binding.bound('input', updateVModel);                 //绑定DOMAutoComplete事件                 binding.bound('DOMAutoComplete', updateVModel);                 //注册对模型的依赖                 elem.value = closetVmodel.binder[binding.expr].apply(binding);                 //更新该DOM节点的方法                 binding.updateView = function(newVal) {                     elem.value = newVal;                 };             break;         } } 

至此我们实现了一个基本的MVVM框架了,虽然只有三个指令,但是基本能够说明如何设计并实现一个MVVM框架了。

原文  http://foio.github.io/mvvm-overview/
正文到此结束
Loading...