【單頁應用】一起來單頁應用吧,實現簡單微博功能!(上)


前言

北上是大城市,魔都這里的節奏確實比成都快得多,在成都老夫一般走的最晚,7點多才撤退,這邊居然8點走了還會有一點點罪惡感!!!

這邊加班就不叫加班啦,幾個同事都是10點左右才走,而且累計工作時長最長的是我們老大!

工作第一周到周三了才把電腦這些事情搞好,期間又要找房子,搬家,所以第一周沒干神馬事情就結束了,老大也沒有分配任務。

第二周一開始就給分配了任務,在新框架上開發,自己也不是很熟悉,所以邊做邊學,第二周就草草的結束了。

周末時候花了兩天時間閱讀同事寫的框架,基本流程算是明白了,明天再請教下同事應該對框架便不陌生了。

在此我心境發生了很大變化,還要感謝之前的經歷以及師傅的教導(http://www.cnblogs.com/aaronjs/

之前我是不太喜歡人家寫的代碼的,想的最多的就是你能寫,我肯定也能寫,於是便寫了。。。。

師傅卻說要真正的成為高手需要閱讀大量代碼,理解別人的思路,這才是王道,所以建議我去讀jquery源碼。

PS:據說師傅沒用多少時間就把司徒寫的框架搞懂了,看來我們差距很大啊

於是,這周先是使用了下同事寫的框架,發現很好用的說,團隊有高手就是好!!!

近期閱讀了其框架所以我們也來試試單頁應用吧,此次的主題是寫一個網頁版的微博功能(因為微博接口完善了),其主要功能:

① 登錄

② 微博廣場內容(列表展示、分頁功能)

③ 微博詳情

----------------也許會出現(測試性目的看工作量)-----------------

④ 發表微博

⑤ 轉播等

博客是一邊思考寫代碼、一邊做文字描述的,有點亂請見諒

整體思路

我們先來構思下整個框架的構成(這里參照了同事的框架):

依賴庫

① requireJS

② jquery

③ underscore.js

PS:本來還想用backbone的,想下還是算了,研究性目的,不要搞的過於復雜吧

程序流程

基本結構我是這么思考的:

① 我們在網頁中輸入URL后便進入Index,以requireJS加載主框架APP

② APP.js做一些初始化操作,其主要功能便是觀察hash變化(URL變化)

若是改變(初始化進入、或者想去其他頁面),便根據規則獲取URL相關信息,初始化新的VIEW,進行顯示,並將URL壓人HASH。

③ 實現VIEW類,其具有基本幾個事件(onCreate、onLoad、onRender、onHide)

一個VIEW可以對應其HTML模板(可以0個或者多個)

④ 事件操作使用jquery的吧

PS:暫時這個樣子吧,我們邊寫邊改,看最后能做到什么樣子,這里還是畫個圖

 

於是,我們開始辛苦的基礎實現吧,畢竟我們現在還神馬都米有!!!

基礎實現之繼承

其實,我們是面向對象的,若是不面向對象應該會被噴吧,於是我們來一起完成一個基礎的繼承類吧:

 1 var b = {};//base
 2 var slice = [].slice;
 3 
 4 b.Class = function (supClass, childAttr) {
 5     //若是傳了第一個類,便繼承之;否則實現新類
 6     if (typeof supClass === 'object') {
 7         childAttr = supClass;
 8         supClass = function () { };
 9     }
10 
11     //定義我們創建的類
12 var newClass = function () { 13 this._propertys_(); 14 this.init.apply(this, arguments); 15 }; 16 newClass.prototype = new supClass(); 17 18 var supInit = newClass.prototype.init || function () { }; 19 var childInit = childAttr.init || function () { }; 20 var _supAttr = newClass.prototype._propertys_ || function () { }; 21 var _childAttr = childAttr._propertys_ || function () { }; 22 23 for (var k in childAttr) { 24 //_propertys_中作為私有屬性 25 childAttr.hasOwnProperty(k) && (newClass.prototype[k] = childAttr[k]); 26 } 27 28 //繼承的屬性有可能重寫init方法 29 if (arguments.length && arguments[0].prototype && arguments[0].prototype.init === supInit) { 30 //重寫新建類,初始化方法,傳入其繼承類的init方法 31 newClass.prototype.init = function () { 32 var scope = this; 33 var args = [function () { 34 supInit.apply(scope, arguments); 35 } ]; 36 childInit.apply(scope, args.concat(slice.call(arguments))); 37 }; 38 } 39 40 //內部屬性賦值 41 newClass.prototype._propertys_ = function () { 42 _supAttr.call(this); 43 _childAttr.call(this); 44 }; 45 46 //成員屬性 47 for (var k in supClass) { 48 supClass.hasOwnProperty(k) && (newClass[k] = supClass[k]); 49 } 50 return newClass; 51 };

代碼還是很好理解的:

PS:我們這個框架建立的所有類,都會經過他!!!

可以傳遞兩個兩個參數:需要繼承的類,新建類的一些參數,這里我們來稍微走一下這個流程:

一個參數

① 我們先初始化一個新的類newClass,並且實例化他,他就會調用本身的兩個方法:

this._propertys_() 用於實例化本身屬性
this.init.apply(this, arguments) init為我們定義的,每個類都會調用的初始化方法

② 讓我們新建的類(newClass)繼承自我們傳入的類

③ 初始化四個變量,顯然這種情況他們都是空函數

④ 將傳入的子屬性的值給予新建類(newClass),這里只有一個參數便忽略吧

⑤ 29行,由於我們的類繼承自supClass,所以這里是滿足條件的,我們看看他里面干了些什么:

他重新定義了,我們創建類的init函數(我們說過他在自身屬性初始化完畢后會執行)。

這里我們為他的參數新增了一個方法,也就是父類的init方法,只不過其this指向為子類。

newClass.prototype.init = function () {
    var scope = this;
    var args = [function () {
        supInit.apply(scope, arguments);
    } ];
    childInit.apply(scope, args.concat(slice.call(arguments)));
};

⑥ 41行,大家注意_propertys_這個函數,他是用於我們為自身屬性賦值的,等下我們舉個例子

⑦ 最后將父類的成員對象賦給子類(因為我們知道成員是不會被繼承的

⑧ 返回新建類,流程結束

情況我這里不多做介紹,我們再說下兩個參數的情況就好。

兩個參數

我們這種場景用的也很多,第一個參數為要繼承的類,第二個參數為新建類的一些初始化信息,我們來走一次流程吧:

① 傳入兩個參數,要繼承的類;新建類的一些屬性和原型方法

② 12行同樣初始化我們的新類,我們實例化時,首先會執行_propertys_方法為自身賦值,然后執行初始化方法(可以傳遞參數哦,參數是new時候傳遞的):

var newClass = function () {
        this._propertys_();
        this.init.apply(this, arguments);//此處的參數請注意
 };

③ 讓新類繼承自傳入的類,此時我們的newClass.prototype其實就擁有很多方法了,包括init與_propertys_

④ 18-21行,初始化四個后面會用到的函數,各位自己注意其意圖

⑤ 23-26行,將將傳入的子屬性對象,賦予newClass,_propertys_也作為原型對象了

⑥ 29-38行,將父類的init作為參數傳遞給子類的init,由子類決定是不是要執行

⑦ 最后的就不多說了

好了,到此各位應該了解他的作用了,我們來一個簡單的例子吧:

簡單例子

實現一個父類Bird(鳥類),及其子類Chicken(雞)Duck(鴨)

首先,我們來看鳥類都能呼吸,都有名字與年齡,所以我們這么干:

 1 var Bird = new b.Class({
 2     //作為自身屬性將被調用,里面必須采用this.XX的方式書寫
 3     _propertys_: function () {
 4         this.name = '鳥類';
 5         this.age = 0;
 6     },
 7     //一定會被執行的初始化方法
 8     init: function () {
 9         alert('一定會執行');
10     },
11     //原型方法
12     breathe: function () {
13         alert('我能呼吸');
14     }
15 });
16 
17 var b = new Bird({des: '測試init方法是否能捕獲參數'});
18 
19 var s = '';

這里我們的init自動執行了,並且,其會用到傳入的參數!!!

這是我們得到的鳥類實例。

好了,我們來實例化一個雞的類,並且他擁有嚎叫打鳴的本領,與性別的特征

 1 var Chicken = new b.Class(Bird, {
 2     _propertys_: function () {
 3         this.sex = '公';
 4     },
 5     //一定會被執行的初始化方法
 6     init: function () {
 7         alert('我是一只雞');
 8     },
 9     //原型方法
10     howl: function () {
11         alert('我能打鳴');
12     }
13 });

這里,父類的init會執行,子類的init會執行,而且,子類的init函數會默認帶上父類的init方法

init: function (superInit) {
    alert('我是一只雞');
},

PS:雖說父類一定會執行,但是若是在此調用父類的superInit方法的話,this指向是子類哦!!!

好了,這個家伙結束,我們進入下一個核心VIEW

基礎實現之視圖

視圖(View)是我們框架的第二個核心,我們來看看到底可能會有哪些view呢?

① 核心之,頁面VIEW

這是最重要的view,他應該包含整個頁面的邏輯,從初始化到最后的展示,事件綁定等應該一應俱全

② 各個級別組件,彈出層,提示層,遮蓋層......

這些都應該是view,但是,先不管那么多,我們來實現我們的核心吧!!!

思考過程

此處使用backbone的視圖到變得簡單了,我們來簡單思考下我們頁面視圖形成的流程吧:

① 用戶第一次進入界面,訪問index,由main導向app,app獲得url,獲取其相關參數(list)

② app根據list實例化view,view調用自身init方法,慢慢開始構建頁面,並會觸發各個流程

PS:在此我認為每個頁面view應該統一繼承一個父類,於是我們來嘗試性試試吧

第一版

  1 b.AbstractView = b.Class({
  2     //基本view應該具有的屬性
  3     _propertys_: function () {
  4         this.id = (new Date()).getTime(); //唯一pageID
  5         this.rootBox = $('body'); //視圖容器
  6         this.root = $('<div/>'); //視圖的根元素,可進行設置
  7         this.header = null;
  8         this.footer = null;
  9         this.template = '';//可能的模板
 10         this.isCreated = false;//是否創建完畢
 11         this.status = b.AbstractView.STATE_NOTCREATE;//當前狀態
 12     },
 13     init: function () {
 14     },
 15     //定義將要用到的事件,其中元素選取都會以root為標准,所以使用內部提供函數吧
 16     events: {
 17         'selector,eventType': 'func'
 18     },
 19     //默認屬性
 20     attrs: {
 21     },
 22     //獲取視圖元素
 23     find: function (selector) {
 24         return this.root.find(selector);
 25     }, 
 26     //創建dom
 27     create: function (opts) {
 28         if(!this.isCreated && this.status != b.AbstractView.STATE_ONCREATE) {
 29             var attr = opts && opts.attr;
 30             var html = this.createHtml();
 31             this.initRoot(attr);//初始化root
 32             this.hide();
 33             this.rootBox.append(this.root);
 34             this.root.html(html);
 35             this.trigger('onCreate');//觸發正在創建事件,其實這里都創建完了
 36             this.status = b.AbstractView.STATE_ONCREATE;
 37             this.isCreated = true;
 38             this.bindEvent();
 39         }
 40     },
 41     //呈現/渲染視圖
 42     show: function (callback) {
 43         if(this.status == b.AbstractView.STATE_ONSHOW) {
 44             return;
 45         }
 46         this.create();
 47         this.root.show();
 48         this.trigger('onShow');
 49         this.status = b.AbstractView.STATE_ONSHOW
 50         callback && (typeof callback == 'function') && callback.call(this);
 51         this.trigger('onLoad');
 52     },
 53     //隱藏dom
 54     hide: function (callback) {
 55         if(!this.root || this.status == b.AbstractView.STATE_ONHIDE) {
 56             return;
 57         }
 58         this.root.hide();
 59         this.trigger('onHide');
 60         this.status = b.AbstractView.STATE_ONHIDE;
 61         callback && (typeof callback == 'function') && callback();
 62     },
 63     //事件綁定
 64     bindEvent: function () {
 65         var events = this.events;
 66         for(var k in events) {
 67             var sec_type = k.replace(/\s/i, '').split(',');
 68             var func = events[k];
 69             if(sec_type &&sec_type.length == 2 && typeof func == 'function') {
 70                 var selector = sec_type[0];
 71                 var type = sec_type[1];
 72                 var scope = this;
 73                 this.find(selector).on(type, function () {
 74                     func.call(scope, $(this));
 75                 })
 76             }
 77         }
 78     },
 79     //此處可以配合模板與相關參數組成html
 80     //解析模板也放到此處
 81     createHtml: function () {
 82         throw new Error('請重新定義createHtml方法');
 83     },
 84     initRoot: function () {
 85         var attr = this.attrs;
 86         if(!attr) {
 87             return;
 88         }
 89         for(var k in attr) {
 90             if(k == 'className') {
 91                 this.root.attr('class', attr[k]);
 92             }else {
 93                 this.root.attr(k, attr[k]);
 94             }
 95         }
 96        this.root.attr('id', this.id);
 97     },
 98     //觸發事件
 99     trigger: function (k, args) {
100         var event = this[k];
101         args = args || [];
102         if(event && typeof event == 'function') {
103             event.apply(this, args)
104         }
105     },
106     setRootBox: function (dom) {
107         this.rootBox = dom;
108     },
109     setAttr: function (k, v) {
110         this.root.attr(k, v);
111     },
112     getAttr: function (k) {
113         return this.root.attr(k);
114     },
115     setCss: function (k, v) {
116         this.root.css(k, v);
117     },
118     getCss: function (k) {
119         return this.root.css(k);
120     },
121     //dom創建后執行
122     onCreate: function () {
123 
124     },
125     //dom創建后數據加載時執行,用於加載后執行我們的邏輯
126     onLoad: function () {
127 
128     },
129     //dom創建后,未顯示
130     onShow: function () {
131 
132     },
133     //dom隱藏前
134     onHide: function () {
135 
136     }
137 });
138 
139 //組件狀態,未創建
140 b.AbstractView.STATE_NOTCREATE = 'notCreate';
141 //組件狀態,已創建但未顯示
142 b.AbstractView.STATE_ONCREATE = 'onCreate';
143 //組件狀態,已顯示
144 b.AbstractView.STATE_ONSHOW = 'onShow';
145 //組件狀態,已隱藏
146 b.AbstractView.STATE_ONHIDE = 'onHide';

代碼注釋寫的很詳細了,我這里就不多說了,我們來用一個例子試一試:

 1 <!DOCTYPE html>
 2 <html xmlns="http://www.w3.org/1999/xhtml">
 3 <head>
 4     <meta charset="utf-8" />
 5     <title></title>
 6     <script src="res/libs/jquery.js" type="text/javascript"></script>
 7     <script src="res/test/c.base.js" type="text/javascript"></script>
 8 </head>
 9 <body>
10 
11 </body>
12 <script type="text/javascript">
13     var PageView = b.Class(b.AbstractView, {
14         _propertys_: function () {
15             this.template = '我是葉小釵';
16         },
17         init: function (superInit) {
18             console.log(superInit);
19             console.log('init');
20         },
21         createHtml: function () {
22             var htm = [
23             '<header>標題</header>',
24             '<div class="main">',
25                 '<input type="text" id="txt" />',
26                 '<input type="button" id="bt" value="點擊我" />',
27                 this.template,
28             '</div>',
29             '<footer>頁尾</footer>'
30             ].join('');
31             return htm;
32         },
33         attrs: {
34             'data-id': 'test',
35             className: 'yexiaoc'
36         },
37         events: {
38             '#bt,click': function (el) {
39                 var txt = this.find('#txt');
40                 alert(txt.val())
41             }
42         },
43         onCreate: function () {
44             console.log('onCreate');
45         },
46         //dom創建后數據加載時執行,用於加載后執行我們的邏輯
47         onLoad: function () {
48             console.log('onLoad');
49         },
50         //dom創建后,未顯示
51         onShow: function () {
52             console.log('onShow');
53         },
54         //dom隱藏前
55         onHide: function () {
56             console.log('onHide');
57         }
58     });
59     var view = new PageView();
60     view.show();
61     var s = '';
62 </script>
63 </html>

初步實現我們的期望

結語

好了,今天暫時到這里,我們明天繼續,希望明天能做完!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM