最近终于在实际项目中使用了 knockout.js,MVVM 模式的双向绑定和通知功能让我兴奋,感觉很不错,这个感觉就像当初从原生的 js 转向 jquery 一样,它开辟了一个新的编写模式,从更高的层次上满足了一些前端应用的需求。jquery 和 knockout 都是我觉得很 nice 的东西,对提高生产力有着很不错的促进作用,它们是前端完美的搭配。
jquery着重解决了同documet交互的问题,knockout着重解决了同business交互的问题
knockout.js 在文档方面写的还是比较详尽的,还有Live Examples和Tutorial,让你能很快的上手,不得不再称赞一下。它原生提供了很多的功能,可以满足绝大所数的开发需求了,唯独一点让我觉得有点遗憾,那就是 ko 的初始化,我最初的做法是先输出 json 给 ko 进行初始化,然后让 ko 更新到 UI,这样本身没什么问题,这也是教科书的做法。
但是倘若目标是一些零散的数据那就变得很繁琐了,我理想的方式还是直接把零散的数据绑定到UI上,然后让 UI 数据反射到 ko,这样就完美了,这样做还有一个好处:UI 可以及时显示该有的状态,因为通过 json 给 ko 初始化的方式总是会让我看到 ko 初始化后更新 UI 那一霎那的值变化,有点代码洁癖的我可受不了。
还好 ko 提供了自定义的扩展功能,使得应用变得无限的宽广了,我的想法也变得有思路来实现了,那就是做一个 reflect binding。
ko.bindingHandlers.reflect = { init: function (element, valueAccessor, allValueAccessor, viewModel) { // observable value var observableValue = valueAccessor(), allAccessor = allValueAccessor(); if (observableValue === false) { return; } // reflect select if (element.tagName === 'SELECT') { var optionsAccessor = allAccessor['options'], txtKey = allAccessor['optionsText'], valKey = allAccessor['optionsValue'], caption = allAccessor['optionsCaption']; var existingOptions = optionsAccessor(); if (!existingOptions || !existingOptions.length) { var options = []; if (caption) { var c = {}; c[txtKey] = caption; c[valKey] = ''; options.push(c); } $(element).find('option').each(function () { var item = {}; item[txtKey] = $(this).text(); item[valKey] = $(this).val(); options.push(item); }); optionsAccessor(options); } observableValue($(element).val()); } // reflect textarea if (element.tagName === 'TEXTAREA') { observableValue($(element).val()); } // reflect input if (element.tagName === 'INPUT') { var t = element.type.toLowerCase(); if (t === 'text' || t === 'hidden') { observableValue($(element).val()); } if (t === 'radio' || t === 'checkbox') { var target = element; if (element.name) { var find = $('input[name="' + element.name + '"]:checked'); if (find.length > 0) { target = find.get(0); } } var elval = $(target).val(); if (target.checked && elval) { observableValue(elval); } else { observableValue(target.checked); } } } } };
<select data-bind="reflect:Passenger, value:Passenger, options:PassengerOptions, optionsText:'tint', optionsValue:'vint'"> <option value="1">1 person</option> <option value="2">2 persons</option> <option value="3">3 persons</option> </select>
<input type="text" data-bind="reflect:Destination, value: Destination" value="XMN" />
function modelClass() { this.Passenger = ko.observable(); this.PassengerOptions = ko.observableArray([]); this.Destination = ko.observable(); } ko.applyBindings(new modelClass());
使用时只需确保 reflect binding 放在 data-bind 第一位即可,这个 reflect binding 是对表单元素的反射绑定,可以按照这个原理在任意 html 元素上反射自己所需要的值。