Knockoutjs 学习系列(一)ko初体验


Posted in Javascript onJune 07, 2016

MVVM框架中Angular是好,但这么大而全的框架,学习难度可不低呢,上手起码也得要个一两周吧。而knockoutjs专注于数据绑定,只需一两天就可以投入使用了,学习成本不要太低!在前端进化如此迅速的时代,学习成本也是不得不考虑的一个因素。很多时候其实我们的项目并没那么复杂,也并不需要万能的框架,更需要的反而是简单顺手的工具。

Before Knockoutjs

假设我们做一个订单系统,需要显示商品单价,然后可以根据输入数量计算出总价并显示出来。使用原生代码也很容易实现,效果:

Knockoutjs 学习系列(一)ko初体验

代码如下:

<!--HTML code-->
Price: <span id="price"></span><br />
Account: <input type="text" id="account" value="" placeholder="请输入数量" /><br />
sum: <span id="sum"></span> 
//js code
var priceNode = document.getElementById('price'),
accountNode = document.getElementById('account'),
sumNode = document.getElementById('sum'),
price = 100,
account = 11,
sum = price * account;
//初始化。
priceNode.innerText = price;
accountNode.value = account;
sumNode.textContent = sum;
//监视 View层的用户输入
accountNode.addEventListener('keydown', function (e) {
window.setTimeout(function () {
account = accountNode.value;
sum = price * account;
sumNode.textContent = sum;
},10);
});

嗯,蛮简单的!哦,对了,我们一次展示50件商品,同时又有10类这样的展示,还有买5盒冈本送一根油条这样的各种促销……

所以,你知道原生实现的问题了吧:
•随着 UI 和数据交互的增多,代码量迅速增长,难以维护
•基于 Dom 查询,id 或 class 的命名难以管理
•代码耦合度高,难以复用

Knockoutjs简介

Knockoutjs(下面简称ko)就是为了解决上述问题而出现的,他是一个轻量级的MVVM库,专注于实现数据与视图的绑定,本身并不提供 UI 类和路由等功能,上手非常快。同时,由于ko出来已经有些年头了,已经是比较成熟的框架了。在做一些动态显示比较多的页面时,ko无疑是一个比较好的选择。关于MVVM楼主就不多说了,一图以蔽之:

Knockoutjs 学习系列(一)ko初体验

ko建立在3大核心特征之上(官网介绍):

1. 可观察对象与依赖跟踪 (Observables and dependency tracking):使用可观察对象在模型数据之间设立隐性关系链,用于数据转换和绑定。

2. 声明式绑定 (Declarative bindings):使用简单易读的语法方便地将模型数据与DOM元素绑定在一起。

3. 模板 (Templating):内置模板引擎、为你的模型数据快速编写复杂的 UI 展现。

使用ko非常简单,直接到官网(http://knockoutjs.com/index.html)下载并用<script>引入即可。

可观察对象

使用ko重写上面的例子(自定价格,这也是我小时候的愿望之一):

Knockoutjs 学习系列(一)ko初体验

代码是这样的:

<!--HTML Code-->
<div id="one">
Price: <input type="text" data-bind="value: price" placeholder="请输入单价" /><br />
Account: <input type="text" data-bind="value: account" placeholder="请输入个数" /><br />
sum: <span data-bind="text: sum"></span>
</div> 
// js Code
var ViewModel = function(p, a) {
//设置为可观察对象并以参数p、a初始化
this.price = ko.observable(p);
this.account = ko.observable(a);
//调用ko函数的时候将this传入,否则执行ko.pureComputed内部代码时,this为ko,ko.price()报错。
this.sum = ko.pureComputed(function() {
//因为可观察对象是一个函数对象,所以要用 price()来读取当前值。
//设置值使用price(NewValue),支持链式写法:this.price(12).account(3)
return this.price() * this.account();
}, this);
};
var vm = new ViewModel(135, 10);
//应用该绑定,绑定开始生效
ko.applyBindings(vm);

1)先看HTML代码:

可以看到在每个标签中都加入了一个 data-bind = "XX:OO" 这样的键-值对。这个就是 ko 的绑定语法,XXOO代表什么东西呢?(XXOO?楼主还是个孩子啊…)从例子可以看到XX为标签的属性,可以是text、value、class、checked等标签属性,其实也可以是click、focus、load等DOM事件。OO看起来像是一个变量,实际上并不是变量,而是一个函数对象,执行这个函数(带个())就能得到相应的绑定值。通过XXOO就可以将元素的属性或事件跟js中的函数对象绑定在一起(XXOO了就要相互负责哈),这就是ko的声明式绑定。绑定的定义其实就是一个观察者模式,只不过这是双向的绑定,发布者和订阅者相互订阅了对方的消息而已,这就是MVVM的双向绑定。ko双向绑定的结果就是一方变化就可以自动更新另一方,也就是通过ViewModel将数据和表现层紧紧绑定在一起了。绑定的效果类似于:

Knockoutjs 学习系列(一)ko初体验

2)再看看js代码:

可以看到js中定义了一个ViewModel对象,在对象中对HTML中绑定的OO进行了操作。这里主要有两个操作: ko.observable()和ko.pureComputed()。

•ko.observable(p):见名知义、这个就是设置可观察对象的方法,传入的参数p就是初始化的值,这里的参数可以是基本数据类型,也可以是一个json对象。被设置为可观察对象后就意味着系统会一直观察这个值。无论是ViewModel中的p还是被绑定对象的p发生变化都会引起刷新事件,将所有用到这个值的地方都更新到最新状态。显然,可观察对象是比较消耗性能的,所以对于不需要动态变更的值(如价格)则不要设置为可观察对象,当然还是需要放入ViewModel中进行集中初始化。

•注意:ko.observable(p)返回的可观察对象是一个函数对象,所以读取可观察对象需要使用price()这种方式;同样的,设置可观察对象需要使用price(newValue)这种方式。比较贴心的是,设置的时候支持链式写法:ViewModel.price(100).account(10)。

•ko.pureComputed()就是所谓的依赖跟踪了,这里是单价*数量等于总价,注意这里不能直接用this.sum = this.price() * this.account();来指定sum,这种写法不能动态刷新被绑定的对象,只是动态改变了sum变量,但要去刷新绑定对象还需要其他操作。所以,与计算相关的绑定值都要用ko的计算函数来设置。当然,返回的也是一个函数对象。另外,ko还有一个computed函数,也可以用其来进行设置,不过推荐使用pure的方式,以提高性能。

•注意这里的写法:ko.pureComputed(fn, this),也就是将fn绑定到ViewModel作用域中,其实就是js中的call/apply。因为在执行ko内部函数的时候,this为ko对象,所以为了得到ViewModel对象的作用域,需要通过上面的写法传入this。当然也可以在ko函数外部用that保存ViewModel对象,然后在ko函数内部使用that来调用ViewModel对象。像这样:

var that = this;
this.sum = ko.pureComputed(function() {
return that.price() * that.account();
});

定义好ViewModel构造函数后便实例化了一个ViewModel对象,然后使用了ko.applyBindings()的方式来使得绑定生效,这一步不要漏掉了。

使用ko的页面简单模式:

<!--HTML Code-->
<span data-bind="text: bindtext"></span> 
// js Code
var viewModel = {
bindtext: ko.observable('initValue')
};
ko.applyBindings(viewModel);

总结起来就是:HTML中使用data-bind="XX: OO"声明绑定,js中建立ViewModel并设置可观察对象,最后应用绑定。

可观察对象数组

再看看可观察对象数组的使用方法,在ko中可不能像js一样数组和变量混用,对于数组对象就要用ko.observableArray([…,…])这种形式,同样的,数组元素也可以是基本类型也可以是json对象。ko中的可观察对象数组有一系列的数组操作方法,如slice()、sort()、push()这种,效果跟原生的js数组操作方法一样,只是通过ko方法所做的改动会通知到订阅者从而刷新界面,但js方法则不会刷新界面。下面是一个简单例子:

Knockoutjs 学习系列(一)ko初体验

<!--HTML Code-->
<select data-bind="options: list"></select> 
// js Code
var vm = {
// list: ko.observableArray()
list: ko.observableArray(['Luffy','Zoro','Sanji'])
};
ko.applyBindings(vm);

关键点:ko监控的是数组的状态,而不是元素本身的状态。也就是说当数组状态变化(增减元素)的时候会触发ko事件引起绑定对象的刷新,但数组内部元素的变化(如:值变化)则不被监控不能触发ko事件。例如:

Knockoutjs 学习系列(一)ko初体验

在控制台中使用原生方法将Luffy动态改成Lucy是不会刷新UI页面的,而使用ko的数组操作改动数组则会立即刷新页面,值得注意的是在刷新的时候,也会将之前的改动刷新出来(Luffy > Lucy)。也就是说其实js内存中的变量是已经改变了,但是还缺少一个刷新DOM的动作。这里大家可以看到,读取数组的方法是vm.list()[0],因为list也是一个函数对象,执行返回值才是我们想要的list内容。同理,也可以通过 vm.list(["妹子","妹子","妹子"]) 这样的方式重置可观察对象数组,也能立即刷新UI。

如果需要将数组元素的改动也动态反应到UI上,需要将数组元素也设置为可观察对象,然后使用ko的方法改变数组元素值。注意,是使用ko的方法 list()[0]("Lucy")!

Knockoutjs 学习系列(一)ko初体验

操作可观察对象数组的方法有两类,一类是与原生js数组方法同名的:pop, push, shift, unshift, reverse, sort, splice,这一部分与js原生方法的用法和效果都一样,就不再赘述了。

另外一些方法是js中没有的,主要有以下几个:

•remove(someItem) -- 删除所有值与someItem相等的元素项并将它们以数组形式返回,这里的意思就是说你可不能直接list.remove(0)来删除第一项,而是要用list.remove(list()[0]) 这种形式来删除。总而言之,传入的参数必须是元素项的值,可以用list()[0] 的形式,也可以直接输入值的字符串(比如“Luffy”这种)。

•remove(function(item) { return item.age < 18;}) -- 删除所有age属性小于18的元素项并将它们以数组形式返回,这种用法跟平常的数组高阶函数没什么区别。Item作为高阶函数的参数传入,遍历数组时,当高阶函数返回值为真值时就删除该项,否则转到下一项。

•removeAll(['Chad', 132, undefined]) -- 删除所有值与 'Chad' 或 123 或 undefined 相等的元素项并将它们以数组形式返回。

•removeAll() -- 删除所有项并以数组形式返回。

小窍门:在处理可观察对象时,若对象数量众多而且交互频繁的情况下,每次变更都立即刷新的话会非常消耗性能,这个时候可以使用扩展 myObservableArray.extend({ rateLimit: 1000 }) 来设置延迟刷新。比如在不断往可观察对象数组中插入元素时,可以设置一个周期时间1000ms,让1000ms内的所有操作集中到一次刷新中去,避免频繁操作 DOM 带来的性能恶化。

总结

本篇主要简单介绍了knockoutjs中最重要的概念:可观察对象(数组)。可观察对象实质上是一个函数对象,通过ko方法操作可观察对象时可以动态刷新UI展现,这个是推荐做法。同时,也可以通过原生的js方法操作可观察对象,只是原生方法并不会刷新UI展现,需要等到下一次刷新事件时才会被刷新到UI中。

Javascript 相关文章推荐
语义化 H1 标签
Jan 14 Javascript
jquery tools之tooltip
Jul 25 Javascript
js 图片等比例缩放代码
May 13 Javascript
广泛收集的jQuery拖放插件集合
Apr 09 Javascript
JQuery伸缩导航练习示例
Nov 13 Javascript
异步动态加载JS并运行(示例代码)
Dec 13 Javascript
js实现瀑布流的三种方式比较
Jun 28 Javascript
使用JavaScript为Kindeditor自定义按钮增加Audio标签
Mar 18 Javascript
Vue.js常用指令的使用小结
Jun 23 Javascript
jQuery实现的简单获取索引功能示例
Jun 04 jQuery
jQuery分组选择器简单用法示例
Apr 04 jQuery
Vue开发环境中修改端口号的实现方法
Aug 15 Javascript
【经典源码收藏】jQuery实用代码片段(筛选,搜索,样式,清除默认值,多选等)
Jun 07 #Javascript
Javascript之Number对象介绍
Jun 07 #Javascript
Javascript之Math对象详解
Jun 07 #Javascript
分享jQuery网页元素拖拽插件
Dec 01 #Javascript
【经典源码收藏】基于jQuery的项目常见函数封装集合
Jun 07 #Javascript
全面解析DOM操作和jQuery实现选项移动操作代码分享
Jun 07 #Javascript
全面解析JavaScript里的循环方法之forEach,for-in,for-of
Apr 20 #Javascript
You might like
关于PHP5 Session生命周期介绍
2010/03/02 PHP
基于PHP array数组的教程详解
2013/06/05 PHP
如何使用php绘制在图片上的正余弦曲线
2013/06/08 PHP
解析数组非数字键名引号的必要性
2013/08/09 PHP
Joomla实现组件中弹出一个模式(modal)窗口的方法
2016/05/04 PHP
php实现JWT(json web token)鉴权实例详解
2019/11/05 PHP
高性能WEB开发 flush让页面分块,逐步呈现 flush让页面分块,逐步呈现
2010/06/19 Javascript
Javascript 加载和执行-性能提高篇
2012/12/28 Javascript
Jquery动态更改一张位图的src与Attr的使用
2013/07/31 Javascript
jquery修改网页背景颜色通过css方法实现
2014/06/06 Javascript
一款基jquery超炫的动画导航菜单可响应单击事件
2014/11/02 Javascript
javascript实现禁止复制网页内容
2014/12/16 Javascript
详解jQuery向动态生成的内容添加事件响应jQuery live()方法
2015/11/02 Javascript
jQuery 3 中的新增功能汇总介绍
2016/06/12 Javascript
Bootstrap图片轮播组件Carousel使用方法详解
2016/10/20 Javascript
JS中用try catch对代码运行的性能影响分析
2016/12/26 Javascript
原生js实现电商侧边导航效果
2017/01/19 Javascript
JS字符串false转boolean的方法(推荐)
2017/03/08 Javascript
基于Bootstrap的标签页组件及bootstrap-tab使用说明
2017/07/25 Javascript
Vue组件通信的几种实现方法
2019/04/25 Javascript
微信小程序swiper实现文字纵向轮播提示效果
2020/01/21 Javascript
js里面的变量范围分享
2020/07/18 Javascript
Python实现的朴素贝叶斯分类器示例
2018/01/06 Python
解决nohup执行python程序log文件写入不及时的问题
2019/01/14 Python
基于python-opencv3的图像显示和保存操作
2019/06/27 Python
Python使用scipy模块实现一维卷积运算示例
2019/09/05 Python
Python代码中如何读取键盘录入的值
2020/05/27 Python
Python中random模块常用方法的使用教程
2020/10/04 Python
详解python中的异常和文件读写
2021/01/03 Python
matplotlib制作雷达图报错ValueError的实现
2021/01/05 Python
欧舒丹英国官网:购买欧舒丹护手霜等明星产品
2017/01/17 全球购物
vue 中 get / delete 传递数组参数方法
2021/03/23 Vue.js
2015年采购部工作总结
2015/04/23 职场文书
CSS实现多个元素在盒子内两端对齐效果
2021/03/30 HTML / CSS
React如何创建组件
2021/06/27 Javascript
攻击最高的10只幽灵系神奇宝贝,坚盾剑怪排第一,第五最为可怕
2022/03/18 日漫