定一個小目標,每週翻譯一篇國外優秀博客
原文鏈接 https://addyosmani.com/writing-modular-js/
以及參考了 http://nuysoft.com/2014/01/24/authoring-umd-modules/
http://www.ruanyifeng.com/blog/2012/11/require_js.html
AMD-異步模塊規範
個人理解:
首先AMD模式,所謂異步模塊加載。
就是先加載數組裏面得模塊,然後等到加載完畢,才執行匿名函數。
隨着RequireJS成爲最流行的實現方式,異步模塊規範(AMD)在前端界已經被廣泛認同。
它採用異步方式加載模塊,模塊的加載不影響它後面語句的運行。所有依賴這個模塊的語句,都定義在一個回調函數中,等到加載完成之後,這個回調函數纔會運行。
// 文件名: foo.js
define(["jquery"], function ($) {
// 方法
function myFunc(){};
// 暴露公共方法
return myFunc;
});
AMD也採用require()語句加載模塊,但是不同於CommonJS,它要求兩個
require([module], callback);參數[module]
第一個參數[module],是一個數組,裏面的成員就是要加載的模塊;第二個參數callback,則是加載成功之後的回調函數。如果將前面的代碼改寫成AMD形式,就是下面這樣:
require(['math'], function (math) {
math.add(2, 3);
});
CommonJS
個人理解:
commonJS的規範是用nodeJS引出來的。
但是瀏覽器前端是不能直接用的,需要有browserify。
因爲有Browserify,它也一直被前端界廣泛認同。
就像前面的格式一樣,下面是用CommonJS規範實現的foo模塊的寫法:
// 文件名: foo.js
// 依賴
var $ = require("jquery");
// 方法
function myFunc(){};
// 暴露公共方法(一個)
module.exports = myFunc;
requireJS
爲什麼要使用requireJS
最早的時候,所有Javascript代碼都寫在一個文件裏面,只要加載這一個文件就夠了。後來,代碼越來越多,一個文件不夠了,必須分成多個文件,依次加載。下面的網頁代碼,相信很多人都見過。
<script src="1.js"></script>
<script src="2.js"></script>
<script src="3.js"></script>
<script src="4.js"></script>
<script src="5.js"></script>
<script src="6.js"></script>
首先需要去官網下載requireJS
下載後,假定把它放在js子目錄下面,就可以加載了。
<script src="js/require.js"></script>
UMD模式
然後我現在要學習的就是這個模式。
如果是在瀏覽器中運行代碼,那麼 AMD 模塊 是個非常好的選擇。
如果運行在服務端環境,例如 RingoJS 或 node.js,那麼 CommonJS 模塊 是最簡單的選擇。
一個AMD包裹爲UMD的例子
(function (define) {
// dependencies are listed in the dependency array
define(['./store', 'meld'], function (store, meld) {
"use strict";
var cache = {};
// create the module
meld.around(store, 'get', function (jp) {
var key = jp.args.join('|');
return key in cache ? cache[key] : cache[key] = jp.proceed();
};
// return your module's exports
return store;
});
}(
typeof define == 'function' && define.amd
? define
: function (ids, factory) {
var deps = ids.map(function (id) { return require(id); });
module.exports = factory.apply(null, deps);
}
));
整個模塊被包裹在一個 IIFE 中,並且函數 define 被作爲一個參數傳入。
文件最後的代碼片段 typeof define == ‘function’ && define.amd 是嗅探 AMD 環境的標準方式。
如果檢測結果爲 true,–define 則說明當前環境是 AMD,可以把全局函數 define 傳入 IIFE。通過由*工廠函數返回一個值,模塊以正常的 AMD 方式輸出。
如果 AMD 環境嗅探的結果爲 false,代碼則模擬一個類似 node.js 的 CommonJS 環境。爲了使 AMD 代碼能夠運行,IIFE 注入了一個行爲類似於 AMD define 的函數:把所有的 ids 加載爲模塊,並把它們作爲參數注入工廠函數。然後,函數 define 獲取到工廠函數的返回值,並以經典的 node.js 方式賦值給 module.exports。
感覺下面的例子好理解一點:
(function (root, factory) {
if (typeof define === "function" && define.amd) {
// AMD
define(["jquery"], factory);
} else if (typeof exports === "object") {
// Node, CommonJS之類的
module.exports = factory(require("jquery"));
} else {
// 瀏覽器全局變量(root 即 window)
root.returnExports = factory(root.jQuery);
}
}(this, function ($) {
// 方法
function myFunc(){};
// 暴露公共方法
return myFunc;
}));
下面是更復雜的例子,它依賴了多個組件並且暴露多個方法:
(function (root, factory) {
if (typeof define === "function" && define.amd) {
// AMD
define(["jquery", "underscore"], factory);
} else if (typeof exports === "object") {
// Node, CommonJS之類的
module.exports = factory(require("jquery"), require("underscore"));
} else {
// 瀏覽器全局變量(root 即 window)
root.returnExports = factory(root.jQuery, root._);
}
}(this, function ($, _) {
// 方法
function a(){}; // 私有方法,因爲它沒被返回 (見下面)
function b(){}; // 公共方法,因爲被返回了
function c(){}; // 公共方法,因爲被返回了
// 暴露公共方法
return {
b: b,
c: c
}
一個用UMD模式寫的print.js
(function(root, factory) {
if (typeof define === 'function' && define.amd) {
//檢測是否爲AMD模式
define([], factory);//這裏沒有引用包或者庫,第一個數組爲空
} else {
// 瀏覽器全局變量(root 即 window)
root.Printer = factory(root);
}
}(this, function(root) {
var Printer = {};
Printer.printer = {
"version": "0.0.1"
};
var init_options = {
"speed": 50, //文字的速度
"selector": 'canvas', //要打印到的標籤的ID
"startIndex": 0, //從第幾個字符開始打印
"endIndex": 0, //打印到第幾個字符結束
"hasCur": true, //是否有光標
"curId": 'cur', //光標的ID
"curStr": '_', //光標字符
"curStyle": 'font-weight: bold;', //光標的樣式(CSS樣式)
"curSpeed": 100, //光標的速度(ms)
"lnStr": ""
};
var str = "",
options = init_options;
var flwCurTimer, dom, curObj, reStr = '',
curSwitch, index = 0;
Printer.init = function(arg_str, arg_options) {
console.log('init');
console.log(arg_str);
console.log(arg_options);
str = arg_str;
for (var option in arg_options) {
options[option] = arg_options[option];
}
dom = document.getElementById(options.selector);
index = options.startIndex;
options.endIndex = options.endIndex == 0 ? str.length : options.endIndex
options.hasCur && flwCur();
return this;
}
Printer.print = function() { //打印函數
console.log('print');
for (var i = 0; i < str.length; i++) {
(function(index) {
setTimeout(function() {
console.log('index: ' + index);
if (str.charAt(index) === '\n') {
reStr += '<br>' + options.lnStr;
} else {
reStr += str.charAt(index);
}
dom.innerHTML = options.lnStr + reStr;
// console.log(reStr);
// console.log(dom.innerHTML);
}, options.speed * (index + 1))
})(i);
}
setTimeout(function() {
if (options.hasCur) {
var element = document.createElement("span");
element.id = options.curId
dom.appendChild(element);
curObj = document.getElementById(options.curId);
clearTimeout(flwCurTimer);
setInterval(chCur, options.curSpeed);
}
}, options.speed * str.length)
}
function flwCur() { //跟隨光標
console.log('flwCur');
dom.innerHTML += '<span id="' + options.curId + '" style="' + options.curStyle + '">' + options.curStr + '</span>';
flwCurTimer = setTimeout(flwCur, 1.5 * options.speed);
}
function chCur() { //閃爍光標
// console.log('chCur');
curObj.innerHTML = curSwitch ? options.curStr : "";
curSwitch = !curSwitch
}
return Printer;//Printer是公有方法
}));