了解KnockOut.js

Knockout是微软出品,是MVVM模型领域内的先驱,使用函数偷龙转凤,最短编辑长度算法实现DOM的同步,兼容IE6。

Knockout是一个JS的MVVM模式的实现,Knockout是建立在3个核心功能之上:

  • 属性监控与依赖追踪
  • 声明式绑定
  • 模板机制

MVVM

MVVM是微软WPF结合发展演变的一种新型的架构框架。

 

了解KnockOut.js_第1张图片

 

MVVM设计模式的优点

  • 低耦合
    Model的修改不会影响视图的展现,ViewModel可绑定不同的View,当View变化时Model可以不变,当Model变化时View也可以不变。
  • 开发独立性
    前端更加专注于界面,后端更加专注于逻辑与数据。
  • 重用性
    ViewModel可对应多个视图,如PC或手机。
  • 测试方便

了解KnockOut.js_第2张图片

 

MVVM(Model-View-View Model)是创建用户界面的设计模式,它将UI界面分为3部分:

  • Model
    Model用于存储应用程序的数据,其数据表示业务领域的对象和数据操作,它独立于任何界面。使用KO时通常是使用AJAX向服务端请求数据赖读写数据模型Model。

  • ViewModel
    纯粹用于描述数据内容和页面操作的数据模型。ViewModel不是UI本身,不具有任何HTML和样式的概念。ViewModel不是持久化的数据模型,它仅仅是用户当前使用的未保存的数据。使用KO时,ViewModel是纯粹的不包含HTML的JS对象,保持ViewModel抽象在使用时可保持简单,因此可更简单的操作管理更简单的行为。

  • View
    View表示ViewModel状态的一个可见、互动的UI界面,主要用于显示ViewModel的数据信息、发送用户命令以及在ViewModel发生变化时保持自动更新。

使用KO时,View层主要是HTML文档式声明式的绑定到ViewModel,将其关联起来。另外可利用模板从ViewModel中获取对象动态生成的HTML。

知识点

了解KnockOut.js_第3张图片

 

操作流程

  • 创建数据模型
  • 添加绑定属性
  • 设置监控属性
  • 激活KO

下载地址:http://knockoutjs.com/downloads/index.html

对于调试使用,推荐使用完整的未压缩版本(knockout-x.x.debug.js),压缩版和未压缩版功能相同,但是未压缩版本具有全变量名和注释,也没有隐藏内部的API,使得源代码更具可读性。

在你的HTML页面中通过

使用KO创建ViewModel仅需声明一个JS对象

var vm= {username:“username”, nickname:"nickname"};

创建视图声明式绑定到ViewModel对象上

 


data-bind属性并不是HTML固有对象,它遵守HTML5尽管验证器指出它是一个无法验证的属性,但HTML4使用不会造成任何问题。只是浏览器并不是它是什么意思,所以需要激活Knockout使其生效。

 

ko.applyBinding(vm, document.getElementById('app'));

激活KO的applyBinding的参数

  • 激活KO时用于声明式绑定的ViewModel对象
  • 设置使用data-binding属性的HTML元素或容器,可选。

监控

创建数据模型后,例如绑定来实现它的属性。KO最重要的属性时当数据模型发生变化时,能够自动更新UI界面。KO是如何知道数据模型已经发生改变呢?此时,需将model属性声明成observable的,因此它是非常特殊的JS对象,能够通知用户它的改变以及自动检测依赖关系。

 

var vm = {
  username:ko.observable("default value"),
  nickname:ko.observable("default value")
};


读取和写入监控

由于并不是所有浏览器都只是JS的getterssetters,为了兼容,ko.observable()监控的对象都是方法。

  1. 读取当前监控的属性值,调用observable()方法。

 

vm.username()
  1. 写入新值到监控属性上,调用observable()方法,将需要写入的值作为参数化传入即可。

 

vo.username("new value");
  1. 一次性改变model对象监控的对个属性值,可使用链式方法。

 

vm.username("new value").nickname("new value")

监控属性最重要的一点是可以随时监控,也就是说,其他代码可以告诉它那些是需要通知发生变化的。这就是为什么KO会有如此多的内置绑定语法。

 



var vm = {username:"defualt value"};
vm.username("new value"); //text会自动更新i新值到对应的DOM对象元素上

监控属性的显示订阅

如果想要注册自己的订阅来通知监控属性的变化可使用 subscribe() 方法

 

vm.username.subscribe(function(newval){
  
});

subscribe() 方法在KO内部使用较多,也可以终止自己的订阅,首先是要创建订阅然后调用 dispose() 方法即可。

 

var subscribe = vm.username.subscribe(function(newval){

});
subcribe.dispose();//终止订阅

