本节换一种方式解读,把我消化过的东西反刍出来可能这样大家容易理解些,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
到这里viewModel中的每个域(firstName, lastName, fullName)只要存在依赖关系都能相互通知了。
标签: knockoutjs