25 November 2015

knockoutjs 使用也有一段时间了,总结了一些坑,希望对你有帮助。

为什么选择 knockoutjs

市面上有很多mvvm框架,angularjs 、 vuejs ,我所知道的MVVM框架 ,详细了解点击这里。

我选择 knockoutjs 的理由是:

  • knockoutjs 支持市面上所有浏览器,少有的能支持 ie6 这个上古神器的 mvvm 框架,这样生产代码就不需要过多的 hack 来解决头疼的兼容问题 。

  • knockoutjs 是一个纯 mvvm 框架,只做UI,不做其他任何事情。这样我很容易把它集成到现有的工程里 。 对于需要改造的页面,针对性使用就好了 。

  • 可以全站使用,也可以部分页面使用,也可以页面部分区域(div块)使用,单页面应用也可以使用。典型的是有位同学把knockoutjs和backbone结合起来搞了个框架,具体可以去github找下。

  • knockoutjs 是微软团队支持开发的,最早一次commit是 Commits on Jul 5, 2010 。

其实说起来,真正让我使用它还是第一条 – 兼容性。angularjs react等,尤其是react,里面有很多黑科技,确实很好用,内部项目对浏览器兼容性没有要求的,我还是会选择后两者。

并不是所有页面都需要使用 knockoutjs ,我认为复杂的DOM操作,VIEW变化频繁的站点,使用 knockoutjs会减少大量代码 ,可维护性也强。不满足上面条件的页面,可以用传统方式来操作。

knockoutjs 的缺点

  • 相对其他 mvvm 框架,不够热,相对小众。

  • 写法繁琐,不如angularjs 的 { { } } 来的直接。

看了上面,如果觉得你需要使用 knockoutjs ,那我们继续往下看吧。

knockoutjs 函数对象为主体

knockoutjs 中,函数是第一对象,只要用 使用 ko.observable() 接口进行数据绑定,那么生成的对象就是个函数了,那么接下来使用它都要以函数的形式 。

举个例子

Today's message is: <span data-bind="text: myMessage"></span>

var viewModel = {
    myMessage: ko.observable() // Initially blank
};
viewModel.myMessage("Hello, world!"); // Text appears
ko.applyBindings(viewModel);

viewModel.myMessage 对象经过 ko.observable() 绑定后,就变成了一个函数 ,这个函数需要一个参数,就是要绑定的值。

这里顺便提一下 foreach 绑定,有些情况是需要在表格第一列显示序列号的,这时候 我们用 $index() ,因为它也是个函数 。

至于不清楚是否是函数的对象,你就先用对象本身,如果报错,再使用函数形式(就是在对象后面加对括号,够直白吧)。

ko.applyBindings 的小技巧

注意 ko.applyBindings() 函数有两个参数,分别是数据对象和绑定的视图 DOM节点 。默认呢,第二个参数为空,如果不写,就代表绑定到整个html文档上去了,这时候如果你再执行一次 ko.applyBindings 就会报错,因为一个DOM节点只能绑定一次。这种情况,我会给ko.applyBindings 传递第二个参数 —— 绑定的DOM节点 。 这样的好处是,当再次执行绑定,只要DOM节点不一样就不会报错,这在单页面应用开发会很有作用 。

viewModel 最好用构造函数进行初始化

像上面那个例子 viewModel 是用 var 来声明的,而我一般会选择用构造函数来声明,比如上面代码可以像下面这样改造

function ViewModel(){
	var self = this;
	self.myMessage = ko.observable() ;
}
var viewModel = new ViewModel();
ko.applyBindings(viewModel,$('body')[0]);


viewModel.myMessage("Hello, world!"); // Text appears

这样写的好处是,一般情况下,我们的视图代码都不会像官方demo那么简单,可能需要大幅代码,这样用构造函数的话,由于js代码中函数第一,声明会优先上升。这样把视图的构造函数放在什么地方都无所谓了。还有就是,构造函数可以传参,初始化值可以在生成对象的时候就传递过去。还有对象的修改不影响构造函数,看着构造函数就很明确视图的结构了。