监控属性数组

使用监控属性observable可发现并响应一个对象的改变,observableArray可针对一个集合的变化进行监控。监控属性数组在显示或编辑多个值以及界面部分显示隐藏(如增减条目)操作中使用频繁。

 

// 实例化监控数组
var arr = ko.observableArray();
// 向监控数组中添加记录
arr.push("value");

//实例化并批量添加监控数组元素
var  arr = ko.observableArray([
  {username:"alice", usertype:"manager"},
  {username:"alibaba", usertype:"operator"}
]);

// 获取监控数组长度
console.log(arr.length, arr()[0]);

监控数组常用方法

 

// 新增元素
push(value)
//删除元素并返回其值
pop()
//数组头部添加
unshihft(value)
//删除头部元素
shift()
//数组反转
reverse()
//数组排序
sort(callback)
//数组截取
splice(start_position, element_count)
//移除元素,匹配元素并删除后返回新数组
remove(element)
remove(callback)
//移除所有
removeAll()
removeAll(match_condition)

数组双向绑定

observableArray跟踪的是数组中的对象,而不是对象的状态。也就是说observableArray仅仅跟踪它所拥有的对象,在对象被添加或删除时,会通知listeners

要监控对象的属性变更,需要编写代码,因为observableArray()返回的是一个数组,可使用原生JS的数组方法对其操作。

 





    
    
    App
    
    



    
错误提示

监控

 

var arylist = ["Manager", "Partner", "Agent"];
var objary = [
    {key:"管理员", value:"MANAGER"},
    {key:"系统管理员", value:"SA"}, 
    {key:"渠道商", value:"PARTNER"}, 
    {key:"代理商", value:"AGENT"}
];
var ViewModel = function() {
    var self = this;
    self.datalist = ko.observableArray(arylist);
    self.objlist = ko.observableArray(objary);
    self.selectedItem = ko.observable();
};
ko.applyBindings(new ViewModel(), document.getElementById("id"));

绑定

 

data-bind="options:datalist, selectedOptions:selectedItem"

data-bind="options:objlist, optionsText:'key', optionsValue:'value', selectedOptions:selectedItem"

了解KnockOut.js_第4张图片

 

计算属性

计算属性computed()依赖于一个或多个监控属性observable,如果任何依赖对象发生改变,他们就会跟着改变。

 

function ViewModel()
{
  this.username = ko.observable("defaule value");
  this.usertype = ko.observable("default value");
  this.account = ko.computed(function(){
    return this.username() +"("+ this.usertype()+")";
  },this);
}


当依赖属性时定义了this的值,没有传递它的进入,注意在默认情况下this是函数本身并不是任何对象的一部分。当需要全程跟踪this时,一种简化的流行惯例是:如果将ViewModel结构this作为一个变量复制一份(传统称为self),其后可使用self来表示ViewModel而不必担心它重定义或指代其他的东西。

 

function ViewModel()
{
  var self = this;
  self.username = ko.observable("default value");
  self.usertype = ko.observable("default value");
  self.account = ko.computed(function(){
    return self.username() + "("+self.usertype+")";
  });
}

由于self在闭合的方法内部也是可以捕获到的,所以在任何嵌套函数中,仍可用并保持一致性。

由于计算属性时通过计算其他监控属性而得到的结果,从这个意义上说,计算属性通常情况下是只读的。如何将计算属性变得可写,只要提供回调函数来实现值的写入即可。当然,也可将可写的计算作为普通的监控属性使用,通过自定义的逻辑来实现它的读写。

分解用户输入

了解KnockOut.js_第5张图片

 

 




 

function ViewModel(){
    this.key = ko.observable();
    this.val = ko.observable();

    this.cfg = ko.computed({
        read:function(){
            return this.key() + "=" + this.val();
        },
        write:function(value){
            var value = value.trim(" ");
            var pos = value.lastIndexOf("=");
            if(pos>0){
                var key = value.substring(0,pos);
                this.key(key);

                var val = value.substring(pos+1);
                this.val(val);
            }
        },
        owner:this
    });
}
ko.applyBindings(new ViewModel(), document.getElementById('app'));

值转换

 



 

function ViewModel(){
    this.price = ko.observable(1.234);
    this.format = ko.computed({
        read:function(){
            return "$" + parseFloat(this.price()).toFixed(2);
        },
        write:function(value){
            value = value.replace(/[^\.\d]/g, "");
            value = parseFloat(value);
            value = isNaN(value) ? 0 : value;
            this.price(value);
        },
        owner:this
    });
}
ko.applyBindings(new ViewModel(), document.getElementById('app'));

了解KnockOut.js_第6张图片

 

