今天整理一下WeX5的綁定機制。
原生的問題
假設我們做一個訂單系統,需要顯示商品單價,然后可以根據輸入數量計算出總價并顯示出來。使用原生代碼也很容易實現,效果:

代碼如下:
<!--HTML code-->
Price: <span id="price"></span><br />
Account: <input type="text" id="account" value="" placeholder="請輸入數量" /><br />
sum: <span id="sum"></span>
//js codevar priceNode = document.getElementById('price'),
accountNode = document.getElementById('account'),
sumNode = document.getElementById('sum'),
price = 100,
account = 11,
sum = price * account;//初始化。
priceNode.innerText = price;
accountNode.value = account;
sumNode.textContent = sum;
//監視 View層的用戶輸入
accountNode.addEventListener('keydown', function (e) {
window.setTimeout(function () {
account = accountNode.value;
sum = price * account;
sumNode.textContent = sum;
},10);
});嗯,蠻簡單的!哦,對了,我們一次展示50件商品,同時又有10類這樣的展示,還有買5盒岡本送一根油條這樣的各種促銷呢……
所以,你知道原生實現的問題了吧:
隨著 UI 和數據交互的增多,代碼量迅速增長,難以維護
基于 Dom 查詢,id 或 class 的命名難以管理
代碼耦合度高,難以復用
WeX5的解決之道
為了解決上述問題,WeX5中引入了knockoutjs(下文簡稱ko)這個MVVM庫。
為何選用ko而不是Angular一類比較全面的框架?Angular是好,但這么大而全的框架,沒有經過足夠的實戰測試的話,很多坑都不會被暴露出來。而ko是一個輕量級的MVVM庫,專注于實現數據與視圖的綁定,本身并不提供 UI 類和路由等功能,所以非常簡單穩定。同時,由于他出來也已經有些年頭了,現在是比較成熟的框架了。所以在做一些移動頁面開發時,ko無疑是一個比較好的選擇。另外,關于MVVM小茄就不多說了,一圖以蔽之:

ko建立在3大核心特征之上(官網介紹):
1. 可觀察對象與依賴跟蹤 (Observables and dependency tracking):使用可觀察對象在模型數據之間設立隱性關系鏈,用于數據轉換和綁定。
2. 聲明式綁定 (Declarative bindings):使用簡單易讀的語法方便地將模型數據與DOM元素綁定在一起。
3. 模板 (Templating):內置模板引擎、為你的模型數據快速編寫復雜的 UI 展現。
下面簡單說說ko的幾大概念:
可觀察對象
使用ko重寫上面的例子(自定價格,這也是我小時候的愿望之一):

代碼是這樣的:
<!--HTML Code--><div id="one">
Price: <input type="text" data-bind="value: price" placeholder="請輸入單價" /><br />
Account: <input type="text" data-bind="value: account" placeholder="請輸入個數" /><br />
sum: <span data-bind="text: sum"></span></div>
// js Codevar ViewModel = function(p, a) {
//設置為可觀察對象并以參數p、a初始化
this.price = ko.observable(p);
this.account = ko.observable(a);
//調用ko函數的時候將this傳入,否則執行ko.pureComputed內部代碼時,this為ko,ko.price()報錯。
this.sum = ko.pureComputed(function() {
//因為可觀察對象是一個函數對象,所以要用 price()來讀取當前值。
//設置值使用price(NewValue),支持鏈式寫法:this.price(12).account(3)
return this.price() * this.account();
}, this);
};var vm = new ViewModel(135, 10);//應用該綁定,綁定開始生效
ko.applyBindings(vm);1)先看HTML代碼:
可以看到在每個標簽中都加入了一個 data-bind = "XX:OO" 這樣的鍵-值對。這個就是 ko 的綁定語法,XXOO代表什么東西呢?(XXOO?小茄還是個孩子啊…)從例子可以看到XX為標簽的屬性,可以是text、value、class、checked等標簽屬性,其實也可以是click、focus、load等DOM事件。OO看起來像是一個變量,實際上并不是變量,而是一個函數對象,執行這個函數(帶個())就能得到相應的綁定值。通過XXOO就可以將元素的屬性或事件跟js中的函數對象綁定在一起(XXOO過了就要相互負責?),這就是ko的聲明式綁定。綁定的定義其實就是一個觀察者模式,只不過這是雙向的綁定,發布者和訂閱者相互訂閱了對方的消息而已,這就是MVVM的雙向綁定。ko雙向綁定的結果就是一方變化就可以自動更新另一方,也就是通過ViewModel將數據和表現層緊緊綁定在一起了。綁定的效果類似于:

