隨着互聯網的飛速發展,前端開發愈來愈複雜。本文將從實際項目中遇到的問題出發,講述模塊化能解決哪些問題,以及如何使用 Sea.js 進行前端的模塊化開發。css
咱們從一個簡單的習慣出發。我作項目時,經常會將一些通用的、底層的功能抽象出來,獨立成一個個函數,好比前端
function each(arr) { // 實現代碼
} function log(str) { // 實現代碼
}
並像模像樣地把這些函數統一放在 util.js 裏。須要用到時,引入該文件就行。這一切工做得很好,同事也很感激我提供了這麼便利的工具包。直到團隊愈來愈大,開始有人抱怨。node
小楊:我想定義一個 each 方法遍歷對象,但頁頭的 util.js 裏已經定義了一個,個人只能叫 eachObject 了,好無奈。git
小高:我自定義了一個 log 方法,爲何小明寫的代碼就出問題了呢?誰來幫幫我。程序員
抱怨愈來愈多。團隊通過一番激烈的討論,決定參照 Java 的方式,引入命名空間來解決。因而 util.js 裏的代碼變成了github
var org = {}; org.CoolSite = {}; org.CoolSite.Utils = {}; org.CoolSite.Utils.each = function (arr) { // 實現代碼
}; org.CoolSite.Utils.log = function (str) { // 實現代碼
};
不要認爲上面的代碼是爲了寫這篇文章而故意捏造的。將命名空間的概念在前端中發揚光大,首推 Yahoo! 的 YUI2 項目。下面是一段真實代碼,來自 Yahoo! 的一個開源項目。後端
if (org.cometd.Utils.isString(response)) { return org.cometd.JSON.fromJSON(response); } if (org.cometd.Utils.isArray(response)) { return response; }
經過命名空間,的確能極大緩解衝突。但往往看到上面的代碼,都忍不住充滿同情。爲了調用一個簡單的方法,須要記住如此長的命名空間,這增長了記憶負擔,同時剝奪了很多編碼的樂趣。api
做爲前端業界的標杆,YUI 團隊下定決心解決這一問題。在 YUI3 項目中,引入了一種新的命名空間機制。瀏覽器
YUI().use('node', function (Y) { // Node 模塊已加載好 // 下面能夠經過 Y 來調用
var foo = Y.one('#foo'); });
YUI3 經過沙箱機制,很好的解決了命名空間過長的問題。然而,也帶來了新問題。性能優化
YUI().use('a', 'b', function (Y) { Y.foo(); // foo 方法到底是模塊 a 仍是 b 提供的? // 若是模塊 a 和 b 都提供 foo 方法,如何避免衝突?
});
看似簡單的命名衝突,實際解決起來並不簡單。如何更優雅地解決?咱們按下暫且不表,先來看另外一個常見問題。
繼續上面的故事。基於 util.js,我開始開發 UI 層通用組件,這樣項目組同事就不用重複造輪子了。其中有一個最被你們喜歡的組件是 dialog.js,使用方式很簡單。
<script src="util.js"></script>
<script src="dialog.js"></script>
<script> org.CoolSite.Dialog.init({ /* 傳入配置 */ }); </script>
但是不管我怎麼寫文檔,以及多麼鄭重地發郵件宣告,時不時總會有同事來詢問爲何 dialog.js 有問題。經過一番排查,發現致使錯誤的緣由常常是
<script src="dialog.js"></script>
<script> org.CoolSite.Dialog.init({ /* 傳入配置 */ }); </script>
在 dialog.js 前沒有引入 util.js,所以 dialog.js 沒法正常工做。一樣不要覺得我上面的故事是虛構的,在我待過的公司裏,至今依舊有相似的腳本報錯,特別是在各類快速製做的營銷頁面中。
上面的文件依賴還在可控範圍內。當項目愈來愈複雜,衆多文件之間的依賴常常會讓人抓狂。下面這些問題,我相信天天都在真實地發生着。
以上不少問題都是由於文件依賴沒有很好的管理起來。在前端頁面裏,大部分腳本的依賴目前依舊是經過人肉的方式保證。當團隊比較小時,這不會有什麼問題。當團隊愈來愈大,公司業務愈來愈複雜後,依賴問題若是不解決,就會成爲大問題。
文件的依賴,目前在絕大部分類庫框架裏,好比國外的 YUI3 框架、國內的 KISSY 等類庫,目前是經過配置的方式來解決。
YUI.add('my-module', function (Y) { // ...
}, '0.0.1', { requires: ['node', 'event'] });
上面的代碼,經過 requires
等方式來指定當前模塊的依賴。這很大程度上能夠解決依賴問題,但不夠優雅。當模塊不少,依賴很複雜時,煩瑣的配置會帶來很多隱患。
命名衝突和文件依賴,是前端開發過程當中的兩個經典問題。下來咱們看如何經過模塊化開發來解決。爲了方便描述,咱們使用 Sea.js 來做爲模塊化開發框架。
Sea.js 是一個成熟的開源項目,核心目標是給前端開發提供簡單、極致的模塊化開發體驗。這裏很少作介紹,有興趣的能夠訪問 seajs.org 查看官方文檔。
使用 Sea.js,在書寫文件時,須要遵照 CMD (Common Module Definition)模塊定義規範。一個文件就是一個模塊。
前面例子中的 util.js 變成
define(function(require, exports) { exports.each = function (arr) { // 實現代碼
}; exports.log = function (str) { // 實現代碼
}; });
經過 exports
就能夠向外提供接口。這樣,dialog.js 的代碼變成
define(function(require, exports) { var util = require('./util.js'); exports.init = function() { // 實現代碼
}; });
關鍵部分到了!咱們經過 require('./util.js')
就能夠拿到 util.js 中經過 exports
暴露的接口。這裏的require 能夠認爲是 Sea.js 給 JavaScript 語言增長的一個語法關鍵字,經過 require
能夠獲取其餘模塊提供的接口。
這其實一點也不神奇。做爲前端工程師,對 CSS 代碼必定也不陌生。
@import url("base.css"); #id { ... } .class { ... }
Sea.js 增長的 require
語法關鍵字,就如 CSS 文件中的 @import
同樣,給咱們的源碼賦予了依賴引入功能。
若是你是後端開發工程師,更不會陌生。Java、Python、C# 等等語言,都有 include
、import
等功能。JavaScript 語言自己也有相似功能,但目前還處於草案階段,須要等到 ES6 標準獲得主流瀏覽器支持後才能使用。
這樣,在頁面中使用 dialog.js 將變得很是簡單。
<script src="sea.js"></script>
<script> seajs.use('dialog', function(Dialog) { Dialog.init(/* 傳入配置 */); }); </script>
首先要在頁面中引入 sea.js 文件,這通常經過頁頭全局把控,也方便更新維護。想在頁面中使用某個組件時,只要經過 seajs.use
方法調用。
好好琢磨以上代碼,我相信你已經看到了 Sea.js 帶來的兩大好處:
一、經過 exports
暴露接口。這意味着不須要命名空間了,更不須要全局變量。這是一種完全的命名衝突解決方案。
二、經過 require
引入依賴。這可讓依賴內置,開發者只需關心當前模塊的依賴,其餘事情 Sea.js 都會自動處理好。對模塊開發者來講,這是一種很好的關注度分離,能讓程序員更多地享受編碼的樂趣。
除了解決命名衝突和依賴管理,使用 Sea.js 進行模塊化開發還能夠帶來不少好處:
一、模塊的版本管理。經過別名等配置,配合構建工具,能夠比較輕鬆地實現模塊的版本管理。
二、提升可維護性。模塊化可讓每一個文件的職責單一,很是有利於代碼的維護。Sea.js 還提供了 nocache、debug 等插件,擁有在線調試等功能,能比較明顯地提高效率。
三、前端性能優化。Sea.js 經過異步加載模塊,這對頁面性能很是有益。Sea.js 還提供了 combo、flush 等插件,配合服務端,能夠很好地對頁面性能進行調優。
四、跨環境共享模塊。CMD 模塊定義規範與 Node.js 的模塊規範很是相近。經過 Sea.js 的 Node.js 版本,能夠很方便實現模塊的跨服務器和瀏覽器共享。
模塊化開發並非新鮮事物,但在 Web 領域,前端開發是新生崗位,一直處於比較原始的刀耕火種時代。直到最近兩三年,隨着 Dojo、YUI三、Node.js 等社區的推廣和流行,前端的模塊化開發理念才逐步深刻人心。
前端的模塊化構建可分爲兩大類。一類是以 Dojo、YUI三、國內的 KISSY 等類庫爲表明的大教堂模式。在大教堂模式下,全部組件都是顆粒化、模塊化的,各組件之間層層分級、環環相扣。另外一類是以 jQuery、RequireJS、國內的 Sea.js、OzJS 等類庫爲基礎的集市模式。在集市模式下,全部組件彼此獨立、職責單一,各組件經過組合鬆耦合在一塊兒,協同完成開發。
這兩類模塊化構建方式各有應用場景。從長遠來看,小而美更具有寬容性和競爭力,更能造成有活力的生態圈。
總之,模塊化能給前端開發帶來不少好處。若是你尚未嘗試,不妨從試用 Sea.js 開始。
若是你聽過js模塊化這個東西,那麼你就應該聽過或CommonJS或AMD甚至是CMD這些規範咯,我也聽過,但以前也真的是聽聽而已。如今就看看吧,這些規範究竟是啥東西,幹嗎的。
1、CommonJS
CommonJS就是爲JS的表現來制定規範,由於js沒有模塊的功能因此CommonJS應運而生,它但願js能夠在任何地方運行,不僅是瀏覽器中。
CommonJS能有必定的影響力,我以爲絕對離不開Node的人氣,不過喔,Node,CommonJS,瀏覽器甚至是W3C之間有什麼關係呢,我找到了個貼切的圖:
|--------------------瀏覽器----- ------------------| |--------------------------CommonJS----------------------------------|
| BOM | | DOM | | ECMAScript | | FS | | TCP | | Stream | | Buffer | |........|
|---------W3C----------| |-----------------------------------------------Node--------------------------------------------------|
CommonJS定義的模塊分爲:{模塊引用(require)} {模塊定義(exports)} {模塊標識(module)}
require()用來引入外部模塊;exports對象用於導出當前模塊的方法或變量,惟一的導出口;module對象就表明模塊自己。
好比說咱們就能夠這樣用了:
//sum.js
exports.sum = function(){...作加操做..}; //calculate.js
var math = require('sum'); exports.add = function(n){ return math.sum(val,n); };
雖然說Node遵循CommonJS的規範,可是相比也是作了一些取捨,填了一些新東西的。不過,說了CommonJS也說了Node,那麼我以爲也得先了解下NPM了。NPM做爲Node的包管理器,不是爲了幫助Node解決依賴包的安裝問題嘛,那它確定也要遵循CommonJS規範啦,它遵循包規範(仍是理論)的。
2、AMD
CommonJS是主要爲了JS在後端的表現制定的,他是不適合前端的,爲何這麼說呢?
這須要分析一下瀏覽器端的js和服務器端js都主要作了哪些事,有什麼不一樣了:
----------------------------------------------服務器端JS | 瀏覽器端JS--------------------------------------------------
相同的代碼須要屢次執行 | 代碼須要從一個服務器端分發到多個客戶端執行
CPU和內存資源是瓶頸 | 帶寬是瓶頸
加載時從磁盤中加載 | 加載時須要經過網絡加載
---------------------------------------------------------------------------------------------------------------------------------------
因而乎,AMD(異步模塊定義)出現了,它就主要爲前端JS的表現制定規範。
AMD就只有一個接口:define(id?,dependencies?,factory);
它要在聲明模塊的時候制定全部的依賴(dep),而且還要當作形參傳到factory中,像這樣:
define(['dep1','dep2'],function(dep1,dep2){...});
要是沒什麼依賴,就定義簡單的模塊,下面這樣就能夠啦:
define(function(){ var exports = {}; exports.method = function(){...}; return exports; });
這裏有define,把東西包裝起來啦,那Node實現中怎麼沒看到有define關鍵字呢,它也要把東西包裝起來呀,其實吧,只是Node隱式包裝了而已.....
RequireJS就是實現了AMD規範的呢。
這有AMD的WIKI中文版,講了不少蠻詳細的東西,用到的時候能夠查看:AMD的WIKI中文版
3、CMD
大名遠揚的玉伯寫了seajs,就是遵循他提出的CMD規範,與AMD蠻相近的,不過用起來感受更加方便些,最重要的是中文版。
define(function(require,exports,module){...});