首页 > 代码库 > KnockoutJs 进阶学习
KnockoutJs 进阶学习
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 = http://www.mamicode.com/[>
假设当前的上下文是在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 进阶学习