在线观看不卡亚洲电影_亚洲妓女99综合网_91青青青亚洲娱乐在线观看_日韩无码高清综合久久

鍍金池/ 教程/ HTML/ MVVM
中介者模式
MVVM
亨元模式
設(shè)計(jì)模式分類概覽表
ES Harmony
組合模式
CommonJS
jQuery 插件的設(shè)計(jì)模式
外觀模式
觀察者模式
建造者模式
構(gòu)造器模式
外觀模式
簡(jiǎn)介
AMD
原型模式
設(shè)計(jì)模式的分類
觀察者模式
命名空間模式
代理模式
編寫設(shè)計(jì)模式
適配器模式
反模式
什么是設(shè)計(jì)模式
模塊化模式
MVC
Mixin 模式
裝飾模式
設(shè)計(jì)模式的結(jié)構(gòu)
單例模式
迭代器模式
命令模式
工廠模式
MVP
暴露模塊模式
惰性初始模式

MVVM

MVVM(Model View ViewModel)是一種基于MVC和MVP的架構(gòu)模式,它試圖將用戶界面(UI)從業(yè)務(wù)邏輯和行為中更加清晰地分離出來。為了這個(gè)目的,很多例子使用聲明變量綁定來把View層的工作從其他層分離出來。

這促進(jìn)了UI和開發(fā)工作在同一代碼庫中的同步進(jìn)行。UI開發(fā)者用他們的文檔標(biāo)記(HTML)綁定到ViewModel,在這個(gè)地方Model和ViewModel由負(fù)責(zé)邏輯的開發(fā)人員維護(hù)。

歷史

MVVM(如其大名)最初是由微軟定義,用于Windows Presentation Foundation(WPF)和Silverlight,在John Grossman2005年的一篇關(guān)于Avalon(WPF的代號(hào))的博文中被官方推出。它也作為方便使用MVC的一種可選方案,為Adobe Flex社區(qū)積累了一些用戶量。

先于微軟采用的MVVM名稱,在社區(qū)中已經(jīng)有了一場(chǎng)由MVC像MVPM遷移的運(yùn)動(dòng):模型-視圖-展現(xiàn)模型。Marton Fowler在2004年為那些對(duì)此感興趣的人寫了一篇關(guān)于展現(xiàn)模型的文章。展現(xiàn)模型的理念的內(nèi)容要遠(yuǎn)遠(yuǎn)長(zhǎng)于這篇文章,然而這篇文章被認(rèn)為是這一理念的重大突破,并且極大的捧紅了它。

在微軟推出作為MVPM的可選方案的MVVM后,就出現(xiàn)了許多沸沸揚(yáng)揚(yáng)的“alt.net”圈子。其中許多聲稱這個(gè)公司在GUI世界的霸主地位給與了它們將社區(qū)統(tǒng)一為整體的機(jī)會(huì),出于市場(chǎng)營(yíng)銷的目的,按照它們所高興的方式對(duì)已有的概念重新命名。一個(gè)進(jìn)步的群體也承認(rèn)MVVM和MVPM其實(shí)實(shí)在是同樣的概念,只是展現(xiàn)出來的是不同的包而已。

在近幾年,MVVM已經(jīng)在Javascript中得到了實(shí)現(xiàn),其構(gòu)造框架的形式諸如KnockoutJS,Kendo MVVM和Knockback.js,獲得了整個(gè)社區(qū)的積極響應(yīng)。

現(xiàn)在就讓我們來看看組成了MVVM的這三個(gè)組件。

模型

和其它MV*家族成員一樣,MVVM中的模型代表我們的應(yīng)用用到的領(lǐng)域相關(guān)的數(shù)據(jù)或者信息。一個(gè)領(lǐng)域相關(guān)的數(shù)據(jù)的典型例子是用戶賬號(hào)(例如名字,頭像,電子郵件)或者音樂唱片(例如唱片名,年代,專輯)。

模型持有信息,但是通常沒有操作行為。它們不會(huì)格式化信息,也不會(huì)影響數(shù)據(jù)在瀏覽器中的表現(xiàn),因?yàn)檫@些不是模型的責(zé)任。相反,數(shù)據(jù)格式化是由視圖層處理的,盡管這種行為被認(rèn)為是業(yè)務(wù)邏輯,這個(gè)邏輯應(yīng)該被另外一個(gè)層封裝,這個(gè)層和模型交互,這個(gè)曾就是視圖模型。