2)再看看js代碼:
可以看到js中定義了一個ViewModel對象,在對象中對HTML中綁定的OO進行了操作。這里主要有兩個操作: ko.observable()和ko.pureComputed()。
ko.observable(p):見名知義、這個就是設置可觀察對象的方法,傳入的參數p就是初始化的值,這里的參數可以是基本數據類型,也可以是一個json對象。被設置為可觀察對象后就意味著系統會一直觀察這個值。無論是ViewModel中的p還是被綁定對象的p發生變化都會引起刷新事件,將所有用到這個值的地方都更新到最新狀態。顯然,可觀察對象是比較消耗性能的,所以對于不需要動態變更的值(如價格)則不要設置為可觀察對象,當然還是需要放入ViewModel中進行集中初始化。
注意:ko.observable(p)返回的可觀察對象是一個函數對象,所以讀取可觀察對象需要使用price()這種方式;同樣的,設置可觀察對象需要使用price(newValue)這種方式。比較貼心的是,設置的時候支持鏈式寫法:ViewModel.price(100).account(10)。
ko.pureComputed()就是所謂的依賴跟蹤了,這里是單價*數量等于總價,注意這里不能直接用this.sum = this.price() * this.account();來指定sum,這種寫法不能動態刷新被綁定的對象,只是動態改變了sum變量,但要去刷新綁定對象還需要其他操作。所以,與計算相關的綁定值都要用ko的計算函數來設置。當然,返回的也是一個函數對象。另外,ko還有一個computed函數,也可以用其來進行設置,不過推薦使用pure的方式,以提高性能。
注意這里的寫法:ko.pureComputed(fn, this),也就是將fn綁定到ViewModel執行環境中,其實就是js中的call/apply。因為在執行ko內部函數的時候,this為ko對象,所以為了得到ViewModel對象,需要通過上面的寫法傳入this。當然也可以在ko函數外部用that保存ViewModel對象,然后在ko函數內部使用that來調用ViewModel對象。像這樣:
var that = this;this.sum = ko.pureComputed(function() {
return that.price() * that.account();
});定義好ViewModel構造函數后便實例化了一個ViewModel對象,然后使用了ko.applyBindings()的方式來使得綁定生效,這一步不要漏掉了。
使用ko的頁面簡單模式:
<!--HTML Code--><span data-bind="text: bindtext"></span>
// js Codevar viewModel = {
bindtext: ko.observable('initValue')
};
ko.applyBindings(viewModel);總結起來就是:HTML中使用data-bind="XX: OO"聲明綁定,js中建立ViewModel并設置可觀察對象,最后應用綁定。
可觀察對象數組
再看看可觀察對象數組的使用方法,在ko中可不能像js一樣數組和變量混用,對于數組對象就要用ko.observableArray([…,…])這種形式,同樣的,數組元素也可以是基本類型也可以是json對象。ko中的可觀察對象數組有一系列的數組操作方法,如slice()、sort()、push()這種,效果跟原生的js數組操作方法一樣,只是通過ko方法所做的改動會通知到訂閱者從而刷新界面,但js方法則不會刷新界面。下面是一個簡單例子:

<!--HTML Code--><select data-bind="options: list"></select>
// js Codevar vm = {
// list: ko.observableArray()
list: ko.observableArray(['Luffy','Zoro','Sanji'])
};
ko.applyBindings(vm);關鍵點:ko監控的是數組的狀態,而不是元素本身的狀態。也就是說當數組狀態變化(增減元素)的時候會觸發ko事件引起綁定對象的刷新,但數組內部元素的變化(如:值變化)則不被監控不能觸發ko事件。例如:

在控制臺中使用原生方法將Luffy動態改成Lucy是不會刷新UI頁面的,而使用ko的數組操作改動數組則會立即刷新頁面,值得注意的是在刷新的時候,也會將之前的改動刷新出來(Luffy > Lucy)。也就是說其實js內存中的變量是已經改變了,但是還缺少一個刷新DOM的動作。這里大家可以看到,讀取數組的方法是vm.list()[0],因為list也是一個函數對象,執行返回值才是我們想要的list內容。同理,也可以通過 vm.list(["妹子","妹子","妹子"]) 這樣的方式重置可觀察對象數組,也能立即刷新UI。
如果需要將數組元素的改動也動態反應到UI上,需要將數組元素也設置為可觀察對象,然后使用ko的方法改變數組元素值。注意,是使用ko的方法 list()[0]("Lucy")!

