輕量級前端MVVM框架avalon - ViewModel

廢話說了大幾篇,咱們開始來點乾貨了~  html

ViewModel的內部機制

  • 在MVVM中,數據是核心。而jQuery則以DOM爲核心。
  • 而DOM只是HTML在JS的世界的抽象,是一個很易變的東西。所以若是業務代碼遍歷選擇器表達式會很是難維護。但不能否認,jQuery是操做DOM的王者,讓咱們操做DOM順手拈來。但若是不讓你操做DOM,不是更好嗎?就像jQuery不讓你用getElementById,getElementsByTagName, querySelecterAll,你們都不知道里面有多少坑,短短几個字母$(expr)是背後sizzle選擇器引擎1700行的實現!!!!jQuery實際上是在用戶代碼與原生API中提供一層厚厚的粘合層,所以摸起來光溜溜。在MVVM中,DOM操做基本是水下運做了。因爲VM與V之間的雙向綁定,操做了VM中的數據(固然只能是監控屬性),就會同步到DOM,咱們透過DOM事件監控用戶對DOM的改動,也會同步到VM。DOM隱形了,就像軟件公司,處處跑出來活動的是業務員與不寫代碼的經理老總,程序員所有關起來加班!雖然這比喻有點殘酷,但這正體現了各司其職的威力。能說會道去拉風投接單子沒什麼不妥,喜歡呆在電腦前的就讓他呆吧。jQuery的世界就是一個混亂的公司,全能的程序員什麼都作。

定義一個ViemMode 程序員

<fieldset ms-controller="simple">
    <legend>例子</legend>
    <p>First name: <input ms-model="firstName" /></p>
    <p>Last name: <input ms-model="lastName"  /></p>
    <p>Hello,    <input ms-model="fullName"></p>
    <div>{{firstName +" | "+ lastName }}</div>
    <p>nick name: <input ms-model="nick.name"  /></p>
    <p>{{nick.name}}</p>
</fieldset>

            
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() {
          returnthis.firstName + " " + this.lastName;
        }
    },
    vm.nick = {
          name: "暗黑之民"
    }
});
    

 

這是官方給出的DEMO,咱們看看對應的操做定義 數組

  HTML中: 瀏覽器

  1. ms-controller是用於指定ViewModel的做用範圍, ms-controller的值等於avalon.define的第一個參數,而且這個值必須是一個命法的變量名, 如aaa, $aaa, aaaSSS, aaa_bbb,不能寫成23432, sdfs-A  
  2. ms-model="firstName" 此類綁定只能用於表單中,框架會在上面綁定一些事件,如input, change, click以進行同步
  3. {{firstName +" | "+ lastName }} 模版機制,插值表達式,用於替換值

    

     Javascript中: app

  1. ViewModel的定義,它是經過avalon.define來建立,在函數內咱們定義它的屬性與方法
  2. vm.firstName 監控屬性:定義時爲一個簡單的數據類型,如undefined, string, number, boolean。
  3. vm.fullName 計算屬性:定義時爲一個最多擁有get,set方法的對象(get方法是必需的),注意,get, set裏面的this不能改成vm,框架內部會幫你調整好指向
  4. 監控數組:定義時爲一個數組
  5. 普通屬性或方法:咱們能夠在vm裏面設置一個$skipArray數組,裏面裝着你不想處理的方法與屬性名

   

由於在ViewModel的轉化中會用到defineProperty的定義,有必要先預先提出來  框架

要了解詳細,見個人一篇譯文 (譯)ECMAScript 5 Objects and Properties 函數

   

JavaScript中有三種不一樣類型的屬性:oop

命名數據屬性(named data properties

     命名數據屬性,就是咱們在IE8碰到的絕對大多數屬性,能夠隨意刪除添加,設置什麼返回什麼,不會在內部作多餘的事。 this

 
var obj = {
    prop: 123
};
console.log(obj.prop); // 123
                
console.log(obj["prop"]); // 123
            
obj.prop = "abc";
obj["prop"] = "abc";

命名訪問器屬性(named accessor properties)

  • 命名訪問器屬性,就是設置或讀取時內部調用一些函數作事情的函數,著名的表明是元素的innerHTML,給它一個字符串會建立一大堆節點,讀它時返回的值與咱們給它的值可能不同。 又如數組的length,可能經過它來添加或刪除元素。IE8添加了set get關鍵字,不過沒什麼人用。不過它又添加了著名的Object.defineProperty方法, 裏面可指定讀取時或寫入時的處理函數。標準瀏覽器老早就支持__defineGetter__,__defineSetter__。
 
var obj = {}
var _a = 1;
Object.defineProperty(obj, "a", {
    get: function() {
    return _a
    },
    set: function(a) {
        _a = a + 10
    }
});

console.log(obj.a) //1;
                
obj.a = 20;

console.log(obj.a) //30;
            
 

 

 
  • 計算屬性的set, get函數其實就是對應它們倆。
  • avalon, emberjs的ViewModel就是基於訪問器實現的,不過emberjs只兼容到IE8。

 內部屬性就是沒法經過JavaScript直接訪問的屬性

   

走進vm的幕後: spa

源碼:

 
 1    avalon.define = function(name, deps, factory) {
 2
                var args = [].slice.call(arguments);
 3
                if (typeof name !== "string") {
 4             name = generateID();
 5
                            args.unshift(name);
 6
                        }
 7
                if (!Array.isArray(args[1])) {
 8             args.splice(1, 0, []);
 9
                        }
10         deps = args[1];
11
                if (typeof args[2] !== "function") {
12             avalon.error("factory必須是函數");
13
                        }
14         factory = args[2];
15
                var scope = {
16
                            $watch: noop
17
                        };
18
                        deps.unshift(scope);
19         factory(scope); //獲得全部定義
            
20
                var model = modelFactory(scope); //轉爲一個ViewModel
            
21         stopRepeatAssign = true;
22         deps[0] = model;
23         factory.apply(0, deps); //重置它的上下文
            
24
                        deps.shift();
25         stopRepeatAssign = false;
26         model.$id = name;
27
                return avalon.models[name] = model;
28     };
 

 

 

咱們一行行分析:

  •  avalon.define 的定義能接受3個實參
  •  var args = [].slice.call(arguments); 轉換數組,arguments是僞數組
  •  保證傳參數知足3個定義 若是第二個參數不是數組,轉換 avalon.define("on",fn); -> avalon.define("on",[],fn); 
var scope = {
        $watch: noop
 };

定義一個做用域,是一個對象,這個東東其實就是暴露給用戶的一個接口,也就是vm了,其實VM是後臺先建立的

   

  factory(scope); //獲得全部定義

 

對象嘛是引用,執行後就會把用戶定義的方法給掛到scope上了,這樣就達到收集用戶在外面的處理方法了

   

var model = modelFactory(scope); //

 

這個就是核心的東東了,把scpoe轉爲一個ViewModel,只有轉化以後,才能讓咱們的東東具備實際的處理能力了

   

factory.apply(0, deps);

 

這是個很是巧妙的設計,用戶定義的函數內部的做用域其實仍是在普通的對象,咱們能夠強制轉化vm

   

return avalon.models[name] = model;

 

很明顯轉化後的模型對象掛在到了全局中,方便在掃描節點綁定中獲取

因此整個VM的建立過程,

核心點就是

modelFactory方法了下篇繼續着中分析~
相關文章
相關標籤/搜索