日本综合一区二区|亚洲中文天堂综合|日韩欧美自拍一区|男女精品天堂一区|欧美自拍第6页亚洲成人精品一区|亚洲黄色天堂一区二区成人|超碰91偷拍第一页|日韩av夜夜嗨中文字幕|久久蜜综合视频官网|精美人妻一区二区三区

RELATEED CONSULTING
相關(guān)咨詢
選擇下列產(chǎn)品馬上在線溝通
服務(wù)時(shí)間:8:30-17:00
你可能遇到了下面的問題
關(guān)閉右側(cè)工具欄

新聞中心

這里有您想知道的互聯(lián)網(wǎng)營(yíng)銷解決方案
從零開始編寫自己的JavaScript框架(二)

2. 數(shù)據(jù)綁定

創(chuàng)新互聯(lián)公司2013年開創(chuàng)至今,先為勐臘等服務(wù)建站,勐臘等地企業(yè),進(jìn)行企業(yè)商務(wù)咨詢服務(wù)。為勐臘企業(yè)網(wǎng)站制作PC+手機(jī)+微官網(wǎng)三網(wǎng)同步一站式服務(wù)解決您的所有建站問題。

2.1 數(shù)據(jù)綁定的原理

數(shù)據(jù)綁定是一種很便捷的特性,一些RIA框架帶有雙向綁定功能,比如Flex和Silverlight,當(dāng)某個(gè)數(shù)據(jù)發(fā)生變更時(shí),所綁定的界面元素也發(fā)生變更,當(dāng)界面元素的值發(fā)生變化時(shí),數(shù)據(jù)也跟著變化,這種功能在處理表單數(shù)據(jù)的填充和收集時(shí),是非常有用的。

在HTML中,原生是沒有這樣的功能的,但有些框架做到了,它們是怎么做到的呢?我們來做個(gè)簡(jiǎn)單的試試,順便探討一下其中原理。

先看數(shù)據(jù)到界面上的的綁定,比如:

 
 
 
  1. var person = {
  2.     name: "Tom"
  3. };

如果我們給name重新賦值,person.name = "Jerry",怎么才能讓界面得到變更?

從直覺來說,我們需要在name發(fā)生改變的時(shí)候,觸發(fā)一個(gè)事件,或者調(diào)用某個(gè)指定的方法,然后才好著手做后面的事情,比如:

 
 
 
  1. var person = {
  2.     name: "Tom",
  3.     setName: function(newName) {
  4.         this.name = newName;
  5.         //do something
  6.     }
  7. };

這樣我們可以在setName里面去給input賦值。推而廣之,為了使得實(shí)體包含的多個(gè)屬性都可以運(yùn)作,可以這么做:

 
 
 
  1. var person = {
  2.     name: "Tom",
  3.     gender: 5
  4.     set: function(key, value) {
  5.         this[key] = value;
  6.         //do something
  7.     }
  8. };

或者合并兩個(gè)方法,只判斷是否傳了參數(shù):

 
 
 
  1. Person.prototype.name = function(value) {
  2.     if (arguments.length == 0) {
  3.         return this._name;
  4.     }
  5.     else {
  6.         this._name = value;
  7.     }
  8. }

這種情況下,賦值的時(shí)候就是person.name("Tom"),取值的時(shí)候就是var name = person.name()了。

有一些框架是通過這種方式來變通實(shí)現(xiàn)數(shù)據(jù)綁定的,對(duì)數(shù)據(jù)的寫入只能通過方法調(diào)用。但這種方式很不直接,我們來想點(diǎn)別的辦法。

在C#等一些語言里,有一種東西叫做存取器,比如說:

 
 
 
  1. class Person
  2. {
  3.     private string name;
  4.     public string Name
  5.     {
  6.         get
  7.         {
  8.             return name;
  9.         }
  10.         set
  11.         {
  12.             name = value;
  13.         }
  14.     }
  15. }

用的時(shí)候,person.Name = "Jerry",就會(huì)調(diào)用到set里,相當(dāng)于是個(gè)方法。

這一點(diǎn)非常好,很符合我們的需要,那JavaScript里面有沒有類似存取器的特性呢?老早以前是沒有的,但現(xiàn)在有了,那就是Object.defineProperty,它的第三個(gè)參數(shù)就是可選的存取函數(shù)。比如說

 
 
 
  1. var person = {};
  2. // Add an accessor property to the object.
  3. Object.defineProperty(person, "name", {
  4.     set: function (value) {
  5.         this._name = value;
  6.         //do something
  7.     },
  8.     get: function () {
  9.         return this._name;
  10.     },
  11.     enumerable: true,
  12.     configurable: true
  13. });

