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

来源:互联网 发布:大兴区区长辞职 知乎 编辑:程序博客网 时间:2024/05/31 19:44

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的各个分支领域做相对深入的分析。

 

 

0 0