1. 程式人生 > >介紹javascript MVC框架:ember框架的基本概念

介紹javascript MVC框架:ember框架的基本概念

write by yinmingjun,引用請註明。

在看過knockout和angular之後,有些意猶未盡,感覺在web領域內對SPA的探索不應該止步於此,於是開始翻看ember.js框架,希望ember.js能給我帶來驚喜。ember.js在web上的資源較少,官方文件覆蓋度和深度不夠,很多細節需要到程式碼中尋找答案。不過迴歸到ember.js框架本身,這個框架的確帶給我很多驚喜,讓我會有寫文字介紹它的慾望。本文只從框架和概念的角度來解讀ember.js,對於ember.js各個部分的深入研究會寫在後續的文章之中。

在最初看ember的核心概念解釋的時候,看到router這個詞這讓我有些詫異,難道。。。。。。

在進一步瞭解ember之後,證實了我的懷疑:ember確實是通過router作為核心概念來組織其MVC體系架構的。這種方式與目前在伺服器端流行的一些MVC框架(ASP.NET MVC、Django或express等)的處理方式相似,當router的概念出現在面向客戶端的ember之中的時候,我們基本上可以猜測出來,ember已經將頁面間的狀態遷移納入自己的問題領域。

此外,ember詳細的規劃了model、view和controller的職責劃分,並支援雙向的資料繫結。

ember的另外一個讓我意外的地方是ember.data。客戶端的js框架很少會涉足實體關係的領域,筆者曾經在所在的公司開做過類似的JS實體關係對映體系,深知其複雜度和價值。ember將實體的定義和實體關係納入其問題領域,是眾多JS開發者的福音,ember.data有助於我們從資料管理的細節中解放出來

。不過ember.data不是本文的重點,後面筆者會寫專文來介紹它,本文只是簡單的介紹其在ember.js體系中的角色。

在讀到這裡的時候,大家會不會有這樣的感覺:ember會不會太重了?ember的js尺寸(v1.0.0版本)壓縮過的有200K,未壓縮過的有800K,的確驚人,還不包括ember.data(v0.13版本,250K,70K尺寸)和handlebar的js尺寸。這些訊息可以給我們一些啟示,ember是面向未來的、重型的web應用支撐框架,使用的時候需要良好的設計和規劃,如果不清楚這個定位也許會導致ember使用上的失敗經歷,這點請大家明白。

接下來進入正題,我們會逐步介紹ember裡面的核心概念,瞭解ember的應用程式的架構。

一、從DEMO開始

下面是來自ember官網的一個DEMO,我們看一下程式碼。

HTML: 

<!DOCTYPE html>

<html>

          <head>

              <meta charset="utf-8">

              <title>Ember Starter Kit</title>

              <link rel="stylesheet" href="css/normalize.css">

              <link rel="stylesheet" href="css/style.css">

          </head>

          <body>

                <script type="text/x-handlebars">

                     <h2>Welcome to Ember.js</h2>

                         {{outlet}}

                </script>

                <script type="text/x-handlebars" data-template-name="index">

                    <ul>      {{#each item in model}}

                        <li>{{item}}</li>

                        {{/each}}

                    </ul>

                </script>

                <script src="js/libs/jquery-1.9.1.js"></script>

                <script src="js/libs/handlebars-1.0.0-rc.4.js"></script>

                <script src="js/libs/ember-1.0.0-rc.5.js"></script>

                <script src="js/app.js"></script>

          </body>

  </html>

app.js:

    App = Ember.Application.create();

    App.Router.map( function() {

        // put your routes here

    });

    App.IndexRoute = Ember.Route.extend({

        model: function() {

            return ['red', 'yellow', 'blue'];

        }

    });

例子很簡單,只是建立一個ember的appliation,然後將model中的資料依次的繫結到li之中。不過應用架構的角度來看,這個例子覆蓋了ember中的主要的功能。需要注意,script標記的型別是text/x-handlebars。Ember在載入頁面時,會抓取這些內容。

在這個例子中,我們基本上可以看到ember應用架構模式的主要脈絡。

1、預設的'/'到App.IndexRoute的對映

      省略的等效程式碼:

                  App.Router.map( function() { this.resource( 'index', { path: '/' } ); });

2、預設的建立IndexRoute的controller

      省略的程式碼:

App.IndexRoute = Ember.Route.extend({

                      setupController: function(controller) {

                           controller.set('content', ['red', 'yellow', 'blue']);

                      }

                  });

3、預設的application template

      省略了applicaion的template名稱:

                  <script type="text/x-handlebars" data-template-name="application">

                         <h1>Application Template</h1>

                         {{outlet}} 

                  </script>

4、App.IndexRoute對名字為'index'的template的引用

5、程式啟動後對{{outlet}}和'index'的template的繫結

這種name mapping的方式可以大幅度減少配置的工作量,在現代的MVC體系中廣泛採用,ember也引入了這種處理方式。後面我們會詳細的闡述ember的name mapping的方式。

實際上,如果最簡單的建立一個ember應用,僅需要一行程式碼:

        App = Ember.Application.create({});

而ember會在後面預設的加上下面的程式碼:

        // Create the application namespace

        App = Ember.Application.create({});

        // Create the global router to manage page state via URLs

        App.Router.map( function() {});

        // Create the default application route to set application-level state properties  

        App.ApplicationRoute = Ember.Route.extend({});

        // Create the default application template

       <script type="text/x-handlebars" data-template-name="application">

             {{outlet}}

       </script>

從這個demo中,我們能看到Application,Router,Template,Controller,Model等概念,這些概念我們會在下一節簡單的闡述。

二、ember的基本概念

為了容易理解,這幾個概念會盡可能簡單的描述,儘量不引入複雜的因素。

1、application的概念

在ember中,建立applicaion非常的簡單,只需要一行程式碼:

        window.App = Ember.Application.create();

App的變數名字是什麼都可以,建立後的app中可以作為應用級別狀態和資料的載體。例如,建立了一個App中的view,可以這麼寫:

        App.MyView = Ember.View.extend();

預設的情況下,呼叫Ember.Application.create()方法會自動的觸發Ember.Application.initialize()方法的呼叫,也可以通過配置來調整application的預設的行為。

在呼叫Ember.Application.create()方法(注1)的時候,可以傳遞一個object進去,以配置&調整應用建立的預設的行為。一般會在傳遞給create方法的中的obect中指定rootElement、ready事件的handler等。

注1:說明一下,create方法是ember的物件體系中的基礎服務

2、router的概念

router是Ember.Router類的例項,通過application例項的Router成員訪問。

在router中,有兩個概念,一個是resource,代表資源定位;另外一個是route,表示特定的頁面路由分發;

從簡單的開始,先看看如何新增route

        App.Router.map(function() {

            this.route("about", { path: "/about" });

            this.route("favorites", { path: "/favs" });

        });

這段程式碼實際上映射了以下的對應關係:

URL                 Route Name          Controller                     Route                         Template

/                       index                        IndexConroller            IndexRoute               index       

/about            about                       AboutController         AboutRoute              about       

/favs                favorites                 FavoritesController    FavoritesRoute        favorites  

其中,index的對映是預設的。

再看看resource是怎麼維護的:

        App.Router.map(function() {

            this.resource('posts', { path: '/posts' }, function() {

                 this.route('new'); 

           });

        });

其實如果resource的名字和path的名字是相同的,可以省略path部分的引數描述,下面是更簡單的寫法:

        App.Router.map(function() {

            this.resource('posts', function() {

                  this.route('new');

            });

        });

上面的程式碼實際上會產生下面的對映關係:  

URL                 Route Name       Controller                                       Route                               Template         

/                       index                     IndexConroller                              IndexRoute                     index                 

N/A                 posts                      PostsController                             PostsRoute                     posts                  

/posts             posts.index           PostsController                             PostsRoute                     posts                  

                                                        ->PostsIndexController               ->PostsIndexRoute       ->posts/index   

/posts/new   posts.new             PostsController                             PostsRoute                     posts                  

->postsNewController                ->PostsNewController  ->posts/new     

route和resource兩個概念,route用於處理具體的URL引數,resource用於做資源的重新定位。使用resource有助於將應用分解成多個小的區域,每個區域獨立的處理頁面的狀態遷移,這種結構是對大專案的任務分解和團隊協作有利的。

3、controller的概念

在ember中,controller是template和model之間的橋樑,將model中的資料轉換成面向template的顯示的資料。總的來說,model是面向server的,而controller是面向template的,擁有template需要顯示的資料,也包含template需要執行的操作。

ember的controller和ViewModel的概念有些相似。

如果一個controller是一個ArrayController,就可以在template中使用{{#each controller}}的語法。

在application層面上也存在controller,不過ember會給application提供預設的例項。如果需要處理application層面上的資料繫結和響應使用者操作,可以這麼做:

HTML:

    <!-- application.handlebars -->

    <header>

        {{view Ember.TextField valueBinding="search" action="query"}}

    </header>

    {{outlet}}

JS:

    App.ApplicationController = Ember.Controller.extend({

        // the initial value of the `search` property

        search: '',

        query: function() {

             // the current value of the text field

             var query = this.get('search');

             this.transitionToRoute('search', { query: query });

        }

    });

上門的程式碼為application提供了一個ApplicationController,響應application層面上的search資料請求和query請求操作。

注2:extend方法是ember物件體系提供的基礎服務

4、model的概念

在ember中,model的概念模糊而抽象,不容易理解。ember的model是純資料的載體,從簡單的層面上來看,model就是一個JSON的資料結構;從ember框架定義的角度上看,model對應後端的server上的資料,是實體和實體關係在客戶端的表達。為了更好的處理資料方面的資料管理的需求,ember.js專門開發了ember.data來處理更高層次的資料服務。

在ember的應用體系中,model的初值來自提供給route的model方法的返回值,並通過route的deserialize方法將獲取到的model資料填充到route的currentModel成員之中(currentModel可以理解為model的快照),而在route對資料的維護上來看currentModel是context最初的資料來源和最終的資料載體,某種意義上的等價物(特點的時間點上等價),而route的currentModel(或其context)成員最終會作為controller的content成員投放到controller之中。conroller中,model成員是其content的別名。

其同步的順序如下:

        route.model

                --->route.currentModel    &&    route.context

                        --->controller.content    &&    controller.model

另外,route的currentModel中資料是陣列還是物件,將會決定ember產生的controller的型別,對於array產生Ember.ArrayController的例項;對於object產生Ember.ObjectController的例項;其他會以Ember.Controller作為模版。

5、templat的概念

ember的template是建立在handlebars的基礎上的,也就是說,在ember的template中可以使用handlebars支援的語法來書寫template。真正屬於ember領域的問題是template和資料提供者controller之間的對應關係,其通過template的script標記的data-template-name屬性來指定:

    <script type="text/x-handlebars" data-template-name="index">

        <ul>

            {{#each item in model}}

                  <li>{{item}}</li>

            {{/each}}

        </ul>

    </script>

上面定義的template和名字是index的controller之間建立了對應關係。

如果需要操縱ArrayController中的內容,可以按下面的方式來書寫程式碼。

HTML:

    <script type="text/x-handlebars" data-template-name="index">
        <ul>
        {{#each item in model}}
            <li>{{item}} {{controller.postFix}}</li>
        {{/each}}
        </ul>

        <button {{action doIncrease}}> doIncrease </button>

    </script>

JS:

    App.IndexController = Ember.Controller.extend(
    {
        postFix: 'post',
        'content': ['aaa', 'bbb'],
        doIncrease: function() {
            var content = this.get('content');
            content.pushObject('demo');
        }
    });


ember對屬性是通過get、set包裝器和觀察者模式來管理的,對controller的資料變化,需要通過對應的方法來發布變更通知,這裡是ember包裝的陣列的pushObject方法來觸發。ember包裝的陣列還有其他類似的方法,詳細內容參考ember的API文件。 

註解:

需要主要的是ArrayController的特殊性,如果route的model中的資料是陣列,那麼ember的each helper會針對each塊內的內容建立子view,並將當前列舉項的內容作為子view的content屬性;所在的索引作為view的contentIndex屬性。ember的each helper也會針對陣列中的每個元素建立一個數據繫結的上下文,並將列舉的元素(這裡是item)、controller和view作為子view(就是each塊內的模版內容)的資料訪問的上下文。如果ArrayController中的陣列元素不是Controller的例項,那麼上下文中的controller就是父view上下文中的controller(這裡是App.IndexController);否則子view的controller是ArrayController的content中的陣列元素中的controller。

handlebars的語法和資料定位的方式,我轉了一篇文章,被小編遮蔽了,不過web上很多,大家可以自己找找。

6、view的概念

ember的view是基於handlebars的擴充套件,其背後是有兩種推動力:

1、是對特定事件的監聽和過濾需要(支援雙向的資料繫結的需求);

2、是避免大量重複的書寫template中的內容;

接下來,我們還是通過例子來了解ember中view的概念吧。

例子1:

通過view的append方法來將view新增到DOM。

HTML:

    <script type="text/x-handlebars" data-template-name="say-hello">
          Hello, <b>{{view.name}}</b>
    </script>

JS:

    App.MyView = Ember.View.create({
        templateName: 'say-hello',
        name: "YMJ"
    });

    App.MyView.append();

上面這段程式碼中,view通過templateName指定了使用的DOM模版,然後在模版中訪問view中的資料,然後通過其append方法將view釋出到document.body的元素的最後。view還有類似的appendTo方法(通過jquery的appendTo實現),將view釋出到指定的位置。

上面是使用view的一種方式。

例子2:

另外一直使用view的方式是在template中使用view。

HTML:

    <script type="text/x-handlebars" data-template-name="say-hello">
        Hello, <b>{{view.name}}</b>
    </script>

    <script type="text/x-handlebars" data-template-name="index">
        {{view App.MyView}}
        <ul>
        {{#each item in model}}
             <li>{{item}} {{controller.postFix}}</li>
        {{/each}}
        </ul>

        <button {{action doIncrease}}> doIncrease </button>

    </script>

JS:

    App.MyView = Ember.View.create({
        templateName: 'say-hello',
        name: "YMJ"
    });

上面的JS部分只給出了必要的程式碼。view和其DOM的template之間的關係還是通過其templateName的成員來指出,關鍵是在index的template中,我們通過view helper來引入了App.MyView的例項,這樣在template中輸出了App.MyView的內容。

ember內建了很多view,實際上ember的雙向的資料繫結就是基於view的概念實現的。

ember內建的view如下:

> Ember.Checkbox

> Ember.TextField

> Ember.Button

> Ember.TextArea

> Ember.SelectOption

> Ember.Select    

詳細的API的說明可以參考ember的官方文件,我們這裡只看一個小例子,看看如何做資料繫結:

<label>
      {{view Ember.CheckboxcheckedBinding="model.isDone"}}
      {{model.title}}
</label>

view相關的深入研究,我們在其他文件中來探討,作為view的使用者,大概瞭解到上面這些內容,基本上就可以動手寫東西了。

三、小結

上面,我們在概念的層面和ember.js做了一個親密的接觸,將ember.js框架中所有的核心概念闡述了一遍,希望讀者能對ember.js有一個整體上的認識,知道這是一個什麼框架,可以做那些事情。

在接觸到的javascript MVC框架中,ember可能是最野心勃勃的一個,從目前我看到的事實是它試圖接管客戶端的一切,對MVC體系的有全方位的支援,相比angular來說更全面、更復雜,對應小組工作提供更多的概念支援,是架構師比較樂於採用的前端框架。ember.js的文件支援方面,很難讓人滿意。線上文件的深度和廣度都不夠,很多東西需要到程式碼中找答案,ember在文件層面落後於angular等其他框架。

在框架的選擇上來看,很難說那個框架更優秀,結合應用領域或問題領域來選擇,可能會有更明確的結果。對應大型&複雜的應用,我本人會傾向於選擇ember.js,因為我對ember足夠的瞭解,其他人或許應考慮ember的文件方面的不足。

後面會寫一些東西,對ember的各個分支領域做相對深入的分析。