賦值的時(shí)候,person.name = "Tom",取值的時(shí)候,var name = person.name,簡(jiǎn)直太美妙了。注意這里define的時(shí)候,是定義在實(shí)例上的,如果想要定義到類型里面,可以在構(gòu)造器里面定義。

現(xiàn)在我們從數(shù)據(jù)到DOM的綁定可以解決掉了,至少我們能夠在變量被更改的時(shí)候去做一些自己的事情,比如查找這個(gè)屬性被綁定到哪些控件了,然后挨個(gè)對(duì)其賦值??蚣茉趺粗缹傩员唤壎ǖ侥男┛丶四??這個(gè)直接在第二部分的實(shí)現(xiàn)過程中討論。

再看控件到數(shù)據(jù)的綁定,這個(gè)其實(shí)很好理解。無非就是給控件添加change之類的事件監(jiān)聽,在這里面把關(guān)聯(lián)到的數(shù)據(jù)更新掉。到這里,我們?cè)谠矸矫嬉呀?jīng)沒有什么問題了,現(xiàn)在開始準(zhǔn)備把它寫出來。

2.2 數(shù)據(jù)綁定的實(shí)現(xiàn)

我們的框架啟動(dòng)之后,要先把前面所說的這種綁定關(guān)系收集起來,這種屬性會(huì)分布于DOM的各個(gè)角落,一個(gè)很現(xiàn)實(shí)的做法是,遞歸遍歷界面的每個(gè)DOM節(jié)點(diǎn),檢測(cè)該屬性,于是我們代碼的結(jié)構(gòu)大致如下所示。

 
 
 
  1. function parseElement(element) {
  2.     for (var i=0; i
  3.         parseAttribute(element.attributes[i]);
  4.     }
  5.     for (var i=0; i
  6.         parseElement(element.children[i]);
  7.     }
  8. }

但是我們這時(shí)候面臨一個(gè)問題,比如你的輸入框綁定在name變量上,這個(gè)name應(yīng)該從屬于什么?它是全局變量嗎?

我們?cè)陂_始做這個(gè)框架的時(shí)候強(qiáng)調(diào)了一個(gè)原則:業(yè)務(wù)模塊不允許定義全局變量,框架內(nèi)部也盡量少有全局作用域,到目前為止,我們只暴露了thin一個(gè)全局入口,所以在這里不能破壞這個(gè)原則。

#p#

因此,我們要求業(yè)務(wù)開發(fā)人員去定義一個(gè)視圖模型,把變量包裝起來,所包裝的不限于變量,也可以有方法。比如下面,我們定義了一個(gè)實(shí)體叫Person,帶兩個(gè)變量,兩個(gè)方法,后面我們來演示一下怎么把它們綁定到HTML界面。

 
 
 
  1. thin.define("Person", [], function() {
  2.     function Person() {
  3.         this.name = "Tom";
  4.         this.age = 5;
  5.     }
  6.     Person.prototype = {
  7.         growUp: function() {
  8.             this.age++;
  9.         }
  10.     };
  11.     return Person;
  12. });

模型方面都準(zhǔn)備好了,現(xiàn)在來看界面:

 
 
 
  1.  
  2.      
  3.      
  4.      
 

為了使得結(jié)構(gòu)更加容易看,我們把界面的無關(guān)屬性比如樣式之類都去掉了,只留下不能再減少的這么一段?,F(xiàn)在我們可以看到,在界面的頂層定義一個(gè)vm- model屬性,值為實(shí)體的名稱。兩個(gè)輸入框通過vm-value來綁定到實(shí)例屬性,vm-init綁定界面的初始化方法,vm-click綁定按鈕的點(diǎn) 擊事件。

好了,現(xiàn)在我們可以來掃描這個(gè)簡(jiǎn)單的DOM結(jié)構(gòu)了。想要做這么一個(gè)綁定,首先要考慮數(shù)據(jù)從哪里來?在綁定name和code屬性之前,毫無疑問,應(yīng)當(dāng)先實(shí)例化一個(gè)Person,我們?cè)趺床拍苤佬枰裀erson模塊實(shí)例化呢?

