KnockOut.js

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

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

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

MVVM

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


4933701-b9f7cc86e239fdd5.png
image.png

MVVM设计模式的优点

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

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。

知识点

4933701-07b855796eb25926.png
Knockout知识点

操作流程

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

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

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

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

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

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")
};

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

读取和写入监控

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

  1. 读取当前监控的属性值,调用observable()方法。
vm.username()
  1. 写入新值到监控属性上,调用observable()方法,将需要写入的值作为参数化传入即可。
vo.username("new value");
  1. 一次性改变model对象监控的对个属性值,可使用链式方法。
vm.username("new value").nickname("new value")

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

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

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的数组方法对其操作。

<!DOCTYPE html>
<html lang="zh-cmn-Hans">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width,initial-scale=1,user-scalable=0,viewport-fit=cover">
    <title>App</title>
    <link rel="stylesheet" href="./assets/css/wx.css" />
    <link rel="stylesheet" href="./assets/css/page.css" />
</head>

<body ontouchstart id="app">
    <div class="container" id="container">
        <div class="toptips toptips-warn js-tooltips">错误提示</div>
        <div class="page js-show">
            <div class="page-header page-clear">
                <img src="./assets/img/2.jpg" width="100%" />
            </div>
            <div class="page-body">
                <div class="cells-title">列表</div>
                <div class="cells">
                    <a class="cell cell-access" href="javascript:;">
                        <div class="cell-header">
                            <img src="https://img-blog.csdnimg.cn/2022010707242327722.png" alt="" style="width:20px;margin-right:5px;display:block">
                        </div>
                        <div class="cell-body">
                            <p>cell standard</p>
                        </div>
                        <div class="cell-footer">说明文字</div>
                    </a>
                    <a class="cell cell-access" href="javascript:;">
                        <div class="cell-header">
                            <img src="https://img-blog.csdnimg.cn/2022010707242327722.png" alt="" style="width:20px;margin-right:5px;display:block">
                        </div>
                        <div class="cell-body">
                            <p>cell standard</p>
                        </div>
                        <div class="cell-footer">说明文字</div>
                    </a>
                </div>
                <div class="cells cells-form">
                    <div class="cell cell-select">
                        <div class="cell-body">
                            <select class="select" name="role" data-bind="options:datalist, selectedOptions:selectedItem">
                            </select>

                        </div>
                        <div class="cell-footer cell-footer-text" data-bind="html:selectedItem">说明文字</div>
                    </div>
                    <div class="cell cell-select cell-select-after">
                        <div class="cell-header">
                            <label class="label">角色</label>
                        </div>
                        <div class="cell-body">
                            <select class="select" name="area" data-bind="options:objlist, optionsText:'key', optionsValue:'value', selectedOptions:selectedItem">
                            </select>
                        </div>
                    </div>
                    <div class="btn-area">
                        <a class="btn btn-primary" href="javascript:">确定</a>
                    </div>
                </div>
            </div>
            <div class="page-footer">
                <p class="footer-text">Copyright © 2018 JunChow</p>
            </div>
        </div>
    </div>
    <script src="https://cdn.bootcss.com/zepto/1.2.0/zepto.js"></script>
    <script src="https://cdn.bootcss.com/knockout/3.4.2/knockout-debug.js"></script>
    <script>
    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"));
    </script>
</body>

监控

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"
4933701-e0f0656332e4f646.png
数组的绑定

计算属性

计算属性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);
}

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

当依赖属性时定义了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在闭合的方法内部也是可以捕获到的,所以在任何嵌套函数中,仍可用并保持一致性。

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

分解用户输入

4933701-aad4308020a55de0.png
分解用户输入
<input data-bind = "value:key" />
<input data-bind = "value:val" />
<input data-bind = "value:cfg" />
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'));

值转换

<input type="text" class="input" placeholder="" data-bind="value:price"/>
<input type="text" class="input" placeholder="" data-bind="value:format"/>
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'));
4933701-b31490051e3d37a1.png
值转换

依赖跟踪

跟踪算法流程

  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元素的显示和隐藏状态

<div data-bind="visible:isShow">visiable show area</div>

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

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

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

<div data-bind="visible:datalist().length > 0">visible show area</div>

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

文本绑定

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

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

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

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

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

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

循环绑定

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">
    <title>app</title>
    <link rel="stylesheet" href="./assets/css/page.css">
    <link rel="stylesheet" href="./assets/css/ui.css">
    <script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.0/knockout-min.js"></script>
</head>
<body ontouchstart id="app">
    <div class="toptips toptips-warn">错误提示</div>
    <div class="container" id="container">
        <div class="page home js-show">
            <div class="page-header">
                <h1 class="page-title">page title</h1>
                <p class="page-desc">page description</p>
            </div>
            <div class="page-body">
                <div class="cells-title">input</div>
                <div class="cells cells-form">
                    <div class="cell">
                        <div class="cell-header">
                            <label for="" class="label">账号</label>
                        </div>
                        <div class="cell-body">
                            <input type="text" class="input" placeholder="" data-bind="value:username"/>
                        </div>
                    </div>
                    <div class="cell">
                        <div class="cell-header">
                            <label for="" class="label">类型</label>
                        </div>
                        <div class="cell-body">
                            <input type="text" class="input" placeholder="" data-bind="value:usertype"/>
                        </div>
                    </div>
                </div>
                <div class="btn-area">
                    <button class="btn btn-primary" data-bind="click:add">添加</button>
                </div>
                <div class="cells-title">list</div>
                <div class="cells" data-bind="foreach:datalist">
                    <a class="cell cell-access" href="javascript:;">
                        <div class="cell-header">
                            #<span data-bind="text:$index"></span>
                        </div>
                        <div class="cell-body">
                            <span data-bind="text:username"></span>
                            <span data-bind="text:usertype"></span>
                        </div>
                        <div class="cell-footer">
                            <span data-bind="click:$parent.delete">del</span>
                        </div>
                    </a>
                </div>
            </div>
            <div class="page-footer"></div>
        </div>
    </div>
    <script>
    </script>
</body>
</html>
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"));
4933701-950aafb2a6b22482.png
image.png

共享的ViewModel

4933701-9220ac0471d5bd4c.png
共享的ViewModel
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值