這個(gè)規(guī)則唯一的例外是驗(yàn)證,由模型進(jìn)行數(shù)據(jù)驗(yàn)證是被認(rèn)為可以接受的,這些數(shù)據(jù)用于定義或者更新現(xiàn)存的模型(例如輸入的電子郵件地址是否滿足特定的正則表達(dá)式要求?)。

在KnockoutJS中,模型遵從上面的定義,但是通常對(duì)服務(wù)端服務(wù)的Ajax調(diào)用被做成即可以讀取也可以寫入模型數(shù)據(jù)。

如果我們正在構(gòu)建一個(gè)簡(jiǎn)單的Todo應(yīng)用,使用KnockoutJS模型來表示一個(gè)Todo條目,看起來像下面這個(gè)樣子:

var Todo = function ( content, done ) {
    this.content = ko.observable(content);
    this.done = ko.observable(done);
    this.editing = ko.observable(false);
};

注意:在上面小段代碼里面,你可能發(fā)現(xiàn)了,我們?cè)贙nockoutJS的名字空間里面調(diào)用observable()方法。在KnockoutJS中,觀察者是一類特殊的JavaScript對(duì)象,可以將變化通知給訂閱者,并且自動(dòng)檢測(cè)依賴關(guān)系。這個(gè)特性使我們?cè)谀P椭敌薷闹螅梢酝侥P秃鸵晥D模型。

視圖

使用MVC,視圖是應(yīng)用程序中用戶真正與之打交道的唯一一個(gè)部分.它們是展現(xiàn)一個(gè)視圖模型狀態(tài)的一個(gè)可交互UI.此種意義而言,視圖是主動(dòng)的而不是被動(dòng)的,而這也是真正的MVC和MVP的觀點(diǎn).在MVC,MVP和MVVM中視圖也可以是被動(dòng)的,而這又是什么意思呢?

被動(dòng)視圖僅僅只輸出要展示的東西,而不去接受任何用戶的輸入。

這樣一個(gè)視圖在我們的應(yīng)用程序中可能也沒有真正的模型的概念,而可以被一個(gè)代理控制.MVVM的主動(dòng)視圖包含數(shù)據(jù)綁定,事件和需要能夠理解視圖模型的行為.盡管這些行為能夠被映射到屬性,視圖仍然處理這來自視圖模型的事件。

記住視圖在這里并不負(fù)責(zé)處理狀態(tài)時(shí)很重要的——它使得其與視圖模型得以同步。

KnockoutJS視圖是簡(jiǎn)單的一個(gè)帶有聲明鏈接到視圖模型的HTML文檔。KnockoutJS視圖展示來自視圖模型的信息,并且傳遞命令給他(比如,用戶在一個(gè)元素上面點(diǎn)擊),并且針對(duì)視圖模型的變化更新狀態(tài)。而使用來自視圖模型的數(shù)據(jù)來生成標(biāo)記的模板也能夠被用在這個(gè)目的上。

未來給出一個(gè)簡(jiǎn)單的初始示例,我們可以看看Javascritpt的MVVM框架KnockoutJS,看它如何允許一個(gè)視圖模型的定義,還有它在標(biāo)記中的相關(guān)綁定。

視圖模型:

var aViewModel = {
    contactName: ko.observable("John")
};
ko.applyBindings(aViewModel);

視圖:

<p><input id="source" data-bind="value: contactName, valueUpdate: 'keyup'" /></p>
<div data-bind="visible: contactName().length > 10">
    You have a really long name!
</div>
<p>Contact name: <strong data-bind="text: contactName"></strong></p>

我們的text-box輸入(源)從contactName獲取它的初始值,無論何時(shí)contactName發(fā)生了改變都會(huì)自動(dòng)更新這個(gè)值.由于數(shù)據(jù)綁定是雙向的,像text-box中輸入也將據(jù)此更新contactName,以此保持值總是同步的。

盡管這個(gè)實(shí)現(xiàn)特定于KnockoutJS,但是包含著"You have a really long name!"文本的

標(biāo)簽包含有簡(jiǎn)單的驗(yàn)證(同樣是以數(shù)據(jù)綁定的形式呈現(xiàn))。如果輸入超過10個(gè)字符,這個(gè)標(biāo)簽就會(huì)顯示,否則保持隱藏。

讓我們看看一個(gè)更高級(jí)的例子,我們可以看看我們的Todo應(yīng)用。一個(gè)用于這個(gè)應(yīng)用的裁剪后的KnockoutJS的視圖,包含有所有必要的數(shù)據(jù)綁定,這個(gè)視圖看起來是下面這個(gè)樣子。

<div id="todoapp">
    <header>
        <h1>Todos</h1>
        <input id="new-todo" type="text" data-bind="value: current, valueUpdate: 'afterkeydown', enterKey: add"
               placeholder="What needs to be done?"/>
    </header>
    <section id="main" data-bind="block: todos().length">

        <input id="toggle-all" type="checkbox" data-bind="checked: allCompleted">
        <label for="toggle-all">Mark all as complete</label>

        <ul id="todo-list" data-bind="foreach: todos">

           <!-- item -->
            <li data-bind="css: { done: done, editing: editing }">
                <div class="view" data-bind="event: { dblclick: $root.editItem }">
                    <input class="toggle" type="checkbox" data-bind="checked: done">
                    <label data-bind="text: content"></label>
                    <a class="destroy" href="#" data-bind="click: $root.remove"></a>
                </div>
                <input class="edit' type="text"
                       data-bind="value: content, valueUpdate: 'afterkeydown', enterKey: $root.stopEditing, selectAndFocus: editing, event: { blur: $root.stopEditing }"/>
            </li>

        </ul>

    </section>
</div>

請(qǐng)注意,這個(gè)標(biāo)記的基本布局是相對(duì)直觀的,包含有一個(gè)輸入文本框(新的todo)用于增加新條目,用于標(biāo)記條目完成的開關(guān),以及一個(gè)擁有模板的列表(todo列表),這個(gè)模板以anli的形式展現(xiàn)Todo條目。

上面標(biāo)記中綁定的數(shù)據(jù)可以分成下面幾塊:

  • 輸入的文本框new-todo 有一個(gè)當(dāng)前屬性的數(shù)據(jù)綁定,當(dāng)前要增加的條目的值存儲(chǔ)在這里。我們的視圖模型(后面就會(huì)看到)觀察當(dāng)前屬性,并且綁定在添加事件上。當(dāng)回車鍵按下的時(shí)候,添加事件就被出發(fā)了,我們的視圖模型就可以處理當(dāng)前的值按照需要并且將其加入到Todo列表中。
  • 輸入勾選框可以通過點(diǎn)擊標(biāo)示所有當(dāng)前條目為完成狀態(tài)。如果勾選了,觸發(fā)完成事件,這個(gè)事件可以被模型視圖觀察到。
  • 有一類條目是進(jìn)行中狀態(tài)。當(dāng)一個(gè)任務(wù)被標(biāo)記為進(jìn)行中,CSS類也會(huì)根據(jù)這個(gè)狀態(tài)進(jìn)行標(biāo)識(shí)。如果雙擊條目,$root.editItem 回調(diào)就會(huì)被執(zhí)行。
  • toggle類的勾選框表明當(dāng)前的進(jìn)行狀態(tài)。
  • 一個(gè)文本標(biāo)簽包含有Todo條目的內(nèi)容
  • 當(dāng)點(diǎn)擊一個(gè)移除按鈕時(shí)可以調(diào)用$root.remove 回調(diào)函數(shù)。
  • 編輯模式下的一個(gè)輸入文本框可以保存Todo條目的內(nèi)容?;剀囨I事件將會(huì)設(shè)定編輯屬性為真或者假。

視圖模型

視圖模型被認(rèn)為是一個(gè)專門進(jìn)行數(shù)據(jù)轉(zhuǎn)換的控制器。它可以把對(duì)象信息轉(zhuǎn)換到視圖信息,將命令從視圖攜帶到對(duì)象。

例如,我們想象我們有一個(gè)對(duì)象的日期屬性是unix格式的(e.g 1333832407),而不是用戶視圖的所需要的日期格式(e.g 04/07/2012 @ 5:00pm),這時(shí)就有必要把unix的日期格式轉(zhuǎn)換為視圖需要的格式。我們的對(duì)象只簡(jiǎn)單保存原始的unix數(shù)據(jù)格式日期,視圖模型作為一個(gè)中間人角色會(huì)格式化原始的unix數(shù)據(jù)格式轉(zhuǎn)換為視圖需要的日期格式。

在這個(gè)場(chǎng)景下,視圖模型可以被看做一個(gè)對(duì)象,它處理很多視圖顯示邏輯。視圖模型也對(duì)外提供更新視圖狀態(tài)的方法,并通過視圖方法和觸發(fā)事件更新對(duì)象。

