介紹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的各個分支領域做相對深入的分析。

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章