請注意
本文為未完稿。
Colorless echo JavaScript framework
Introduction
This is a JavaScript module framework that is simple to use.
本計畫的目的是建立一個能簡單上手的 JavaScript 模組架構。您可想成是一個類似於
YUI 的 library。
Feature
地址/住址輸入表單現有臺灣可用。
注意事項
本 library 將使用到 global 變數 'CeL'。您可使用 .get_old_namespace() 來獲得原先之 CeL 值,或是以 .recover_namespace() 來復原變數 'CeL'。
Demonstration
您可將整個計畫下載到本機硬碟上做測試。
線上演示可參考
demo page。
Tested browsers
Opera 11, firefox 5, Safari 5, Internet Explorer 8, Chrome 10.
Download: 如何獲得原始碼
您可以利用
CeJS@GitHub 或
CeJS @ Google Project Hosting 來取得最新原始碼。
Usage: How to include resource
本 library 與 HeadJS 相同,皆可以單行指令載入其他 resource 與本 library 所附的 module。
首先 include 基本 script:
<!-- load library -->
<script type="text/javascript" src="path/to/ce.js"></script>
<script type="text/javascript">
// 預防 initialization 到一半彈出警告視窗,所以設大一點。
CeL.log.max_length = 20;
// set debug
CeL.set_debug();
// 判別是否已經 load 過
if(typeof CeL !== 'function' || CeL.Class !== 'CeL')
// CeL has not been loaded
;
</script>
或使用初始序列:
<script type="text/javascript" src="path/to/ce.js">
{
"set_run" : "initialization.js"
}
</script>
<script type="text/javascript" src="path/to/ce.js">
{
"set_run" : [ "initialization_1.js", "initialization_2.js" ]
}
</script>
之後,載入 resource 的方式有下列幾種:
- .set_run( running sequence )
-
照 sequence 執行。推薦使用 .set_run 此泛用方法。
// include single JavaScript file
CeL.set_run(
"path/to/JavaScript.js",
callback
);
CeL.set_run(
"http://host.name/include.full.URL/JavaScript.js"
);
// include single CSS file
CeL.set_run(
"path/to/CSS.css",
callback
);
CeL.set_run(
"http://host.name/include.full.URL/CSS.css",
callback
);
// include single module
CeL.set_run(
"full.module.name",
callback
);
// 進階用法:以下依序載入 4個同步載入組。
CeL.set_run(
[
// 下面的幾項將會同時載入。
"path/to/JavaScript.0.js",
"full.module.name.0",
"full.module.name.1",
"path/to/JavaScript.1.js",
"path/to/JavaScript.2.js"
],
[
// 下兩函數與 resource 將會等待上一同步載入組載入完成之後才執行。
function(){
// codes
},
function(){
// codes
},
"full.module.name.2"
]
// 下一個同步載入組:下面這幾項亦會一同載入。
[
"full.module.name.3",
"path/to/JavaScript.3.js"
],
function(){
// 本函數將會於最後執行。
}
);
本函數會先嘗試使用 XMLHttpRequest 同步的方式依序取得、載入 module。
無法以 XMLHttpRequest 循序載入時,則插入 node,以非同步的方式載入 resource。
特別需要注意的是,本函數亦具有 jQuery.ready() 一般的功能,會在 DOM 準備好之後才觸發。其原理為本函數會在 DOM 準備好前,先把序列存入 queue 中。待準備好後再執行。但為了效率考量,本函數會在 DOM 準備之後置換 .set_run 自身,因此建議若要作 alias of .set_run,請在 DOM 準備之後再作 alias:
// alias of CeL.set_run
var sr;
CeL.set_run(
function() { sr = CeL.set_run; },
// 之後再使用 cache
function() {
sr('module_name', function(){
// FunctionBody
});
});
- .get_file( file path )
-
get_file 是這幾種方法中第二常用的 function,常常在 function 中使用。以同步的方式依序取得、載入單一 resource。由於使用 XMLHttpRequest,可能為了 browser 安全性設定而無法取得 resource。
// get contents of [path/to/file]:
var file_contents = CeL.get_file('path/to/file');
CeL.log(file_contents);
- .use( full module name )
-
載入指定 module。本函數通常可以 .set_run 替代。
// include module
// 注意:以下的 code 中,CeL.warn 不一定會被執行(可能會、可能不會),因為執行時 log 可能尚未被 include。此時應該改用 CeL.set_run('application.log', callback);
CeL.use("application.log");
CeL.warn('WARNING message');
// include module than run callback()
CeL.use(
"full.module.name",
function callback(){
// codes
}
);
- .include_resource( URL )
-
以非同步的方式載入單一 resource 之後執行 callback。實際機制為在 <head> 中插入對應的 document object,戴載入後執行 callback。本函數通常可以 .set_run 替代。
// Including JavaScript file asynchronously.
CeL.include_resource("path/to/JavaScript.js", callback);
// Including CSS file asynchronously.
CeL.include_resource("path/to/CSS.css", callback);
Concepts: 資源載入的內部運作原理
.set_run 會循序讀入同步載入組。
對組內每一項,先判別是 module 或者一般 URL。對尚未載入的 module 會使用
.use(module_name, callback)
載入,.use 會先嘗試 .get_file,若可用 XMLHttpRequest,則會循序載入資源;若失敗則以
.include_resource(module_path, callback)
載入,.include_resource return 後則 wait module file loaded to call .setup_module。對一般 URL,.set_run 亦會經過類似的步驟,先嘗試循序載入,否則 call .include_resource。
於 module file 內,module 會 call .setup_module 來作初始設定(見下一節)。若 module 有 dependencies,則會先嘗次載入 dependencies,並把依附其下的其他 resource 移至之後載入。
如何製作 module(或是說 plugin)
下面示範幾種較常用的 module 寫法。
// 簡單範例 @ full/module/name_1.js
typeof CeL == 'function' &&
CeL.setup_module(
'full.module.name_1',
function(){
// code_for_including
var _ = function(){};
_.good_method = function(){/* .. */};
return _;
}
);
// use it
CeL.set_run(
'full.module.name_1',
function(){
CeL.good_method(/* .. */);
}
);
module name 以 '.' 分隔,代表位在哪個目錄分組中。例如 'full.module.name_2' 表示位在 full/module/name_2.js 之 JavaScript 檔案。因為本 library 允許同時 loading,因此
必須設定 module name 以確定 loading 的是哪個 module。
此外 module
必須將 require 設定於 module.require,而無法在 code_for_including 中才設定。
// 簡單範例2 @ full/module/name_2.js
typeof CeL == 'function' &&
CeL.setup_module(
/**
* 本 module 之 name(id),不設定時並不會從呼叫時之 path 取得。
* @type String
* @constant
* @inner
* @ignore
*/
'full.module.name_2',
{
require : 'full.module.name.required.1.method_1|full.module.name.required.2.method_2|full.module.name.required.3.|full.module.name.required.4.',
/**
* 若欲 include 整個 module 時,需囊括之 code。
* @type Function
* @param {Function} library_namespace namespace of library
* @param [load_arguments] 需要使用繼承功能,呼叫 code 時之 argument(s)
* @return module namespace
* @constant
* @inner
* @ignore
*/
code : function(library_namespace){
// requiring
var method_1, method_2;
eval(library_namespace.use_function(this));
// 設定 module name
var module_name = this.module_name;
var data1 = method_1(/* .. */),
data2 = method_2();
// code 中,library_namespace === CeL
var _ = function(){};
_.good_method = function(){
library_namespace.debug('Some debug message', /* debug level */ 1, module_name + '.good_method');
};
return _;
}
}
);
// use it: see above
Concepts: module 載入的內部運作原理
.setup_module 會 call .parse_require 依 (module name-space).require 設定:
- (module name-space).require_module = module name[]
- (module name-space).require_variable = {variable_name: full_name_with_module_name}
- (module name-space).require_URL = URL[]
若有未載入之 dependencies,則先 check 登錄。若本身已經在需求名單中,可能是
循環參照,則還是放行(執行 module 中 code_for_including),避免相互需要造成
堆疊空間不足。
若本身未在需求名單中,則登錄 module_name 正在 call 之記錄,module_require_chain[module_name] = dependencies by .parse_require()。之後 call .use(require_module)。無法採用 XMLHttpRequest,造成有未載入之 dependencies,則不載入 module,直接 throw。此時 .include_resource 會 call callback(path, module_name),傳入此 module 所需之 dependency list 來進一步處置。
若 dependencies 皆已載入,.setup_module 會執行 module 中的 code_for_including。
於 module: code_for_including 內,可於一開始以 .use_function 設定 required local variables。但須注意可能因為
循環參照,事實上 required 並未正確設定;於其後執行時將之當作 function 直接 call 即可解決。
.setup_module 執行完會清除載入中之登錄:
delete module_require_chain[module_name];
。
設計理念
盡可能別消耗資源
例如採用
lazy loading;除非必要,否則當有需要時才執行。
最簡化:僅部屬必須組件
對於永遠不會使用到的功能,例如針對其他 browser 設計的功能,則捨棄之。
除非成本過高,否則不使用單一函數打包一般性功能,並在每次執行時再檢測一次。要這麼作,不如在第一次執行到時就檢測好(不在一開始就檢測,是為了盡可能別消耗資源。),並只採用需要的針對性函數。
舉例來說,若處於 IE 環境,則僅載入 IE 需要的組件。但須注意的是,應採用
功能檢測法,而非
瀏覽器偵測法,除非功能檢測無法鑑別或不管用(成本過高)。
盡可能相容於長期標準
除非成本過高(implementation 過於消耗資源、標準制定或實現得不好)、有必要考量不能放棄的使用者(如 IE 6 user),或是不得已需要暫時性措施(例如在 core function 中需要一個極為簡單的 JSON.parse,並且也僅提供當前 context 短暫使用。);否則應使 library 的使用者盡可能在任何環境中,皆可以標準的寫法(此標準已經過認定,並預期長期間不會變更。)實現應有的功能。
例如採用補全 Array.isArray 的方法,而不是自己訂出功能相同之函數。但對 Object 來說,由於標準不存在 Object.isObject 來判別是否為原生 "Object";在「最小化影響」的理念下,則以 library name-space 下的函數實現。
最小化影響
除了標準規定之簡單核心物件外(如 Array, Boolean, Date, Function, Math, Number, RegExp, String, Object),盡量不影響其他外部物件。因此就算要符合長期標準,使程式碼與舊版相容,也盡可能不動到 prototype。其他物件包括環境本身 (global object, window),更不用說是計算成本高昂之 HTML element。
嚆矢:為什麼會有這計畫?以及雜談。
會想到要做這個 library,主要是因十多年來寫了一些 code;近幾年隨著各種 JavaScript framework 的興起與 ECMAScript 標準的制定,想說該讓這些舊 code 踏入新世紀了。因此就算是天馬行空,最起碼還不算平地樓臺。更應該說,就因為是有一筆爛帳擺那邊,所以才想到要讓這些東西有個歸宿吧。畢竟我有盡力不使由己所出的東西被白白浪費的偏執。
今天既然要趕流行寫(一個較為全方位性的)library set,鑒於過去寫過各種類型的 functions,第一項要務就是分類。當然我對 JavaScript 的認識還遠不及大師級的程度,可以從底層以及設計概念來制定出所需要的規範。因此我所能做的就是將過去所寫過的 code 分出適當的類別。之前開始做本 library 時,其實已經從功能來分化過,但最近則想從另一種角度來試試。
Structure: framework source tree 架構
我的想法是,JavaScript 作為一種程式語言,對內有演算的部分,也有對底層(例如機種依存之功能。其實我們熟知的
瀏覽器支援度等
平臺依存問題、
回溯相容問題等,都是因為對概念的實踐過程中,不同的實作會造成不一樣的終端解決方法。)、對資料結構的操作。對外的介面操作部分,則有對 DOM 的操作與事件處理。
所以對常用 JavaScript 操作之底層暫時決定粗分下面幾種類別:
- data/
-
處理資料的 code,包含資料結構、直接處理資料的演算法。例如 file system IO、Ajax 等。
- structure/
-
資料結構。
- algorithm/
-
純演算法相關的 code。
- math/
-
數學演算相關的 code。
- code/
-
對程式碼之支援(將 source 也視為一種資料),包括代碼重構等。
- code/compatibility.js
-
對不同 JavaScript 版本之支援。這些功能應當於 application/ 下作 link。參考 es5-shim。
- native.js
-
對 native objects 或 future objects 以至 DOM 之擴展。這些功能應當於 application/ 下作 link。
- CSV.js
-
包含了處理 CSV data 的 functions。
- XML.js
-
包含了處理 XML file 的 functions。
- check/
-
檢驗 id、ISBN、MD5 等。
- interact/
-
介面操作、與使用者操作互動相關的 code。包括許多 web 介面互動函數。因為 "interface" 已經成為 strict mode 的 FutureReservedWord,所以改用 interact。
- DOM/
-
web DOM 操作,例如 create/search/modify document object。這中間傳入的 function 應該以 .call(document object) 呼叫,使其中的 'this' 即為 document object 本身。
我考慮了一下最簡單的操作模式,發現 object.action(argument/條件) 是比較恰當的。事實上 jQuery 就是以這樣的方式,先以 $(選擇條件) 包裝物件,再使用 .action(argument),並以 return this
串接。
- form/
-
web form 操作。
- drag_drop.js
-
web drag-and-drop 操作。
- event.js
-
事件操作。event 先決條件包括 function/files loading, timeset。
- integrate/
-
包含某些科際整合但用途專一且特殊的功能,例如處理 SVG、map。
- application/
-
應用功能、內部動作與處理用 code。
- debug.js
-
debug 用的 functions。
- debug/log.js
-
記錄用 functions。
- deploy.js
-
程式部署、安裝配置、distribution 用的 functions。
- API.js
-
包含了 include web API 專用的 functions。
- net/
-
包含了處理網路傳輸相關功能的 functions。
- net/Ajax.js
-
包含了 Ajax、XMLHttpRequest 的 functions。
- storage/
-
IO 存儲處理。
- storage/file.js
-
檔案處理的 meta class。可能會 require OS/ 下的東西。
- locale/
-
i18n 系列。
- OS/
-
對底層 OS 之支持處理機種依存問題。
- OS/Windows/
-
包含了 Windows 系統管理專用或相關的 functions。
- OS/Windows/WMI.js
-
包含了 Windows Management Instrumentation 的 functions。
- OS/Windows/HTA.js
-
包含了支援 Microsoft Windows 上 HTML Application 的 functions。
- OS/Windows/registry.js
-
包含了操作註冊表用的 functions。
- browser/
-
對不同瀏覽器相容性問題之支持。
- transform/
-
針對 object 的轉換,包括物件轉換:object (class, DOM) transformation。前述 jQuery 的包裝方式即為其一。對於這常用功能,考量 JavaScript 至今演變,因為單 char 可用符號剩下不多,大概就 "$", "_" 等,因此 jQuery 就設定為 '