簡(jiǎn)單來說,視圖模型位于我們UI層后面層。它通過視圖發(fā)布對(duì)象的公共數(shù)據(jù),同時(shí)它作為視圖源提供數(shù)據(jù)和方法。

KnockoutJS描述視圖模型作為數(shù)據(jù)的表現(xiàn)和操作可以在UI上訪問和執(zhí)行。視圖模型并不是一個(gè)UI對(duì)象,也不是數(shù)據(jù)持久化對(duì)象,而是一個(gè)能夠?yàn)橛脩籼峁﹥?chǔ)存狀態(tài)及操作的層次對(duì)象。Knockout的視圖模型實(shí)現(xiàn)了JavaScript對(duì)象與HTML語言無關(guān)性。通過這個(gè)實(shí)現(xiàn)使開發(fā)保持了簡(jiǎn)單,意味著我們可以在視圖層更加簡(jiǎn)單的管理更多的組合方法。

對(duì)于我們的ToDo應(yīng)用程序的一部分KnockoutJS視圖模型可以是像下面這樣:

// our main ViewModel
    var ViewModel = function ( todos ) {
        var self = this;

    // map array of passed in todos to an observableArray of Todo objects
    self.todos = ko.observableArray(
    ko.utils.arrayMap( todos, function ( todo ) {
        return new Todo( todo.content, todo.done );
    }));

    // store the new todo value being entered
    self.current = ko.observable();

    // add a new todo, when enter key is pressed
    self.add = function ( data, event ) {
        var newTodo, current = self.current().trim();
        if ( current ) {
            newTodo = new Todo( current );
            self.todos.push( newTodo );
            self.current("");
        }
    };

    // remove a single todo
    self.remove = function ( todo ) {
        self.todos.remove( todo );
    };

    // remove all completed todos
    self.removeCompleted = function () {
        self.todos.remove(function (todo) {
            return todo.done();
        });
    };

    // writeable computed observable to handle marking all complete/incomplete
    self.allCompleted = ko.computed({

        // always return true/false based on the done flag of all todos
        read:function () {
            return !self.remainingCount();
        },

        // set all todos to the written value (true/false)
        write:function ( newValue ) {
            ko.utils.arrayForEach( self.todos(), function ( todo ) {
                //set even if value is the same, as subscribers are not notified in that case
                todo.done( newValue );
            });
        }
    });

    // edit an item
    self.editItem = function( item ) {
        item.editing( true );
    };
 ..

上面我們基本上提供了必需的加入、編輯或者移除記錄的方法,還有標(biāo)記所有現(xiàn)存的記錄已經(jīng)被完成的邏輯。注意:唯一真正需要關(guān)注的同前面我們的視圖模型的示例的不同之處就是觀察數(shù)組.在KnockoutJS中,如果我們希望監(jiān)測(cè)到并且去回應(yīng)一個(gè)單獨(dú)的對(duì)象發(fā)生的改變,我們可以使用觀察.然而如果我們希望檢測(cè)并且去回應(yīng)一個(gè)集合的事物所發(fā)生的改變,我們可以換用一個(gè)觀察數(shù)組.如何使用觀察數(shù)組的一個(gè)簡(jiǎn)單示例就像下面這樣:

// Define an initially an empty array
var myObservableArray = ko.observableArray();

// Add a value to the array and notify our observers
myObservableArray.push( 'A new todo item' );

注意:感興趣的話,我們?cè)谇懊嫠岬降耐暾腒nockoutJS Todo應(yīng)用程序可以從 TodoMVC 獲取到。

扼要重述: 視圖和視圖模型

視圖和視圖模型使用數(shù)據(jù)綁定和事件進(jìn)行通信。正如我們之前的視圖模型例子所見,視圖模型不僅僅發(fā)布對(duì)象屬性,它還提供其他的方法和特性,諸如驗(yàn)證。

我們的視圖處理自己的用戶接口事件,并會(huì)把相關(guān)事件映射到視圖模型。對(duì)象和它屬性與視圖模型是同步的,且通過雙向數(shù)據(jù)綁定進(jìn)行更新。

觸發(fā)器(數(shù)據(jù)觸發(fā)器)允許我們進(jìn)一步在視圖狀態(tài)變化后改變我們的對(duì)象屬性。

小結(jié):視圖模型和模型

雖然可能會(huì)出現(xiàn)在MVVM中視圖模型完全對(duì)模型負(fù)責(zé)的情況,這些關(guān)系確實(shí)有一些值得關(guān)注的微妙之處.處于數(shù)據(jù)綁定的目的,視圖模型可以暴露出來一個(gè)模型或者模型屬性,而且也能夠包含獲取和操作視圖中暴露出來的屬性。

