成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

從零開始編寫自己的JavaScript框架(二)

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

2. 數據綁定

2.1 數據綁定的原理

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

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

先看數據到界面上的的綁定,比如:

  1. <input vm-value="name"/> 
  2. var person = { 
  3.     name: "Tom" 
  4. }; 

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

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

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

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

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

或者合并兩個方法,只判斷是否傳了參數:

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

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

有一些框架是通過這種方式來變通實現數據綁定的,對數據的寫入只能通過方法調用。但這種方式很不直接,我們來想點別的辦法。

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

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

用的時候,person.Name = "Jerry",就會調用到set里,相當于是個方法。

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

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

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

現在我們從數據到DOM的綁定可以解決掉了,至少我們能夠在變量被更改的時候去做一些自己的事情,比如查找這個屬性被綁定到哪些控件了,然后挨個對其賦值。框架怎么知道屬性被綁定到哪些控件了呢?這個直接在第二部分的實現過程中討論。

再看控件到數據的綁定,這個其實很好理解。無非就是給控件添加change之類的事件監聽,在這里面把關聯到的數據更新掉。到這里,我們在原理方面已經沒有什么問題了,現在開始準備把它寫出來。

2.2 數據綁定的實現

我們的框架啟動之后,要先把前面所說的這種綁定關系收集起來,這種屬性會分布于DOM的各個角落,一個很現實的做法是,遞歸遍歷界面的每個DOM節點,檢測該屬性,于是我們代碼的結構大致如下所示。

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

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

我們在開始做這個框架的時候強調了一個原則:業務模塊不允許定義全局變量,框架內部也盡量少有全局作用域,到目前為止,我們只暴露了thin一個全局入口,所以在這里不能破壞這個原則。

#p#

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

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

模型方面都準備好了,現在來看界面:

  1. <div vm-model="Person">  
  2.     <input type="text" vm-value="name"/>  
  3.     <input type="text" vm-value="age"/>  
  4.     <input type="button" vm-click="growUp" value="Grow Up"/>  
  5. </div>  

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

好了,現在我們可以來掃描這個簡單的DOM結構了。想要做這么一個綁定,首先要考慮數據從哪里來?在綁定name和code屬性之前,毫無疑問,應當先實例化一個Person,我們怎么才能知道需要把Person模塊實例化呢?

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

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

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

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

現在我們開始關注parseAttribute函數,可能的attribute有哪些種類呢?我列舉了一些很常用的:

init,用于綁定初始化方法

click,用于綁定點擊

value,綁定變量

enable和disable,綁定可用狀態

visible和invisible,綁定可見狀態

然后就可以實現我們parseAttribute函數了:

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

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

#p#

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

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

我們假定每個模型實例上帶有一個$watch方法,用于監控某變量的變化,可以傳入一個監聽函數,當變量變化的時候,自動調用這個函數,并且把新舊兩個值傳回來。

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

接下來,也對element的幾個可能導致值變化的事件進行了監聽,在里面把模型上對應的值更新掉。這樣雙向綁定就做好了。

然后回頭來看$watch的實現。很顯然這里也要一個map,我們給它取名為$watchers,存放屬性的綁定關系,對于每個屬性,它的值需要保存一份,供getter獲取,同時還有一個數組,存放了該屬性綁定的處理函數。當屬性發生變更的時候,去挨個把它們調用一下。

  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.  
  9.             Object.defineProperty(this, key, { 
  10.                 set: function (val) { 
  11.                     var oldValue = this.$watchers[key].value; 
  12.                     this.$watchers[key].value = val; 
  13.  
  14.                     for (var i = 0; i < this.$watchers[key].list.length; i++) { 
  15.                         this.$watchers[key].list[i](val, oldValue); 
  16.                     } 
  17.                 }, 
  18.  
  19.                 get: function () { 
  20.                     return this.$watchers[key].value; 
  21.                 } 
  22.             }); 
  23.         } 
  24.  
  25.         this.$watchers[key].list.push(watcher); 
  26.     } 
  27. }; 

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

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

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

先解釋extend干了什么,它做的是一個對象的淺拷貝,也就是說,把Binder的屬性和方法都復制給了創建出來的model實例,注意,這個所 謂的復制,如果是簡單類型,那確實復制了,如果是引用類型,那復制的其實只是一個引用,所以如果$watchers也放在Binder里,不同的 instance就共享一個$watchers,邏輯就是錯誤的。那為什么$watcher又可以放在這里復制呢?因為它是函數,它的this始終指向當 前的執行主體,也就是說,如果放在instance1上執行,指向的就是instance1,放在instance2上執行,指向的就是 instance2,我們利用這一點,就可以不用讓每個實例都創建一份$watcher方法,而是共用同一個。