当然demo的写法也不是不好,只是用构造函数可能更利于代码的组织。

对象多值绑定的时候,用 with

当需要绑定一个对象下面多个属性的值的时候,请使用with,它会code看起来更简洁。不如这个例子,

<h1 data-bind="text: city"> </h1>
<p data-bind="with: coords">
    Latitude: <span data-bind="text: latitude"> </span>,
    Longitude: <span data-bind="text: longitude"> </span>
</p>
 
<script type="text/javascript">
    ko.applyBindings({
        city: "London",
        coords: {
            latitude:  51.5001524,
            longitude: -0.1262362
        }
    });
</script>

如果我们不使用 with 的话,需要在视图里面做多个属性的 ko.observable() 函数调用,如果大对象的,想想都疯了。

使用自定义过滤函数,让你的代码更优雅

这个是借鉴 angularjs的filter属性,在之前的文章也有提到,这里就简单介绍下。

对于需要过滤处理的后端数据,一般我们是在js代码里面直接过滤,比如把后端数据 long 型数值转换成 1,111,000.00 这种形式。原来的做法:

<span data-bind="text: amount"></span>

<script type="text/javascript">
    function ViewModel(){
		var self = this;
		self.amount = ko.observable() ;
	}
	var viewModel = new ViewModel();
	ko.applyBindings(viewModel,$('body')[0]);

	function longToNumber(val){
		// some codes
	}

	var amount = 1111000;
	var newAmount = longToNumber(amount);
	viewModel.amount(newAmount);

</script>

如果我们把数据的处理放在 html 代码里面做,代码会更优美点

<span data-bind="text: $root.longToNumber(amount)"></span>

<script type="text/javascript">
    function ViewModel(){
		var self = this;
		self.longToNumber = longToNumber;
		self.amount = ko.observable() ;
	}
	var viewModel = new ViewModel();
	ko.applyBindings(viewModel,$('body')[0]);

	function longToNumber(val){
		// some codes
	}

	var amount = 1111000;
	viewModel.amount(amount);

</script>

这样,传递原始值,在html过滤,返回正确的值。业务逻辑更清楚了。

data-bind 关键字传递的是一个JSON对象

如果你足够细心,你会发现,所有的 data-bind 后面跟的都是一个 json 对象,如果不是就报错了。例如 text 绑定,

<span data-bind="text: amount"></span>

看到 data-bind 后面是一个 {text: amount} 对象。

同一个DOM节点绑定多个对象

比如需要在一个按钮上动态绑定显示的文本,同时按钮的点击事件也需要跟着文本变化 ,这就是个典型的同一个DOM节点绑定多个对象。

绑定的方法是在多值绑定之间用逗号分隔。

<button data-bind="text: btnText,click: btnClick"></button>

<script type="text/javascript">
    function ViewModel(){
		var self = this;
		self.btnClick = btnClick;
		self.btnText = ko.observable() ;
	}
	var viewModel = new ViewModel();
	ko.applyBindings(viewModel,$('body')[0]);

	function btnClick(val){
		// some codes
	}

	viewModel.btnText('like me');

</script>

还是上面说的 data-bind 关键字传递的是一个JSON对象 ,看到现在传递的是

{
	text: btnText,
	click: btnClick
}

绑定的属性避免使用js保留字 class 等

因为在老浏览器中,js 保留字作为属性名会报错。

学会使用注释绑定(虚拟元素)

<!-- ko text: name --><!-- /ko -->

虚拟元素绑定的好处是,不会引入多余的DOM元素来影响页面样式,(react就会强制引入span做绑定)。

foreach 绑定记得使用 as别名

防止引用出错,详细见foreach 绑定

绑定的上下文要弄清楚

绑定的上下文容易导致一些错误,初学者常犯。具体分析看这篇文章——绑定的上下文

待续…