當(dāng)掃描到一個(gè)DOM元素的時(shí)候,我們要先檢測(cè)它的vm-model屬性,如果有值,就取這個(gè)值來實(shí)例化,然后,把這個(gè)值一直傳遞下去,在掃描其他屬 性或者下屬DOM元素的時(shí)候都帶進(jìn)去。這么一來,parseElement就變成一個(gè)遞歸了,于是它只好有兩個(gè)參數(shù),變成了這樣:

 
 
 
  1. function parseElement(element, vm) {
  2.     var model = vm;
  3.     if (element.getAttribute("vm-model")) {
  4.         model = bindModel(element.getAttribute("vm-model"));
  5.     }
  6.     for (var i=0; i
  7.         parseAttribute(element, element.attributes[i], model);
  8.     }
  9.     for (var i=0; i
  10.         parseElement(element.children[i], model);
  11.     }
  12. }

看看我們打算怎么來實(shí)例化這個(gè)模型,這個(gè)bindModel方法的參數(shù)是模塊名,于是我們先去use一下,從工廠里生成出來,然后new一下,先這么return出去吧。

 
 
 
  1. function bindModel(modelName) {
  2.     thin.log("model" + modelName);
  3.     var model = thin.use(modelName, true);
  4.     var instance = new model();
  5.     return instance;
  6. }

現(xiàn)在我們開始關(guān)注parseAttribute函數(shù),可能的attribute有哪些種類呢?我列舉了一些很常用的:

init,用于綁定初始化方法

click,用于綁定點(diǎn)擊

value,綁定變量

enable和disable,綁定可用狀態(tài)

visible和invisible,綁定可見狀態(tài)

然后就可以實(shí)現(xiàn)我們parseAttribute函數(shù)了:

 
 
 
  1. function parseAttribute(element, attr, model) {
  2.     if (attr.name.indexOf("vm-") == 0) {
  3.         var type = attr.name.slice(3);
  4.         switch (type) {
  5.             case "init":
  6.                 bindInit(element, attr.value, model);
  7.                 break;
  8.             case "value":
  9.                 bindValue(element, attr.value, model);
  10.                 break;
  11.             case "click":
  12.                 bindClick(element, attr.value, model);
  13.                 break;
  14.             case "enable":
  15.                 bindEnable(element, attr.value, model, true);
  16.                 break;
  17.             case "disable":
  18.                 bindEnable(element, attr.value, model, false);
  19.                 break;
  20.             case "visible":
  21.                 bindVisible(element, attr.value, model, true);
  22.                 break;
  23.             case "invisible":
  24.                 bindVisible(element, attr.value, model, false);
  25.                 break;
  26.             case "element":
  27.                 model[attr.value] = element;
  28.                 break;
  29.         }
  30.     }

注意到最后還有個(gè)element類型,本來可以不要這個(gè),但我們考慮到將來,一切都是組件化的時(shí)候,界面上打算不寫id,也不依靠選擇器,而是用某個(gè)標(biāo)志來定位元素,所以加上了這個(gè),文章最后的示例中使用了它。

#p#

這么多綁定,不打算都講,用bindValue函數(shù)來說明一下吧:

 
 
 
  1. function bindValue(element, key, vm) {
  2.     thin.log("binding value: " + key);
  3.     vm.$watch(key, function (value, oldValue) {
  4.         element.value = value || "";
  5.     });
  6.     element.onkeyup = function () {
  7.         vm[key] = element.value;
  8.     };
  9.     element.onpaste = function () {
  10.         vm[key] = element.value;
  11.     };
  12. }

我們假定每個(gè)模型實(shí)例上帶有一個(gè)$watch方法,用于監(jiān)控某變量的變化,可以傳入一個(gè)監(jiān)聽函數(shù),當(dāng)變量變化的時(shí)候,自動(dòng)調(diào)用這個(gè)函數(shù),并且把新舊兩個(gè)值傳回來。

在這個(gè)代碼里,我們使用$watch方法給傳入的key添加一個(gè)監(jiān)聽,監(jiān)聽器里面給監(jiān)聽元素賦值。我們這里偷懶了一下,假定所有的綁定元素都是輸入 框,所以直接給element.value設(shè)置值,為了防止值為空導(dǎo)致顯示undefined,把值跟空字符串用短路表達(dá)式做了個(gè)轉(zhuǎn)換。

接下來,也對(duì)element的幾個(gè)可能導(dǎo)致值變化的事件進(jìn)行了監(jiān)聽,在里面把模型上對(duì)應(yīng)的值更新掉。這樣雙向綁定就做好了。

然后回頭來看$watch的實(shí)現(xiàn)。很顯然這里也要一個(gè)map,我們給它取名為$watchers,存放屬性的綁定關(guān)系,對(duì)于每個(gè)屬性,它的值需要保存一份,供getter獲取,同時(shí)還有一個(gè)數(shù)組,存放了該屬性綁定的處理函數(shù)。當(dāng)屬性發(fā)生變更的時(shí)候,去挨個(gè)把它們調(diào)用一下。

 
 
 
  1. var Binder = {
  2.     $watch: function (key, watcher) {
  3.         if (!this.$watchers[key]) {
  4.             this.$watchers[key] = {
  5.                 value: this[key],
  6.                 list: []
  7.             };
  8.             Object.defineProperty(this, key, {
  9.                 set: function (val) {
  10.                     var oldValue = this.$watchers[key].value;
  11.                     this.$watchers[key].value = val;
  12.                     for (var i = 0; i < this.$watchers[key].list.length; i++) {
  13.                         this.$watchers[key].list[i](val, oldValue);
  14.                     }
  15.                 },
  16.                 get: function () {
  17.                     return this.$watchers[key].value;
  18.                 }
  19.             });
  20.         }
  21.         this.$watchers[key].list.push(watcher);
  22.     }
  23. };

但是vm怎么就有$watcher呢,每個(gè)地方都去判斷一下非空然后再去創(chuàng)建其實(shí)挺麻煩的,所以,這個(gè)屬性我們可以直接在實(shí)例化模型的時(shí)候創(chuàng)建出來。

 
 
 
  1. function bindModel(name) {
  2.     thin.log("binding model: " + name);
  3.     var model = thin.use(name, true);
  4.     var instance = new model().extend(Binder);
  5.     instance.$watchers = {};
  6.     return instance;
  7. }

看看這里的寫法,為什么$watchers要額外設(shè)置,而$watch就可以放在Binder里面來extend呢?

先解釋extend干了什么,它做的是一個(gè)對(duì)象的淺拷貝,也就是說,把Binder的屬性和方法都復(fù)制給了創(chuàng)建出來的model實(shí)例,注意,這個(gè)所 謂的復(fù)制,如果是簡(jiǎn)單類型,那確實(shí)復(fù)制了,如果是引用類型,那復(fù)制的其實(shí)只是一個(gè)引用,所以如果$watchers也放在Binder里,不同的 instance就共享一個(gè)$watchers,邏輯就是錯(cuò)誤的。那為什么$watcher又可以放在這里復(fù)制呢?因?yàn)樗呛瘮?shù),它的this始終指向當(dāng) 前的執(zhí)行主體,也就是說,如果放在instance1上執(zhí)行,指向的就是instance1,放在instance2上執(zhí)行,指向的就是 instance2,我們利用這一點(diǎn),就可以不用讓每個(gè)實(shí)例都創(chuàng)建一份$watcher方法,而是共用同一個(gè)。

