轻量级前端MVVM框架avalon - 模型转换(一)

接上一章 ViewModel


modelFactory工厂是如何加工用户定义的VM?

附源码

  • 洋洋洒洒100多行内部是魔幻般的实现
   1:      function modelFactory(scope) {
   2:          var skipArray = scope.$skipArray, //要忽略监控的属性名列表
   3:                  model = {},
   4:                  Descriptions = {}, //内部用于转换的对象
   5:                  json = {},
   6:                  callSetters = [],
   7:                  callGetters = [],
   8:                  VBPublics = Object.keys(watchOne); //用于IE6-8
   9:          skipArray = Array.isArray(skipArray) ? skipArray.concat(VBPublics) : VBPublics;
  10:          forEach(scope, function(name, value) {
  11:              if (!watchOne[name]) {
  12:                  json[name] = value;
  13:              }
  14:              var valueType = avalon.type(value);
  15:              if (valueType === "Function") {
  16:                  VBPublics.push(name); //函数无需要转换
  17:              } else {
  18:                  if (skipArray.indexOf(name) !== -1) {
  19:                      return VBPublics.push(name);
  20:                  }
  21:                  if (name.charAt(0) === "$" && !systemOne[name]) {
  22:                      return VBPublics.push(name);
  23:                  }
  24:                  var accessor, oldArgs;
  25:                  if (valueType === "Object" && typeof value.get === "function" && Object.keys(value).length <= 2) {
  26:                      var setter = value.set,
  27:                              getter = value.get;
  28:                      accessor = function(neo) { //创建计算属性
  29:                          if (arguments.length) {
  30:                              if (stopRepeatAssign) {
  31:                                  return; //阻止重复赋值
  32:                              }
  33:                              if (typeof setter === "function") {
  34:                                  setter.call(model, neo);
  35:                              }
  36:                              if (oldArgs !== neo) { //由于VBS对象不能用Object.prototype.toString来判定类型,我们就不做严密的检测
  37:                                  oldArgs = neo;
  38:                                  notifySubscribers(accessor); //通知顶层改变
  39:                                  model.$events && model.$fire(name, neo, value);
  40:                              }
  41:                          } else {
  42:                              if (openComputedCollect || !accessor.locked) {
  43:                                  collectSubscribers(accessor);
  44:                              }
  45:                              return value = json[name] = getter.call(model); //保存新值到json[name]
  46:                          }
  47:                      };
  48:                      accessor.nick = name;
  49:                      callGetters.push(accessor);
  50:                  } else {
  51:                      value = NaN;
  52:                      callSetters.push(name);
  53:                      accessor = function(neo) { //创建监控属性或数组
  54:                          if (arguments.length) {
  55:                              if (stopRepeatAssign) {
  56:                                  return; //阻止重复赋值
  57:                              }
  58:                              if (value !== neo) {
  59:                                  var old = value;
  60:                                  if (valueType === "Array" || valueType === "Object") {
  61:                                      if (value && value.$id) {
  62:                                          updateViewModel(value, neo, Array.isArray(neo));
  63:                                      } else if (Array.isArray(neo)) {
  64:                                          value = Collection(neo, model, name);
  65:                                      } else {
  66:                                          value = modelFactory(neo);
  67:                                      }
  68:                                  } else {
  69:                                      value = neo;
  70:                                  }
  71:                                  json[name] = value && value.$id ? value.$json : value;
  72:                                  notifySubscribers(accessor); //通知顶层改变
  73:                                  model.$events && model.$fire(name, value, old);
  74:                              }
  75:                          } else {
  76:                              collectSubscribers(accessor); //收集视图函数
  77:                              return value;
  78:                          }
  79:                      };
  80:                  }
  81:                  accessor[subscribers] = [];
  82:                  Descriptions[name] = {
  83:                      set: accessor,
  84:                      get: accessor,
  85:                      enumerable: true
  86:                  };
  87:              }
  88:          });
  89:          if (defineProperties) {
  90:              defineProperties(model, Descriptions);
  91:          } else {
  92:              model = VBDefineProperties(Descriptions, VBPublics);
  93:          }
  94:          VBPublics.forEach(function(name) {
  95:              if (!watchOne[name]) {
  96:                  model[name] = scope[name];
  97:              }
  98:          });
  99:          callSetters.forEach(function(prop) {
 100:              model[prop] = scope[prop]; //为空对象赋值
 101:          });
 102:          callGetters.forEach(function(fn) {
 103:              Publish[expose] = fn;
 104:              callSetters = model[fn.nick];
 105:              fn.locked = 1;
 106:              delete Publish[expose];
 107:          });
 108:          model.$json = json;
 109:          model.$events = {}; //VB对象的方法里的this并不指向自身,需要使用bind处理一下
 110:          model.$watch = Observable.$watch.bind(model);
 111:          model.$unwatch = Observable.$unwatch.bind(model);
 112:          model.$fire = Observable.$fire.bind(model);
 113:          model.$id = generateID();
 114:          model.hasOwnProperty = function(name) {
 115:              return name in model.$json;
 116:          };
 117:          return model;
 118:      }
  • VM是用ecma262v5的新API, Object.defineProperties生成的一个充满访问器的对象,这样的对象,能通过用户对它的属性的读写,触发定义时的getter, setter函数。getter, setter对rubyer, pythoner, C#er应该很熟悉,我就不展开了。
  • 旧式IE,avalon利用VBScript的类实例,它也存在其他语言的访问器。不过,VBS对象不像JS对象那样随意添加新属性,删除已有属性,因此我们就无法监后添加的新属性。Object.defineProperties也一样,它能处理的属性也只是它定义时的属性,想监控后来的,需要再调用一次Object.defineProperties。

整个工厂方法内部都是围绕着scope处理

  1. 过滤监控的属性
  2. 收集视图函数
  3. 转换用于定义

skipArray //要忽略监控的属性名列表

0: "$json"
1: "$skipArray"
2: "$watch"
3: "$unwatch"
4: "$fire"
5: "$events"

我们还是已官网的demo为列

    avalon.define("simple", function(vm) {
vm.firstName = "司徒"
vm.lastName = "正美"
vm.fullName = {//一个包含set或get的对象会被当成PropertyDescriptor,
set: function(val) {//set, get里面的this不能改成vm
var array = (val || "").split(" ");
this.firstName = array[0] || "";
this.lastName = array[1] || "";
},
get: function() {
return this.firstName + " " + this.lastName;
}
}
})
avalon.scan(document.querySelector("fieldset"));

此时传入的vm为

   $watch: function noop() {
firstName: "司徒"
fullName: Object
lastName: "正美"

意图很明显就是遍历这些属性,给出相对应的处理,具体我们接着往下看

纯净的js对象,所有访问器与viewModel特有的方法属性都去掉

   1:     if (!watchOne[name]) {
   2:              json[name] = value;
   3:       }

几个简单的条件过滤:

   1:      //判断类型
   2:          var valueType = avalon.type(value);
   3:   
   4:          if (valueType === "Function") {
   5:              // 第一个就是$watch" 被重复假如到列表了
   6:              VBPublics.push(name); //函数无需要转换
   7:          } else {

跳过过滤的条件后:


核心的转换

  • 转换计算属性
  • 转化监控属性

转换计算属性:

  1. 定义时为一个最多拥有get,set方法的对象(get方法是必需的)
  2. 注意,get, set里面的this不能改为vm,框架内部会帮你调整好指向。

判断的条件,值类型是对象,并且有get方法,并且方法要少于等于2个

if (valueType === "Object" && typeof value.get === "function" && Object.keys(value).length <= 2) {

满足条件的

 vm.fullName = {//一个包含set或get的对象会被当成PropertyDescriptor,
set: function(val) {//set, get里面的this不能改成vm
var array = (val || "").split(" ");
this.firstName = array[0] || "";
this.lastName = array[1] || "";
},
get: function() {
return this.firstName + " " + this.lastName;
}
}

具体有什么用我们接着往下看

转化监控属性

  1. 定义时为一个简单的数据类型,如undefined, string, number, boolean。
  2. 监控数组:定义时为一个数组
firstName: "司徒"

accessor[subscribers] = [];  
  • 别看这个代码是空的函数,不起眼,双向绑定就是看他了,我们先Mark下

       //生成defineProperties需要的配置属性
Descriptions[name] = {
set: accessor,
get: accessor,
enumerable: true
};
  • Descriptions临时对象  //收集内部用于转换的对象
  • enumerable 很重要,为false的话 ,for in就找不到它了

这样循环后就把该干嘛的不该干嘛的都给区分开了

最后都保存在Descriptions中

此时的Descriptions

   1:  Descriptions: Object
   2:   
   3:  firstName: Object
   4:      enumerable: true
   5:      get: function (neo) { //创建监控属性或数组
   6:      set: function (neo) { //创建监控属性或数组
   7:   
   8:  fullName: Object
   9:       enumerable: true
  10:      get: function (neo) { //创建计算属性
  11:      set: function (neo) { //创建计算属性
  12:   
  13:  lastName: Object
  14:       enumerable: true
  15:      get: function (neo) { //创建监控属性或数组
  16:      set: function (neo) { //创建监控属性或数组
 

看吧就是这样给包装了一下,只是定义了但是还没生效

所以defineProperties(model, Descriptions); 给执行以下  (defineProperties的方法见前面)


model 就是工厂模式转换后的新的vm模型对象了, 因为在开始遍历scope的过滤了一些东东,原本也是用户定义的,所以这时候我们还得加到新的vm-》model中去、

    //添加用户定义的未转换的函数到模型
VBPublics.forEach(function(name) {
if (!watchOne[name]) {
model[name] = scope[name];
}
});
 
 
分类: avalonMVVM

前端MVVM框架avalon - 模型转换1的更多相关文章

  1. 轻量级前端MVVM框架avalon - 模型转换

    接上一章 ViewModel modelFactory工厂是如何加工用户定义的VM? 附源码 洋洋洒洒100多行内部是魔幻般的实现 1: function modelFactory(scope) { ...

  2. 轻量级前端MVVM框架avalon - 初步接触

    迷你简单易用的MVVM框架 avalon的介绍http://rubylouvre.github.io/mvvm/ 按照作者的介绍,在HTML中添加绑定,在JS中用avalon.define定义View ...

  3. 轻量级前端MVVM框架avalon - 执行流程2

    接上一章 执行流程1 在这一大堆扫描绑定方法中应该会哪些实现? 首先我们看avalon能帮你做什么? 数据填充,比如表单的一些初始值,切换卡的各个面板的内容({{xxx}},{{xxx|html}}, ...

  4. 轻量级前端MVVM框架avalon源码分析-总结

    距avalon0.7版本发布有一段时间,由于之前的稳定性,就停止一段时间更新,期间研究了下Knockout源码,也尝试写了一个小型的mvvm的实现模型,仅仅只是仿造ko的核心实现,把无关的东西给剥离掉 ...

  5. 轻量级前端MVVM框架avalon - 执行流程1

    基本上确定了avalon的几个重要元素的关系: M,即model,一个普通的JS对象,可能是后台传过来的,也可能是直接从VM中拿到,即VM.$json.有关的这个$json的名字还在商讨 V,即Vie ...

  6. 轻量级前端MVVM框架avalon - ViewModel

    废话说了大几篇,我们开始来点干货了~ ViewModel的内部机制 在MVVM中,数据是核心.而jQuery则以DOM为核心. 而DOM只是HTML在JS的世界的抽象,是一个很易变的东西.因此如果业务 ...

  7. 轻量级前端MVVM框架avalon - 整体架构

    官网提供架构图 单看这个图呢,还木有说明,感觉有点蛋疼,作者的抽象度太高了,还好在前面已经大概分析过了执行流程 如图 左边是View视图,我们就理解html结构,换句话就是说用户能看到的界面,渲染页面 ...

  8. 轻量级前端MVVM框架avalon - 控制器

    引子: 最近工作挺忙,avalon只能断断续续的写下去了,大概看了下angular的源码,看到小一半就比较难坚持了,是块硬骨头,慢慢啃吧 不过angular的的文档中用词还是很优雅: HTML编译器 ...

  9. 前端MVVM框架avalon揭秘 - HTML编译器

    MVVM试图更加清晰的讲用户界面(UI)开发从应用程序的业务逻辑与行为中心分离,因为,很多这样的模式的实现都需要利用声明式数据绑定来实现讲View(视图)工作从其他层分离 所以出现了一大堆自定义的声明 ...

随机推荐

  1. Quartz使用-入门使用(java定时任务实现)

    注:这里使用的是Quartz1.6.5版本号(包:quartz-1.6.5.jar) //測试main函数 //QuartzTest.java package quartzPackage; impor ...

  2. HHVM Installation and Configuration(HHVM 安装及配置)

    Installation and Configuration¶ General Installation Considerations Installation on Linux systems Ub ...

  3. HDU 4324 Triangle LOVE 拓扑排序

    Problem Description Recently, scientists find that there is love between any of two people. For exam ...

  4. jquery插件分类与编写详细讲解

    jquery插件分类与编写详细讲解 1. 插件种类 插件其实就是对现有的方法(或者叫函数)做一个封装,方便重用提高开发效率.   jQeury主要有2种类型   1)实例对象方法插件 开发能让所有的j ...

  5. :link,:visited,:focus,:hover,:active详解

    原文::link,:visited,:focus,:hover,:active详解 CSS 又名 层叠样式表,所谓层叠,就是后面的样式会覆盖前面的样式,所以在样式表中,各样式排列的顺序很有讲究.   ...

  6. [Dev Blog] KCV插件 —— Provissy Tools 。

    承蒙各位支持! 正式版已推出,请前往http://tieba.baidu.com/p/3398574166 或者前往:http://provissy.com/?p=7 请不要在这里回复,我无法保证回复 ...

  7. jQuery中开发插件的两种方式

    jQuery中开发插件的两种方式(附Demo) 做web开发的基本上都会用到jQuery,jQuery插件开发两种方式:一种是类扩展的方式开发插件,jQuery添加新的全局函数(jQuery的全局函数 ...

  8. 临时对象与NRV技术

    <More Effective C++>中讲到,在C++中真正的临时对象是看不见的,它们不出现在你的源代码中.建立一个没有命名的非堆(non-heap)对象会产生临时对象,这种未命名的对象 ...

  9. 开源框架之TAB控件

    我的开源框架之TAB控件   需求 (1)支持iframe.html.json格式的tab内容远程请求 (2)支持动态添加tab (3)支持远程加载完成监听,支持tab激活事件监听 (4)支持relo ...

  10. IOC容器在框架中的应用

    IOC容器在框架中的应用 前言 在上一篇我大致的介绍了这个系列所涉及到的知识点,在本篇我打算把IOC这一块单独提取出来讲,因为IOC容器在解除框架层与层之间的耦合有着不可磨灭的作用.当然在本系列前面的 ...