knockout雙工綁定基於 observe 模式,性能高。核心就是observable對象的定義。這個函數最後返回了一個也叫作 observable 的函數,也就是用戶定義值的讀寫器(accessor)。javascript
this.firstName=ko.observable(「Bert」); this.firstName(); this.firstName(「test」);
ko.observable作了什麼java
ko.observable = function (initialValue) { var _latestValue = initialValue; //保留上一次的參數,與observable造成閉包 function observable() { if (arguments.length > 0) { // Write,Ignore writes if the value hasn't changed if (observable.isDifferent(_latestValue, arguments[0])) { observable.valueWillMutate(); _latestValue = arguments[0]; if (DEBUG) observable._latestValue = _latestValue; observable.valueHasMutated(); } return this; // Permits chained assignments } else { // Read ko.dependencyDetection.registerDependency(observable); // The caller only needs to be notified of changes if they did a "read" operation return _latestValue; } } ko.subscribable.call(observable); ko.utils.setPrototypeOfOrExtend(observable, ko.observable['fn']); if (DEBUG) observable._latestValue = _latestValue; /**這裏省略了專爲 closure compiler 寫的語句**/ return observable; }
經過 ko.subscribable.call(observable); 使這個函數有了被訂閱的功能,讓 firstName 在改變時能通知全部訂閱了它的對象。其實就是維護了一個回調函數的隊列,當本身的值改變時,就執行這些回調函數。根據上面的代碼,回調函數是在 observable.valueHasMutated(); 執行的。node
ko.computed作了什麼express
this.fullName = ko.computed(function() { return this.firstName() + " " + this.lastName(); }, this);
$.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(v !== neo ){ setter.call(scope, neo); v = neo; } } return ret; }else{ v = getter.call(scope); return v; } } return ret; }
$.dependencyDetection = (function () { var _frames = []; return { begin: function (ret) { _frames.push(ret); }, end: function () { _frames.pop(); }, collect: function (self) { if (_frames.length > 0) { 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(); } } }
$.buildEvalWithinScopeFunction = function (expression, scopeLevels) { var functionBody = "return (" + expression + ")"; for (var i = 0; i < scopeLevels; i++) { functionBody = "with(sc[" + i + "]) { " + functionBody + " } "; } return new Function("sc", functionBody); } $.applyBindings = function(model, node){ var nodeBind = $.computed(function (){ var str = "{" + node.getAttribute("data-bind")+"}" var fn = $.buildEvalWithinScopeFunction(str,2); var bindings = fn([node,model]); for(var key in bindings){ if(bindings.hasOwnProperty(key)){ var fn = $.bindingHandlers["text"]["update"]; var observable = bindings[key] $.dependencyDetection.collect(observable);//綁定viewModel與UI fn(node, observable) } } },node); return nodeBind } $.bindingHandlers = {} $.bindingHandlers["text"] = { 'update': function (node, observable) { var val = observable() if("textContent" in node){ node.textContent = val; } } } window.onload = function(){ var model = new MyViewModel(); var node = document.getElementById("node"); $.applyBindings(model, node); }
KO使用閉包
一、ko綁定方式,當即執行用於須要後處理的一些數值app
//點擊事件 data-bind="click:$root.fun1.bind($param1,param2)" //當即執行 data-bind="attr: { src : $root.fun2(param1,param2) }」 //缺省參數 data-bind="event: { mouseover: myFunction }"
<script type="text/javascript"> var viewModel = { myFunction: function(data, event) { if (event.shiftKey) { //do something different when user has shift key down } else { //do normal action } } }; ko.applyBindings(viewModel); </script>
注意:在bind方式傳遞參數時,data和event兩個參數依然被缺省傳遞。 新加入的參數,在使用時排在第一位,定義時只能排在$data後面ide
二、event事件函數
<input type="text" placeholder="輸入關鍵字搜索" data-bind="event:{keyup:$root.fun1.bind($data,$element)}">
三、性能
self.weeklyRecommend(this); //監控對象總體發生變化時響應 self.weeklyRecommend(ko.mapping.fromJs(this)); //能夠監控對象下每一個元素的改變
四、ko事件註冊ui
ko.bindingHandlers.singleExamHover = { init: function(element, valueAccessor){ $(element).hover( function(){ //todo }, function(){ //todo } ); }, update:function(element, valueAccessor){ var _value = ko.unwrap(valueAccessor()); if(_value){ $(element).addClass("current"); }else{ $(element).removeClass("current"); } } };
<div class="h-set-homework" data-bind="singleExamHover:question.checked」>
五、事件冒泡
By default, Knockout will allow the click event to continue to bubble up to any higher level event handlers。
If necessary, you can prevent the event from bubbling by including an additional binding that is named clickBubble
and passing false to it
<div data-bind="click: myDivHandler"> <button data-bind="click: myButtonHandler, clickBubble: false"> Click me </button> </div>
Normally, in this case myButtonHandler
would be called first, then the click event would bubble up to myDivHandler
. However, the clickBubble
binding that we added with a value of false
prevents the event from making it past myButtonHandler
.
六、$data
$data
and
$root
are equivalent. Inside a nested binding context, this parameter will be set to the current data item (e.g., inside a
with: person
binding,
$data
will be set to
person
).
$data
is useful when you want to reference the viewmodel itself, rather than a property on the viewmodel.
<div data-bind="click:changeEditor.bind($data,$element,param1,param2)"></div> <script> changeEditor : function(ele,param1,param2){ console.log(this) console.log(ele==event.currenttarget) } </script>