同理,我們可以把enable,visible,init,click這些都做起來,init的執(zhí)行時(shí)間放在掃描完vm-model那個(gè)element之下的所有DOM節(jié)點(diǎn)之后。

嗯,我們是不是可以試一下了?來寫個(gè)代碼:

 
 
 
  1.     Simple binding demo
  2.     
  3.     
  4.     
  5.     
  6.     
  7.     
  8.     
  9.     
  10.     
  •     
  •     
  •     

  • 或者訪問這里:http://xufei.github.io/thin/demo/simple-binding.html

    以剛才文章提到的內(nèi)容,還不能完全解釋這個(gè)例子的效果,因?yàn)闆]看到在哪里調(diào)用parseElement的。說來也簡(jiǎn)單,就在thin.js里面,直 接寫了一個(gè)thin.ready,在那邊調(diào)用了這個(gè)函數(shù),去解析了document.body,于是測(cè)試頁面里面才可以只寫綁定和視圖模型。

    我們還有一個(gè)更實(shí)際一點(diǎn)的例子,結(jié)合了另外一個(gè)系列里面寫的簡(jiǎn)單DataGrid控件,做了一個(gè)很基礎(chǔ)的人員管理界面:http://xufei.github.io/thin/demo/binding.html

    2.3 小結(jié)

    到此為止,我們的綁定框架勉強(qiáng)能夠運(yùn)行起來了!雖然很簡(jiǎn)陋,而且要比較新的瀏覽器才能跑,但畢竟是跑起來了。

    注意Object.defineProperty僅在Chrome等瀏覽器中可用,IE需要9以上才比較正常。在司徒正美的avalon框架中,巧 妙使用VBScript繞過這一限制,利用vbs的property和兩種語言的互通,實(shí)現(xiàn)了低版本IE的兼容。我們這個(gè)框架的目標(biāo)不是兼容,而是為了說 明原理,所以感興趣的朋友可以去看看avalon的源碼。


    網(wǎng)站名稱:從零開始編寫自己的JavaScript框架(二)
    瀏覽地址:http://www.dlmjj.cn/article/ccichic.html