(一)Knockout 计算属性

Computed Observables

若是您有一个用于firstName的 observable 对象,和一个用于lastName的可观察对象,而且您想显示全名,该怎么办?这就是计算可观察性的用武之地——这些函数依赖于一个或多个其余可观察性,而且当这些依赖关系中的任何一个发生变化时,都会自动更新。
例如,给定如下视图模型类,javascript

function AppViewModel() {
    this.firstName = ko.observable('Bob');
    this.lastName = ko.observable('Smith');
}

您能够添加一个 computed observable 来返回全名:html

function AppViewModel() {
    // ... 让 firstName 和 lastName 不变 ...
 
    this.fullName = ko.computed(function() {
        return this.firstName() + " " + this.lastName();
    }, this);
}

如今您能够将UI元素绑定到它,例如:java

The name is <span data-bind="text: fullName"></span>

1.1 依赖链是有效的

固然,若是您愿意,您能够建立整个 computed observables 链。例如,你可能有:程序员

  • 表示一组 itemsobservable 的称为项的 items
  • 另外一个 observable 称为selectedIndexes,它存储用户选择的项索引
  • 一个名为selectedItemscomputed observable,返回与所选索引对应的项对象数组
  • 另外一个computed observable,返回truefalse,这取决于selectedItems是否具备某些属性(如new或unsaved)。一些UI元素,好比按钮,可能会基于这个值启用或禁用。

itemsselectedIndexes的更改将波及computed observables链,而computed observables链又将更新绑定到它们的任何UI元素。c#

1.2 管理 ‘this’

ko.computed的第二个参数(咱们在上面的例子中经过this在哪里)在计算 computed observable 时定义了this值。若是不传递它,就不可能引用this.firstName()this.lastName()。经验丰富的JavaScript程序员会认为这是显而易见的,但若是您仍然在学习JavaScript,这可能看起来很奇怪。(像c#和Java这样的语言从不指望程序员为此设置值,可是JavaScript会,由于它的函数自己在默认状况下不属于任何对象。)数组

一种简化事物的流行惯例
也有一种通用的简化方式,即将this在构造函数一开始就赋给另外一个变量,这样在以后须要用到this的部分便可以经过调用另外一个变量来实现:闭包

function MyViewModel() {
  var self = this;

  self.firstName = ko.observable("Chiaki");
  self.lastName = ko.observable("Izumi");
  self.fullName = ko.computed(function() {
    return self.firstName()+ " " + self.lastName();
  });
}

我的分析,之因此可以经过这种方式来简化,跟javascript中的this机制有关,可能在javascript中每当遇到一个this的时候就分析当前的object究竟是哪一个,进而对this进行替代,可是进入到ko.computed函数里面以后,因为函数并不算是object的一部分,this的值也就再也不是当前的object(MyViewModel),而变成了window,使用self之后就涉及到闭包的问题了,使得self的值并不会更改,这个能够留做之后研究。app

1.3 Pure computed observables

若是computed observable知识基于一些observable的简单计算的话(其求值器不直接修改其余对象或状态),使用pureComputed会比computed更好,以下:less

self.fullName = ko.pureComputed(function() {
   return self.firstName()+ " " + self.lastName();
 })

1.4 强制计算的监控对象始终通知订阅者

咱们也能够对computed或是pureComputed进行强制订阅,以下:
当 computed observable 返回一个基本值(数字、字符串、布尔值或null)时,一般只有当值实际更改时才会通知可观察对象的依赖关系。函数

this.fullName = ko.pureComputed(function() {
        return this.firstName() + " " + this.lastName();
      }, this).extend({notify: "always"});

1.5 延迟和/或禁止更改通知 rateLimit

一样的,能够经过调用extend方法中的rateLimit属性来指定响应的延时。

// Ensure updates no more than once per 50-millisecond period
myViewModel.fullName.extend({ rateLimit: 50 });

1.6 肯定一个属性是不是计算监控的 isComputed

for (var prop in myObject) {
    if (myObject.hasOwnProperty(prop) && !ko.isComputed(myObject[prop])) {
        result[prop] = myObject[prop];
    }
}

在某些时候,咱们可能须要断定某个变量究竟是不是computed observable,这时能够用到ko.isComputed来进行判断,相似的方法还包括isObservablem,isWritableObservable等,其中

  • isObservable对于observables、observable arrays、computed observables均会返回true;
  • isWritableObservable对于observables、observable arrays、writable computed observables均会返回true,这部分能够留做之后研究。

1.7 当计算监控属性(computed observables)仅在UI中使用时

参考:

官网,computedObservables
CharlieYuki,KnockoutJs学习笔记(三),Using computed observables

2 可写计算监控属性 Writable computed observables

  初学者可能但愿跳过这一节——可写的计算监控属性是至关高级的,在大多数状况下是没必要要的。

  一般,计算监控属性的值是从其余监控属性计算出来的,所以是只读的。那么,可能看起来使人惊讶的是,使计算出的监控属性数据可写是可能的。您只须要提供您本身的回调函数,它对写入的值执行一些合理的操做。

  您可使用与常规可写可计算可观察对象彻底同样的可写可计算可观察对象,并使用您本身的自定义逻辑拦截全部的读和写。与observables同样,您可使用连接语法将值写入模型对象上的多个可观察或计算的可观察属性。例如:

myViewModel.fullName('Joe Smith').age(50).

例1:分解用户的输入

返回到经典的“first name + last name = full name” 例子上,你可让事情调回来看: 让依赖监控属性fullName可写,让用户直接输入姓名全称,而后输入的值将被解析并映射写入到基本的监控属性firstName和lastName上:

var viewModel = {
    firstName: ko.observable("Planet"),
    lastName: ko.observable("Earth")
};

viewModel.fullName = ko.dependentObservable({

    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: viewModel
});

这个例子里,写操做的callback接受写入的值,把值分离出来,分别写入到“firstName”和“lastName”上。 你能够像普通状况同样将这个view model绑定到DOM元素上,以下:

<p>First name: <span data-bind="text: firstName"></span></p>
<p>Last name: <span data-bind="text: lastName"></span></p>
<h2>Hello, <input data-bind="value: fullName"/>!</h2>


这是一个Hello World 例子的反例子,姓和名都不可编辑,相反姓和名组成的姓名全称倒是可编辑的。

上面的view model演示的是经过一个简单的参数来初始化依赖监控属性。你能够给下面的属性传入任何JavaScript对象:

  • read — 必选,一个用来执行取得依赖监控属性当前值的函数。
  • write — 可选,若是声明将使你的依赖监控属性可写,别的代码若是这个可写功能写入新值,经过自定义逻辑将值再写入各个基础的监控属性上。
  • owner — 可选,若是声明,它就是KO调用read或write的callback时用到的this。查看“管理this”获取更新信息。

例2:全选/反选

当向用户显示一个可选择项列表时,包含一个选择或取消选择全部项的方法一般颇有用。这能够很是直观地用一个布尔值表示,该值表示是否选择了全部项。当设置为true,它将选择全部项目,当设置为false,它将取消选择他们。

Source code: View

<div class="heading">
    <input type="checkbox" data-bind="checked: selectedAllProduce" title="Select all/none"/> Produce
</div>
<div data-bind="foreach: produce">
    <label>
        <input type="checkbox" data-bind="checkedValue: $data, checked: $parent.selectedProduce"/>
        <span data-bind="text: $data"></span>
    </label>
</div>

Source code: View model

function MyViewModel() {
    this.produce = [ 'Apple', 'Banana', 'Celery', 'Corn', 'Orange', 'Spinach' ];
    this.selectedProduce = ko.observableArray([ 'Corn', 'Orange' ]);
    this.selectedAllProduce = ko.pureComputed({
        read: function () {
            // Comparing length is quick and is accurate if only items from the
            // main array are added to the selected array.
            return this.selectedProduce().length === this.produce.length;
        },
        write: function (value) {
            this.selectedProduce(value ? this.produce.slice(0) : []);
        },
        owner: this
    });
}
ko.applyBindings(new MyViewModel());

例3:Value转换器

有时候你可能须要显示一些不一样格式的数据,从基础的数据转化成显示格式。好比,你存储价格为float类型,可是容许用户编辑的字段须要支持货币单位和小数点。你能够用可写的依赖监控属性来实现,而后解析传入的数据到基本 float类型里:

Source code: View

<div>Enter bid price: <input data-bind="value: formattedPrice"/></div>
<div>(Raw value: <span data-bind="text: price"></span>)</div>

Source code: View model

function MyViewModel() {
    this.price = ko.observable(25.99);
 
    this.formattedPrice = ko.pureComputed({
        read: function () {
            return '$' + this.price().toFixed(2);
        },
        write: function (value) {
            // Strip out unwanted characters, parse as float, then write the 
            // raw data back to the underlying "price" observable
            value = parseFloat(value.replace(/[^\.\d]/g, ""));
            this.price(isNaN(value) ? 0 : value); // Write to underlying storage
        },
        owner: this
    });
}
 
ko.applyBindings(new MyViewModel());

如今,每当用户输入一个新价格时,文本框都会更新,以显示它的格式是货币符号和两位小数,不管输入的格式是什么。这给了一个很好的用户体验,由于用户看到了软件如何将他们的数据输入理解为一个价格。他们知道他们不能输入超过两位小数,由于若是他们尝试输入,额外的小数位就会被删除。相似地,它们不能输入负值,由于write回调去掉了任何负号。

例4:过滤并验证用户输入

例1展现的是写操做过滤的功能,若是你写的值不符合条件的话将不会被写入,忽略全部不包括空格的值。

再多走一步,你能够声明一个监控属性 isValid 来表示最后一次写入是否合法,而后根据真假值显示相应的提示信息。稍后仔细介绍,先参考以下代码:

Source code: View

<div>Enter a numeric value: <input data-bind="textInput: attemptedValue"/></div>
<div class="error" data-bind="visible: !lastInputWasValid()">That's not a number!</div>
<div>(Accepted value: <span data-bind="text: acceptedNumericValue"></span>)</div>

Source code: View model

function MyViewModel() {
    this.acceptedNumericValue = ko.observable(123);
    this.lastInputWasValid = ko.observable(true);
 
    this.attemptedValue = ko.pureComputed({
        read: this.acceptedNumericValue,
        write: function (value) {
            if (isNaN(value))
                this.lastInputWasValid(false);
            else {
                this.lastInputWasValid(true);
                this.acceptedNumericValue(value); // Write to underlying storage
            }
        },
        owner: this
    });
}
 
ko.applyBindings(new MyViewModel());

如今,acceptedNumericValue 将只接受数字,其它任何输入的值都会触发显示验证信息,而会更新acceptedNumericValue

备注:上面的例子显得杀伤力太强了,更简单的方式是在<input>上使用jQuery Validationnumber class。Knockout能够和jQuery Validation一块儿很好的使用,参考例子:grid editor 。固然,上面的例子依然展现了一个如何使用自定义逻辑进行过滤和验证数据,若是验证很复杂而jQuery Validation很难使用的话,你就能够用它。

3 依赖跟踪如何工做的

新手不必知道太清楚,可是高级开发人员能够须要知道为何依赖监控属性可以自动跟踪而且自动更新UI…

事实上,很是简单,甚至说可爱。跟踪的逻辑是这样的:

  1. 当你声明一个依赖监控属性的时候,KO会当即调用执行函数而且获取初始化值
  2. 当你的执行函数运行的时候,KO会把全部须要依赖的依赖属性(或者监控依赖属性)都记录到一个Log列表里。
  3. 执行函数结束之后,KO会向全部Log里须要依赖到的对象进行订阅。订阅的callback函数是从新运行你的执行函数。而后回头从新执行上面的第一步操做(而且注销再也不使用的订阅)。
  4. 最后KO会通知上游全部订阅它的订阅者,告诉它们我已经设置了新值。

因此,KO不只仅是在第一次执行函数执行时候探测你的依赖项,每次它都会探测。举例来讲,你的依赖属性能够是动态的:依赖属性A表明你是否依赖于依赖属性B或者C,这时候只有当A或者你当前的选择B或者C改变的时候执行函数才从新执行。你不须要再声明其它的依赖:运行时会自动探测到的。

另一个技巧是:一个模板输出的绑定是依赖监控属性的简单实现,若是模板读取一个监控属性的值,那模板绑定就会自动变成依赖监控属性依赖于那个监控属性,监控属性一旦改变,模板绑定的依赖监控属性就会自动执行。嵌套的模板也是自动的:若是模板X render模板 Y,而且Y须要显示监控属性Z的值,当Z改变的时候,因为只有Y依赖它,因此只有Y这部分进行了从新绘制(render)。

4 PureComputed

Pure computed observables相对于通常的computed observables,在性能和存储上有优点,这是由于pure computed observables在不存在订阅者的时候是不会保持订阅关系的。这也使得pure computed observables有以下两点特色:

  • 能够防止没有被订阅的computed observables的存储泄露。
  • 能够下降因重复计算未被订阅的computed observables而形成的运算过载。

一个pure computed observable可以依据它是否拥有订阅者而自动地在两种状态下切换:
1.当不存在订阅者的时候,pure computed observable会进入休眠状态,此时的它,会关闭全部依赖于它的订阅关系,同时也不会再追踪它所关联的observables。一旦处于休眠状态的computed observable的值被读取的话,它就须要从新计算以便以确保值得正确性。
2.当它拥有订阅者的时候,pure computed observable会进入监听状态。一旦进入监听状态,它会当即调用它的function和订阅程序来追踪它所关联的observables。在这种状态下,pure computed observables和普通的computed observables无异。更为详细的内容需参考How dependency tracking works部分。

按照文档的说明,选择pure computed observables有两条原则。一是computed observable在运算的时候不能产生反作用(不能对其余的observables产生影响);二是computed observable的值应该仅仅依赖于它所关联的observables的值,而不是其余隐含的信息。

Pure computed observables有两种定义方式:

this.fullName = ko.pureComputed(function() {
    return this.firstName() + " " + this.lastName();
}, this);

或者

this.fullName = ko.computed(function() {
    return this.firstName() + " " + this.lastName();
}, this, { pure: true });

4.1 参考:

官网,Pure computed observables

Computed Observable 参考

下面的文档描述了如何构造和使用 Computed Observable。

构建一个 Computed Observable

  1. ko.computed( evaluator [, targetObject, options] ) — 此表单支持建立 computed observable 的最多见状况。
    • evaluator — 用于computed observable 值的当前值的函数
    • targetObject — 若是给定,则在KO调用回调函数时定义this的值。有关更多信息,请参见管理this功能的部分。
    • options — An object with further properties for the computed observable. See the full list below.
  2. ko.computed( options ) — This single parameter form for creating a computed observable accepts a JavaScript object with any of the following properties.
相关文章
相关标签/搜索