JS組件系列——KnockoutJS用法


前言:出於某種原因,需要學習下Knockout.js,這個組件很早前聽說過,但一直沒嘗試使用,這兩天學習了下,覺得它真心不錯,雙向綁定的機制簡直太爽了。今天打算結合bootstrapTable和Knockout去實現一個簡單的增刪改查,來體驗一把神奇的MVVM。關於WebApi的剩余部分,博主一定抽時間補上。

一、Knockout.js簡介

1、Knockout.js和MVVM

如今,各種前端框架應接不暇,令人眼花繚亂,有時不得不感嘆作為程序猿也真是苦逼,總有學不完的技術,何時是盡頭,除非你轉化!苦海無邊,回頭是不是岸,由你決定!

Knockout.js是一個基於MVVM模式的輕量級的前端框架,有多輕?根據官網上面顯示的最新版本v3.4.0,僅22kb。能夠友好地處理數據模型和界面DOM的綁定,最重要的是,它的綁定是雙向的,也就是說數據模型變化了,界面DOM上的數據也會跟着發生變化,反過來,界面DOM上的數據變化了,數據模型也會相應這個變化。這樣能夠大大減少我們的前端代碼量,並且使得我們界面易於維護,再也不用寫一大堆事件監控數據模型和界面DOM的變化了。下面博主會根據一個使用實例來說明這兩點。

Knockout.js官網:http://knockoutjs.com

Knockout.js開源地址:https://github.com/knockout/knockout

MVVM模式:這是一種創建用戶界面的設計模式,MVVM把它拆分成三塊就是Model、View、ViewModel,Model就是數據模型,View就是我們的視圖,ViewModel就是一個視圖模型,用來綁定數據模型和視圖上面的dom元素。如果你使用過WPF和Silverlight,理解這個應該不是啥問題;沒有使用過也什么關系,看完此文,你會有一個大致的認識。

2、最簡單的實例

 一般來說,如果你從零開始使用Knockout.js,你至少需要做以下四部

2.1、去官網下載knockout.js文件,然后引用到view頁面里面。

<script src="~/scripts/knockout/knockout-3.4.0.min.js"></script>

注意:knockout.js並不需要jquery的支持,如果你的項目需要用到jquery的相關操作,則引用jquery;否則只引用以上文件即可。

2.2、定義ViewModel

viewmodel是什么?其實,在js里面,它看上去就像一個json對象。我們定義一個viewmodel:

        var myViewModel = {
            Name: "Lilei",
            profession: "軟件工程師",
        };

2.3、view視圖里面定義綁定data-bind的標簽

    <div> 
        姓名:<label data-bind="text:Name"></label><br />
        職業:<input type="text" data-bind="textinput:Profession" />
    </div>

注意:對應input標簽的文本,需要使用textinput,而普通標簽的文本使用text即可。

2.4、激活綁定

做了以上三步,還需要激活knockout的綁定

ko.applyBindings(myViewModel);

做到這四部,基本實現了一個最簡單的viewmodel的數據綁定。得到效果:

如果你夠細心,會發現ko.applyBindings()方法有兩個參數,第一個就是我們需要綁定的viewmodel,第二個又是什么呢?由 ko.applyBindings(myViewModel); 可知,第二個參數是一個可選參數,它表示viewmodel綁定的標簽的作用域,比如,我們將以上代碼改一下:

    <div> 
        姓名:<label id="lb_name" data-bind="text:Name"></label><br />
        職業:<input type="text" data-bind="textinput:Profession" />
    </div>
ko.applyBindings(myViewModel,document.getElementById("lb_name"));

得到結果:

由此可知:第二個參數限定了myViewModel的作用范圍,也就是說,只有在id="lb_name"的標簽上面綁定才會生效,如果第二個參數是div等容器標簽,它表示該綁定的范圍為該div下面的所有子標簽。

3、監控屬性

截止到上面的四步,我們看不到任何效果,看到的無非就是將一個json對象的的數據綁定到了html標簽上面,這樣做有什么意義呢?不是把簡單的問題復雜化嗎?別急,馬上見證奇跡!上文說了,knockout最重要的意義在於雙向綁定,那么如何實現我們的雙向綁定呢?答案就是監控屬性。

在knockout里面,核心的有三個監控屬性:Observables,DependentObservables,ObservableArray,Observe的意思翻譯過來是觀察、觀測的意思,如果說成觀察屬性或者觀測屬性感覺不太恰當,我們暫且叫監控屬性。

3.1、Observables:監控屬性

我們將上面的例子改成這樣:

復制代碼
<head>
    <meta name="viewport" content="width=device-width" />
    <title>Index3</title>
    <script src="~/scripts/jquery-1.9.1.min.js"></script>
    <script src="~/scripts/knockout/knockout-3.4.0.min.js"></script>
</head>
<body>
    <div> 
        姓名:<label data-bind="text:Name"></label><br />
        職業:<input type="text" data-bind="textinput:Profession" />
    </div>
    <div>
        <input type="text" id="txt_testobservable" />
    </div>
    <script type="text/javascript">
        //1.定義ViewModel
        var myViewModel = {
            Name: ko.observable("Lilei"),
            Profession: "軟件工程師",
        };

        //2.激活綁定
        ko.applyBindings(myViewModel);

        $(function () {
            //注冊文本框的textchange事件
            $("#txt_testobservable").on("input", function () {
                myViewModel.Name($(this).val());
            });
        });
    </script>
</body>
復制代碼

 ko.observable("Lilei") 這一句的意義是將viewmodel的Name屬性添加成為監控屬性,一定Name屬性變成監控屬性,神奇的事情就發生了,我們來看看當我們寫myViewModel.的時候:

Name由原來的屬性變成方法了,也就是說一旦添加了ko.observable(),那么對應的屬性就會變成方法,那么對於Name的取值和賦值都需要使用myViewModel.Name()來處理。我們先來看看效果:

代碼釋疑:很顯然  myViewModel.Name($(this).val()); 這一句將當前文本框的值賦給了Name屬性,由於界面綁定了Name屬性,所以label里面的值也隨之發生了變化。或者你會說,這個使用textchange事件也可以做到的,只要將當前文本框的值賦給label標簽,也可以達到這個效果,這個不算什么。確實,你的寫法也可以達到目的,但是我們的監控屬性的意義在於,任何地方改變了Name的值,界面都會隨之變化,而不用每個地方去給label標簽賦值,JS里面只需要把關注點方法myViewModel.Name()上面即可。是不是很厲害~~

3.2、DependentObservables:監控依賴屬性

如果看了上面的監控屬性還沒過癮?下面再來看看監控依賴屬性的使用。

我們將代碼再改下看看:

復制代碼
<head>
    <meta name="viewport" content="width=device-width" />
    <title>Index3</title>
    <script src="~/scripts/jquery-1.9.1.min.js"></script>
    <script src="~/scripts/knockout/knockout-3.4.0.min.js"></script>
</head>
<body>
    <div> 
        姓名:<input type="text" data-bind="textinput:Name" /><br />
        職業:<input type="text" data-bind="textinput:Profession" /><br />
        描述:<label data-bind="text:Des"></label>
    </div>
    <script type="text/javascript">
        //1.定義ViewModel
        var myViewModel = {
            Name: ko.observable("Lilei"),
            Profession: ko.observable("軟件工程師"),
        };
        myViewModel.Des = ko.dependentObservable(function () {
            return "本人姓名——" + myViewModel.Name() + ",職業——" + myViewModel.Profession();
        });

        //2.激活綁定
        ko.applyBindings(myViewModel);</script>
</body>
復制代碼

先來看看效果:

代碼釋疑:通過添加監控依賴屬性  ko.dependentObservable() 將Des屬性的值能同時監控到Name和Profession兩個的變化,其中任何一個發生變化,Des綁定的標簽都會觸發改變,這樣做的最大好處就是避免了我們js去操作dom的麻煩,有點意思吧。

3.3、ObservableArray;監控數組

