最近校招要來了,不少大三的同窗必定按捺不住心中的焦躁,其中有期待也有彷徨,或許更多的是些許擔心,最近在開始瘋狂的複習了吧javascript
這裏小釵有幾點建議給各位:php
① 不要看得過重,關心則亂,太緊張反而表現很差css
② 好的選擇比堅持更重要html
這點小釵便深有體會了,由於當年我是搞.net的,憑着這項技能想進bat簡直就是妄想,因而當時我就很是機智的轉了前端,另外一個同窗也很是機智的轉了安卓前端
因此各位想進大公司,還須要提早關注各個公司最大的缺口是什麼,找不許缺口基本無望進大公司的java
③ 積累最重要/沒有積累如今就專精git
想進大公司除了運氣,最重要的仍是平時積累,如果大三以前沒有5W行代碼量的同窗便須要專精一門,不要東搞西搞,最後什麼都不會,那就完了程序員
④ 不要扯設計模式github
這裏雖然有點偏激,可是就個人觀察,我原來的leader、我身邊的同事,咱們公司的架構師,真的沒有幾個徹底理解了設計模式,咱們不少架構師甚至連個依賴圖,時序圖都搞不出來web
因此各位面試的同窗除非真的對設計模式有深刻了解,或者代碼量達到了10-20w行,除此以外不要在面試扯設計模式,確定會中招的
PS:尼瑪,這裏差點忘了加以前,萬一被leader看見了就完了!
最後,進得了大公司的同窗通常以前大學時光沒有怎麼浪費,或者說天資原本就好,進不了的同窗便先積累吧,搞很差哪天就中彩票不用工做了
好了,咱們進入今天的正題,這是與blade框架有關的第三篇博客,第二篇是關於webapp seo難題的解決方案
因爲該方案自己具備必定難度還在實現之中,因此這裏就先出第三篇了......
PS:此文只是我的粗淺的認識,有誤請指正
我原來常與leader討論,javascript以前不適合作大規模應用的一個主要緣由是其沒法分模塊,這個就帶來了多人維護一個文件的難題
程序員每每都是2B,咱們可能不會以爲本身的代碼寫得多好,可是通常會以爲別人的代碼寫得很爛!
因而你會看見一個有趣的現象即是兩個有必定差距的程序員維護了一個js文件後,可能之後他們都不能愉快的玩耍了
讓2個程序員維護一個文件都很是困難了,況且是多個?因此分模塊、分文件是javascript或者說是前端一個極大的進步
他讓前端合做變成了可能,由此纔出現了幾萬甚至幾十萬代碼量的前端應用,這裏requireJS相關的模塊庫功不可沒
這裏咱們先來回顧下以前咱們是怎麼寫代碼的
最初,咱們的應用通常都不復雜,咱們會這樣寫前端代碼:
<body> <div> <span onclick="test()">測試</span> </div> <script type="text/javascript"> function test() { alert('do something'); } </script> </body>
慢慢的咱們感受好像不對,由於B同事好像也有個test方法,因爲B同事比咱們早來幾年咱們也很差噴他,因而只好尋求解決方案,因而出現了命名空間
<div> <span onclick="yexiaochai.test()">測試</span> </div> <script type="text/javascript"> var yexiaochai = { test: function () { alert('do something'); } }; </script>
這裏解決了B同事沖刷我代碼的問題後,忽然B同事便離職了,B同事的代碼交給了咱們,因而咱們很是看不慣其以B開頭的命名空間!
此類狀況也出現與了最初的ASP、JSP甚至現今的PHP,他們有一些共同的特色即是:
① web中的asp代碼與javascript代碼交替,代碼雜亂
② 界面設計與程序設計混雜,維護升級困難
一個經典的例子即是著名論壇框架discuz的源碼(他js其實控制的比較好),看過其源碼的同窗想必對其印象極其深入
動則一個文件成千上萬,內中html php代碼混雜,整個維護閱讀成本很是之高
這個時候因爲社會發展,愈來愈多的複雜需求層出不窮,爲了知足社會的須要,各大框架分分求變,因而有了一些變化
這一次的變化我認爲集中體如今UI分離這塊,裏面作的最成功的固然是ASP.net,javascript有與之對應的struts2
這一次的革新兩大巨頭不在將邏輯操做以及數據庫操做寫在與HTML有關的文件中了,而是寫在與之對應的後臺文件中,這裏最優雅的是.net的codebehind方案
此次的變化倒不是說程序自己發生了什麼變化,事實上程序自己並無發生變化,以小釵熟悉的.net爲例
咱們一次新建頁面會具備兩個文件:
① index.apsx
② index.aspx.cs
<%@ Page Language="C#" AutoEventWireup="true" CodeFile="index.aspx.cs" Inherits="_00綜合_11mvc_index" %>
public partial class _00綜合_10doc_write_index : System.Web.UI.Page { protected void Page_Load(object sender, EventArgs e) { } }
值得關注的是兩個文件的這些代碼,這裏的內部實現咱們不去深究,可是小釵能夠告訴你,最終的編譯會將這兩個文件和到一塊兒,以上的代碼映射即是他們合併的憑據
由於他們最終會和到一塊兒,因此index.apsx與其index.apsx.cs纔會具備方法數據通訊的能力,跨頁面就不行了
UI分離帶來的第二個好處即是經典的.net三層架構以及經典的Java SSH框架,在UI分離以前,這類分離是很差實現的
因而.net與java的代碼能夠基於此一再細分,各個領域的程序員專一點與擅長點徹底分開了,因此不少複雜的軟件出現了
最初.net的作法也帶來了不少質疑,由於懂行的都會知道,這樣的代碼事實上效率低了,理解差了;這裏便和現今javascript的一些特徵驚人的一致
咱們的代碼存在着這樣一種遞進的關係:
① 最簡單的代碼
<span onclick="test()">測試</span>
② 考慮方法重名問題後的代碼
<span onclick="yexiaochai.test()">測試</span>
③ 考慮一個標籤具備多個事件的實現
<body> <div> <span id="test">測試</span> </div> <script type="text/javascript"> var yexiaochai = { test: function () { alert('do something'); } }; document.getElementById('test').addEventListener('click', yexiaochai.test); </script> </body>
以上三種遞進關係其實詮釋了一客觀因素:
業務需求變得複雜、業務複雜後處理業務的方法便多了,因此方法會重複,也是由於邏輯的複雜度致使一個標籤可能具備多個事件
因此,現階段前端代碼複雜度的提高的根本緣由即是原來的寫法不知足需求了,咱們須要這樣寫
以上三種遞進關係只是一個開始,因爲業務邏輯的膨脹,javascript代碼爆炸性的膨脹起來了,因而咱們作了一下事情:
① 將javascript文件放到一個文件裏面
② 一個文件太多很差維護了,因而分紅多個文件
③ 文件太多了,很差管理,因而開始適應模塊化管理工具
以上是前端分模塊的主要緣由
前端作久了咱們會發現,不少時候咱們仍是在操做html,不管是js事件操做,或者css操做,或者數據來了須要改變dom結構,總而言之咱們老是在操做咱們的UI
這個時候咱們也會遇到一種現象是,這裏須要一個信息提示框,那裏也須要一個信息提示框,好像長得差很少
這樣類似的需求積累,咱們又不傻,固然會尋求統一的解決方案,因而便出現了UI組件,UI組件的出現是前端一大進步
這樣前端便不僅是寫點小特效,小驗證的碼農了,搖身一變成爲了高大上的交互設計師,並且還有不少妹子,工做環境好得不得了,想來的同窗請給位私信,發簡歷,工資高,妹子多!!!
咱們剛開始多是這樣作UI的:
function showAlert(msg) { //構架複雜dom結構,略 var el = $('<div class="msg">' + msg + '</div>') //綁定複雜的事件,略 el.click(function () { }); $('body').append(el); } showAlert('史上最強孫組長!');
這裏面涉及到幾個元素:
① 數據 => msg
② 界面 => el
③ 事件 => click
PS:此段虛構
逐漸的,咱們發現這樣寫很不方便,緣由是UED新來一個叫左盟主的抽風給我說什麼語義化,要動個人dom結構!!!
尼瑪,他當我傻啊!HTML有什麼語義化可言嘛!我這邊力排衆議,由於他不懂嘛,他對不懂的事物便比較恐懼,因此我要把他說明白
可是史上最強、宇宙無敵孫組長帶來了CSS天使小靜mm給我語重心長的聊了一下小時代與後會無期的故事與淵源,最後還唱起了女兒情
因而我深深的理解了左盟主是正確的,前端確實應該考慮語義化,因而我決定修改個人DOM結構!!!
真正的修改下來,發現這裏工做量不小:
首先是原來代碼過程重來沒有考慮過dom或者classname會變,這裏一變的直接影響即是我那些可憐的事件綁定所有完蛋
因而我這裏便考慮是否是應該將,表現與行爲分離
這裏的UI操做不該該影響我具體事件邏輯,並且UI樣式的變化是屢見不鮮,就算UED不便,不一樣的業務場景總會要求一點不同的東西,這裏便引入了偉大的模板引擎
前端模板引擎的出現具備劃時代的意義,他是實現表現與行爲分離的基石,這裏再配以zepto的事件機制,最後的結論就是左盟主的需求很簡單
這裏以blade的一個組件代碼爲例:
咱們看到這裏的alert組件的dom結構就是一個簡單的html片斷,因而咱們要改某個dom結構便直接修改即是,不須要修改咱們的js文件
固然這裏的前提是這裏的className映射不能丟,這個映射丟了,事件綁定一塊仍然須要修改
到這裏,咱們是時候進入今天MVC的深刻發掘了
正如.net引入Code Behind爲了解決UI與業務分離同樣,前端javascript也採用了MVC的方式分離UI與業務,前端MVC出現的主要緣由其實即是:
職責分離!
其實小釵以前一直對MVC不太瞭解,不知道應該從哪一個方案來了解MVC,以structs2爲例,我好像就只看到幾個配置向並無看到控制器就完了
再以Backbone爲例,其View的實現以及Model的實現基本處於分離狀態,徹底能夠單獨使用,Router一層看似扮演着控制器的角色可是我這裏依舊以爲他僅僅是路由
再拿小釵最近寫的Blade框架的app模塊,他卻是有點像全局控制器了,控制着各個View的建立、銷燬、通訊,可是這裏的Model好像由不在了
PS:傳統的MVC的控制器是負責轉發請求處理請求,生成View的,這點能夠參考Blade的app
因此要想理解MVC還真不是一件容易的事情,當有人問起什麼是MVC時每每咱們都是一圖打發:
事實上這個模型,在前端來講不多出現,他常常不上缺了一個Controller就是缺了一個Model,而每次問起級別較高的同事也只是將上圖簡單描述一下即只
好像MVC變成了一個玄之又玄的東西,到處透露着只可意會不可言傳的神祕
PS:因此面試的同窗當心了,通常問到什麼設計模式或者MVC要麼這個面試官很牛,要麼狠喜歡裝B,二者對你都不利
所謂MVC即是:
① View就只處理View的事情,其它神馬都不要管
② 數據由Model處理,而且爲View提供渲染須要的數據
③ 因爲後端可能抽風可能將name變成Name坑前端因此會衍生出一套viewModel的東西做爲Model與View的映射
④ 業務代碼集中與viewController,提供事件讓用戶與View交互,入口點爲View
因此通常邏輯是,Controller加載,初始化狀態Model得到數據生成ViewModel,View生成,用戶操做View觸發事件由ViewController處理引發數據更新,而後Model通知view作更新
這裏我認爲實現最爲優雅的是我與原leader的一個開源項目:
https://github.com/leewind/dalmatians
我以爲這個代碼便很好的說明了MVC這個思想,各個模塊只是關心了本身的職責,舉個例子來講:
<!doctype html> <html lang="en"> <head> <meta charset="UTF-8"> <title>ToDoList</title> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <link rel="stylesheet" type="text/css" href="http://designmodo.github.io/Flat-UI/bootstrap/css/bootstrap.css"> <link rel="stylesheet" type="text/css" href="http://designmodo.github.io/Flat-UI/css/flat-ui.css"> <link href="../style/main.css" rel="stylesheet" type="text/css" /> <style type="text/css"> .cui-alert { width: auto; position: static; } .txt { border: #cfcfcf 1px solid; margin: 10px 0; width: 80%; } ul, li { padding: 0; margin: 0; } .cui_calendar, .cui_week { list-style: none; } .cui_calendar li, .cui_week li { float: left; width: 14%; overflow: hidden; padding: 4px 0; text-align: center; } </style> </head> <body> <article id="container"> </article> <script type="text/underscore-template" id="template-ajax-init"> <div class="cui-alert" > <div class="cui-pop-box"> <div class="cui-hd"> <%=title%> </div> <div class="cui-bd"> <div class="cui-error-tips"> </div> <div class="cui-roller-btns" style="padding: 4px; "><input type="text" placeholder="設置最低價 {day: '', price: ''}" style="margin: 2px; width: 100%; " id="ajax_data" class="txt" value="{day: , price: }"></div> <div class="cui-roller-btns"> <div class="cui-flexbd cui-btns-sure"><%=confirm%></div> </div> </div> </div> </div> </script> <script type="text/underscore-template" id="template-ajax-suc"> <ul> <li>最低價:本月<%=ajaxData.day %>號,價格:<%=ajaxData.price %> 元</li> </ul> </script> <script type="text/underscore-template" id="template-ajax-loading"> <span>loading....</span> </script> <script src="../../vendor/underscore-min.js" type="text/javascript"></script> <script src="../../vendor/zepto.min.js" type="text/javascript"></script> <script src="../../src/underscore-extend.js" type="text/javascript"></script> <script src="../../src/util.js" type="text/javascript"></script> <script src="../../src/mvc.js" type="text/javascript"></script> <script type="text/javascript"> //模擬Ajax請求 function getAjaxData(callback, data) { setTimeout(function () { if (!data) { data = {day: 3, price: 20}; } callback(data); }, 1000); } var AjaxView = _.inherit(Dalmatian.View, { _initialize: function ($super) { //設置默認屬性 $super(); this.templateSet = { init: $('#template-ajax-init').html(), loading: $('#template-ajax-loading').html(), ajaxSuc: $('#template-ajax-suc').html() }; } }); var AjaxAdapter = _.inherit(Dalmatian.Adapter, { _initialize: function ($super) { $super(); this.datamodel = { title: '標題', confirm: '刷新數據' }; this.datamodel.ajaxData = {}; }, format: function (datamodel) { //處理datamodel生成viewModel的邏輯 return datamodel; }, ajaxLoading: function () { this.notifyDataChanged(); }, ajaxSuc: function (data) { this.datamodel.ajaxData = data; this.notifyDataChanged(); } }); var AjaxViewController = _.inherit(Dalmatian.ViewController, { _initialize: function ($super) { $super(); //設置基本的屬性 this.view = new AjaxView(); this.adapter = new AjaxAdapter(); this.viewstatus = 'init'; this.container = '#container'; }, //處理datamodel變化引發的dom改變 render: function (data) { //這裏用戶明確知道本身有沒有viewdata var viewdata = this.adapter.getViewModel(); var wrapperSet = { loading: '.cui-error-tips', ajaxSuc: '.cui-error-tips' }; //view具備惟一包裹器 var root = this.view.root; var selector = wrapperSet[this.viewstatus]; if (selector) { root = root.find(selector); } this.view.render(this.viewstatus, this.adapter && this.adapter.getViewModel()); root.html(this.view.html); }, //顯示後Ajax請求數據 onViewAfterShow: function () { this._handleAjax(); }, _handleAjax: function (data) { this.setViewStatus('loading'); this.adapter.ajaxLoading(); getAjaxData($.proxy(function (data) { this.setViewStatus('ajaxSuc'); this.adapter.ajaxSuc(data); }, this), data); }, events: { 'click .cui-btns-sure': function () { var data = this.$el.find('#ajax_data').val(); data = eval('(' + data + ')'); this._handleAjax(data); } } }); var a = new AjaxViewController(); a.show(); </script> </body> </html> 完成HTML
"use strict"; // ------------------華麗的分割線--------------------- // // @description 正式的聲明Dalmatian框架的命名空間 var Dalmatian = Dalmatian || {}; // @description 定義默認的template方法來自於underscore Dalmatian.template = _.template; Dalmatian.View = _.inherit({ // @description 構造函數入口 initialize: function (options) { this._initialize(); this.handleOptions(options); this._initRoot(); }, _initRoot: function () { //根據html生成的dom包裝對象 //有一種場景是用戶的view自己就是一個只有一個包裹器的結構,他不想要多餘的包裹器 this.root = $(this.defaultContainerTemplate); this.root.attr('id', this.viewid); }, // @description 設置默認屬性 _initialize: function () { var DEFAULT_CONTAINER_TEMPLATE = '<section class="view" id="<%=viewid%>"><%=html%></section>'; // @description view狀態機 // this.statusSet = {}; this.defaultContainerTemplate = DEFAULT_CONTAINER_TEMPLATE; // @override // @description template集合,根據status作template的map // @example // { 0: '<ul><%_.each(list, function(item){%><li><%=item.name%></li><%});%></ul>' } // this.templateSet = {}; this.viewid = _.uniqueId('dalmatian-view-'); }, // @description 操做構造函數傳入操做 handleOptions: function (options) { // @description 從形參中獲取key和value綁定在this上 if (_.isObject(options)) _.extend(this, options); }, // @description 經過模板和數據渲染具體的View // @param status {enum} View的狀態參數 // @param data {object} 匹配View的數據格式的具體數據 // @param callback {functiion} 執行完成以後的回調 render: function (status, data, callback) { var templateSelected = this.templateSet[status]; if (templateSelected) { // @description 渲染view var templateFn = Dalmatian.template(templateSelected); this.html = templateFn(data); //這裏減小一次js編譯 // this.root.html(''); // this.root.append(this.html); this.currentStatus = status; _.callmethod(callback, this); return this.html; } }, // @override // @description 能夠被複寫,當status和data分別發生變化時候 // @param status {enum} view的狀態值 // @param data {object} viewmodel的數據 update: function (status, data) { if (!this.currentStatus || this.currentStatus !== status) { return this.render(status, data); } // @override // @description 可複寫部分,當數據發生變化可是狀態沒有發生變化時,頁面僅僅變化的能夠是局部顯示 // 能夠經過獲取this.html進行修改 _.callmethod(this.onUpdate, this, data); } }); Dalmatian.Adapter = _.inherit({ // @description 構造函數入口 initialize: function (options) { this._initialize(); this.handleOptions(options); }, // @description 設置默認屬性 _initialize: function () { this.observers = []; // this.viewmodel = {}; this.datamodel = {}; }, // @description 操做構造函數傳入操做 handleOptions: function (options) { // @description 從形參中獲取key和value綁定在this上 if (_.isObject(options)) _.extend(this, options); }, // @override // @description 設置 format: function (datamodel) { return datamodel; }, getViewModel: function () { return this.format(this.datamodel); }, registerObserver: function (viewcontroller) { // @description 檢查隊列中若是沒有viewcontroller,從隊列尾部推入 if (!_.contains(this.observers, viewcontroller)) { this.observers.push(viewcontroller); } }, unregisterObserver: function (viewcontroller) { // @description 從observers的隊列中剔除viewcontroller this.observers = _.without(this.observers, viewcontroller); }, //統一設置全部觀察者的狀態,由於對應觀察者也許根本不具有相關狀態,因此這裏須要處理 // setStatus: function (status) { // _.each(this.observers, function (viewcontroller) { // if (_.isObject(viewcontroller)) // viewcontroller.setViewStatus(status); // }); // }, notifyDataChanged: function () { // @description 通知全部註冊的觀察者被觀察者的數據發生變化 var data = this.getViewModel(); _.each(this.observers, function (viewcontroller) { if (_.isObject(viewcontroller)) _.callmethod(viewcontroller.update, viewcontroller, [data]); }); } }); Dalmatian.ViewController = _.inherit({ _initialize: function () { //用戶設置的容器選擇器,或者dom結構 this.container; //根元素 this.$el; //必定會出現 this.view; //可能會出現 this.adapter; //初始化的時候便須要設置view的狀態,不然會渲染失敗,這裏給一個默認值 this.viewstatus = 'init'; }, // @description 構造函數入口 initialize: function (options) { this._initialize(); this.handleOptions(options); this._handleAdapter(); this.create(); }, //處理dataAdpter中的datamodel,爲其注入view的默認容器數據 _handleAdapter: function () { //不存在就不予理睬 if (!this.adapter) return; this.adapter.registerObserver(this); }, // @description 操做構造函數傳入操做 handleOptions: function (options) { if (!options) return; this._verify(options); // @description 從形參中獲取key和value綁定在this上 if (_.isObject(options)) _.extend(this, options); }, setViewStatus: function (status) { this.viewstatus = status; }, // @description 驗證參數 _verify: function (options) { //這個underscore方法新框架在報錯 // if (!_.property('view')(options) && (!this.view)) throw Error('view必須在實例化的時候傳入ViewController'); if (options.view && (!this.view)) throw Error('view必須在實例化的時候傳入ViewController'); }, // @description 當數據發生變化時調用onViewUpdate,若是onViewUpdate方法不存在的話,直接調用render方法重繪 update: function (data) { // _.callmethod(this.hide, this); if (this.onViewUpdate) { _.callmethod(this.onViewUpdate, this, [data]); return; } this.render(); // _.callmethod(this.show, this); }, /** * @override */ render: function () { // @notation 這個方法須要被複寫 this.view.render(this.viewstatus, this.adapter && this.adapter.getViewModel()); this.view.root.html(this.view.html); }, _create: function () { this.render(); //render 結束後構建好根元素dom結構 this.view.root.html(this.view.html); this.$el = this.view.root; }, create: function () { //l_wang這塊不是很明白 //是否檢查映射關係,不存在則recreate,可是在這裏dom結構未必在document上 // if (!$('#' + this.view.viewid)[0]) { // return _.callmethod(this.recreate, this); // } // @notation 在create方法調用先後設置onViewBeforeCreate和onViewAfterCreate兩個回調 _.wrapmethod(this._create, 'onViewBeforeCreate', 'onViewAfterCreate', this); }, /** * @description 若是進入create判斷是否須要update一下頁面,sync view和viewcontroller的數據 */ _recreate: function () { this.update(); }, recreate: function () { _.wrapmethod(this._recreate, 'onViewBeforeRecreate', 'onViewAfterRecreate', this); }, //事件註冊點 bindEvents: function (events) { if (!(events || (events = _.result(this, 'events')))) return this; this.unBindEvents(); // @description 解析event參數的正則 var delegateEventSplitter = /^(\S+)\s*(.*)$/; var key, method, match, eventName, selector; //注意,此處作簡單的字符串數據解析便可,不作實際業務 for (key in events) { method = events[key]; if (!_.isFunction(method)) method = this[events[key]]; if (!method) continue; match = key.match(delegateEventSplitter); eventName = match[1], selector = match[2]; method = _.bind(method, this); eventName += '.delegateEvents' + this.view.viewid; if (selector === '') { this.$el.on(eventName, method); } else { this.$el.on(eventName, selector, method); } } return this; }, //取消全部事件 unBindEvents: function () { this.$el.off('.delegateEvents' + this.view.viewid); return this; }, _show: function () { this.bindEvents(); $(this.container).append(this.$el); this.$el.show(); }, show: function () { _.wrapmethod(this._show, 'onViewBeforeShow', 'onViewAfterShow', this); }, _hide: function () { this.forze(); this.$el.hide(); }, hide: function () { _.wrapmethod(this._hide, 'onViewBeforeHide', 'onViewAfterHide', this); }, _forze: function () { this.unBindEvents(); }, forze: function () { _.wrapmethod(this._forze, 'onViewBeforeForzen', 'onViewAfterForzen', this); }, _destory: function () { this.unBindEvents(); this.$el.remove(); // delete this; }, destory: function () { _.wrapmethod(this._destory, 'onViewBeforeDestory', 'onViewAfterDestory', this); } }); 完整MVC想法
<script type="text/underscore-template" id="template-ajax-init"> <div class="cui-alert" > <div class="cui-pop-box"> <div class="cui-hd"> <%=title%> </div> <div class="cui-bd"> <div class="cui-error-tips"> </div> <div class="cui-roller-btns" style="padding: 4px; "><input type="text" placeholder="設置最低價 {day: '', price: ''}" style="margin: 2px; width: 100%; " id="ajax_data" class="txt" value="{day: , price: }"></div> <div class="cui-roller-btns"> <div class="cui-flexbd cui-btns-sure"><%=confirm%></div> </div> </div> </div> </div> </script> <script type="text/underscore-template" id="template-ajax-suc"> <ul> <li>最低價:本月<%=ajaxData.day %>號,價格:<%=ajaxData.price %> 元</li> </ul> </script> <script type="text/underscore-template" id="template-ajax-loading"> <span>loading....</span> </script>
//模擬Ajax請求 function getAjaxData(callback, data) { setTimeout(function () { if (!data) { data = {day: 3, price: 20}; } callback(data); }, 1000); } var AjaxView = _.inherit(Dalmatian.View, { _initialize: function ($super) { //設置默認屬性 $super(); this.templateSet = { init: $('#template-ajax-init').html(), loading: $('#template-ajax-loading').html(), ajaxSuc: $('#template-ajax-suc').html() }; } }); var AjaxAdapter = _.inherit(Dalmatian.Adapter, { _initialize: function ($super) { $super(); this.datamodel = { title: '標題', confirm: '刷新數據' }; this.datamodel.ajaxData = {}; }, format: function (datamodel) { //處理datamodel生成viewModel的邏輯 return datamodel; }, ajaxLoading: function () { this.notifyDataChanged(); }, ajaxSuc: function (data) { this.datamodel.ajaxData = data; this.notifyDataChanged(); } }); var AjaxViewController = _.inherit(Dalmatian.ViewController, { _initialize: function ($super) { $super(); //設置基本的屬性 this.view = new AjaxView(); this.adapter = new AjaxAdapter(); this.viewstatus = 'init'; this.container = '#container'; }, //處理datamodel變化引發的dom改變 render: function (data) { //這裏用戶明確知道本身有沒有viewdata var viewdata = this.adapter.getViewModel(); var wrapperSet = { loading: '.cui-error-tips', ajaxSuc: '.cui-error-tips' }; //view具備惟一包裹器 var root = this.view.root; var selector = wrapperSet[this.viewstatus]; if (selector) { root = root.find(selector); } this.view.render(this.viewstatus, this.adapter && this.adapter.getViewModel()); root.html(this.view.html); }, //顯示後Ajax請求數據 onViewAfterShow: function () { this._handleAjax(); }, _handleAjax: function (data) { this.setViewStatus('loading'); this.adapter.ajaxLoading(); getAjaxData($.proxy(function (data) { this.setViewStatus('ajaxSuc'); this.adapter.ajaxSuc(data); }, this), data); }, events: { 'click .cui-btns-sure': function () { var data = this.$el.find('#ajax_data').val(); data = eval('(' + data + ')'); this._handleAjax(data); } } }); var a = new AjaxViewController(); a.show();
程序的執行流程由控制器發起,控制器至少須要一個View的實例,可能須要一個Model的實例
事件業務所有被控制器負責了,每次View的操做會引發Model的Setter操做從而影響數據模型的變化便會通知其觀察者View作出相應的改變
可是,上述的實如今實際使用中發現並非那麼好用,爲何呢?
分層過細
分層思惟應該大力倡導,可是層次的劃分也有一個度,由於總的來講分層多了業務實現複雜度或者閱讀門檻就會上來
以上述的方案若是去作UI的話是至關得不償失的,一個UI的造成,便須要一個view的實例,一個Model的實例,再變態一點設置會被劃分到不一樣的模塊
這樣的話維護成本以及代碼編寫成本便有所提升,總之分層有理,但也要適度!這個時候便須要改造,改造點集中表現爲:
① 個人View不須要具備狀態值,我就只有一個模塊
② 個人Model不想與人共享,我就放在本身的內部屬性便可
define([], function () { //閉包保存全部UI共用的信息,好比z-index var getBiggerzIndex = (function () { var index = 3000; return function (level) { return level + (++index); }; })(); var UIContainerUtil = (function () { //一個閉包對象存放全部實例化的ui實例 var UIContainer = {}; return { addItem: function (id, ui) { UIContainer[id] = ui; }, removeItem: function (id) { if (UIContainer[id]) delete UIContainer[id]; }, getItem: function (id) { if (id) return UIContainer[id]; return UIContainer; } }; })(); return _.inherit({ //默認屬性 propertys: function () { //模板狀態 this.template = ''; this.datamodel = {}; this.events = {}; this.wrapper = $('body'); this.id = _.uniqueId('ui-view-'); //自定義事件 //此處須要注意mask 綁定事件先後問題,考慮scroll.radio插件類型的mask應用,考慮組件通訊 this.eventArr = {}; //初始狀態爲實例化 this.status = 'init'; // this.availableFn = function () { } }, //綁定事件,這裏應該提供一個方法,代表是insert 或者 push on: function (type, fn, insert) { if (!this.eventArr[type]) this.eventArr[type] = []; //頭部插入 if (insert) { this.eventArr[type].splice(0, 0, fn); } else { this.eventArr[type].push(fn); } }, off: function (type, fn) { if (!this.eventArr[type]) return; if (fn) { this.eventArr[type] = _.without(this.eventArr[type], fn); } else { this.eventArr[type] = []; } }, trigger: function (type) { var _slice = Array.prototype.slice; var args = _slice.call(arguments, 1); var events = this.eventArr; var results = [], i, l; if (events[type]) { for (i = 0, l = events[type].length; i < l; i++) { results[results.length] = events[type][i].apply(this, args); } } return results; }, createRoot: function () { this.$el = $('<div class="view" style="display: none; " id="' + this.id + '"></div>'); }, setOption: function (options) { for (var k in options) { if (k == 'datamodel') { _.extend(this.datamodel, options[k]); continue; } this[k] = options[k] } // _.extend(this, options); }, initialize: function (opts) { this.propertys(); this.setOption(opts); this.resetPropery(); this.createRoot(); //添加系統級別事件 this.addSysEvents(); this.addEvent(); //開始建立dom this.create(); this.initElement(); //將當前的ui實例裝入容器 UIContainerUtil.addItem(this.id, this); }, //返回全部實例化的UI組件集合 getUIContainer: function () { return UIContainerUtil.getItem(); }, //內部重置event,加入全局控制類事件 addSysEvents: function () { if (typeof this.availableFn != 'function') return; this.removeSysEvents(); this.$el.on('click.system' + this.id, $.proxy(function (e) { if (!this.availableFn()) { e.preventDefault(); e.stopImmediatePropagation && e.stopImmediatePropagation(); } }, this)); }, removeSysEvents: function () { this.$el.off('.system' + this.id); }, $: function (selector) { return this.$el.find(selector); }, //提供屬性重置功能,對屬性作檢查 resetPropery: function () { }, //各事件註冊點,用於被繼承 addEvent: function () { }, create: function () { this.trigger('onPreCreate'); // this.$el.html(this.render(this.getViewModel())); this.render(); this.status = 'create'; this.trigger('onCreate'); }, //實例化須要用到到dom元素 initElement: function () { }, render: function (data, callback) { data = this.getViewModel() || {}; var html = this.template; if (!this.template) return ''; if (data) { html = _.template(this.template)(data); } typeof callback == 'function' && callback.call(this); this.$el.html(html); return html; }, //刷新根據傳入參數判斷是否走onCreate事件 //這裏原來的dom會被移除,事件會所有丟失 須要修復***************************** refresh: function (needEvent) { this.resetPropery(); if (needEvent) { this.create(); } else { this.render(); } this.initElement(); if (this.status == 'show') this.show(); }, show: function () { this.wrapper.append(this.$el); this.trigger('onPreShow'); this.$el.show(); this.status = 'show'; this.bindEvents(); this.trigger('onShow'); }, hide: function () { this.trigger('onPreHide'); this.$el.hide(); this.status = 'hide'; this.unBindEvents(); this.removeSysEvents(); this.trigger('onHide'); }, destroy: function () { this.unBindEvents(); this.removeSysEvents(); UIContainerUtil.removeItem(this.id); this.$el.remove(); delete this; }, getViewModel: function () { return this.datamodel; }, setzIndexTop: function (el, level) { if (!el) el = this.$el; if (!level || level > 10) level = 0; level = level * 1000; el.css('z-index', getBiggerzIndex(level)); }, /** * 解析events,根據events的設置在dom上設置事件 */ bindEvents: function () { var events = this.events; if (!(events || (events = _.result(this, 'events')))) return this; this.unBindEvents(); // 解析event參數的正則 var delegateEventSplitter = /^(\S+)\s*(.*)$/; var key, method, match, eventName, selector; // 作簡單的字符串數據解析 for (key in events) { method = events[key]; if (!_.isFunction(method)) method = this[events[key]]; if (!method) continue; match = key.match(delegateEventSplitter); eventName = match[1], selector = match[2]; method = _.bind(method, this); eventName += '.delegateUIEvents' + this.id; if (selector === '') { this.$el.on(eventName, method); } else { this.$el.on(eventName, selector, method); } } return this; }, /** * 凍結dom上全部元素的全部事件 * * @return {object} 執行做用域 */ unBindEvents: function () { this.$el.off('.delegateUIEvents' + this.id); return this; } }); });
核心點變成了幾個屬性:
① template,根據他生成UI
② datamodel,根據他生成viewModel提供給template使用
③ eventArr,業務事件註冊點
這裏簡單以alert組件作說明:
1 define(['UILayer', getAppUITemplatePath('ui.alert')], function (UILayer, template) { 2 3 return _.inherit(UILayer, { 4 propertys: function ($super) { 5 $super(); 6 7 //數據模型 8 this.datamodel = { 9 title: 'alert', 10 content: 'content', 11 btns: [ 12 { name: 'cancel', className: 'cui-btns-cancel' }, 13 { name: 'ok', className: 'cui-btns-ok' } 14 ] 15 }; 16 17 //html模板 18 this.template = template; 19 20 //事件機制 21 this.events = { 22 'click .cui-btns-ok': 'okAction', 23 'click .cui-btns-cancel': 'cancelAction' 24 }; 25 }, 26 27 initialize: function ($super, opts) { 28 $super(opts); 29 }, 30 31 addEvent: function ($super) { 32 $super(); 33 this.on('onCreate', function () { 34 this.$el.addClass('cui-alert'); 35 }); 36 this.maskToHide = false; 37 }, 38 39 okAction: function () { 40 this.hide(); 41 console.log('ok'); 42 }, 43 44 cancelAction: function () { 45 this.hide(); 46 console.log('cancel'); 47 48 }, 49 50 setDatamodel: function (datamodel, okAction, cancelAction) { 51 if (!datamodel) datamodel = {}; 52 _.extend(this.datamodel, datamodel); 53 this.okAction = okAction; 54 this.cancelAction = cancelAction; 55 this.refresh(); 56 } 57 58 }); 59 60 });
<div class="cui-pop-box"> <div class="cui-hd"> <%=title%> </div> <div class="cui-bd"> <div class="cui-error-tips"> <%=content%></div> <div class="cui-roller-btns"> <% for(var i = 0, len = btns.length; i < len; i++ ) {%> <div class="cui-flexbd <%=btns[i].className%>"> <%=btns[i].name%></div> <% } %> </div> </div> </div>
實例化時,alert組件會執行基類的方法,最終反正會執行AbstractView的程序邏輯
首先會根據datamodel以及template生成DOM結構,而後在使用事件代理的方式用eventArr綁定業務事件,具體實現請移步至:
https://github.com/yexiaochai/blade/tree/master/blade/ui
今天又作了一回標題黨,引面試党進來看了看,而後談了本身對前端MVC、分紅的一些理解,最後說了Blade UI一塊的設計思路,但願對各位有幫助
有時候感受知道了卻寫不出來,而後原本也想好好的解析下代碼卻感受沒什麼說的,煩勞各位本身看看吧