怎么着初叶一个模块化可扩展的Web App

即使从没有觉得自己是一个前端开发者,但不知不觉中也积累下了部分前端开发的阅历。正巧之前蒙受一道面试题,于是就顺便梳理了瞬间谈得来关于Web
App的局地思路并整治为本文。

对于广大简单易行的网站或Web应用来说,引入jQuery以及部分插件,在当前页面内写入不难逻辑已经得以知足半数以上亟需。可是如果假定四人支付,应用的复杂程度上涨,就会有好多题材初始暴光出来:

  1. 数据源一般都与页面分离,那么App启动一般都亟待等待数据源读入。
  2. UI交互复杂时,须求将逻辑通过面向对象抽象后才能更好的复用。
  3. 效果间一般都设有依靠关系,须要引入支持看重关系的模块加载器。

那就是说什么样解决这个题材,就以一个简短的订餐App为例,从零开首一个模块化可扩张Web
App

那几个不难的App基于HTML5 Boilerplate、requireJS、jQuery
Mobile、Underscore.js,后端逻辑用jStorage模仿完结。落成后的产品在此。所有代码可以在github查看。下文将顺序介绍完毕的思绪与方法。

从拔取一个好模板起头

初步一个Web项目,HTML的书写总是第一,一个好的HTML能从根源上规避大批量秘密难题,所以Web
App应该全套应用一个准绳的高品质HTML模板,而不是将有所页面交由开发人员自由发挥。

此地推荐应用HTML5
Boilerplate项目
用作App的默许模板以及文件路径规范,无论是网站或者富UI的App,都能够行使那几个模板作为启动。

能够使用

git clone git://github.com/h5bp/html5-boilerplate.git

抑或直接下载HTML5
Boilerplate项目代码
。HTML5
Boilerplate的文书结构如下,

.
├── css
│   ├── main.css
│   └── normalize.css
├── doc
├── img
├── js
│   ├── main.js
│   ├── plugins.js
│   └── vendor
│       ├── jquery.min.js
│       └── modernizr.min.js
├── .htaccess
├── 404.html
├── index.html
├── humans.txt
├── robots.txt
├── crossdomain.xml
├── favicon.ico
└── [apple-touch-icons]

从上向下看

  • css
    用于存放css文件,并内置了Normalize.css作为默许CSS重置手段(其实诺玛lize.css不可能算是CSS
    reset)。
  • doc 存放项目文档
  • img 存放项目图片
  • js 存放javascript文件,其中第三方类库推荐放在vendor
  • .htaccess
    内置了累累对此静态文件在Apache下的优化策略,如果Web服务器不是Apache则可以参考其他Web服务器配置优化
  • 404.html 默认的404页面,
  • index.html 项目模板
  • humans.txt
    相对于面向机器人的robots.txt,humans.txt更像是小幽默,那在里可以写关于项目/团队的牵线,或者放置一些彩蛋给这几个喜欢对你的行使刨根问底的用户们。
  • robots.txt 用于告诉搜索引擎蜘蛛爬行规则
  • crossdomain.xml
    用于配置Flash的跨域策略
  • favicon.ico apple-touch-icon.png等小图标。

一旦是一个重大面向移动装备,还有更具针对性的Mobile
Boilerplate
可供参考。

制订统一的编码规范

在专业发轫编码往日,无论是多大范围的行使,几个人的团社团,一定要有一个统一的业内,才能有限支持持续的支付不会乱套。

前端规范其实又要分成三有的:HTML、CSS、Javascript应该分别有谈得来的标准。HTML/CSS紧要约定id/class的命名规则、属性的书写顺序。JavaScript可能须要细化到缩进、编码风格、面向对象写法等等。

最便利的方法自然如故参考已有些规范,比如谷歌(Google)的HTML/CSS风格指南谷歌(Google)的Javascript编码指南等等。

HTML篇

HTML5
Boilerplate的沙盘要旨部分不过30行,不过每一行都可谓句酌字斟,可以用很小的用度解决部分前端的刚愎难点:

使用标准注释区分IE浏览器

<!DOCTYPE html>
<!--[if lt IE 7]>      <html class="no-js lt-ie9 lt-ie8 lt-ie7"> <![endif]-->
<!--[if IE 7]>         <html class="no-js lt-ie9 lt-ie8"> <![endif]-->
<!--[if IE 8]>         <html class="no-js lt-ie9"> <![endif]-->
<!--[if gt IE 8]><!--> <html class="no-js"> <!--<![endif]-->

故此要如此写

  1. 可以运用class作为全局条件区分低版本的IE浏览器并展开调整,这肯定要促销使用CSS
    Hack。
  2. 可防止止IE6条件注释引起的高版本IE文件阻塞难点,原文的缓解措施是在头里加一个空荡荡的标准化注释,但是此地肯定将本来无用空白的规格注释变得有意义了。
  3. 如故可以由此HTML验证。
  4. 与Modernizr等特性检测类库使用同样的class,更具备通用性。

no-js标签是索要与Modernizr等类库同盟使用的,若是你不想在档次中引入Modernizr,必要在Head部分加入一行使no-js标签变为js,代码来自Avoiding
the
FOUC

 <script>(function(H){H.className=H.className.replace(/\bno-js\b/,'js')})(document.documentElement)</script>

经过地点的基准注释,就足以在CSS中针对不相同境况分别处理

.lt-ie7 {} /* IE6等版本时 */
.no-js {} /* JavaScript没有启用时 */

meta标签的书写顺序

为了让浏览器识别正确的编码,meta charset标签应该先于title标签出现。

meta
X-UA-Compatible标签可以指定IE8以上版本浏览器以最高级情势渲染文档,同时借使已经安装Google
Chrome
Frame
则平素动用Chrome
Frame渲染。而指定渲染格局的meta
X-UA-Compatible标签同样须求事先出现

<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<title></title>

设置移动设备显示窗口宽度

<meta name="viewport" content="width=device-width">

那是移动设备专属的价签,具体设置要求依照项目其实景况调整。

利用Modernizr做浏览器差别检测

Modernizr常做前端的应当都不生疏。引入Modernizr后,html标签的no-js将会被活动替换为js,同时Modernizr会向html标签添加代表版本检测结果的class。

对于低版本浏览器的上扬包容需求根据项目实际须要处理,Modernizr也要命完美的交给的大部HTML5效应的合营方法

CSS篇

CSS重置及狠抓功用

HTML5
Boilerplate选择Normalize.css重置CSS。即使项目陈设引入TwitterBootstrap、YUI
3那些前端框架的话则可以移除,因为这一个框架已经嵌入了诺玛lize.css。

同时HTML5
Boilerplate又引入了一个main.css,内置了部分基本的排版样式以及打印样式。

使用LESS或Sass生成CSS

在复杂应用中,即使还手写CSS的话将是一件忧伤的业务,大量的class前缀,复用样式须要来回copy等等。为了更好的扩充性,那里提出在类型中引入LESSSass。那意味着:

  • 支持变量与简单运算
  • 支撑CSS片段复用
  • class/id样式嵌套

等局地更像是编程语言的特征。这对于增加支付功能是意义非凡引人注目的。

以LESS为例,简单介绍一下LESS在Windows下哪些选用到那几个种类中:

  1. 下载Nodejs并安装,nodejs会自动将协调进入连串路径。
  2. 在cmd运行
    npm install -g less
  3. 然后就可以透过lessc指令将less源文件编译为css
    lessc avnpc.less avnpc.css
  4. 若是不采取nodeJs作为后端,最好在写LESS时拔取watch方式,每回保存自动编译为css。那里需求设置一个扶植模块recess:
    npm install -g recess
    接下来运行watch
    recess avnpc.less:avnpc.css --watch

Javascript篇

选用requireJS按需加载

模块加载器的定义可能有点接触过前端开发的童鞋都不会陌生,通过模块加载器可以使得的缓解这一个难题:

  1. JS文件的看重关系。
  2. 通过异步加载优化script标签引起的梗塞难题
  3. 可以省略的以文件为单位将成效模块化并贯彻复用

主流的JS模块加载器有requireJSSeaJS等,加载器之间或者会因为依据的正式不相同有神秘的出入,从纯用户的角度出发,之所以选requireJS而不是SeaJS紧借使因为:

  • 职能完结上两者相差无几,没有明确的质量差距或根本难点。
  • 文档丰盛程度上,requireJS远远好于SeaJS,就拿最简便易行的加载jQuery和jQuery插件那回事,即便两者的得以完结格局相差无几,但requireJS就有可以直接拿来用的Demo,SeaJS还要读文档自己渐渐折腾。一些标题标缓解上,requireJS为机要词也更易于找到答案。

requireJS 加载jQuery + jQuery插件

唯恐对此一般Web
App来说,引入jQuery及连锁插件的票房价值是最大的,requireJS也接近的交付了相应的化解方案及动态加载jQuery及插件的文档及实例代码

在最新的jQuery1.9.X中,jQuery已经在最终直接将协调注册为一个AMD模块,即是说可以平昔被requireJS作为模块加载。即使是加载旧版的jQuery有二种艺术:

1. 让jQuery先于requireJS加载

2. 对jQuery代码稍做一些甩卖,在jQuery代码包裹一句:

define(["jquery"], function($) {
    // $ is guaranteed to be jQuery now */
});

requireJS的示范中,直接将requireJS与jQuery合并为一个文件,要是是使用jQuery作为基本库的话推荐那种做法。

一律对于jQuery插件来说也有三种艺术

1. 在插件外包裹代码

define(["jquery"], function($){
     // Put here the plugin code. 
});

2. 在利用reuqireJS代码加载前注册插件(比如在main.js)中

requirejs.config({
    "shim": {
        "jquery-cookie"  : ["jquery"]
    }
});

requireJS加载第三方类库

在实例的App中还用到了jQuery以外的第三方类库,固然类库不是一个正式的英特尔模块而又不想改变那些类库的代码,同样必要超前开展定义:

require.config({
      paths: {
            'underscore': 'vendor/underscore'
      },
      shim: {
          underscore: {
              exports: '_'
          }
      }
});

CSS文件的模块化处理

在requireJS中,模块的定义仅限于JS文件,即便急需加载图片、JSON等非JS文件,requireJS完成了一多级加载插件

只是遗憾的是requireJS官方没有对CSS举行模块化处理,而大家在实际项目中却再三能遇上有的光景,比如一个轮播的图片体现栏,比如高档编辑器等等。差不离所有的富UI组件都会由JS与CSS两有些构成,而CSS之间也设有着模块的概念以及凭借关系。

为了更好的与requireJS整合,那里运用require-css来缓解CSS的模块化与依靠难题。

require-css是一个requireJS插件,下载后将css.jsnormalize.js放于main.js同级即可默许被加载,比如在大家的品类中要求加载jQuery
Mobile的css文件,那么可以直接那样调用:

require(['jquery', 'css!../css/jquery.mobile-1.3.0.min.css'], function($) {
});

不过出于那些CSS本质上是属于jQuery
Mobile模块的一部分,更好的做法是将这几个CSS文件的定义放在jQuery
Mobile的信赖关系中,最后大家的requireJS定义部分为:

require.config({
      paths: {
            'jquerymobile': 'vendor/jquery.mobile-1.3.0',
            'jstorage' : 'vendor/jstorage',
            'underscore': 'vendor/underscore'
      },
      shim: {
          jquerymobile : {
            deps: [
                'css!../css/jquery.mobile-1.3.0.min.css'
            ]
          },
          underscore: {
              exports: '_'
          }
      }
});

在运用模块时,只须要:

require(['jquery', 'underscore', 'jquerymobile', 'jstorage'], function($, _) {
});

jQuery
Mobile的CSS文件就会被电动加载,那样CSS与JS就被重组为一个模块了。同理其余有复杂看重关系的模块也足以做类似处理,requireJS会解决敬爱关系的逻辑。

数据源的加载与等待

Web
App一般都会动态加载后端的数码,数据格式一般可以是JSON、JSONP也得以一贯是一个JS变量。那里以JS变量为例

var restaurants = [
    {
        "name": "KFC"
    },
    {
        "name": "7-11"
    },
    {
        "name": "成都小吃"
    }
]

载入那段数据:

$.getScript('data/restaurants.json', function(e){
    var data = window.restaurants;
    alert(data[0].name); //KFC
});

纯净的数据源确实很粗略,不过频仍一个使用中会有四个数据源,比如在这一个实例App中UI就需求载入用户信息、餐厅音讯、订餐新闻三种多少后才能办事。如果单单靠多层嵌套回调函数的话,可能代码的耦合就不行重了。

为了缓解多少个数据加载的题材,我习惯的解决方法是构造一个dataReady事件响应机制。

var foodOrder = {

    //数据载入后要执行的函数暂存在这里
    dataReadyFunc : []

    //数据源URL及载入状态
    , dataSource : [
        { url : 'data/restaurants.json', ready : false, data : null },
        { url : 'data/users.json', ready : false, data : null },
        { url : 'data/foods.json', ready : false, data : null }
    ]

    //检查数据源是否全部载入完毕
    , isReady : function(){
        var isReady = true;
        for(var key in this.dataSource){
            if(this.dataSource[key].ready !== true){
                isReady = false;
            }
        }
        return isReady;
    }

    //数据源全部加载完毕,则逐一运行dataReadyFunc中存放的函数
    , callReady : function(){
        if(true === this.isReady()){
            for(var key in this.dataReadyFunc){
                this.dataReadyFunc[key]();
            }
        }
    }

    //供外部调用,会将外部输入的函数暂存在dataReadyFunc中
    , dataReady : function(func){
        if (typeof func !== 'function') {
            return false;
        } 
        this.dataReadyFunc.push(func);
    }

    , init : function(){
        var self = this;
        var _initElement = function(key, url){
            $.getScript(url, function(e){
                //每次载入数据后,将数据存放于dataSource中,将ready状态置为true,并调用callReady
                self.dataSource[key].data = window[key];
                self.dataSource[key].ready = true;
                self.callReady();
            });
        }
        for(var key in this.dataSource){
            _initElement(key, this.dataSource[key].url);
        }
    }
}

用法为

foodOrder.dataReady(function(){
   alert(1);     
});
foodOrder.init();

dataReady内的alert将会在富有数据载入完成后开首施行。

那段处理的逻辑并不复杂,将拥有要执行的方法通过dataReady暂存起来,等待数据总体加载已毕后再实践,越发扑朔迷离的气象此方法仍旧通用。

利用JS模板引擎

数码载入后,最后都会以某种格局显得在页面上。简单意况,大家或许会这么做:

$('body').append('<div>' + data.name + '</div>');

借使页面逻辑一旦复杂,比如需要有if判断或者多层循环时,那种连接字符串的法门就方枘圆凿了,而这也就催生出了JS模板引擎。

主流的JS模板引擎有underscore.jsJadeEJS等等,可以横向相比较一下这几个JS模板引擎的优缺点

对于相对不难的页面逻辑(只必要接济if和for/each)来说,我更赞成选取轻巧的underscore.js或者JavaScript
Templates

在当下例子中,使用underscore.js生成列表就万分不难了,页面模板为:

<ul data-role="listview" data-inset="true">
<script id="tmpl-restaurants" type="text/template">
    <% _.each(data, function(restaurant) { %>
        <li>
            <a href="#" data-rel="back" data-value="<%- restaurant.name%>"><%- restaurant.name%></a>
        </li>
    <% }); %>
</script>
</ul>

调用引擎

$("#tmpl-restaurants").replaceWith(
    _.template($("#tmpl-restaurants").html(), {
        data : restaurants
    })
);

面向对象与模块化

因而上边那几个工具的组成,大家有了模块的概念,有了模版引擎,有数据的加载。最后仍旧要透过javascript将那整个社团在一起并投入应用所须求的逻辑。为了能最大限度的复用代码,用面向对象的方法去协会内容是比较好的挑选。

JavaScript尽管原生并不援救面向对象,但是仍然得以因而重重情势模拟出面向对象的特色。例子中使用了自己个人比较欣赏的一种办法是:

var foodOrder = function(ui, options){
    //构造函数
    this.init(ui, options);
}
foodOrder.prototype = {
   defaultUI :  {
       form : '#form-order'
   }
   , defaultOptions : {
       debug : false
   }
   , init : function(ui, options){
       this.ui = $.extend({}, this.defaultUI, ui);
       this.options = $.extend({}, this.defaultOptions, options);
   }
}
var order = new foodOrder({
    form : '#real-form'
}, {
    debug : true
});

将页面的UI元素以及配置项目抽象出来,在其实社团对象时则能够透过入口参数复写,可以分离整个项目标逻辑与UI,使拍卖的主意越来越灵敏。

相关文章