同理,我們可以把enable,visible,init,click這些都做起來,init的執行時間放在掃描完vm-model那個element之下的所有DOM節點之后。

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

  1. <!DOCTYPE html> 
  2. <html> 
  3. <head> 
  4.     <title>Simple binding demo</title> 
  5.     <meta charset="utf-8"> 
  6.     <meta name="viewport" content="width=device-width, initial-scale=1.0"> 
  7.     <meta name="description" content="binding"> 
  8.     <meta name="author" content="xu.fei@outlook.com"> 
  9.     <script type="text/javascript" src="../js/thin.js"></script> 
  10. </head> 
  11. <body> 
  12. <div vm-model="test.Person"> 
  13.     <input type="text" vm-value="name"/> 
  14.     <input type="text" vm-value="age"/> 
  15.     <input type="text" vm-value="age"/> 
  16.     <input type="button" vm-click="growUp" value="Grow Up"/> 
  17. </div> 
  18.  
  19. <div vm-model="test.Person" vm-init="init"> 
  20.     <input type="text" vm-value="name"/> 
  21.     <input type="text" vm-value="age"/> 
  22.     <input type="button" vm-click="growUp" value="Grow Up"/> 
  23. </div> 
  24. <script type="text/javascript"> 
  25.     thin.define("test.Person", [], function () { 
  26.         function Person() { 
  27.             this.name = "Tom"
  28.             this.age = 5
  29.         } 
  30.  
  31.         Person.prototype = { 
  32.             init: function () { 
  33.                 this.name = "Jerry"
  34.                 this.age = 3
  35.             }, 
  36.  
  37.             growUp: function () { 
  38.                 this.age++; 
  39.             } 
  40.         }; 
  41.  
  42.         return Person; 
  43.     }); 
  44. </script> 
  45. </body> 
  46. </html>

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

以剛才文章提到的內容,還不能完全解釋這個例子的效果,因為沒看到在哪里調用parseElement的。說來也簡單,就在thin.js里面,直 接寫了一個thin.ready,在那邊調用了這個函數,去解析了document.body,于是測試頁面里面才可以只寫綁定和視圖模型。

我們還有一個更實際一點的例子,結合了另外一個系列里面寫的簡單DataGrid控件,做了一個很基礎的人員管理界面:http://xufei.github.io/thin/demo/binding.html

2.3 小結

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

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

原文鏈接:http://www.ituring.com.cn/article/48463

責任編輯:陳四芳 來源: 圖靈社區
相關推薦

2013-07-10 10:38:48

JavaScript框

2023-12-05 13:10:00

ReflexPython

2024-12-06 17:02:26

2020-07-02 15:32:23

Kubernetes容器架構

2015-11-17 16:11:07

Code Review

2019-01-18 12:39:45

云計算PaaS公有云

2018-04-18 07:01:59

Docker容器虛擬機

2016-11-02 14:18:45

搭建論壇Flask框架

2013-09-11 09:37:17

企業級移動應用

2022-09-01 10:46:02

前端組件庫

2011-04-19 13:32:01

jQueryjavascript

2024-05-15 14:29:45

2010-05-26 17:35:08

配置Xcode SVN

2018-09-14 17:16:22

云計算軟件計算機網絡

2018-08-20 08:15:50

編程語言Go語言切片

2020-02-11 16:49:24

React前端代碼

2015-10-15 14:16:24

2024-04-10 07:48:41

搜索引擎場景

2011-04-06 15:55:50

開發webOS程序webOS

2024-11-28 10:35:47

點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 日韩精品一区二区三区免费视频 | 日韩欧美国产一区二区 | 久久99精品国产麻豆婷婷 | 尤物视频在线免费观看 | 亚洲欧美第一视频 | 黄色一级大片在线免费看产 | 99av成人精品国语自产拍 | 99pao成人国产永久免费视频 | 成人不卡 | 欧美日韩成人影院 | 成人不卡视频 | 欧美激情一区二区三区 | 国产精品一区二 | 黄色片网站在线观看 | 国产91综合一区在线观看 | 国产激情网 | 国产欧美日韩一区二区三区在线 | 国产精产国品一二三产区视频 | 成人午夜免费视频 | 亚洲国产一区二区三区在线观看 | 中文字幕视频免费 | av网站免费观看 | 久久a久久 | 国产精品一区二区三 | 国产成人影院 | 久久久影院 | 欧美激情一区二区 | 成人午夜电影在线观看 | 国产一级在线观看 | 狠狠色综合网站久久久久久久 | 午夜激情一区 | 亚洲国产精品人人爽夜夜爽 | 色五月激情五月 | 美日韩精品| 国产日韩欧美二区 | 日韩av一区二区在线观看 | 在线观看av网站 | 手机三级电影 | 日韩视频在线播放 | 久久久久久国产精品免费免费 | 国产精品色一区二区三区 |