操作可觀察對象數組的方法有兩類,一類是與原生js數組方法同名的:pop, push, shift, unshift, reverse, sort, splice,這一部分與js原生方法的用法和效果都一樣,就不再贅述了。
另外一些方法是js中沒有的,主要有以下幾個:
remove(someItem) -- 刪除所有值與someItem相等的元素項并將它們以數組形式返回,這里的意思就是說你可不能直接list.remove(0)來刪除第一項,而是要用list.remove(list()[0]) 這種形式來刪除??偠灾?,傳入的參數必須是元素項的值,可以用list()[0] 的形式,也可以直接輸入值的字符串(比如“Luffy”這種)。
remove(function(item) { return item.age < 18;}) -- 刪除所有age屬性小于18的元素項并將它們以數組形式返回,這種用法跟平常的數組高階函數沒什么區別。Item作為高階函數的參數傳入,遍歷數組時,當高階函數返回值為真值時就刪除該項,否則轉到下一項。
removeAll(['Chad', 132, undefined]) -- 刪除所有值與 'Chad' 或 123 或 undefined 相等的元素項并將它們以數組形式返回。
removeAll() -- 刪除所有項并以數組形式返回。
小竅門:在處理可觀察對象時,若對象數量眾多而且交互頻繁的情況下,每次變更都立即刷新的話會非常消耗性能,這個時候可以使用擴展 myObservableArray.extend({ rateLimit: 1000 }) 來設置延遲刷新。比如在不斷往可觀察對象數組中插入元素時,可以設置一個周期時間1000ms,讓1000ms內的所有操作集中到一次刷新中去,避免頻繁操作 DOM 帶來的性能惡化。
WeX5中如何使用ko?
WeX5作為Html5 開發工具界的翹楚,少不了集成優秀的ko框架,使用的方法非常簡單:在可視化編輯器中指定組件的bind屬性,然后在js代碼中操作相應綁定值。
先在可視化編輯器中指定:


這種方法在hello world篇也有簡單介紹,不熟悉的同學可以先去看看哈。通過可視化編輯器我們就可以綁定相應的屬性或者事件了,這里我們為 bind-ref 綁定了一個字符串“hello world”,至于其他的屬性和事件將在下一篇中介紹。綁定后我們打開代碼編輯器,發現里面并沒有出現1)那樣的綁定代碼。那綁定代碼寫到哪里去了呢?請打開HTML源碼:

可以看見代碼中出現了“bind-ref='Hello World’”這個跟上文說的data-bind是不是很相似呢?這里WeX5將每個組件可以綁定的屬性都添加到可視化編輯器中,這樣就不用再去記某個組件可以綁什么屬性了,鼠標指哪就綁哪!當然綁定字符串意義不大, 我們一般會綁定一個變量(實際上是返回值為所需變量的函數對象)。例如:

這里綁定了text為myText,這種形式的綁定為直接綁定在model對象下的,所以可以在js源碼中的Model下操作這個myText對象。
1 define(function(require){ 2 var $ = require("jquery"); 3 var justep = require("$UI/system/lib/justep"); 4 5 var Model = function(){ 6 this.callParent(); 7 this.myText = justep.Bind.observable("bind!"); 8 }; 9 Model.prototype.button2Click = function(event){ 10 this.myText.set("changed"); 11 }; 12 return Model; 13 }); 14
可以看到ko組件已經被封裝到justep的Bind對象里面去了,另外對可觀察對象的操作也跟ko中有點不同,這里采用的是set/get分別來設置和獲取可觀察對象的值。其他諸如compute等大部分方法的用法跟ko中一致。
總結
本篇主要簡單介紹了WeX5中數據綁定的由來和背后的優秀框架(knockoutjs),著重介紹了ko中最重要的概念:可觀察對象(數組),然后簡單示范了如何在WeX5中使用綁定機制以及 WeX5中的綁定與ko中的差異點。
關于可觀察對象的簡單介紹就到這里了,下一篇將具體介紹各種綁定的用法!碼字不易,隨手點贊哈~
參考資料:
1. ko官方教程:http://knockoutjs.com/documentation/introduction.html
2. WeX5綁定教程:http://docs.wex5.com/data-bind-instro/
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。