[轉(zhuǎn)](HTML5單頁面架構(gòu))backbone + requirejs + zepto + underscore

http://www.cnblogs.com/kenkofox/p/4648472.html
轉(zhuǎn)自 拂曉風(fēng)起蜘澜,方便理解下前端的一些架構(gòu)惧蛹。


首先右冻,來看看整個項目結(jié)構(gòu)滑频。


151507268601017.jpg

跟上一篇angular類似淳地,libs里多了underscore和zepto怖糊。三個根目錄文件:

index.html:唯一的html
main.js:requirejs的配置帅容,程序的入口
router.js:整個app或網(wǎng)站的單頁面路由配置
 

第一步,還是建立單頁面唯一的HTML

<!DOCTYPE html>
<html>
<head lang="en">
    <meta charset="UTF-8">
    <title>Backbone & Requirejs</title>
</head>
<body>
<div id="container"></div>
<script data-baseurl="./" data-main="main.js" src="libs/require.js" id="main"></script>
</body>
</html>

backbone沒有在dom屬性上做文章伍伤,我們還是按原生的或者說熟悉的方法寫東西并徘。這里定義了一個container div作為backbone的視圖。

然后引入requirejs扰魂,data-main表示主程序入口麦乞。

第二步,配置main.js

(function (win) {
    //配置baseUrl
    var baseUrl = document.getElementById('main').getAttribute('data-baseurl');

    /*
     * 文件依賴
     */
    var config = {
        baseUrl: baseUrl,           //依賴相對路徑
        paths: {                    //如果某個前綴的依賴不是按照baseUrl拼接這么簡單阅爽,就需要在這里指出
            zepto: 'libs/zepto.min',
            jquery: 'libs/zepto.min',
            underscore: 'libs/underscore',
            backbone: 'libs/backbone',
            text: 'libs/text'             //用于requirejs導(dǎo)入html類型的依賴
        },
        shim: {                     //引入沒有使用requirejs模塊寫法的類庫路幸。backbone依賴underscore
            'underscore': {
                exports: '_'
            },
            'jquery': {
                exports: '$'
            },
            'zepto': {
                exports: '$'
            },
            'backbone': {
                deps: ['underscore', 'jquery'],
                exports: 'Backbone'
            }
        }
    };

    require.config(config);

    //Backbone會把自己加到全局變量中
    require(['backbone', 'underscore', 'router'], function(){
        Backbone.history.start();   //開始監(jiān)控url變化
    });

})(window);

關(guān)于requirejs的語法,還是不多說付翁,大家自己去官網(wǎng)看吧简肴。這個是基礎(chǔ)。

使用backbone百侧,不得不強(qiáng)調(diào)requirejs的shim配置砰识。backbone這個土匪,要的東西多了佣渴,要給他“鞋”(underscore)辫狼,還要給他美金$(jquery)。

由于終端使用jquery就太龐大了辛润,所以這里做了個小把戲膨处,用zepto充當(dāng)jquery,騙了土匪一把砂竖。用幾張越南盾真椿,戲稱是美金,沒想到土老冒也信了乎澄。

有個地方需要注意的是突硝,

無論在哪里用requirejs引入backbone后,就會多了Backbone和$這兩個全局變量置济,所以后續(xù)再使用backbone就不需要拘束于requirejs的AMD寫法了解恰。適當(dāng)放松透透氣也是好的。

配置好依賴關(guān)系后浙于,就可以引入router护盈,并調(diào)用關(guān)鍵的

Backbone.history.start
開始路由監(jiān)控。

第三步羞酗,配置router腐宋,路由表

define(['backbone'], function () {

    var Router = Backbone.Router.extend({

        routes: {
            'module1': 'module1',
            'module2(/:name)': 'module2',
            '*actions': 'defaultAction'
        },

        //路由初始化可以做一些事
        initialize: function () {
        },

        module1: function() {
            var url = 'module1/controller1.js';
            //這里不能用模塊依賴的寫法,而改為url的寫法,是為了grunt requirejs打包的時候斷開依賴鏈脏款,分開多個文件
            require([url], function (controller) {
                controller();
            });
        },

        //name跟路由配置里邊的:name一致
        module2: function(name) {
            var url = 'module2/controller2.js';
            require([url], function (controller) {
                controller(name);
            });
        },

        defaultAction: function () {
            console.log('404');
            location.hash = 'module2';
        }

    });

    var router = new Router();
    router.on('route', function (route, params) {
        console.log('hash change', arguments);  //這里route是路由對應(yīng)的方法名
    });

    return router;    //這里必須的,讓路由表執(zhí)行
});

Backbone.Router.extend這個語法裤园,相信就不必多說了撤师,說多了也說不清楚,大家去官網(wǎng)才是王道:http://backbonejs.org

backbone的路由寫法跟angular類似拧揽,但對于可選參數(shù)的寫法是不一樣的剃盾。angular使用:param?的方式,而backbone使用(:param)淤袜,哪個方式好痒谴,見仁見智吧。

這里定義了一個默認(rèn)路由铡羡,和兩個業(yè)務(wù)路由积蔚。

原理很簡單,就是遇到module1的哈希(hash)就執(zhí)行后邊這個字符串對應(yīng)的函數(shù)

估計大家早就知道這個玩意烦周。而上述代碼中尽爆,關(guān)鍵不同點是,這里利用了requirejs做了模塊化读慎,路由跳轉(zhuǎn)后做的所有邏輯都在另外的js中定義漱贱。

關(guān)鍵的關(guān)鍵,這里使用了url夭委,而且是獨立變量的方式配置模塊的js幅狮,而不是

        require(['module1/controller1'], function (controller) {
            controller();
        });

目的是grunt做requirejs打包時,能切斷兩側(cè)的js株灸,不要合并在一個大js中崇摄。

再另外,大家可以善用一下router.on('route', function)這個接口蚂且,及時做一下事件解綁和一些清理工作配猫。

第四步,寫一個簡單模塊

controller1.js

define(['module1/view1'], function (View) {

    var controller = function () {
        var view = new View();
        view.render('kenko');
    };
    return controller;
});

view1.js

define(['text!module1/tpl.html'], function (tpl) {

    var View1 = Backbone.View.extend({
        el: '#container',

        initialize: function () {
        },

        render: function (name) {
            this.$el.html(_.template(tpl, {name: name}));
        }
    });

    return View1;
});

tpl.html

<div>
    Here is module 1. My name: <%=name %><br>
    <a href="#module2">turn to module 2</a>
</div>

模版的寫法跟angular不一樣杏死,采用的是更普遍的方式泵肄,jquery、underscore都是這個模式淑翼。簡單說就是<%%>包括js代碼腐巢,用=等號可以直接輸出變量值。

這里做了最簡單的MVC玄括,M只是一個值name冯丙,C就是controller了,V就是view1。

View1的寫法需要遵循Backbone的語法胃惜,不然這里用Backbone就沒意義了泞莉。el指向?qū)?yīng)的視圖dom元素,用的是css選擇器船殉,在View中可以使用this.$el獲取到這個jquery風(fēng)格變量鲫趁。render是自定義的函數(shù)。

到這里利虫,運行程序挨厚,就能看到module1的效果了。

第五步糠惫,再寫第二個模塊疫剃,嚴(yán)格MVC的

model2.js

define([], function () {
    var Model2 = Backbone.Model.extend({

        //模型默認(rèn)的數(shù)據(jù)
        defaults: function () {
            return {
                name: "noname"
            };
        },

        // 定義一些方法
        fetch: function () {
            var o = this;
            //可以做一些http請求
            setTimeout(function(){
                o.set({name:'vivi'});
                o.trigger('nameEvent');     //向view觸發(fā)事件
            }, 1000);
        }

    });

    return Model2;
});

Model的語法也是遵循Backbone要求了,defaults是默認(rèn)屬性值硼讽。讀寫這些屬性巢价,需要通過model.get/set接口,否則就是用toJSON返回整個對象固阁,再不然就解剖式的使用model.attributes.xxx蹄溉。

fetch是自定義方法,模擬http請求您炉,這是很常規(guī)的做法了柒爵,不過這個例子沒使用backbone的rest化接口。

數(shù)據(jù)返回后赚爵,使用backbone內(nèi)建的trigger觸發(fā)事件棉胀,通知監(jiān)聽者,也就是view層了冀膝。backbone跟angular最大區(qū)別就是唁奢,backbone不關(guān)注view層的組件化,更關(guān)注的是model和事件機(jī)制窝剖,而angular則不重點提事件機(jī)制麻掸,采用雙向綁定把數(shù)據(jù)更新的破事隱藏起來。各有各的好處赐纱,見仁見智吧脊奋。

view2.js

define(['text!module2/tpl.html'], function (tpl) {

    var View2 = Backbone.View.extend({
        el: '#container',

        events: {
            'click button': 'clickSpan'     //使用代理監(jiān)聽交互,好處是界面即使重新rander了疙描,事件還能觸發(fā)诚隙,不需要重新綁定。如果使用zepto手工逐個元素綁定起胰,當(dāng)元素刷新后久又,事件綁定就無效了
        },

        initialize: function () {
            this.model.on('nameEvent', this.render, this);      //監(jiān)聽事件
        },

        render: function () {
            this.$el.html(_.template(tpl, {name: this.model.get('name')}));     //類似java的DAO思想,一切通過get set操作
        },

        clickSpan: function (e) {
            alert('you clicked the button');
        }
    });

    return View2;
});

接著地消,我們看看backbone一個典型視圖怎么玩炉峰。先看initialize方法脉执,這個是new View2()時先執(zhí)行的初始化邏輯。

我們在這里監(jiān)聽nameEvent這個消息适瓦,也就是model2拋出的事件。收到這個通知谱仪,就更新界面玻熙。邏輯很簡單。

這里有一個比較好用的events疯攒,交互事件代理機(jī)制嗦随。

我們不需要單獨的寫zepto on對dom分別綁定事件敬尺,只需要在這里配置一個events映射表即可。

click button等同于zepto的

$('button').on('click', function)
這里綁定的就是clickSpan事件砂吞。

這個事件代理機(jī)制署恍,好處是,在路由切換的時候蜻直,可以輕松移除事件監(jiān)聽盯质。

view.undelegateEvents()

tpl.html

<div>
    Here is module 2. My name: <%=name %><br>
    <button>click me!</button>
    <a href="#module1">turn to module 1</a>
</div>

controller2.js

define(['module2/model2', 'module2/view2'], function (Model, View) {

    var controller = function (name) {
        var model = new Model();
        name && model.set({
            name:name               //設(shè)置默認(rèn)的屬性值
        });
        var view = new View({model:model});
        view.render();      //利用Model定義的默認(rèn)屬性初始化界面
        model.fetch();          //拉取cgi等等,獲取數(shù)據(jù)概而,再觸發(fā)事件呼巷,界面收到消息做相應(yīng)的動作
    };

    return controller;
});

controller負(fù)責(zé)的做的事就是揉合數(shù)據(jù)赎瑰,放到view中王悍。先讓view用默認(rèn)數(shù)據(jù)渲染餐曼,再讓model去拉取最新數(shù)據(jù),最后通過事件機(jī)制更新界面源譬。

當(dāng)然,這個controller并不是backbone規(guī)范瓶佳,大家可以盡情發(fā)揮。

最后回到路由表中,當(dāng)hash變成module2時为朋,就執(zhí)行:

    module2: function(name) {
        var url = 'module2/controller2.js';
        require([url], function (controller) {
            controller(name);
        });
    },

至此,簡單的requirejs+backbone框架已經(jīng)完成了习寸。除了router的耦合度很高外,每個模塊邏輯代碼都已經(jīng)獨立霞溪,app可以輕松實現(xiàn)按需加載。

那么追求機(jī)制的騷年鸯匹,要停下來嗎?按這個方案殴蓬,在實際開發(fā)中匿级,多人經(jīng)常會在router這個節(jié)骨眼上打架染厅,這里的配置化還不夠完美。

第六步肖粮,優(yōu)化router,徹底配置化

現(xiàn)有方案的問題是涩馆,router中除了寫路由配置外,還需要添加相應(yīng)的function凌净,這樣既冗余又容易沖突,那么能否監(jiān)聽route事件冰寻,做一個統(tǒng)一的路由處理器?一個處理函數(shù)斩芭,處理全部路由響應(yīng)。

define(['backbone'], function () {

    var routesMap = {
        'module1': 'module1/controller1.js',            //原來應(yīng)該是一個方法名划乖,這里取巧改為模塊路徑
        'module2(/:name)': 'module2/controller2.js',
        '*actions': 'defaultAction'
    };

    var Router = Backbone.Router.extend({

        routes: routesMap,

        defaultAction: function () {
            console.log('404');
            location.hash = 'module2';
        }

    });

    var router = new Router();
    //徹底用on route接管路由的邏輯,這里route是路由對應(yīng)的value
    router.on('route', function (route, params) {
        require([route], function (controller) {
            if(router.currentController && router.currentController !== controller){
                router.currentController.onRouteChange && router.currentController.onRouteChange();
            }
            router.currentController = controller;
            controller.apply(null, params);     //每個模塊約定都返回controller
        });
    });

    return router;
});

上述代碼琴庵,把路由表抽離仰美,目的是可以放到index.html中,可以在服務(wù)器做直出咖杂,保持0緩存,輕松實現(xiàn)對外網(wǎng)版本的控制诉字。

另外Router中,沒有了每個路由對應(yīng)的函數(shù)壤圃,而路由表中的key/value改為真正意義的一個字符串——模塊路徑。

感謝backbone的健壯伍绳,我開始還以為這樣肯定會報錯,結(jié)果backbone沒找到對應(yīng)函數(shù)就停止執(zhí)行了,不錯止毕,贊一個。

沒有了一個個的相應(yīng)函數(shù)扁凛,取而代之的是route事件處理器。

處理器中谨朝,利用了配置表的value,拉取對應(yīng)的模塊字币,并調(diào)用相應(yīng)的controller。有了這個小把戲洗出,大家可以自由發(fā)揮了士复,配置成各種字符串翩活,多個controller集合在一個requirejs模塊中等等。菠镇。。

另外利耍,這里約定controller中有onRouteChange的接口盔粹,用于接收路由切換的通知,好做一些銷毀工作魂毁。

來看看新的controller代碼:

define(['module2/model2', 'module2/view2'], function (Model, View) {

    var controller = function (name) {
        var model = new Model();
        name && model.set({
            name:name               //設(shè)置默認(rèn)的屬性值
        });
        var view = new View({model:model});
        view.render();      //利用Model定義的默認(rèn)屬性初始化界面
        model.fetch();          //拉取cgi等等,獲取數(shù)據(jù)咬崔,再觸發(fā)事件,界面收到消息做相應(yīng)的動作

        controller.onRouteChange = function () {
            console.log('change');  //可以做一些銷毀工作垮斯,例如view.undelegateEvents()
            view.undelegateEvents();
        };
    };

    return controller;
});

至此,大功告成兜蠕,多人開發(fā)中,需要修改路由熊杨,只需要修改一個配置,不在這里寫任何邏輯晶府,利用svn合并功能,輕松完成協(xié)同開發(fā)川陆。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市较沪,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌尸曼,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,946評論 6 518
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件骡苞,死亡現(xiàn)場離奇詭異,居然都是意外死亡解幽,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,336評論 3 399
  • 文/潘曉璐 我一進(jìn)店門躲株,熙熙樓的掌柜王于貴愁眉苦臉地迎上來镣衡,“玉大人霜定,你說我怎么就攤上這事⊥疲” “怎么了?”我有些...
    開封第一講書人閱讀 169,716評論 0 364
  • 文/不壞的土叔 我叫張陵磨德,是天一觀的道長。 經(jīng)常有香客問我典挑,道長,這世上最難降的妖魔是什么您觉? 我笑而不...
    開封第一講書人閱讀 60,222評論 1 300
  • 正文 為了忘掉前任,我火速辦了婚禮琳水,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘在孝。我一直安慰自己,他們只是感情好浑玛,可當(dāng)我...
    茶點故事閱讀 69,223評論 6 398
  • 文/花漫 我一把揭開白布噩咪。 她就那樣靜靜地躺著,像睡著了一般胃碾。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上仆百,一...
    開封第一講書人閱讀 52,807評論 1 314
  • 那天,我揣著相機(jī)與錄音俄周,去河邊找鬼。 笑死峦朗,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的波势。 我是一名探鬼主播橄维,決...
    沈念sama閱讀 41,235評論 3 424
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼争舞!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起竞川,我...
    開封第一講書人閱讀 40,189評論 0 277
  • 序言:老撾萬榮一對情侶失蹤蕉汪,失蹤者是張志新(化名)和其女友劉穎流译,沒想到半個月后者疤,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體福澡,經(jīng)...
    沈念sama閱讀 46,712評論 1 320
  • 正文 獨居荒郊野嶺守林人離奇死亡驹马,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,775評論 3 343
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了糯累。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,926評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡效拭,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出胖秒,到底是詐尸還是另有隱情,我是刑警寧澤阎肝,帶...
    沈念sama閱讀 36,580評論 5 351
  • 正文 年R本政府宣布,位于F島的核電站风题,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏沛硅。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 42,259評論 3 336
  • 文/蒙蒙 一摇肌、第九天 我趴在偏房一處隱蔽的房頂上張望桂塞。 院中可真熱鬧弟头,春花似錦涉茧、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,750評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至歧焦,卻和暖如春肚医,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背肠套。 一陣腳步聲響...
    開封第一講書人閱讀 33,867評論 1 274
  • 我被黑心中介騙來泰國打工你稚, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人刁赖。 一個月前我還...
    沈念sama閱讀 49,368評論 3 379
  • 正文 我出身青樓乾闰,卻偏偏與公主長得像盈滴,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子巢钓,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,930評論 2 361

推薦閱讀更多精彩內(nèi)容