依赖跟踪

跟踪算法流程

  1. 当声明一个依赖属性时,KO会立即调用求值算法获得其初始值。
  2. 当计算函数运行时,KO会把监控属性通过计算得到的结果记录到Log中。
  3. 当计算结束时,KO会订阅能够访问的监控属性或依赖属性,订阅的回调函数时重新运行计算函数,循环整个过程,回到步骤1,并注销不再使用的订阅。
  4. KO通知所有订阅者,依赖属性已经被设置了新值。

KO并不仅仅是在第一次执行计算函数时,检测依赖项。而是每次都会检测,这意味着,依赖可以是动态的。
声明绑定是依赖属性的简单实现,当一个绑定为读取监控属性的值,这个绑定会变为监控属性的依赖。当监控属性发生改变的时候,会引起绑定被重新计算。

数据绑定

数据绑定有三种形式:

  • 一次性绑定one-time
    一次性绑定为从ViewModel绑定到UI只进行一次绑定操作,程序不会继续追踪数据在两者中的变化。可用于报表数据,数据仅仅会加载一次。
  • 单向绑定one-way
    单向绑定即object-UI的绑定,只有当ViewModel中数据发生变化时,UI的数据才会随之发生变化,反之不然。
  • 双向绑定two-way
    双向绑定是无论数据在object还是UI中发生变化时,应用程序将会更新另一方。这是最灵活的绑定方式,同时代价也是最大的。数据绑定只是作为元素的自定义属性写入标签内,并不能决定它是何种绑定。

KO中如果值是通过ko.observable定义的则说明是双向绑定,否则为一次性绑定,在KO中是不存在单向绑定的。

绑定参数

显示隐藏绑定

visible绑定是通过绑定一个 值来确定DOM元素的显示和隐藏状态

 

visiable show area
var vm = { isShow:ko.observable(true) }; vm.isShow(false);

visible绑定会设置元素的element.style.display,当visible为false时为隐藏状态即display为none的状态,它的优先级要高于CSS中定义的隐藏样式。

可使用函数或表达式来控制元素的显示和隐藏

 

visible show area
var vm = { datalist:ko.observableArray([]); }; datalist.push(element);

文本绑定

text绑定主要是让DOM元素显示参数值,通常绑定在这样的元素上非常有用,实际上可绑定在任何DOM元素上。

 



var vm = {
  message:ko.observable();
};
vm.message("hello");

KO将参数值设置为DOM元素内容,DOM元素之前的内容将会被覆盖掉。

  • 如果参数是observable值,text绑定将会在值变化时更新内容。
  • 如果参数不是observable值,text绑定仅仅会设置DOM元素内容一次,以后不会再更新。
  • 如果传入的不是一个值或一个字符串,那么显示的文本将是toString()处理后的结果。
  • 可使用函数或表达式来决定text值,如果想要text值可编程化,可创建计算属性,在运算函数中编码决定text中显示内容。

 



var vm = {};
vm.price:ko.observable(10.11);
vm.rating = ko.computed(function(){
  return this.price() > 100 ? "expensive" : "affordable";
}, vm);

循环绑定

 




    
    
    
    app
    
    
    


    
错误提示
input
list

 

var datalist = [
    {id:1, username:"alice", usertype:"manager"},
    {id:2, username:"alibaba", usertype:"operator"},
    {id:3, username:"alex", usertype:"boss"},
];

function ViewModel()
{
    var self = this;

    self.username = ko.observable();
    self.usertype = ko.observable();
    self.datalist = ko.observableArray(datalist);

    self.add = function(){
        var item = {};
        item.id = self.datalist().length + 1;
        item.username = self.username();
        item.usertype = self.usertype();
        console.log(item);
        self.datalist.push(item)
    };
    self.delete = function(){
        self.datalist.remove(this);
    };
}
ko.applyBindings(new ViewModel(), document.getElementById("app"));

 

var datalist = [
    {id:1, username:"alice", usertype:"manager"},
    {id:2, username:"alibaba", usertype:"operator"},
    {id:3, username:"alex", usertype:"boss"},
];

var vm = {};
vm.username = ko.observable();
vm.usertype = ko.observable();
vm.datalist = ko.observableArray(datalist);
vm.add = function(){
    var item = {};
    item.id = vm.datalist.length + 1;
    item.username = vm.username();
    item.usertype = vm.usertype();
    vm.datalist.push(item)
};
vm.delete = function(){
    vm.datalist.remove(this);
};
ko.applyBindings(vm, document.getElementById("app"));

了解KnockOut.js_第7张图片

 

共享的ViewModel

了解KnockOut.js_第8张图片

 

你可能感兴趣的:(了解KnockOut.js)