除了上面兩種,ko還支持對數組對象的監控。我們來看一個例子:

復制代碼
<head>
    <meta name="viewport" content="width=device-width" />
    <title>Index3</title>
    <script src="~/scripts/jquery-1.9.1.min.js"></script>
    <script src="~/scripts/knockout/knockout-3.4.0.min.js"></script>
</head>
<body>
    <div><select data-bind="options:deptArr,
                           optionsText:'Name'"></select>

    </div>
    <div>
        <input type="text" id="txt_testobservable" /><input type="button" id="btn_test" value="新增部門" />
    </div>
    <script type="text/javascript">
            var deptArr = ko.observableArray([
                { id: 1, Name: '研發部' },
                { id: 2, Name: '行政部' },
                { id: 3, Name: '人事部' }
            ]);
            var viewModel = {
                deptArr: deptArr,
            };

            ko.applyBindings(viewModel);

            var i=4;
            $(function () {
                $("#btn_test").on("click", function () {
                    deptArr.push({ id: i++, Name: $("#txt_testobservable").val() });
                });
            });
    </script>
</body>
復制代碼

看看效果:

代碼釋疑:以上通過ko.observableArray()這個方法添加了對數組對象的監控,也就是說,js里面任何地方只要對deptArr數組對象做了數組的改變,都會觸發UI給出相應。需要注意的一點是,監控數組實際上是監控的數組對象本身,對於數組對象里面的子對象屬性發生變化,是無法監控到的。比如我們將點擊事件改成這樣:

            $(function () {
                $("#btn_test").on("click", function () {
                    deptArr[1].Name = "aaa";
                });
            });

效果:

由此說明數組監控實際上監控的是數組對象本身,對於數組里面元素的屬性變化不會監控。如果確實需要對數據里面對象的屬性變化進行監控,需要再對數據里面對象屬性使用ko.observable(),兩者聯合使用。有興趣的可以試試。

4、ko里面常見的data-bind屬性

上文中,我們使用了多個data-bind屬性,那么在knockout里面,到底有多少個這種data-bind的屬性呢。這里我們列出一些常用的屬性。

4.1、text和inputText

text,顧名思義就是文本的意思,這個綁定屬性一般用於<label>、<span>、<div>等標簽顯示文本,當然,如果你願意,任何標簽都可以使用這個綁定。它是使用基本上沒什么好說的。如果沒有使用ko.observable(),則是靜態綁定,否則是動態綁定。

inputText,input標簽的文本,相當於input標簽的value屬性。

  <div> 
        姓名:<label data-bind="text:Name"></label><br />
        職業:<input type="text" data-bind="textinput:Profession" />
    </div>
復制代碼
     //1.定義ViewModel
        var myViewModel = {
            Name: ko.observable("Lilei"),
            Profession: "軟件工程師",
        };

        //2.激活綁定
        ko.applyBindings(myViewModel);
復制代碼

4.2、value

這個綁定屬性一般用於input標簽,和上面的inputText基本相似。只不過value更加規范。

和value一起使用的還有一個參數valueUpdate,它表示界面做一個什么操作的時候更新該value。valueUpdate主要取值有change/keyup/keypress/afterkeydown等。分別表示文本變化、鍵盤縮起、鍵盤按下、鍵盤按下之后等操作時候更新value對應的viewmodel的值。

姓名:<input type="text" data-bind="value:Name,valueUpdate:'keyup'" /><br />
    var myViewModel = {
            Name: ko.observable("Lilei"),
        };//2.激活綁定
        ko.applyBindings(myViewModel);

上述代碼表示鍵盤收起的時候更新文本框的value屬性和myViewModel的Name屬性。

4.3、checked

checked綁定一般用於checkbox、radio等可以選中的表單元素,它對應的值是bool類型。和value的用法基本相似,就不做重復說明。

4.4、enable

enable綁定一般用於是否啟用標簽元素,一般用於表單元素的啟用和禁用。和disabled相反,對應的值也是bool類型。

復制代碼
   <div>
        <input type="text" data-bind="enable:IsMen"/>
    </div>
    <script type="text/javascript">
        //1.定義ViewModel
        var myViewModel = {
            Name: ko.observable("Lilei"),
            Profession: ko.observable("軟件工程師"),
            Age: ko.observable(40),
            IsMen:ko.observable(true)
        };

        //2.激活綁定
        ko.applyBindings(myViewModel);
        myViewModel.IsMen(false);

    </script>
復制代碼

由於IsMen屬性變成了false,所有對應的文本框會顯示禁用狀態。

4.5、disabled

和enable相反,用法和enable類似。

4.6、options

上文中在使用select的綁定時候使用過options,它表示select標簽的option的集合,對應的值為一個數組,表示這個下拉框的數據源。可以使用observableArray啟用對這個數據源的監控。用法見上面。

4.7、html

text綁定實際上是對標簽innerText的設置和取值,那么同理,html綁定也是對innerHTML的設置和取值。它對應的值為一段html標簽。

4.8、css

css綁定是添加或刪除一個或多個樣式(class)到DOM元素上。使用格式:

    <style type="text/css">
        .testbold {
        background-color:powderblue;
        }
    </style>
<div data-bind="css:{testbold:myViewModel.Name()=='Lilei'}">aaaa</div>
        var myViewModel = {
            Name: ko.observable("Lilei"),
            Profession: ko.observable("軟件工程師"),
            Age:ko.observable(40)
        };

該div會顯示背景色。

如果需要增加或移除多個樣式,只要稍微改下即可,比如:

<div data-bind="css:{testbold:myViewModel.Name()=='Lilei',testborder:myViewModel.Profession()=='PHP工程師'}">aaaa</div>

4.9、style

如果css綁定的作用是向標簽動態添加或移除class樣式,那么style綁定的作用就是想標簽動態添加或移除某一個樣式。比如:

<div data-bind="css:{ padding: 0px; color: rgb(0, 0, 255); line-height: 1.5 !important;">>aaaa</div>

如果是添加或者移除多個,同css綁定的用法

4.10、attr

attr綁定主要用於向標簽添加移除某一個或多個屬性(包括自定義屬性),永和和css類似。

4.11、click

click綁定表示在對應的DOM元素上面添加點擊事件的執行方法。可以在任意元素上面使用。

    <div>
        <input type="button" value="測試click綁定" data-bind="click:ClickFunc" />
    </div>
復制代碼
     var myViewModel = {
            ClickFunc:function(){
                alert($(event.currentTarget).val());
            }
        };

        ko.applyBindings(myViewModel);
復制代碼

event.currentTarget表示當前點擊的DOM元素。有時為了簡便,我們直接使用匿名函數的方式綁定,比如:

    <div>
        <input type="button" value="測試click綁定" data-bind="click:function(){
                alert('點擊了');
            }" />
    </div>

但是這種將js揉到html里面的寫法讓博主難以接受,並且覺得維護起來相對不方便,尤其是點擊事件里面的邏輯略復雜時。所以,如非必須,不建議直接寫這種匿名函數的方式。

4.12、其他

關於data-bind的所有綁定,可以看官網上面的介紹,這里就不一一列舉了。需要用的時候去官網上查下就好了。看看官網上面列舉的所有綁定:

5、Json對象和監控屬性的轉化及關系

我們知道,為了避免不同語言直接的展現方式,一般情況下我們前端和后端交互的時候統一使用Json格式的數據,我們通過http請求從后端取到的數據模型,而要使用我們的ko的一些特性,必須要將這些普通的數據模型轉換成ko的監控屬性;反過來,我們使用ko的監控屬性,有時又需要把這些屬性轉換為普通的json數據傳到后台,那么如何實現這個轉換呢?

5.1、JSON對象轉換成ViewModel

比如我們從后台取到一個Json對象,然后把它變成到我們的viewmodel,然后綁定到我們的界面DOM。

復制代碼
            $.ajax({
                url: "/Home/GetData",
                type: "get",
                data: {},
                success: function (data, status) {
                    var oJson = data;
                }
            });