優(yōu)點(diǎn)和缺點(diǎn)

現(xiàn)在,我們完全對(duì)MVVM是什么,以及它是如何工作的,有了一個(gè)更好的了解.現(xiàn)在就讓我們來看看使用這種模式的優(yōu)點(diǎn)和缺點(diǎn)吧:

優(yōu)點(diǎn):

  • MVVM更加便于UI和驅(qū)動(dòng)UI的構(gòu)造塊,這兩部分的并行開發(fā)
  • 抽象視圖使得背后所需要的業(yè)務(wù)邏輯(或者粘合劑)的代碼數(shù)量得以減少
  • 視圖模型比事件驅(qū)動(dòng)代碼更加容易進(jìn)行單元測(cè)試
  • 視圖模型(比視圖更加像是模型)能夠在不用擔(dān)心UI自動(dòng)化和交互的前提下被測(cè)試

缺點(diǎn):

  • 對(duì)于更簡(jiǎn)單的UI而言,MVVM可能矯枉過正了
  • 雖然數(shù)據(jù)綁定可以是聲明性質(zhì)的并且工作得很好,但在我們簡(jiǎn)單設(shè)置斷點(diǎn)的地方,它們比當(dāng)務(wù)之急的代碼更加難于調(diào)試
  • 在非凡的應(yīng)用程序中的數(shù)據(jù)綁定能夠創(chuàng)造許多的賬簿.我們也并不希望以綁定比被綁定目標(biāo)對(duì)象更加重量級(jí),這樣的境地告終
  • 在大型的應(yīng)用程序中,將視圖模型的設(shè)計(jì)提升到獲取足夠所需數(shù)量的泛化,會(huì)變得更加的困難

MVVM 的低耦合數(shù)據(jù)綁定

常見到有著MVC或者M(jìn)VP開發(fā)經(jīng)驗(yàn)的JavaScript程序員評(píng)論MVVM的時(shí)候在抱怨它會(huì)分散他們的關(guān)注點(diǎn)。也就是說,他們習(xí)慣在一個(gè)視圖中有相當(dāng)數(shù)量的數(shù)據(jù)被耦合在了HTML標(biāo)簽中。

我必須承認(rèn)當(dāng)我第一次體驗(yàn)實(shí)現(xiàn)了MVVM的JavaScript框架后(例如 KnockoutJS, Knockback),我很驚訝很多程序員都想要回到一個(gè)難以維護(hù)的混淆了邏輯(JavaScript代碼)和HTML標(biāo)簽做法的過去。然而現(xiàn)實(shí)是使用MVVM會(huì)有很多好處(我們之前說過),包括設(shè)計(jì)師能更容易的通過他們的標(biāo)記去綁定相關(guān)邏輯。

在我們中間的傳統(tǒng)程序員,你會(huì)很開心知道現(xiàn)在我們能夠通過數(shù)據(jù)綁定這個(gè)特性大量減少程序代碼的耦合程度,且KnockoutJS從1.3這個(gè)版本就開始提供自定義綁定功能。

KnockoutJS 默認(rèn)有一個(gè)數(shù)據(jù)綁定提供者,這個(gè)提供者搜索所有的附屬有數(shù)據(jù)綁定屬性的元素,如下面的例子:

<input id="new-todo" type="text" data-bind="value: current, valueUpdate: 'afterkeydown', enterKey: add" placeholder="What needs to be done?"/>

當(dāng)這個(gè)提供者定位一個(gè)到包含有該屬性的元素時(shí),這個(gè)工具將會(huì)分析該元素,使用當(dāng)前的數(shù)據(jù)上下文來將其轉(zhuǎn)化成一個(gè)綁定對(duì)象。這種方式是 KnockoutJS百分百可以工作的方式,通過這種方式,我們可以采用聲明式的方法對(duì)元素增加綁定,KnockoutJS之后會(huì)在該層上將數(shù)據(jù)綁定到元素上。

當(dāng)我們開始構(gòu)建復(fù)雜的視圖的時(shí)候,我們最終就可能得到大量的元素和屬性在標(biāo)記中綁定數(shù)據(jù),這種方式將會(huì)變得很難管理。通過自定義的綁定提供者,這就不算個(gè)問題。

