輕量級前端MVVM框架avalon - 模型轉換

接上一章 ViewModelhtml

 


modelFactory工廠是如何加工用戶定義的VM?

附源碼python

  • 洋洋灑灑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 //要忽略監控的屬性名列表json

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爲ruby

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

 

意圖很明顯就是遍歷這些屬性,給出相對應的處理,具體咱們接着往下看框架

           純淨的js對象,全部訪問器與viewModel特有的方法屬性都去掉函數

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

幾個簡單的條件過濾:oop

   1:      //判斷類型
   2:          var valueType = avalon.type(value);
   3:   
   4:          if (valueType === "Function") {
   5:              // 第一個就是$watch" 被重複假如到列表了
   6:              VBPublics.push(name); //函數無須要轉換
   7:          } else {

 

跳過過濾的條件後:this


核心的轉換

  • 轉換計算屬性
  • 轉化監控屬性

 

轉換計算屬性:

  1. 定義時爲一個最多擁有get,set方法的對象(get方法是必需的)
  2. 注意,get, set裏面的this不能改成vm,框架內部會幫你調整好指向。

判斷的條件,值類型是對象,而且有get方法,而且方法要少於等於2個spa

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];
        }
    });
相關文章
相關標籤/搜索