標籤:ko knockoutjs mvvm
Q: KnockOut的雙向繫結是如何工作的呢?
A: 原理上還是挺簡單的:
- 聲明Observable的時候記住當前的值, var personName = ko.observable("");
- 在HTML裡declarative binding時,即data-binding="text: personName",會註冊一個subscriber 到personName
- 當personName的值發生變化時,會通知所有的subscriber,觸發對應的行為
- 重新記錄這個新的值,迴圈#c步驟。
順便提下,如果想讓personName改變後還是原值的時候也觸發subscriber,可以用extender
myViewModel.personName.extend({ notify: 'always' });
Q: 為什麼ViewModel的官方規範裡面需要var self= this;
A: 防止在事件觸發的時候ViewModel的this和事件自己的this混淆
Q: 區別: subscribe vs computed vs extend
如果需要處理由值變化而引發另一個操作的情境,可以有幾種方式:
- Subscribe在單值變化的時候使用;
- Computed在多個值變化的時候使用;
- Extend則一般定義了通用方法,可以讓多個observable公用同一個extend方法,類似於C#裡面的filter.
具體用法如下:
- Subscribe, 該方法有3個參數:
- callback是由值變化後觸發的callback函數
- Target:可選項,callback的this值
- event可以定義callback 函數觸發的事件,比如: beforeChange,預設是用change
myViewModel.personName.subscribe(function(newValue) { alert("Theperson's new name is " + newValue);});
PS: KO的官方文檔裡解釋是,KO在HTML裡的聲明綁定(Decliaritivebinding),就是使用subscribe的機制來實現的。
既然可以顯示聲明subscribe,也就可以把它dispose掉
var subscription =myViewModel.personName.subscribe(function(newValue) { /* do stuff */ });// ...then later...subscription.dispose(); // I no longer want notifications
- Computed:
如果在computed裡面的KO對象的值有變更,都會觸發computed函數執行:
function AppViewModel() { this.firstName = ko.observable('Bob'); this.lastName = ko.observable('Smith');}function AppViewModel() { // ... leave firstName and lastNameunchanged ... this.fullName = ko.computed(function() { return this.firstName() + " " +this.lastName(); }, this);}
這裡,傳遞this是 為了在computed裡讀取fistName和lastName.
如果要在改變fullName的時候反向改變firstName和LastName,可以定義裡面的write callback, 我使用過的一個情境是一個checkbox控制所有其他Itemcheckbox的 select和unselect.
function MyViewModel() { this.firstName =ko.observable('Planet'); this.lastName = ko.observable('Earth'); this.fullName = ko.pureComputed({ read:function () { return this.firstName() + " " +this.lastName(); }, write:function (value) { var lastSpacePos = value.lastIndexOf(" "); if (lastSpacePos > 0) { // Ignore values with no space character this.firstName(value.substring(0,lastSpacePos)); // Update "firstName" this.lastName(value.substring(lastSpacePos+ 1)); // Update "lastName" } }, owner: this });}
- Extender:
Target參數傳遞被extend的對象,option傳遞外部的值
ko.extenders.logChange = function(target, option) { target.subscribe(function(newValue) { console.log(option+ ": " + newValue); }); return target;};this.firstName =ko.observable("Bob").extend({logChange: "first name"});
區別:Computed VS Pure Computed
Pure Computed只有當有其他subscriber的時候才會有這個對象,所對應的DOM對象 不啟用的時候不存在,這樣可以防止記憶體泄露,在Component等情境下不用擔心dispose的問題。
如果computed裡面的對象不會有其他寫的操作,優先使用pure computed
"text" binding 注意事項:
Text binding非常常用,使用也非常簡單,但是要注意一些情境:
Today's message is: <span data-bind="text:myMessage"></span> <script type="text/javascript"> var viewModel = { myMessage:ko.observable() // Initially blank }; viewModel.myMessage("Hello,world!"); // Text appears </script>
- 如果text是非number或string, 它的值會轉換成ToString()的形式顯示,比如顯示成Object
- text會過濾掉所有HTML元素防止Javascript注入,如果要保護HTML元素,使用HTML綁定
關於綁定上下文:
首先理解下上下文Context和上下文ViewModel的對象,以$parentContext和$parent為例:
var initialData = [ { firstName: "Danny",lastName: "LaRusso", id:"C6A2D391-6B68-42EA-9EE2-3E25756143C2", phones: [ { type: "Mobile",number: "(555) 121-2121", id:"C6A2D391-6B68-42EA-9EE2-3E25756143C1" }, { type: "Home", number:"(555) 123-4567", id:"C6A2D391-6B68-42EA-9EE2-3E25756143C5" }] }, { firstName: "Sensei",lastName: "Miyagi", id:"C6A2D391-6B68-42EA-9EE2-3E2575614334", phones: [ { type: "Mobile",number: "(555) 444-2222", id:"C6A2D391-6B68-42EA-9EE2-3E25756143C20" }, { type: "Home", number:"(555) 999-1212", id:"C6A2D391-6B67-42EA-9EE2-3E25756143C2" }] } ];
假設當前的上下文是在Phones, 如果要訪問父級的firstName, lastName對象,可以用$parentContext先擷取父級的上下文,然後再取出對象
<spandata-bind="text: $parentContext.contact.firstName"></span>
也可以用$parent取得父級內容相關的object: { firstName:"Danny", lastName: "LaRusso", id:"C6A2D391-6B68-42EA-9EE2-3E25756143C2"},
然後直接取得firstName的值 <span data-bind="text:$parent.firstName"></span>
KO定義很一些上下文綁定對象,可以方便的找到當前和不同層別的上下文。
- $parent, $parents and $root
$parent可以找到上級的對象, $parents[0], $parents[1].$parents[$parents.length -1]則一級一級往上找。
<h1 data-bind="text:name"></h1><div data-bind="with:manager"> <!-- Now we're inside a nested binding context --> <span data-bind="text:name"></span> is the manager of <span data-bind="text: $parent.name"></span></div>
如果當前的上下文是root, $parent和$parents都會是undefined, $parents[$parents.length-1]也相當於$root.
- $data, 儲存當前內容相關的ViewModel對象,同樣, 在root上下文,$data相當於$root
<table> <thead> <tr><th>Firstname</th><th>Last name</th></tr> </thead> <tbody data-bind="foreach:people"> <tr> <td data-bind="text: $data.firstName"></td> <td data-bind="text: lastName"></td> </tr> </tbody></table><script type="text/javascript"> ko.applyBindings({ people: [ { firstName: 'Bert', lastName: 'Bertington' }, { firstName: 'Charles', lastName: 'Charlesforth' }, { firstName: 'Denise', lastName: 'Dentiste' } ] });</script>
- $context and $parentContext
$context是當前的上下文,$parentContext對應的是父級的上下文
- $element
當前綁定的DOM對象
<div id="item1"data-bind="text:$element.id"></div>
- Foreach會改變當前的上下文,和 with 綁定可以主動的改變當前的上下文
<h1 data-bind="text: city"> </h1> <p data-bind="with: coords"> Latitude: <span data-bind="text:latitude"> </span>, Longitude: <spandata-bind="text: longitude"> </span></p> <script type="text/javascript"> ko.applyBindings({ city:"London", coords: { latitude: 51.5001524, longitude:-0.1262362 } }); </script>
非常好用的資料轉化observable外掛程式:Ko.mapping
這是一個外掛程式,能複雜物件的資料和子層資料轉化成Observable, 沒有在KO內建的js裡,需要額外下載,用法:
self.changeRequest = ko.mapping.fromJS(data); //data裡面的所有資料都會變成observable
如果要更新整個對象,通過直接的賦值self.changeRequest沒有作用,可以通過以下方法:
ko.mapping.fromJS(result,instance.changeRequestViewModel.changeRequest);
區別 If binding vs visible binding vs with
if 綁定會根據條件判斷是否產生對應的DOM對象,而Visiable只是通過css顯示和隱藏DOM對象,兩者的使用情境還是有所差別的。比如,根據使用者當前的許可權判斷時候可以顯示某個操作時,最好還是使用if,可以防止別人通過修改HTML 屬性看到這個DOM對象。
with除了改變內容相關的層級,還有另一個特性,當內部的元素不存在時,不會出現找不到元素的綁定錯誤。
Click 綁定注意事項:
Click 事件會預設傳遞當前上下的 ViewModel值和觸發的事件對象給函數, 如果要改變函數,可以有兩個辦法:
<button data-bind="click: function(data, event) {myFunction('param1', 'param2', data, event) }"> Click me </button> <button data-bind="click: myFunction.bind($data,'param1', 'param2')"> Click me </button>
一般情況下,聲明了click綁定之後,就不會觸發其他的點擊事件了,比如a標籤的href,如果還想繼續觸發,在click的函數裡返回true
Form相關的綁定注意事項:
- Checkbox 和radio 按鈕請使用checked 綁定(不用value), 只需聲明個數組 this.checkedList= ko.observableArray(); 然後在HTML綁定時指定value和checked就能實現多選,不需要很複雜的代碼: databind="value: Id, checked: checkedList", checkedList取到的就是選中的Id列表。
- Input標籤需要使用textInput 綁定而不是value綁定, 區別在於,textInput在值改變的時候就能觸發, value要等到keydown才行。
參考:http://knockoutjs.com/documentation/introduction.html
KnockoutJs 進階學習