前言 javascript
SeaJS是一個遵循CommonJS規範的JavaScript模塊加載框架,能夠實現JavaScript的模塊化開發及加載機制。與jQuery等JavaScript框架不一樣,SeaJS不會擴展封裝語言特性,而只是實現JavaScript的模塊化及按模塊加載。SeaJS的主要目的是令JavaScript開發模塊化並能夠輕鬆愉悅進行加載,將前端工程師從繁重的JavaScript文件及對象依賴處理中解放出來,能夠專一於代碼自己的邏輯。SeaJS能夠與jQuery這類框架完美集成。使用SeaJS能夠提升JavaScript代碼的可讀性和清晰度,解決目前JavaScript編程中廣泛存在的依賴關係混亂和代碼糾纏等問題,方便代碼的編寫和維護。
css
SeaJS的做者是淘寶前端工程師玉伯。
SeaJS自己遵循KISS(Keep It Simple, Stupid)理念進行開發,其自己僅有個位數的API,所以學習起來毫無壓力。在學習SeaJS的過程當中,到處能感覺到KISS原則的精髓——僅作一件事,作好一件事。
本文首先經過一個例子直觀對比傳統JavaScript編程和使用SeaJS的模塊化JavaScript編程,而後詳細討論SeaJS的使用方法,最後給出一些與SeaJS相關的資料。 html
傳統模式 vs SeaJS模塊化 前端
假設咱們如今正在開發一個Web應用TinyApp,咱們決定在TinyApp中使用jQuery框架。 java
TinyApp的首頁會用到module1.js,module1.js依賴module2.js和module3.js,同時module3.js依賴module4.js。
jquery
傳統開發
使用傳統的開發方法,各個js文件代碼以下:
git
//module1.js var module1 = { run: function() { return $.merge(['module1'], $.merge(module2.run(), module3.run())); } } //module2.js var module1 = { run: function() { return ['module2']; } } //module3.js var module3 = { run: function() { return $.merge(['module3'], module4.run()); } } //module4.js var module4 = { run: function() { return ['module4']; } }此時index.html須要引用module1.js及其全部下層依賴(注意順序):
<!DOCTYPE HTML> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <title>TinyApp</title> <script src="./jquery-min.js"></script> <script src="./module4.js"></script> <script src="./module2.js"></script> <script src="./module3.js"></script> <script src="./module1.js"></script> </head> <body> <p class="content"></p> <script> $('.content').html(module1.run()); </script> </body> </html>
隨着項目的進行,js文件會愈來愈多,依賴關係也會愈來愈複雜,使得js代碼和html裏的script列表每每變得難以維護。
SeaJS模塊化開發
下面看看如何使用SeaJS實現相同的功能。
首先是index.html: github
<!DOCTYPE HTML> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <title>TinyApp</title> </head> <body> <p class="content"></p> <script src="./sea.js"></script> <script> seajs.use('./init', function(init) { init.initPage(); }); </script> </body> </html>
//jquery.js define(function(require, exports, module) = { //原jquery.js代碼... module.exports = $.noConflict(true); }); //init.js define(function(require, exports, module) = { var $ = require('jquery'); var m1 = require('module1'); exports.initPage = function() { $('.content').html(m1.run()); } }); //module1.js define(function(require, exports, module) = { var $ = require('jquery'); var m2 = require('module2'); var m3 = require('module3'); exports.run = function() { return $.merge(['module1'], $.merge(m2.run(), m3.run())); } }); //module2.js define(function(require, exports, module) = { exports.run = function() { return ['module2']; } }); //module3.js define(function(require, exports, module) = { var $ = require('jquery'); var m4 = require('module4'); exports.run = function() { return $.merge(['module3'], m4.run()); } }); //module4.js define(function(require, exports, module) = { exports.run = function() { return ['module4']; } });乍看之下代碼彷佛變多變複雜了,這是由於這個例子太簡單,若是是大型項目,SeaJS代碼的優點就會顯現出來。不過從這裏咱們仍是能窺探到一些SeaJS的特性:
使用SeaJS 正則表達式
下載及安裝
要在項目中使用SeaJS,你全部須要作的準備工做就是下載sea.js而後放到你項目的某個位置 算法
SeaJS項目目前託管在GitHub上,主頁爲https://github.com/seajs/seajs/。能夠到其git庫(https://github.com/seajs/seajs/tree/master/dist)下載sea.js(已壓縮)或sea-debug.js(未壓縮)。
下載完成後放到項目的相應位置,而後在頁面中經過<script>標籤引入,你就可使用SeaJS了。
SeaJS基本開發原則
在討論SeaJS的具體使用前,先介紹一下SeaJS的模塊化理念和開發原則。
使用SeaJS開發JavaScript的基本原則就是:一切皆爲模塊。引入SeaJS後,編寫JavaScript代碼就變成了編寫一個又一個模塊,SeaJS中模塊的概念有點相似於面向對象中的類——模塊能夠擁有數據和方法,數據和方法能夠定義爲公共或私有,公共數據和方法能夠供別的模塊調用。
另外,每一個模塊應該都定義在一個單獨js文件中,即一個對應一個模塊。
下面介紹模塊的編寫和調用。
模塊的定義及編寫
SeaJS中使用「define」函數定義一個模塊。由於SeaJS的文檔並無關於define的完整參考,因此我閱讀了SeaJS源代碼,發現define能夠接收三個參數:
/** * Defines a module. * @param {string=} id The module id. * @param {Array.|string=} deps The module dependencies. * @param {function()|Object} factory The module factory function. */ fn.define = function(id, deps, factory) { //code of function… }上面是我從SeaJS源碼中摘錄出來的,define能夠接收的參數分別是模塊ID,依賴模塊數組及工廠函數。我閱讀源代碼後發現define對於不一樣參數個數的解析規則以下:
define(function(require, exports, module) { //code of the module... });我的建議遵循SeaJS官方示例的標準,用一個參數的define定義模塊。那麼id和deps會怎麼處理呢?
這三個參數能夠根據須要選擇是否須要顯示指定。
下面說一下module。module是一個對象,存儲了模塊的元信息,具體以下:
1. module.id —— 模塊的ID。
2. module.dependencies —— 一個數組,存儲了此模塊依賴的全部模塊的ID列表。
3. module.exports —— 與exports指向同一個對象。
第一種定義模塊的模式是基於exports的模式:
define(function(require, exports, module) { var a = require('a'); //引入a模塊 var b = require('b'); //引入b模塊 var data1 = 1; //私有數據 var func1 = function() { //私有方法 return a.run(data1); } exports.data2 = 2; //公共數據 exports.func2 = function() { //公共方法 return 'hello'; } });上面是一種比較「正宗」的模塊定義模式。除了將公共數據和方法附加在exports上,也能夠直接返回一個對象表示模塊,以下面的代碼與上面的代碼功能相同:
define(function(require) { var a = require('a'); //引入a模塊 var b = require('b'); //引入b模塊 var data1 = 1; //私有數據 var func1 = function() { //私有方法 return a.run(data1); } return { data2: 2, func2: function() { return 'hello'; } }; });若是模塊定義沒有其它代碼,只返回一個對象,還能夠有以下簡化寫法:
define({ data: 1, func: function() { return 'hello'; } });第三種方法對於定義純JSON數據的模塊很是合適。
模塊的載入和引用
require("http://example/js/a");就表明載入「http://example/js/a.js」。
require("./c");則載入「http://example/js/c.js」。
require("./module1-style.css");路徑中含有」?」時,如
require(<a href="http://example/js/a.json?cb=func">http://example/js/a.json?cb=func</a>);路徑以」#」結尾時,如
require("http://example/js/a.json#");
根據應用場景的不一樣,SeaJS提供了三個載入模塊的API,分別是seajs.use,require和require.async,下面分別介紹。
//單一模式 seajs.use('./a'); //回調模式 seajs.use('./a', function(a) { a.run(); }); //多模塊模式 seajs.use(['./a', './b'], function(a, b) { a.run(); b.run(); });通常seajs.use只用在頁面載入入口模塊,SeaJS會順着入口模塊解析全部依賴模塊並將它們加載。若是入口模塊只有一個,也能夠經過給引入sea.js的script標籤加入」data-main」屬性來省略seajs.use,例如,上面TinyApp的index.html也能夠改成以下寫法:
<!DOCTYPE HTML> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <title>TinyApp</title> </head> <body> <p class="content"></p> <script src="./sea.js" data-main="./init"></script> </body> </html>這種寫法會令html更加簡潔。
var m = require('/path/to/module/file');這裏簡要介紹一下SeaJS的自動加載機制。上文說過,使用SeaJS後html只要包含sea.js便可,那麼其它js文件是如何加載進來的呢?SeaJS會首先下載入口模塊,而後順着入口模塊使用正則表達式匹配代碼中全部的require,再根據require中的文件路徑標識下載相應的js文件,對下載來的js文件再迭代進行相似操做。整個過程相似圖的遍歷操做(由於可能存在交叉循環依賴因此整個依賴數據結構是一個圖而不是樹)。
require('module' + '1'); require('Module'.toLowerCase());這都會形成SeaJS沒法進行正確的正則匹配如下載相應的js文件。
require.async('/path/to/module/file', function(m) { //code of callback... });這樣只有在用到這個模塊時,對應的js文件纔會被下載,也就實現了JavaScript代碼的按需加載。
seajs.config({ base: 'path/to/jslib/', alias: { 'app': 'path/to/app/' }, charset: 'utf-8', timeout: 20000, debug: false });其中base表示基址尋址時的基址路徑。例如base設置爲」http://example.com/js/3-party/」,則
var $ = require('jquery');會載入」http://example.com/js/3-party/jquery.js」。
define(function() { //{{{jQuery原有代碼開始 /*! * jQuery JavaScript Library v1.6.1 * http://jquery.com/ * * Copyright 2011, John Resig * Dual licensed under the MIT or GPL Version 2 licenses. * http://jquery.org/license * * Includes Sizzle.js * http://sizzlejs.com/ * Copyright 2011, The Dojo Foundation * Released under the MIT, BSD, and GPL Licenses. * * Date: Thu May 12 15:04:36 2011 -0400 */ //... //}}}jQuery原有代碼結束 return $.noConflict(); });SeaJS項目的打包部署
一個完整的例子
上文說了那麼多,知識點比較分散,因此最後我打算用一個完整的SeaJS例子把這些知識點串起來,方便朋友們概括回顧。這個例子包含以下文件:
index.html——主頁面。
sea.js——SeaJS腳本。
init.js——init模塊,入口模塊,依賴data、jquery、style三個模塊。由主頁面載入。
data.js——data模塊,純json數據模塊,由init載入。
jquery.js——jquery模塊,對 jQuery庫的模塊化封裝,由init載入。
style.css——CSS樣式表,做爲style模塊由init載入。
sea.js和jquery.js的代碼屬於庫代碼,就不贅述,這裏只給出本身編寫的文件的代碼。
html:
<!DOCTYPE HTML> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <title></title> </head> <body> <div id="content"> <p class="author"></p> <p class="blog"><a href="#">Blog</a></p> </div>< <script src="./sea.js" data-main="./init"></script> </body> </html>javascript:
//init.js define(function(require, exports, module) { var $ = require('./jquery'); var data = require('./data'); var css = require('./style.css'); $('.author').html(data.author); $('.blog').attr('href', data.blog); }); //data.js define({ author: 'ZhangYang', blog: 'http://leoo2sk.cnblogs.com' });css:
.author{color:red;font-size:10pt;} .blog{font-size:10pt;}
運行效果以下:
主要參考文獻&SeaJS學習資源
[1] SeaJS主頁 – http://seajs.com
[2] SeaJS的GitHub庫(可獲取源碼) – https://github.com/seajs/seajs
[3] SeaJS做者玉伯的博客 - http://lifesinger.wordpress.com/
本文基於 署名-非商業性使用 3.0 許可協議發佈,歡迎轉載,演繹,可是必須保留本文的署名 張洋 (包含連接),且不得用於商業目的。如您有任何疑問或者受權方面的協商,請 與我聯繫 。