Web應用開發中的幾個問題
Introduction
由於Ajax技術在Gmail中的成功應用和高效能的V8引擎的推出使得編寫Web應用變得流行 起來,使用前端技術也可以編寫具有複雜互動的應用。相對於native應用,Web應用具 有如下優點:
- 跨平臺,開發和維護成本低;
- 升級和釋出方便,沒有版本的概念,隨時隨地釋出,使用者沒有感知,不需要安裝;
- 響應式設計(Responsive Design)使得Web應用可以跨平臺,同一份程式碼自適應各種 螢幕大小
- 即使最終不採用Web應用方案,也很適合開發原型
當然,Web應用也不是沒有缺點。由於不同平臺和廠商的瀏覽器並不完全一樣,跨平臺 也有一些相容成本。另外,Web應用的效能不如native應用,互動有時候不是很流暢, 再加上HTML5的API上的限制,使得有些功能採用Web應用不太合適。由於這些原因,結 合兩者優點的混合方案變得流行起來(比如微信、手機QQ和手機QQ瀏覽器中會嵌入一 些Web頁面)。
根據筆者的開發經驗,下面總結一些Web應用開發過程中的要面臨的幾個問題。
模組化程式設計
模組化程式設計是編寫大規模應用必不可少的一個特性,與其它主流的程式語言相比 Javascript沒有對模組提供直接的支援,更不用說維護模組之間的依賴關係,這使得維 護Javascript程式碼變得異常困難,在<script>標籤中包含程式碼的順序需要人工維護。
要支援模組化程式設計必須解決兩個問題:
- 支援編寫模組併為模組命名,防止名字衝突和全域性變數的使用;
- 支援顯示指定模組之間的依賴關係,並在程式執行時自動載入依賴的模組。
Douglas Crockford在”Javascript: The Good Parts”一書中提出的Module Pattern利 用Javascript的閉包技術來模擬模組的概念,防止名字衝突和全域性變數的使用。這解 決了第一個問題。
var moduleName = function () { // Define private variables and functions var private = ... // Return public interface. return { foo: ... }; }();
為了解決第二個問題CommonJS組織定義了 AMD規範方便 開發者顯示指定模組之間的依賴關係,並在需要時載入依賴的模組。 RequireJS是AMD規範的一個比較流行的實現。
首先我們在a.js中定義模組A.
define(function () { return { color: "black", size: 10 }; });
然後定義模組B依賴模組A.
define(["a"], function (A) { // ... });
當模組B執行時RequireJS保證模組A已被載入。具體細節可參考RequireJS官方文 檔。
指令碼載入
最簡單的指令碼載入方式是放在<head>載入。
<head> <script src="base.js" type="text/javascript"></script> <script src="app.js" type="text/javascript"></script> </head>
其缺點是:
- 載入和解析是順序是同步執行的,先下載base.js然後解析和執行,然後再下載 app.js;
- 載入指令碼時還會阻塞對<script>之後的DOM元素的渲染。
為了緩解這些問題,現在的普遍做法是將<script>放在<body>的底部。
<script src="base.js" type="text/javascript"></script> <script src="app.js" type="text/javascript"></script> </body>
但並不是所有的指令碼都可以放在<body>的底部,比如有些邏輯要在頁面渲染時執行, 不過大多數指令碼沒有這樣的要求。
將指令碼放在<body>底部仍然沒有解決順序下載的問題,一些瀏覽器廠商也意識到了 這個問題並開始支援非同步下載。HTML5也提供了標準的解決方案:
<script src="base.js" type="text/javascript" async></script> <script src="app.js" type="text/javascript" async></script>
標上async屬性的指令碼表明你沒有在裡面使用document.write之類的程式碼。瀏覽器 將非同步下載和執行這些指令碼,並且不會組織DOM樹的渲染。但是這會導致另一個問題: 由於是非同步執行,app.js可能在base.js之前執行,如果它們之間有依賴關係這將 導致錯誤。
講到這裡從開發者角度來看我們其實需要的是這些特性:
- 非同步下載,不要阻塞DOM的渲染;
- 按照模組的依賴關係解析和執行指令碼。
所以指令碼的載入其實需要與模組化程式設計問題結合起來解決。RequireJS不僅記錄了模 塊之間的依賴關係,並且提供了根據依賴關係的按需載入和執行(詳情請參考 RequireJS官方文件)。
關於指令碼載入的更多方案請看 這裡.
靜態資原始檔的部署
這裡的靜態資原始檔是指CSS、Javascript和CSS需要的一些圖片檔案。它們的部署需 要考慮兩個問題:
- 下載速度
- 版本管理
靜態資原始檔的一個特點變化不頻繁,且與使用者身份無關(即與Cookie無關),因此 很適合快取。另一方面,一旦靜態資原始檔變化時,瀏覽器必須從Web伺服器下載最新 的版本。當釋出新版本的Web應用時,並不是所有使用者馬上就用上新版本,老版本和新 版本將會共存,這就涉及到版本匹配問題。老版本的應用需要下載老版本的CSS和 Javascript,新版本的應用需要下載新版本的靜態資源。
- 為了防止版本不一致,每當釋出新版本的應用時靜態資原始檔都需要改名,讓舊的 HTML引用舊的靜態檔案,新的HTML引用新的靜態檔案。一個常見辦法就是在檔名 中加時間戳;
- 為了防止懸掛引用,資原始檔應該比HTML先發布。
上述方案可以解決版本問題,這樣每個靜態檔案的快取時間可以設定得任意大,防止 重複下載,同時在新版本釋出時瀏覽器將及時更新。
為解決下載速度問題,可以考慮以下幾個方案:
- 合併靜態檔案以免檔案數量過多,過多的檔案需要更多的連線來下載,瀏覽器通常 對同一個域名的連線數量有限制;
- 壓縮靜態檔案;為了可讀性,CSS和Javascript通常有很多空行、縮排和註釋,這 些在釋出時都可以去掉;
- 靜態檔案通常與Cookie沒有關係,所以為了減小傳輸大小和增加快取命中率(快取 的key需要考慮Cookie),靜態檔案最好託管在沒有Cookie的域名上;
最後也是最重要的,要使上述過程自動化。
MVC程式設計模型
Web應用採用的是事件驅動程式設計模型,與native應用是一樣的,區別僅在於基礎設施提 供的API不一樣。UI程式設計通常採用MVC設計模式,以流行的 Backbone.js為例包括如下部分:
-
Model
- 資料的唯一來源
- 負責獲取和儲存資料
- 可提供快取機制
- 資料變化時通過事件通知其它物件
-
View
- 負責渲染
- 監聽UI事件和Model事件並重繪UI
- 渲染結果取決於兩類資料:Model和UI互動狀態
- UI的互動狀態通常存在View物件中,有時候為了方便也存在DOM樹節點中
- 為了降低渲染成本,儘量減少需要渲染的區域,每次當資料變化時只渲染受影響 的區域
-
Router
- 負責監聽URL的變化,並通知相應的View物件渲染頁面
為了有效地使用MVC,有幾個問題需要注意。
Model應與View完全隔離
Model僅提供資料的訪問,不應該依賴View,因此Model不應該知道View的存在。所以 Model不能持有對任何View物件的引用。Model的資料發生變化時只能通過事件通知 View.
View在初始化時採用委派方式監聽UI事件
這裡有兩個關鍵點:
- 在初始化時監聽事件var View = Backbone.View.extend({ initialize: function () { this.$el.on(‘click’, ‘#id’, function () { // … }); } });
除了一些特殊情況外(請看下文),所有UI事件都應該在View初始化時初始化,防止同 一個事件被繫結多次。即使有些事件是動態監聽的(有時候需要監聽,有時候有不需要 監聽,比如有些按鈕有時候是有效的,有時候又無效),也需要在初始化時監聽,然後 在事件回撥函式裡判斷是否需要處理。這樣邏輯更簡單,更容易維護。
- 採用委派方式監聽UI事件
關於委派方式監聽請參考jQuery文件.
上面已強調要在初始化時監聽事件,但是初始化時需要監聽的DOM節點可能還不存在, 所以沒法直接繫結事件,只能採用委派方式。不過採用委派方式要求事件可以冒泡。
對於那些沒法冒泡的事件(比如<img>的load事件)只能在保證其存在的情況下直 接繫結,而不一定要在初始化時繫結。
複雜的View組織成樹形層次結構
函式太大了需要拆分成幾個子函式。同樣,View的邏輯如果過於複雜也應根據頁面結 構拆成幾個子View:
- 父View通過引用訪問子View,但是子View不應該持有父View的引用;
- 子View只負責自己區域的渲染,其它區域由父View負責渲染;
- 父View通過函式呼叫訪問子View的功能,子View通過事件與父View通訊;
- 子View之間不能直接通訊。
其它技巧可檢視 Backbone技巧與模式.
離線應用快取
為使Web應用體驗更加流暢,可考慮使用HTML5離線應用快取,不過有以下幾點需要注 意:
- 不要將離線應用快取與HTTP快取機制搞混淆,前者是HTML5引入的新特性,與HTTP緩 存機制是相互獨立並存的;
- Cache manifest檔案不應被HTTP快取太久(通過HTTP頭Cache-Control控制快取 時間),否則釋出新版後瀏覽器不會及時監測到變化並下載新檔案;
- 在Cache manifest檔案的NETWORK節放一個*,否則沒有列在這個檔案的資源不 會被請求;
- 不適合快取的請求最好都放在NETWORK節;
- 如果之前使用過離線應用快取現在不想再使用了,從<html>刪除manifest屬性, 併傳送404響應給manifest檔案請求。僅僅刪除manifest屬性是沒有效的。
線上錯誤報告
Javascript是一個動態語言,許多檢查都是在執行時執行的,所以大多數錯誤只有執 行到的時候才能檢查到,只能在釋出前通過大量測試來發現。即使這樣仍可能有少數 沒有執行到的路徑有錯誤,這隻能通過線上錯誤報告來發現了。
window.onerror = function (errorMsg, fileLoc, linenumber) { var s = 'url: ' + document.URL + '\nfile: ' + fileLoc + '\nline number: ' + linenumber + '\nmessage: ' + errorMsg; Log.error(s); // 發給伺服器統計監控 console.log(s); };
通常線上的Javascript都是經過了合併和壓縮的,上報的檔名和行號基本上沒法對 應到原始碼,對查錯幫助不是很大。不過最新版的Chrome支援在onerror的回撥函式 中獲取出錯時的棧軌跡:window.event.error.stack.
相關文章
- 開發Web應用程式中Cookie使用的問題 (轉)WebCookie
- web應用中的路徑問題Web
- WEB應用開發中的ServletWebServlet
- 移動 web 開發幾個明顯的相容性問題Web
- 微信小程式開發中遇到的幾個小問題微信小程式
- apache web 中的CGI應用問題(轉)ApacheWeb
- 開發以太坊遇到的幾個問題
- 今年安卓開發中碰到的幾個稀奇古怪的問題安卓
- 開發網校原始碼時應該注意的幾個問題原始碼
- 求助:Spring web framework(jpetstore)中幾個簡單的問題SpringWebFramework
- 怎麼處理WEB應用中的JAVA多執行緒問題(併發問題)WebJava執行緒
- 做SAP開發的必看的幾個問題(轉)
- Web 開發中的檔案下載問題Web
- Typora 使用中的幾個問題
- 提高Web應用程式開發的7個技巧Web
- C#開發中,學習整理的 New 的幾個常見問題C#
- Web前端開發應該避免的幾個思維誤區Web前端
- 開發Web應用Web
- 一個關於 Web 應用國際化的問題Web
- 多重web應用中webapp. root重用的問題WebAPP
- Pentaho 使用中發現的幾個問題和解決方法
- azkaban 安裝中的幾個問題
- WEB應用訪問緩慢的問題定位Web
- web開發安全框架中的Apache Shiro的應用Web框架Apache
- Web 開發中應用 HTML5 技術的10個例項教程WebHTML
- 關於大資料在藍芽系統中的應用的幾個問題的答案大資料藍芽
- 求職Python開發,面試官最喜歡問的幾個問題求職Python面試
- 開發和部署一個簡單的Clojure Web應用Web
- 10個優秀的移動Web應用開發框架Web框架
- PHP+新浪微博開放平臺+新浪雲平臺(SAE)開發微博應用——必須交待的幾個問題PHP
- uniapp開發企業微信應用中的定位問題記錄APP
- rss在web開發過程中的全方位應用Web
- Web前端開發最好用的幾個WebGL框架Web前端框架
- WEB開發者應該反問自己的10個問題Web
- web開發的一些問題Web
- 購買硬碟應該注意的幾個問題硬碟
- 一個jboss的應用問題
- Node助力Web應用開發——在新的開發平臺,打造高效能Web應用Web