knockout双工绑定基于 observe 模式,性能高。核心就是observable对象的定义。这个函数最后返回了一个也叫做 observable 的函数,也就是用户定义值的读写器(accessor)。
this.firstName=ko.observable(“Bert”); this.firstName(); this.firstName(“test”);
ko.observable做了什么
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(); 执行的。
ko.computed做了什么
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使用
1、ko绑定方式,立即执行用于需要后处理的一些数值
//点击事件 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后面
2、event事件
<input type="text" placeholder="输入关键字搜索" data-bind="event:{keyup:$root.fun1.bind($data,$element)}">
完整的 key press 过程分为两个部分,按键被按下,然后按键被松开并复位。
当按钮被松开时,发生 keyup 事件。它发生在当前获得焦点的元素上。
keydown事件发生在键盘的键被按下的时候,接下来触发keypress事件。 keyup 事件在按键被释放的时候触发。
KeyPress 只能捕获单个字符;KeyDown 和KeyUp 可以捕获组合键。
3、
self.weeklyRecommend(this); //监控对象整体发生变化时响应 self.weeklyRecommend(ko.mapping.fromJs(this)); //可以监控对象下每个元素的改变
4、ko事件注册
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”>
5、事件冒泡
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
.
6、$data
This is the view model object in the current context. In the root context, $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>