復制代碼

我們發送一個請求到后端,取到一個json對象,賦值到oJson,然后我們把oJson轉換成viewmodel,最直觀的方式就是手動轉換了。比如我們可以這樣:

    var myViewModelJson = {
            DeptName: ko.observable(),
            DeptLevel: ko.observable(),
            DeptDesc:ko.observable()
        };
        ko.applyBindings(myViewModelJson);

然后在ajax請求的success里面

                success: function (data, status) {
                    var oJson = data;
                    myViewModelJson.DeptName(oJson.DeptName);
                    myViewModelJson.DeptLevel(oJson.DetpLevel);
                    myViewModelJson.DeptDesc(oJson.DeptDesc);
                }

這樣,通過手動綁定,實現了json對象到viewmodel的綁定。這樣做的好處就是靈活,壞處顯而易見,手工代碼量太大。

還好,有我們萬能的開源,總有人想到更好的辦法,我們使用knockout.Mapping組件就能很好地幫助我們界面json對象到viewmodel的轉換。

knockout.Mapping開源地址:下載

下面來簡單看看它如何使用,還是上面的例子,我們不用實現定義任何viewmodel,首先需要引用knockout.mapping.js

    <script src="~/scripts/knockout/knockout-3.4.0.min.js"></script>
    <script src="~/scripts/knockout/extensions/knockout.mapping-latest.js"></script>

注意:這里knock.mapping-lastest.js必須要放在knockout-3.4.0.min.js的后面,否則調用不到ko.mapping。

然后直接在success函數里面這樣使用

         success: function (data, status) {
                    var myViewModelJson2 = ko.mapping.fromJS(data);
                    ko.applyBindings(myViewModelJson2);
                }

我們來看效果:

代碼釋疑:通過ajax請求從后台取到的json對象,通過ko.mapping.fromJS(),很方便地將其轉換成了viewmodel,是不是猴犀利!當然除了這種用法,還可以更新已經存在viewmodel,使用如下:

復制代碼
    var myViewModelJson = {
            DeptName: ko.observable(),
            DeptLevel: ko.observable(),
            DeptDesc:ko.observable()
        };
        ko.applyBindings(myViewModelJson);
        $(function () {
            $.ajax({
                url: "/Home/GetData",
                type: "get",
                data: {},
                success: function (data, status) {
                    ko.mapping.fromJS(data, myViewModelJson)
                }
            });
        });
復制代碼

在success里面,根據data的值去更新myViewModelJson這個viewmodel。

5.2、ViewModel轉換成JSON對象

 上面說了JSON對象轉化為viewmodel,那么反過來,如果我們需要將viewmodel轉換為Json對象傳遞到后端,怎么辦呢?

knockout里面提供了兩個方法:

  • ko.toJS():將viewmodel轉換為JSON對象
  • ko.toJSON():將viewmodel轉換為序列化過的Json string。

比如我們的代碼如下:

復制代碼
     $(function () {
            var oJson1 = ko.toJS(myViewModelJson);
            var oJson2 = ko.toJSON(myViewModelJson);
        });
        var myViewModelJson = {
            DeptName: ko.observable("研發部"),
            DeptLevel: ko.observable("2"),
            DeptDesc: ko.observable("開發一伙人")
        };
        ko.applyBindings(myViewModelJson);
復制代碼

那么我們來監控下oJson1和oJson2的值:

代碼釋疑:通過上面這張圖,很容易理解兩個方法的區別,這里需要說明一點的是,這兩個方法是ko自帶的,並不需要mapping組件的支持。

6、創建自己的data-bind屬性

上面講了那么多,都是介紹knockout里面的一些綁定和監控,那么,有些時候,我們需要自定義我們的data-bind,型如: <label data-bind="myBind:Name"></label> ,這種需求再做組件封裝的時候尤其有用,是否可以實現呢?當然可以。

在knockout里面,提供了ko.bindingHandlers屬性,用於自定義data-bind屬性,它的語法如下:

復制代碼
ko.bindingHandlers.MySelect = {
    init: function (element, valueAccessor, allBindingsAccessor, viewModel) {

    },

    update: function (element, valueAccessor, allBindingsAccessor, viewModel) {

    }
};
復制代碼

就這樣申明一下,然后在我們的html標簽里面就可以使用自定義data-bind了。

復制代碼
    <div> 
        <select data-bind="MySelect:$root">
            <option id="1">研發部</option>
            <option id="2">人事部</option>
            <option id="3">行政部</option>
        </select>
    </div>
復制代碼

MySelect就是我們自定義的綁定屬性,$root暫且可以理解為初始化(雖然這樣解釋並不嚴謹,如果有更加合理的解釋歡迎指正)。

代碼釋疑:通過上面的ko.bindingHandlers就能簡單實現自定綁定屬性,需要說明兩點:

  • init,顧名思義初始化自定義綁定,它里面包含多個參數,一般使用較多的是前兩個參數,第一個參數表示初始化自定義綁定的DOM元素,第二個參數一般用來傳遞初始化的參數。
  • update,更新回調,當對應的監控屬性變化時,會進入到這個方法。如果不需要回調,此方法可以不聲明。

 在此博主就結合原來分享過的一個下拉框組件MutiSelect來簡單說明下自定義綁定的使用。

6.1、最簡單的MutiSelect

一般情況下,如果我們需要使用ko去封裝一些通用組件,就需要用到我們的ko.bindingHandlers,下面博主就結合MutiSelect組件來說說如何使用。

首先聲明自定義的ko.bindingHandlers,在init方法里面初始化我們的select標簽

復制代碼
ko.bindingHandlers.MySelect = {
    init: function (element, valueAccessor, allBindingsAccessor, viewModel) {
        $(element).multiselect();
    },

    update: function (element, valueAccessor, allBindingsAccessor, viewModel) {

    }
};
復制代碼

然后在頁面標簽里面使用

復制代碼
    <div style="text-align:center;"> 
        <select data-bind="MySelect:$root">
            <option id="1">研發部</option>
            <option id="2">人事部</option>
            <option id="3">行政部</option>
        </select>
    </div>
復制代碼

最后第三部,激活綁定

$(function () {
    var MultiSelect = {};
    ko.applyBindings(MultiSelect);
});

如果不需要傳遞參數,這里只需要綁定一個空的viewmodel即可。有人疑惑了,第三部感覺沒啥實際意義呢。博主的理解是,DOM元素需要使用data-bind去綁定數據,必須要啟用ko的綁定,也就是這里的ko.applyBindings()。

得到效果:

6.2、參數傳遞

 第一步還是自定義ko.bindingHandlers

復制代碼
ko.bindingHandlers.MySelect = {
    init: function (element, valueAccessor, allBindingsAccessor, viewModel) {
        var oParam = valueAccessor();
        $(element).multiselect(oParam);
    },

    update: function (element, valueAccessor, allBindingsAccessor, viewModel) {

    }
};
復制代碼

第二步和上面相同,在html標簽里面使用這個自定義綁定。

第三步,在激活綁定的時候傳入參數

復制代碼
$(function () {
    var MultiSelect = {
        enableClickableOptGroups: true,//收起分組
        onChange: function (option, checked) {
            alert("選擇改變");
        }
    };
    ko.applyBindings(MultiSelect);
});
復制代碼

通過這三步即可將參數傳到我們的MutiSelect的初始化里面:

代碼釋疑:init事件的第二個參數,我們說了,它主要作用是獲取我們viewmodel里面傳過來的參數,只不過這里要把它當做方法使用,為什么會這么用,還有待研究!

二、第一個增刪改查實例

至此基礎的東西終於是鋪墊完了,本來打算一篇搞定的,可以沒料到基礎的東西展開來這么多篇幅!增刪改查的示例放到下篇吧。歡迎學習交流,當然也歡迎推薦

PS:下篇已經完成,有需要的可以看看:JS組件系列——BootstrapTable+KnockoutJS實現增刪改查解決方案(二)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM