本節換一種方式解讀,把我消化過的東西反芻出來可能這樣大家容易理解些,knockout.js大量使用閉包,非常難讀。
我們從viewModel看起:
function MyViewModel() { this.firstName = $.observable('Planet'); this.lastName = $.observable('Earth'); this.fullName = $.computed({ getter: function () { return this.firstName() + " " + this.lastName(); }, setter: 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" } }, scope: this }); } var a = new MyViewModel(); a.fullName("xxx yyy")
這里包含兩種observable,沒有依賴的與有依賴的,有依賴的通過沒有依賴的計算出來,因此叫做computed!
但不管怎么樣,它們都是返回一個函數,我們通過如下代碼就可以模擬它們了:
//注:這里會用到mass Framework的種子模塊的API https://github.com/RubyLouvre/mass-Framework/blob/master/src/mass.js //observable的傳參必須是基本類型 var validValueType = $.oneObject("Null,NaN,Undefined,Boolean,Number,String"); $.observable = function(value){ var v = value;//將上一次的傳參保存到v中,ret與它構成閉包 function ret(neo){ if(arguments.length){ //setter if(!validValueType[$.type(neo)]){ $.error("arguments must be primitive type!") return ret } if(v !== neo ){ v = neo; } return ret; }else{ //getter return v; } } value = validValueType[$.type(value)] ? value : void 0; ret(arguments[0]);//必須先執行一次 return ret } $.computed = function(obj, scope){//為一個惰性函數,會重寫自身 //computed是由多個$.observable組成 var getter, setter if(typeof obj == "function"){ getter = obj }else if(obj && typeof obj == "object"){ getter = obj.getter; setter = obj.setter; scope = obj.scope; } var v var ret = function(neo){ if(arguments.length ){ if(typeof setter == "function"){//setter不一定存在的 if(!validValueType[$.type(neo)]){ $.error("arguments must be primitive type!") return ret } if(v !== neo ){ setter.call(scope, neo); v = neo; } } return ret; }else{ v = getter.call(scope); return v; } } ret(); //必須先執行一次 return ret; }
因此當我們執行new MyViewModel(),就會依次執行$.observable, $.observable, $.computed, $.computed中的參數的getter, getter再調用兩個observable。
問題來了,當我們調用computed時,總會通知其依賴(即firstName ,lastName)進行更新,但firstName 發生改變時沒有手段通知fullName 進行更新。ko把這邏輯寫在dependencyDetection模塊中。我簡化如下:
$.dependencyDetection = (function () { var _frames = []; return { begin: function (ret) { _frames.push(ret); }, end: function () { _frames.pop(); }, collect: function (self) { if (_frames.length > 0) { if(!self.list) self.list = []; var fn = _frames[_frames.length - 1]; if ( self.list.indexOf( fn ) >= 0) return; self.list.push(fn); } } }; })();
我們把它加入到 $.computed 與 $.observable中,再添加一個發布更新函數valueWillMutate
var validValueType = $.oneObject("Null,NaN,Undefined,Boolean,Number,String") $.dependencyDetection = (function () { var _frames = []; return { begin: function (ret) { _frames.push(ret); }, end: function () { _frames.pop(); }, collect: function (self) { if (_frames.length > 0) { if(!self.list) self.list = []; var fn = _frames[_frames.length - 1]; if ( self.list.indexOf( fn ) >= 0) return; self.list.push(fn); } } }; })(); $.valueWillMutate = function(observable){ var list = observable.list if($.type(list,"Array")){ for(var i = 0, el; el = list[i++];){ el(); } } } $.observable = function(value){ var v = value;//將上一次的傳參保存到v中,ret與它構成閉包 function ret(neo){ if(arguments.length){ //setter if(!validValueType[$.type(neo)]){ $.error("arguments must be primitive type!") return ret } if(v !== neo ){ v = neo; $.valueWillMutate(ret);//向依賴者發送通知 } return ret; }else{ //getter $.dependencyDetection.collect(ret);//收集被依賴者 return v; } } value = validValueType[$.type(value)] ? value : void 0; ret(arguments[0]);//必須先執行一次 return ret } $.computed = function(obj, scope){//為一個惰性函數,會重寫自身 //computed是由多個$.observable組成 var getter, setter if(typeof obj == "function"){ getter = obj }else if(obj && typeof obj == "object"){ getter = obj.getter; setter = obj.setter; scope = obj.scope; } var v var ret = function(neo){ if(arguments.length ){ if(typeof setter == "function"){//setter不一定存在的 if(!validValueType[$.type(neo)]){ $.error("arguments must be primitive type!") return ret } if(v !== neo ){ setter.call(scope, neo); v = neo; } } return ret; }else{ $.dependencyDetection.begin(ret);//讓其依賴知道自己的存在 v = getter.call(scope); $.dependencyDetection.end(); return v; } } ret(); //必須先執行一次 return ret; } function MyViewModel() { this.firstName = $.observable('Planet'); this.lastName = $.observable('Earth'); this.fullName = $.computed({ getter: function () { return this.firstName() + " " + this.lastName(); }, setter: 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" } }, scope: this }); this.card = $.computed(function(){ return this.fullName() +" 屌絲" },this) } var a = new MyViewModel(); //============測試代碼============ $.log(a.firstName())//Planet $.log(a.lastName())//Earth $.log(a.fullName())//Planet Earth 通過上面兩個計算出來 a.fullName("xxx yyy");//更新fullName會自動更新firstName與lastName $.log(a.firstName())//xxx $.log(a.lastName())//yyy a.firstName("ooo");//更新firstName會自動更新fullName $.log(a.fullName())//ooo yyy $.log(a.card())//ooo yyy 屌絲
到這里viewModel中的每個域(firstName, lastName, fullName)只要存在依賴關系都能相互通知了。