輕量級前端MVVM框架avalon - ViewModel


廢話說了大幾篇,我們開始來點干貨了~ 

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中:

  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中有三種不同類型的屬性:

命名數據屬性(named data properties

     命名數據屬性,就是我們在IE8碰到的絕對大多數屬性,可以隨意刪除添加,設置什么返回什么,不會在內部做多余的事。

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的幕后:

源碼:

 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方法了

下篇繼續着中分析~


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM