【移動前端開發實踐】從無到有(統計、請求、MVC、模組化)H5開發須知

發表於2015-09-28

不知不覺來百度已有半年之久,這半年是996的半年,是孤軍奮戰的半年,是跌跌撞撞的半年,一個字:真的是累死人啦!

我所進入的團隊相當於公司內部創業團隊,人員基本全部是新招的,最初開發時連資料庫都沒設計,當時評審需求的時候居然有一個產品經理拿了一份他設計的資料庫,當時我作為一個前端就驚呆了……

最初的前端只有我1人,這事實上與我想來學習學習的願望是背道而馳的,但既然來都來了也只能獨挑大樑,馬上投入開發,當時涉及的專案有:

① H5 站點

② PC 站點

③ Mis 後臺管理系統

④ 各種百度渠道接入

第一階段的重點為H5站點與APP,我們便需要在20天內從無到有的完成第一版的產品,而最初的Native人力嚴重不足,很多頁面依賴於H5這邊,所以前端除了本身業務之外還得約定與Native的互動細節。

這個情況下根本無暇思考其它框架,熟悉的就是最好的!便將自己git上的開源框架直接拿來用了起來:[置頂]【blade利刃出鞘】一起進入移動端webapp開發吧

因為之前的經驗積累,工程化、Hybrid 互動、各種相容、體驗問題已經處理了很多了,所以基礎架構一層比較完備,又有完善的 UI 元件可以使用,這個是最初的設計構想:

構想總是美好的,而在巨大的業務壓力面前任何技術願景都是蒼白的,最初我在哪裡很傻很天真的用 CSS3 畫圖示,然後產品經理天天像一個蒼蠅一樣在我面前嗡嗡嗡,他們事實上是不關注頁面效能是何物的,我也馬上意識的到工期不足,於是便直接用圖示了!

依賴於完善的框架,20天不到的時間,第一版的專案便結束了,業務程式碼有點不堪入目,頁面級的程式碼也沒有太遵循 MVC 規則,這導致了後續的迭代,全部在那裡操作 dom。

其實初期這樣做問題不大,如果專案比較小(比如什麼一次性的活動頁面)問題也不大,但是核心專案便最好不要這樣玩了,因為新需求、新場景,會讓你在原基礎上不斷的改程式碼,如果頁面沒有一個很好的規範,那麼他將不再穩定,也不再容易維護,如何編寫一個可穩定、擴充套件性高、可維護性高的專案,是我們今天討論的重點。

認真閱讀此文可能會在以下方面對你有所幫助:

文中是我半年以來的一些業務開發經驗,希望對各位有用,也希望各位多多支援討論,指出文中不足以及提出您的一些建議

統計需求

通用統計需求

對於伺服器端來說,後期最重要的莫過於監控日誌,對於前端來說,統計無疑是初期最重要的,通用的統計需求包括:

① PV/UV 統計

② 機型/瀏覽器/系統統計

③ 各頁面載入速度統計

④ 某些按鈕的點選統計

⑤ ……

這類統計直接通過百度統計之類的工具即可,算是最基礎的統計需求。百度產品的文件、支援團隊爛估計是公認的事情了,我便只能挖掘很少一部分用法。但是這類資料也是非常重要了,對於產品甚至是老闆判斷整個產品的發展有莫大的幫助與引導作用,如果產品死了,任何技術都是沒有意義的,所以站點沒有這類統計的速度加上吧!

http://tongji.baidu.com/web/welcome/login

渠道統計

所謂渠道統計便是這次訂單來源是哪裡,就我們產品的渠道有:

① 手機百度 APP 入口(由分為生活+入口、首頁 banner 入口、廣告入口……)

② 百度移動站點入口

③ 百度地圖入口(包括 H5 站點)

④ wise 卡片入口(包括:唯一答案、白卡片、極速版、點到點卡片……)

⑤ 各種大禮包、活動入口

⑥ SEM 入口

⑦ ……

你永遠不能預料到你究竟有多少入口,但是這種渠道的統計的重要性直接關乎了產品的存亡,產品需要知道自己的每次的活動,每次的引流是有意義的,比如一次活動便需要得到這次活動每天產生的訂單量,如果你告訴產品,爺做不到,那麼產品會真叫你爺爺。

當然,渠道的統計前端單方面是完成不了的,需要和伺服器端配合,一般而言可以這樣做,前端與伺服器端約定,每次請求皆會帶特定的引數,我一般會與伺服器約定以下引數:

這個head引數是每次 ajax 請求都會帶上的,而us引數一般由url而來,他要求每次由其它渠道落地到我們的站點一定要帶有us引數,us引數拿到後便是我們自己的事情了,有幾種操作方法:

① 直接種到 cookie,這個需要伺服器端特殊處理

② 存入 localstorage,每次請求拿出來,組裝請求引數

③ 因為我們 H5 站點的每一次跳轉都會經過框架中轉,所以我直接將us資料放到了 url 上,每次跳轉都會帶上,一直到跳出網站。

SEM 需求

SEM 其實屬於渠道需求的一類,這裡會獨立出來是因為,他需要統計的資料更多,還會包含一個投放詞之類的資料,SEM 投放人員需要確切的知道某個投放詞每天的訂單量,這個時候上面的引數可能就要變化了:

這個時候可能便需要一個 extra 的擴充套件欄位記錄投放詞是什麼,當然 SEM 落地到我們網站的特殊引數也需要一直傳下去,這個需要做框架層的處理,這裡順便說下我的處理方案吧

統一跳轉

首先我們 H5 站點基本不關注 SEO,對於 SEO 我們有特殊的處理方案,所以在我們的 H5 站點上基本不會出現a標籤,我們站點的每次跳轉皆是由 js 控制,我會在框架封裝幾個方法處理跳轉:

這樣做的好處是:

① 統一封裝跳轉會讓前端控制力增加,比如 forward 可以是 location 變化,也可以是 pushState/hash 的方式做單頁跳轉,甚至可以做 Hybrid 中多 Webview 的跳轉

② 誠如上述,forward 時可以由 url 獲取渠道引數帶到下一個頁面

③ 統一跳轉也可以統一為站點做一些打點的操作,比如單頁應用時候的統一加統計程式碼

最簡單的理解就是:封裝一個全域性方法做跳轉控制,所有的跳轉由他發出。

請求模組

ajax是前端到伺服器端的基石,但是前端和伺服器端的互動:

如果不寫文件的話,你就等著吧,因為端上是入口,一旦出問題,老闆會直觀認為是前端的問題,如果發現是伺服器的欄位不統一導致,而伺服器端打死不承認,你就等著吧!

無論什麼時候,前端請求模組的設計是非常關鍵的,因為前端只是資料的搬運工,負責展現資料而已:)

封裝請求模組

與封裝統一跳轉一致,所有的請求必須收口,最爛的做法也是封裝一個全域性的方法處理全站請求,這樣做的好處是:

① 處理公共引數

比如每次請求必須帶上上面所述 head 業務引數,便必須在此做處理

② 處理統一錯誤碼

伺服器與前端一般會有一個格式約定,一般而言是這樣的:

比如錯誤碼為1的情況就代表需要登入,系統會引導使用者進入登入頁,比如非0的情況下,需要彈出一個提示框告訴使用者出了什麼問題,你不可能在每個地方都做這種錯誤碼處理吧

③ 統一快取處理

有些請求資料不會經常改變,比如城市列表,比如常用聯絡人,這個時候便需要將之存到 localstorage 中做快取

④ 資料處理、日誌處理

伺服器端給前端的資料可能是鬆散的,前端真實使用時候會對資料做處理,同一請求模組如果在不同地方使用,就需要多次處理,這個是不需要的,比如:

這裡我說下 blade 框架中請求模組的處理:

blade 的請求模組

我們現在站點主要還是源於blade框架,實際使用時候做了點改變,後續會迴歸到 blade 框架,專案目錄結構為:

其中 store 依賴於 storage 模組,是處理 localstorage 快取的,他與 model是獨立的,以下為核心程式碼:

 

 

真實的使用場景業務model首先得做一層業務封裝,然後才是真正的使用:

 

 

複雜的前端頁面

我覺得三端的開發中,前端的業務是最複雜的,因為IOS與Andriod的落地頁往往都是首頁,而前端的落地頁可能是任何頁面(產品列表頁,訂單填寫頁,訂單詳情頁等),因為使用者完全可能把這個url告訴朋友,讓朋友直接進入這個產品填寫頁。

而隨著業務發展、需求迭代,前端的頁面可能更加複雜,最初穩定的頁面承受了來自多方的挑戰。這個情況在我們團隊大概是這樣的:

在第一輪產品做完後,產品馬上安排了第二輪迭代,這次迭代的重點是訂單填寫頁,對訂單填寫有以下需求:

① 新增優惠券功能

② 優惠券在H5站點下預設不使用,在IOS、andriod下預設使用(剛好這個時候IOS還在用H5的頁面囧囧囧)

③ 預設自動填入使用者上一次的資訊(站點常用功能)

這裡1、3是正常功能迭代,但是需求2可以說是IOS APP 暫時使用H5站點的頁面,因為當時IOS已經招到了足夠的人,也正在進行訂單填寫的開發,事實上一個月以後他們APP便換掉了H5的訂單填寫,那麼這個時候將對應IOS的邏輯寫到自己的主邏輯中是非常愚蠢的,而且後續的發展更是超出了所料,因為H5站點的容器變成了:

① IOS APP裝載部分H5頁面

② Andriod APP裝載部分H5頁面

PS:這裡之所以把andriod和ios分開,因為andriod都開發了20多天了,ios才招到一個人,他們對H5頁面的需求完全是兩回事囧!

③ 手機百度裝載H5頁面(基本與H5站點邏輯一致,有一些特殊需求,比如登入、支付需要使用clouda呼叫apk)

④ 百度地圖webview容器

於是整個人就一下傻逼了,因為主邏輯基本相似,總有容器會希望一點特殊需求,從重構角度來說,我們不會希望我們的業務中出現上述程式碼太多的if else;

從效能優化角度來說,就普通瀏覽器根本不需要理睬Hybrid互動相關,這個時候我們完善的框架便派上了用場,抽離公共部分了:

H5仍然只關注主邏輯,並且將內部的每部操作儘可能的細化,比如初始化操作,對某一個按鈕的點選行為等都應該儘可能的分解到一個個獨立的方法中,真實專案大概是這個樣子的:

依賴框架自帶的繼承抽象,以及控制器路由層的按環境載入的機制,可以有效解決此類問題,也有效降低了頁面的複雜度,但是他改變不了頁面越來越複雜的事實,並且這個時候迎來了第三輪迭代:

① 加入保險功能

② H5站點在某些渠道下預設開啟使用優惠券功能(囧囧囧!!!)

③ 限制優惠券必須達到某些條件才能使用

④ 訂單填寫頁作為某一合作方的落地頁,請求引數和url有所變化,但是返回的欄位一致,互動一致……

因為最初20天的慌亂處理,加之隨後兩輪的迭代,我已經在訂單填寫頁中買下了太多坑,而且網頁中隨處可見的dom操作讓程式碼可維護程度大大降低,而點選某一按鈕而導致的連鎖變化經常發生,比如,使用者增減購買商品數量時:

① 會改變本身商品數量的展示

② 會根據當前條件去重新整理優惠卷使用資料

③ 改變支付條上的最終總額

④ ……

於是這次迭代後,你會發現訂單填寫頁尼瑪經常出BUG,每次改了又會有地方出BUG,一段時間不在,同事幫助修復了一個BUG,又引起了其它三個BUG,這個時候迎來了第四輪迭代,而這種種跡象表明:

前端的MVC

不太MVC的做法

如果在你的頁面(會長久維護的專案)中有以下情況的話,也許你應該重構你的頁面或者換掉你框架了:

① 在js中大規模的拼接HTML,比如這樣:

 

對於這個情況,你應該使用前端模板引擎

② 在js中出現大規模的獲取非文字框元素的值

③ 在html頁面中看到了大規模的資料鉤子,比如這個樣子:

④ 你在js中發現,一個資料由js變數可獲取,也可以由dom獲取,並你對從哪獲取資料猶豫不決

⑤ 在你的頁面中,click事件分散到一個頁面的各個地方

⑥ 當你的js檔案超過1000行,並且你覺得沒法拆分

以上種種跡象表明,喲!這個頁面好像要被玩壞了,好像可以用MVC的思想重構一下啦!

什麼是MVC

其實MVC這個東西有點懸,一般人壓根都不知道他是幹嘛的,就知道一個model-view-controller;

知道一點的又說不清楚;

真正懂的人要麼喜歡東扯西扯,要麼不願意寫部落格或者部落格一來便很難,曲高和寡。

所以前端MVC這個東西一直是一個玄之又玄的東西,很多開發了很久的朋友都不能瞭解什麼是MVC。

今天我作為一個自認為懂得一點的人,便來說一說我對MVC在前端的認識,希望對大家有幫助。

前端給大家的認識便是頁面,頁面由HTML+CSS實現,如果有互動便需要JS的介入,其中:

上述例子可能不一定準確,但他可以表達一些中心思想,那就是:

所以,資料才是我們應該關注的核心,這裡回到我們MVC的基本概念:

MVC即Model-View-Controller三個詞的縮寫

Model

是資料模型,是客觀事物的一種抽象,比如機票訂單填寫的常用聯絡人模組便可以抽象為一個Model類,他會有一次航班最多可選擇多少聯絡人這種被當前業務限制的屬性,並且會有增減聯絡人、獲取聯絡人、獲取最大可設定聯絡人等業務資料。

Model應該是一個比較穩定的模組,不會經常變化並且可被重用的模組;當然最重要的是,每一次資料變化便會有一個通知機制,通知所有的controller對資料變化做出響應

View

View就是檢視,在前端中甚至可簡單理解為html模板,Controller會根據資料組裝為最終的html字串,然後展示給我們,至於怎麼展示是CSS的事情,我們這裡不太關注。

PS:一般來說,過於複雜的if else流程判斷,不應該出現在view中,那是controller該做的事情

當然並不是每次model變化controller都需要完整的渲染頁面,也有可能一次model改變,其響應的controller只是操作了一次dom,只要model的controller足夠細分,每個controller就算是在操作dom也是無所謂的

Controller

控制器其實就是負責與View以及Model打交道的,因為View與Model應該沒有任何互動,model中不會出現html標籤,html標籤也不應該出現完整的model對應資料,更不會有model資料的增刪

PS:html標籤當然需要一些關鍵model值用於controller獲取model相關標誌了

這裡拷貝一個圖示來幫助我們解析:

這個圖基本可以表達清楚MVC是幹嘛的,但是卻不能幫助新手很好的瞭解什麼是MVC,因為真實的場景可能是這樣的:

一個model例項化完畢,通知controller1去更新了view

view發生了click互動通過controller2改變了model的值

model馬上通知了controller3、controller4、controller5響應資料變化

所以這裡controller影響的model可能不止一個,而model通知的controller也不止一個,會引起的介面連鎖反應,上圖可能會誤導初學者只有一個controller在做這些事情。

這裡舉一個簡單的例子說明情況:

① 大家看到新浪微博首頁,你發了一條微博,這個時候你關注的好友轉發了該微博

② 伺服器響應這次微博,並且將這次新增微博推送給了你(也有可能是頁面有一個js不斷輪詢去拉取資料),總之最後資料變了,你的微博Model馬上將這次資料變化通知了至少以下響應程式:

1)訊息通知控制器,他引起了右上角訊息變化,使用者看見了有人轉發我的weib

2)微博主頁面顯示多了一條微博,讓我們點選檢視

3)……

這是一條微博新增產生的變化,如果頁面想再多一個模組響應變化,只需要在微博Model的控制器集合中新增一個控制器即可

MVC的實現

千言不如一碼,我這裡臨時設計一個例子並書寫程式碼來說明自己對MVC的認識,,考慮到簡單,便不使用模組化了,我們設計了一個部落格頁面,大概是這個樣子的:

無論什麼功能,都需要第三方庫,我們這裡選擇了:

① zepto

② underscore

這裡依舊用到了我們的繼承機制,如果對這個不熟悉的朋友煩請看看我之前的部落格:【一次面試】再談javascript中的繼承

Model的實現

我們只是資料的搬運工,所以要以資料為先,這裡先設計了Model的基類:

然後我們開始設計真正的部落格相關model:

這個時候再附上業務程式碼:

 

完整程式碼&示例

http://sandbox.runjs.cn/show/bvux03nx

分析

這裡註釋寫的很詳細,例子也很簡單很完整,其實並不需要太多的分析,對MVC還不太理解的朋友可以換自己方式實現以上程式碼,然後再加入評論模組,或者其它模組後,體會下開發難度,然後再用這種方式開發試試,體會不同才能體會真理,道不證不明嘛,這裡的程式碼組成為:

① 公共的繼承方法

② 公共的View抽象類,主要來說完成了view的事件繫結功能,可以將所有click事件全部寫在events中

PS:這個view是我閹割便於各位理解的view,真實情況會比較複雜

③ 公共的Model抽象類,主要完成model的骨架相關,其中比較關鍵的是update後的通知機制

④ 業務model,這個是關於部落格model的功能體現,單純的資料操作

⑤ 業務View,這個為類例項化後執行了show方法,便繫結了各個事件

這裡以一次部落格新增為例說明一下程式流程:

① 使用者填好資料後,點選增加部落格,會觸發相應js函式

② js獲取文字框資料,為model新增資料

③ model資料變化後,分發事件通知各個控制器響應變化

④ 各個controller執行,並根據model產生view的變化

好了,這個例子就到此為止,希望對幫助各位瞭解MVC有所幫助

優勢與不足

對於移動端的頁面來說,一個頁面對應著一個View.js,即上面的業務View,其中model可以完全的分離出來,如果以AMD模組化的做法的話,View.js的體積會非常小,而主要邏輯又基本拆分到了Model業務中,controller做的工作由於前端模板的介入反而變得簡單

不足之處,便是所有的controller全部繫結到了view上,互動的觸發點也全部在view身上,而更好的做法,可能是元件化,但是這類模組包含太多業務資料,做成元件化似乎重用性不高,於是就有了業務元件的誕生。

業務元件&公共頻道

所謂業務元件或者公共頻道都是網站上了一定規模會實際遇到的問題,我這裡舉一個例子:

最初我們是做機票專案於是目錄結構為:

blade 框架目錄

flight 機票業務頻道

static 公共樣式檔案

然後逐漸我們多了酒店專案以及用車專案目錄結構變成了:

blade 框架目錄

car 用車頻道

hotel 酒店頻道

flight 機票業務頻道

static 公共樣式檔案

於是一個比較實際的問題出現了,最初機票頻道的城市列表模組以及登入模組與常用聯絡人模組好像其他兩個頻道也能用,但是問題也出現了:

① 將他們抽離為UI元件,但他們又帶有業務資料

② 其它兩個頻道並不想引入機票頻道的模組配置,而且也不信任機票頻道

這個時候便會出現一個叫公共頻道的東西,他完成的工作與框架類似,但是他會涉及到業務資料,並且除了該公司,也許便不能重用:

blade 框架目錄

common 公共頻道

car 用車頻道

hotel 酒店頻道

flight 機票業務頻道

static 公共樣式檔案

各個業務頻道引入公共頻道的產品便可解決重用問題,但這樣也同時發生了耦合,如果公共頻道的頁面做的不夠靈活可配置,業務團隊使用起來會是一個噩夢!

於是更好的方案似乎是頁面模組化,儘可能的將頁面分為一個個可重用的小模組,有興趣的朋友請到這裡看看:

【前端優化之拆分CSS】前端三劍客的分分合合

【shadow dom入UI】web components思想如何應用於實際專案

網站慢了

關於系統優化的建議我之前寫了很多文章,有興趣的朋友可以移駕至這裡看看:

淺談移動前端的最佳實踐

我這裡補充一點業務優化點:

① ajax請求剝離無意義的請求,命名使用短拼

這條比較適用於新團隊,伺服器端的同事並不會關注網路請求的耗時,所以請求往往又臭又長,一個真實的例子就是,上週我推動伺服器端同事將城市列表的無意義欄位刪除後容量由90k降到了50k,並且還有優化空間!!!

② 工程化打包時候最好採用MD5的方式,這樣可做到比較舒服的application cache效果,十分推崇!

③ ……

結語&核心點

半年了,專案由最初的無趣到現在可以在上面玩MVC、玩ABTesting等高階東西了,而看著產品訂單破一,破百,破千,破萬,雖然很累,但是這個時候還是覺得是值得的。

只可惜我廠的一些制度有點過於噁心,跨團隊交流跟吃屎一樣,工作量過大,工資又低,這些點滴還是讓人感到失望的。

好了,抱怨結束,文章淺談了一些自己對移動端從0到1做業務開發的一些經驗及建議,沒有什麼高深的知識,也許還有很多錯誤的地方,請各位不吝賜教,多多指點,接下來時間學習的重點應該還是IOS,偶爾會穿插MVVM框架(angularJS等)的相關學習,有興趣的朋友可以一起關注,也希望自己儘快打通端到端吧,突破自身瓶頸。

相關文章