一個(gè)綁定提供者主要關(guān)心兩件事:

  • 給定一個(gè)DOM節(jié)點(diǎn),這個(gè)節(jié)點(diǎn)是否包含任何數(shù)據(jù)綁定?
  • 如果節(jié)點(diǎn)回答是YES,那么這個(gè)綁定的對(duì)象在當(dāng)前數(shù)據(jù)上下文中,看起來是什么樣的?

綁定提供者實(shí)現(xiàn)了兩個(gè)功能:

  • nodeHasBindings:這個(gè)有一個(gè)DOM的節(jié)點(diǎn)參數(shù),這個(gè)參數(shù)不一定是一個(gè)元素
  • getBindings:返回一個(gè)對(duì)象代表當(dāng)前數(shù)據(jù)上下文下的要使用的綁定

一個(gè)框架綁定提供者看起來如下:

var ourBindingProvider = {
  nodeHasBindings: function( node ) {
      // returns true/false
  },

  getBindings: function( node, bindingContext ) {
      // returns a binding object
  }
};

在我們充實(shí)這個(gè)提供者之前,讓我們先簡(jiǎn)要的討論一下數(shù)據(jù)綁定屬性中的邏輯。

當(dāng)使用Knockout的MVVM,我們會(huì)對(duì)將應(yīng)用邏輯過度綁定到視圖上的這種方法不滿。我們可以實(shí)現(xiàn)像CSS類一樣的東西,將綁定根據(jù)名字賦值給元素。Ryan Niemeyer(knockmeout.net上的)之前提出使用數(shù)據(jù)類用于這個(gè)目的,來避免將展示類和數(shù)據(jù)類混淆,讓我們改造我們的nodeHasBindings 函數(shù),來支持這個(gè)概念:

// does an element have any bindings?
function nodeHasBindings( node ) {
    return node.getAttribute ? node.getAttribute("data-class") : false;
};

接下來,我們需要一個(gè)敏感的getBindings()函數(shù)。既然我們堅(jiān)持使用CSS類的概念,為什么不考慮一下支持空格分割類呢,這樣可以使我們?cè)诓煌刂g共享綁定標(biāo)準(zhǔn)。

讓我們首先看一下我們的綁定長(zhǎng)什么樣子。我們建立一個(gè)對(duì)象用于持有它們,在這些綁定處,我們的屬性名需要和我們數(shù)據(jù)類中使用的關(guān)鍵字相匹配。

注意:對(duì)于將使用傳統(tǒng)數(shù)據(jù)綁定方式的KnockoutJS應(yīng)用轉(zhuǎn)化成一個(gè)使用自定義綁定提供者的不引人矚目的綁定方式。我們簡(jiǎn)單的拉取我們所有的數(shù)據(jù)綁定屬性,使用數(shù)據(jù)類屬性來替換它們,并且像之前做的一樣,將我們的綁定放到綁定對(duì)象中去。

var viewModel = new ViewModel( todos || [] ),
    bindings = {

        newTodo:  {
            value: viewModel.current,
            valueUpdate: "afterkeydown",
            enterKey: viewModel.add
        },

        taskTooltip : {
            visible: viewModel.showTooltip
        },
        checkAllContainer : {
            visible: viewModel.todos().length
        },
        checkAll: {
            checked: viewModel.allCompleted
        },

        todos: {
            foreach: viewModel.todos
        },
        todoListItem: function() {
            return {
                css: {
                    editing: this.editing
                }
            };
        },
        todoListItemWrapper: function() {
            return {
                css: {
                    done: this.done
                }
            };
        },
        todoCheckBox: function() {
            return {
                checked: this.done
            };
        },
        todoContent: function() {
            return {
                text: this.content,
                event: {
                    dblclick: this.edit
                }
            };
        },
        todoDestroy: function() {
            return {
                click: viewModel.remove
            };
        },       

        todoEdit: function() {
            return {
                value: this.content,
                valueUpdate: "afterkeydown",
                enterKey: this.stopEditing,
                event: {
                    blur: this.stopEditing
                }
            };
        },

        todoCount: {
            visible: viewModel.remainingCount
        },
        remainingCount: {
            text: viewModel.remainingCount
        },
        remainingCountWord: function() {
            return {
                text: viewModel.getLabel(viewModel.remainingCount)
            };
        },
        todoClear: {
            visible: viewModel.completedCount
        },
        todoClearAll: {
            click: viewModel.removeCompleted
        },
        completedCount: {
            text: viewModel.completedCount
        },
        completedCountWord: function() {
            return {
                text: viewModel.getLabel(viewModel.completedCount)
            };
        },
        todoInstructions: {
            visible: viewModel.todos().length
        }
    };

    ....

上面代碼中,我們丟掉了兩行,我們?nèi)匀恍枰猤etBindings函數(shù),這個(gè)函數(shù)遍歷數(shù)據(jù)類屬性中每一個(gè)關(guān)鍵字,并從中構(gòu)建最終對(duì)象。如果我們檢測(cè)到綁定對(duì)象是個(gè)函數(shù),我們使用當(dāng)前的數(shù)據(jù)調(diào)用它。我們的完成版自定義綁定提供中,如下:

 // We can now create a bindingProvider that uses
    // something different than data-bind attributes
    ko.customBindingProvider = function( bindingObject ) {
        this.bindingObject = bindingObject;

        // determine if an element has any bindings
        this.nodeHasBindings = function( node ) {
            return node.getAttribute ? node.getAttribute( "data-class" ) : false;
        };
      };

    // return the bindings given a node and the bindingContext
    this.getBindings = function( node, bindingContext ) {

        var result = {},
            classes = node.getAttribute( "data-class" );

        if ( classes ) {
            classes = classes.split( "" ); 

            //evaluate each class, build a single object to return
            for ( var i = 0, j = classes.length; i < j; i++ ) {

               var bindingAccessor = this.bindingObject[classes[i]];
               if ( bindingAccessor ) {
                   var binding = typeof bindingAccessor === "function" ? bindingAccessor.call(bindingContext.$data) : bindingAccessor;
                   ko.utils.extend(result, binding);              
               }  

            }
        }

        return result;
    };
};

我們綁定對(duì)象最后的幾行,定義如下:

// set ko's current bindingProvider equal to our new binding provider
ko.bindingProvider.instance = new ko.customBindingProvider( bindings );  

// bind a new instance of our ViewModel to the page
ko.applyBindings( viewModel );

})();

我們?cè)谶@里所做的是為我們的綁定處理器有效的定義構(gòu)造器,綁定處理器接受一個(gè)我們用來查找綁定的對(duì)象(綁定)。然后我們可以使用數(shù)據(jù)類為我們應(yīng)用程序視圖的重寫標(biāo)記,像下面這樣做:

<div id="create-todo">
                <input id="new-todo" data-class="newTodo" placeholder="What needs to be done?" />
                <span class="ui-tooltip-top" data-class="taskTooltip" style="display: none;">Press Enter to save this task</span>
            </div>
            <div id="todos">
                <div data-class="checkAllContainer" >
                    <input id="check-all" class="check" type="checkbox" data-class="checkAll" />
                    <label for="check-all">Mark all as complete</label>
                </div>
                <ul id="todo-list" data-class="todos" >
                    <li data-class="todoListItem" >
                        <div class="todo" data-class="todoListItemWrapper" >
                            <div class="display">
                                <input class="check" type="checkbox" data-class="todoCheckBox" />
                                <div class="todo-content" data-class="todoContent" style="cursor: pointer;"></div>
                                <span class="todo-destroy" data-class="todoDestroy"></span>
                            </div>
                            <div class="edit'>
                                <input class="todo-input" data-class="todoEdit'/>
                            </div>
                        </div>
                    </li>
                </ul>
            </div>

Nei Kerkin 已經(jīng)使用上面的方式組合成了一個(gè)完整的TodoMVC示例,它可以 從 這里獲取到。 雖然上面的解釋看起來像是有許多的工作要做,現(xiàn)在我們就有一個(gè)一般的getBindingmethod方法要寫。比起為了編寫我們的KnockoutJS應(yīng)用程序而嚴(yán)格使用數(shù)據(jù)綁定,簡(jiǎn)單的重用和使用數(shù)據(jù)類更加的瑣碎。最終的結(jié)果是希望得到一個(gè)干凈的標(biāo)記,其中我們的數(shù)據(jù)綁定會(huì)從視圖切換到一個(gè)綁定對(duì)象。

MVC VS MVP VS MVVM

MVP和MVVM都是MVC的衍生物。它和它的衍生物之間關(guān)鍵的不同之處在于每一層對(duì)于其它層的依賴,以及它們相互之間是如何緊密結(jié)合在一起的。

在MVC中,視圖位于我們架構(gòu)的頂部,其背后是控制器。模型在控制器后面,而因此我們的視圖了解得到我們的控制器,而控制器了解得到模型。這里,我們的視圖有對(duì)模型的直接訪問。然而將整個(gè)模型完全暴露給視圖可能會(huì)有安全和性能損失,這取決于我們應(yīng)用程序的復(fù)雜性。MVVM則嘗試去避免這些問題。

在MVP中,控制器的角色被代理器所取代,代理器和視圖處于同樣的地位,視圖和模型的事件都被它偵聽著并且接受它的調(diào)解。不同于MVVM,沒有一個(gè)將視圖綁定到視圖模型的機(jī)制,因此我們轉(zhuǎn)而依賴于每一個(gè)視圖都實(shí)現(xiàn)一個(gè)允許代理器同視圖去交互的接口。

MVVM進(jìn)一步允許我們創(chuàng)建一個(gè)模型的特定視圖子集,包含了狀態(tài)和邏輯信息,避免了將模型完全暴露給視圖的必要。不同于MVP的代理器,視圖模型并不需要去引用一個(gè)視圖。視圖可以綁定到視圖模型的屬性上面,視圖模型則去將包含在模型中的數(shù)據(jù)暴露給視圖。像我們所提到過的,對(duì)視圖的抽象意味著其背后的代碼需要較少的邏輯。

對(duì)此的副作用之一就是視圖模型和視圖層之間新增的的用于翻譯解釋的一層會(huì)有性能損失。這種解釋層的復(fù)雜度根據(jù)情況也會(huì)有所差異——它可能像復(fù)制數(shù)據(jù)一樣簡(jiǎn)單,也可能會(huì)像我們希望用視圖理解的一種形式去操作它們,那樣復(fù)雜。由于整個(gè)模型是現(xiàn)成可用的,從而這種操作可以被避免掉,所以MVC沒有這種問題。

Backbone.js Vs KnockoutJS

了解MVC,MVP和MVVM之間的細(xì)微差別是很重要的,然而基于我們已經(jīng)了解到的東西,開發(fā)者最終會(huì)問到是否它們應(yīng)該考慮使用KnockoutJS而不是Backbone這個(gè)問題。下面的一些相關(guān)事項(xiàng)對(duì)此可能有些幫助:

  • 兩個(gè)庫都設(shè)計(jì)用于不同的目標(biāo),它常常不僅僅簡(jiǎn)單的知識(shí)選擇MVC或者M(jìn)VVM的問題。
  • 如果數(shù)據(jù)綁定和雙向通信是你主要關(guān)注的問題,KnockoutJS絕對(duì)是應(yīng)該選擇的方式。實(shí)踐中任何存儲(chǔ)在DOM節(jié)點(diǎn)中的值或者屬性都能夠使用此方法映射到Javascript對(duì)象上面。
  • Backbone在同RESTful服務(wù)的易于整合方面有其過人之處,而KnockoutJS模型就是簡(jiǎn)單的Javascript對(duì)象,而更新模型所需要的代碼也必須要由開發(fā)者自己來寫。
  • KnockoutJS專注于自動(dòng)的UI綁定,如果嘗試使用Backbone來做的話則會(huì)要求更加細(xì)節(jié)的自定義代碼。由于Backbone自身就意在獨(dú)立于UI而存在,所以這并不是它的問題。然而Knockback也確實(shí)能協(xié)助并解決此問題。 使用KnockoutJS,我們能夠?qū)⑽覀冏约簱碛械暮瘮?shù)綁定到視圖模型觀察者上面,任何時(shí)候觀察者一旦發(fā)生了變化,它都會(huì)執(zhí)行。這允許我們能夠擁有同在Backbone中發(fā)現(xiàn)的一樣級(jí)別的靈活性。
  • Backbone內(nèi)置有一個(gè)堅(jiān)實(shí)的路由解決方案,而KnockoutJS則沒有提供路由供我們選擇。然而人們?nèi)绻枰脑?,可以很容易的加入這個(gè)行為,使用Ben Alman的BBQ插件或者一個(gè)像Miller Medeiros優(yōu)秀的Crossroads就行了。

總結(jié)下來,我個(gè)人發(fā)覺KnockoutJS更適合于小型的應(yīng)用,而Backbone的特性在任何東西都是無序的場(chǎng)景下面才會(huì)是亮點(diǎn)。那就是說,許多開發(fā)者兩個(gè)框架都已經(jīng)使用過來編寫不同復(fù)雜度的應(yīng)用程序,而我建議在一個(gè)小范圍內(nèi)兩種都嘗試一下,在你決定哪一種能更好的為你工作之前。

上一篇:迭代器模式下一篇:工廠模式