Knockout 最棒的一个特点就是它的可扩展性。Knockout 存在大量的扩展点,包含大量的工具来创建我们的应用程序。许多开发者除了 Knockout 核心库之外没有使用任何其他的脚本库 ( 甚至包括 jQuery ) 就创建了非常棒的站点。

Subscribables

在创建我们的库存管理程序的时候,很容易发现在 Knockout 中 Observable 是一个核心对象。在 Observable,ObservableArray 和 Computed Observables 的底层是 Subscribable,Subscribable 是包含三个方法和一个 Subscriptions 数组的对象,这三个方法是:

  • subscribe:这个方法添加一个订阅到主题对象上,当订阅的主题发出提醒的时候,订阅就会被调用,默认的提醒类型是 change。
  • notifySubscribers:这个方法调用所有的订阅,并且会传递一个参数到订阅的回调方法中。
  • extend:这个方法为主题对象添加一个扩展

下面的代码演示了前两个方法的使用,实现了发布和订阅。

var test = ko.observable();

// create a subscription for the "test-event"

test.subscribe(function (val) {

    console.log(val);

}, test, "test-event");

test.notifySubscribers("Hello World", "test-event");

Knockout 中订阅的一个很酷的特性是可以混合到任何的 Javascript 对象中,下面的代码演示了在普通代码中使用的方式。

// Dummy Subscribable
function PubSub() {
// inherit Subscribable
ko.subscribable.call(this);
} // create an instance of our Subscribable
var pubsub = new PubSub();
// make a subscription
var subscription = pubsub.subscribe(function (val) {
console.log(val);
}, pubsub, 'test-topic'); pubsub.notifySubscribers("hello world", "test-topic");
// console: "hello world"
// clean up things
subscription.dispose();

在调用可订阅对象的subscribe 方法之后,我们得到了一个 Subscription 对象,通常开发者可以忽略这个 subscribe 方法返回的订阅对象,但是需要注意的是通过这个订阅对象 Subscription ,我们可以释放订阅的回调方法和应用中对于回调的引用,直接调用 dispose 方法就可以确信你的程序不会造成内存的泄露。

现在,你已经理解了 Knockout 的一个核心处理机制,Subscribable,我们将学习 Knockout 如何扩展它的用法在其它的模式。

Observables

Knockout 的 Observable 对象是实现订阅机制的头等对象,也是 Knockout 库中最为简单,也最为强大的部分。下面的代码演示了实现 Observable 的基本思想。

// Very Simple Knockout Observable Implementation
// ko.observable is actually a function factory ko.observable = function (initialValue) { // private variable to hold the Observable's value
var _latestValue = initialValue; // the actual "Observable" function
function observable() {
// one or more args, so it's a Write
if (arguments.length > 0) {
// set the private variable
_latestValue = arguments[0];
// tell any subscribers that things have changed
observable["notifySubscribers"](_latestValue);
return this; // Permits chained assignments
} else { // no args, so it's a Read
// just hand back the private variable's value
return _latestValue;
}
} // inherit from Subscribable
ko.subscribable.call(observable); // return the freshly created Observable function
return observable;
};

这是 Knockout 中 observable 的一个简单实现 ( 真正的实现代码中包含了大量的强壮性处理代码,验证,依赖检测等等 ),从这个实现中可以看到实际上 observable 是一个函数的工厂,当调用这个工厂方法的时候,它生成了一个新的函数。这里实现了一个简单的 set/get API。如果你在不提供参数的情况下调用返回的函数,就会返回 _initialValue ,如果调用的时候提供了一个参数,就会设置 _initialValue ,并且通知所有的订阅。

当你创建事件驱动的程序的时候,Observable 可以得到大量的应用。我们可以创建事件驱动的程序架构,仅仅依赖事件 ( 比如按钮的点击,或者输入元素的变化等等 )而不是过程化的方法。

Observable Array

我在前面已经提到过可观察的数组,而且提高还会深入讨论这个特性。前面的示例中已经演示了 Observable 比较简单,而可观察的数组也一样简单。下面的代码演示了实现的机制。

// Very Simple Knockout Observable Array Implementation
// function factory for observable arrays
ko.observableArray = function (initialValues) { // make sure we have an array
initialValues = initialValues || []; // create a Knockout Observable around our Array
var result = ko.observable(initialValues); // add our Observable Array member functions
// like "push", "pop", and so forth
ko.utils.extend(result, ko.observableArray['fn']); // hand back the Observable we've created
return result; };

如你所见,实际上还是一个可观察对象,除了这个可观察对象的值是一个数组而已。另外还添加了匹配数组本地方法的扩展方法。Knockout 的作者已经帮我们完成的这些工作,使得开发者可以像使用普通的数组一样使用可观察的数组。

可观察数组的一个扩展点是它的 fn 属性,你可以添加自己的函数到这个属性中,那么就可以使得应用中所有的可观察数组都提供这个方法,下面的代码演示了如何添加一个过滤方法来过滤数组中的元素。

ko.observableArray.fn['filter'] = function (filterFunc) {

    // get the array
var underlyingArray = this();
var result = []; for (var i = 0; i < underlyingArray.length; i++) {
var value = underlyingArray[i]; // execute filter logic
if (filterFunc(value)) {
result.push(value);
}
}
return result;
};
var list = ko.observableArray([1, 2, 3]); // filter down the list to only odd numbers
var odds = list.filter(function (item) {
return (item % 2 === 1);
}); console.log(odds); // [1, 3]

使用 fn 函数在许多脚本库中是常见的模式,包括 jQuery 脚本库,Knockout 也不例外。fn 也存在于 Observable 中,所以你也可以通过同样的方式进行扩展。

Computed Observable

计算出的可观察对象可以说是 Knockout 中最强大的特性。在你学会使用 Observable 和 Observable Array 而不是用其他技术来创建视图模型之后,已经是一个巨大的进步,但是,更棒的是你可以创建仅仅依赖于其他 Observable 和 Observalbe Array 的属性,而且在底层属性发生变化的时候,计算的结果会同时发生变化。

计算出的可观察对象类似于普通的可观察对象,除了没有保存自己的值之外,它提供了一个方法来计算应该返回的值,这个函数仅仅在所依赖的底层对象发生变化的时候重新计算返回值,下面的代码演示了实现的机制。

var a = ko.observable(1);

var b = ko.observable(2);
var sum = ko.computed(function () {
var total = a() + b();
// let's log every time this runs
console.log(total);
return total;
}); // console: 3
a(2); // console: 4
b(3); // console: 5
b(3); // (nothing logged)

计算出的可观察对象有着非常有趣和巧妙的实现,你可以自己挖掘一下,初次之外,还有一些需要注意的要点。

首先,计算出的可观察对象创建了一个依赖列表 ( 或者订阅 ),当你的计算函数执行的时候,如果你希望依赖于某个对象,就需要保证在计算函数中调用这个 Observable ,下面的代码演示了如何设置依赖和动态改变计算对象的依赖对象。

var dep1 = ko.observable(1);
var dep2 = ko.observable(2);
var skipDep1 = false;
var comp = ko.computed(function () { dep2(); // register dep2 as dependency
if (!skipDep1) {
dep1(); // register dep1 as dependency
}
console.log('evaluated');
}); // console: evaluated
dep1(99); // console: evaluated
skipDep1 = true;
dep2(98); // console: evaluated
dep1(97); // (nothing logged)

一般来说,上面的代码不是什么好注意,这使得代码更难调试,使其他的开发人员更加难以直观地意识到发生的状况。相反,建议的处理方式是首先从依赖对象中获取所有的值,然后,使用私有的变量来评估处理逻辑问题。

第二点,这一点使用所有的可观察对象,订阅的回调函数仅仅在属性的值发生变化的时候,每个可观察对象都拥有一个名为equalityComparer 的属性,用来检测属性之前和之后的值是否不同,如果你注意前面的例子,最后一个调用什么都没有做,这是因为第二次设置的值还是 3。默认的equalityComparer 实现仅仅比较 javascript 的基础值,如果可计算对象依赖所有的对象,包括复杂的或者不复杂的,那么,这个函数就会执行多次,下面的代码演示了这个重要的概念。

var depPrimitive = ko.observable(1);
var depObj = ko.observable({ val: 1 });
var comp = ko.computed(function () { // register dependencies
var prim = depPrimitive();
var obj = depObj();
console.log("evaluated");
}); // console: evaluated
depPrimitive(1); // (nothing logged) var previous = depObj();
depObj(previous); // console: evaluated

重要的是理解这一点,很多 Knockout 的新手创建了复杂的可计算对象,导致应用的性能降低。

最后一点,计算的可观察对象也可以拥有自己的 set/get ,这一点很有用,因为这允许我们在视图模型上拥有私有的可观察对象,可以通过公共的 set/get 类似于普通的可观察对象一样使用,下面的代码演示了这个技术。

var _val = ko.observable(1);

var vm = {
val: ko.computed({ read: function () {
return _val();
}, write: function (newVal) {
_val(newVal);
}
})
}; vm.val(2);
console.log(_val()); // console: 2
_val(3);
console.log(vm.val()); // console: 3

翻译:Knockout 快速上手 - 4: 你需要知道的顶级特性的更多相关文章

  1. 翻译:Knockout 快速上手 - 5: 你需要知道的顶级特性 续

    Utilities Knockout 提供了许多可以你开发中使用的工具,你可以在 ko.utils 命名空间中找到它们,我最喜欢的工具如下所示: extend: 这个方法将两个对象合并在一起,调用这个 ...

  2. 翻译:Knockout 快速上手 - 3: knockoutJS 快速上手

    许多时候,学会一种技术的有效方式就是使用它解决实际中的问题.在这一节,我们将学习使用 Knockout 来创建一个常见的应用,库存管理应用. 应用概览 在创建我们的应用之前,我们需要一个公司,来理解应 ...

  3. 翻译:Knockout 快速上手 - 2: 安装 knockoutJS

    只需要五个简单的步骤,就可以做好使用 Knockout 开发的准备! 第一步 我们需要什么? 最低限度,为了完成后面的教程,你需要如下的准备 Web 浏览器 文本编辑器 你的电脑上大约 2M 的磁盘空 ...

  4. [翻译]lithium 快速上手(QuickStart)

      ​ 快速入门 经典博客教程 很感谢你尝试Li3!这一部分栏目为那些想了解这个框架可以做什么的php用户所设计.像这样深入代码是一种很好的方式去体会快速应用开发(Rapid Application ...

  5. knockoutJS 快速上手

    翻译:Knockout 快速上手 - 3: knockoutJS 快速上手 许多时候,学会一种技术的有效方式就是使用它解决实际中的问题.在这一节,我们将学习使用 Knockout 来创建一个常见的应用 ...

  6. 快速上手RaphaelJS-Instant RaphaelJS Starter翻译(一)

       (目前发现一些文章被盗用的情况,我们将在每篇文章前面添加原文地址,本文源地址:http://www.cnblogs.com/idealer3d/p/Instant_RaphaelJS_Start ...

  7. EF Core 快速上手——EF Core 入门

    EF Core 快速上手--EF Core 介绍 本章导航 从本书你能学到什么 对EF6.x 程序员的一些话 EF Core 概述 1.3.1 ORM框架的缺点 第一个EF Core应用   本文是对 ...

  8. Flask入门和快速上手

    目录 Flask入门和快速上手 python三大主流框架对比 Flask安装 依赖 可选依赖 创建flask项目 flask最小应用--hello word 非法导入名称 调试模式 路由 唯一的 UR ...

  9. 第2章 初学 emWin 的准备工作及其快速上手

    以下内容转载自安富莱电子论坛:http://forum.armfly.com/forum.php?mod=viewthread&tid=24552&extra=page%3D3%26f ...

随机推荐

  1. C++资料大全

    本文内容源自GitHub<Awesome C/C++>. 关于 C++ 框架.库和资源的一些汇总列表,由 fffaraz 发起和维护. 内容包括:标准库.Web应用框架.人工智能.数据库. ...

  2. Redis容灾部署(哨兵Sentinel)

    Redis容灾部署(哨兵Sentinel) 哨兵的作用 1. 监控:监控主从是否正常2. 通知:出现问题时,可以通知相关人员3. 故障迁移:自动主从切换4. 统一的配置管理:连接者询问sentinel ...

  3. C语言每日一题之No.5

    总在想,但凡编程基础正常点,都不至于惨败到这个地步.也像大多数人毕业出来,新鲜的第一份工作,如果做得好还可以略有成就感,做得一般还有提升的空间,但至少不至于像我这样基本没基础的被鄙视得一塌糊涂,被外界 ...

  4. DatagridView 最后编辑状态数据无法自动提交的问题

    DataGridView1.CurrentCell = null 从一个帖子上看的,绝招! http://bbs.csdn.net/topics/120020614

  5. activiti自定义流程之Spring整合activiti-modeler5.16实例(六):启动流程

    注:(1)环境搭建:activiti自定义流程之Spring整合activiti-modeler5.16实例(一):环境搭建        (2)创建流程模型:activiti自定义流程之Spring ...

  6. php全角字符转换为半角函数

    <?php /** * 全角字符转换为半角 * * @param string $str * @return string public function Sbc2Dbc($str) { $ar ...

  7. Servlet间的跳转

       Forward        转向(Forward)是通过RequestDispatcher对象的forward(HTTPServletRequest req, HttpSerletRespon ...

  8. Report_SRW在RDF中初始化的重要性(案例)

    2015-02-01 Created By BaoXinjian 一.摘要 在开发oracle report(report 6i)的时候,常常会用到fnd_global或fnd_profile来获取当 ...

  9. POJ 1155 TELE 背包型树形DP 经典题

    由电视台,中转站,和用户的电视组成的体系刚好是一棵树 n个节点,编号分别为1~n,1是电视台中心,2~n-m是中转站,n-m+1~n是用户,1为root 现在节点1准备转播一场比赛,已知从一个节点传送 ...

  10. NeHe OpenGL教程 第三十一课:加载模型

    转自[翻译]NeHe OpenGL 教程 前言 声明,此 NeHe OpenGL教程系列文章由51博客yarin翻译(2010-08-19),本博客为转载并稍加整理与修改.对NeHe的OpenGL管线 ...