-
- 1. 什麼是子產品化程式設計
- 2. 為什麼要子產品化
- 3. AMD
- 4. CommonJS
- 5. ES6 Module
- 總結
了解一個技術,首先要了解這個技術産生的背景及解決的問題,而不應該隻是單純的知道該怎麼用。之前的狀态可能就是隻是為了了解而了解,并不知道實際産生的原因及帶來的好處,是以今天就來總結一下。
1. 什麼是子產品化程式設計
來看百度百科的定義
子產品化程式設計是指在進行程式設計時将一個大程式按照功能劃分為若幹小程式子產品,每個小程式子產品完成一個确定的功能,并在這些子產品之間建立必要的聯系,通過子產品的互相協作完成整個功能的程式設計方法。
比如 java 的 import,C# 的 using。我的了解是通過子產品化程式設計,可以将不同的功能獨立出來,修改某個功能時不會對其他功能産生影響。
2. 為什麼要子產品化
來看下面一個例子
// A.js
function sayWord(type){
if(type === ){
console.log("hello");
}else if(type === ){
console.log("world");
}
}
// B.js
function Hello(){
sayWord();
}
// C.js
Hello()
假設上面三個檔案,B.js 引用了 A.js 裡面的内容,C.js 又引用了 B.js 裡面的内容,如果編寫 C.js 的人隻知道引用了 B.js,那他就不會引用 A.js 就會導緻程式出錯,而且檔案的引用順序也不能出錯。給整體代碼的調試修改帶來不便。
還有個問題,上述代碼暴露了兩個全局變量,容易造成全局變量的污染
3. AMD
AMD 即 Asynchronous Module Definition(異步子產品定義)。采取異步加載的方式加載子產品,子產品的加載不會影響它後面的語句執行。而且隻有用到的時候才會去加載相關檔案,屬于浏覽器端的标準
假設下面這種情況
// util.js
define(function(){
return {
getFormatDate:function(date,type){
if(type === ){
return '2018-08-9'
}
if(type === ){
return '2018 年 8 月 9 日'
}
}
}
})
// a-util.js
define(['./util.js'],function(util){
return {
aGetFormatDate:function(date){
return util.getFormatDate(date,)
}
}
})
// a.js
define(['./a-util.js'],function(aUtil){
return {
printDate:function(date){
console.log(aUtil.aGetFormatDate(date))
}
}
})
// main.js
require(['./a.js'],function(a){
var date = new Date()
a.printDate(date)
})
console.log();
// 使用
// <script src = "/require.min.js" data-main="./main.js"></script>
頁面上先列印
1
,然後才會列印
2018 年 8 月 9 日
。是以 AMD 的加載并不會影響後續的語句執行。
如果不是異步加載會出現什麼情況呢
var a = require('a');
console.log()
後面的語句需要等待 a 加載完成才能執行,如果加載時間過長,整個程式都會卡在這。是以,浏覽器不能同步加載資源,這也是 AMD 的産生背景。
AMD 是在浏覽器端實作子產品化開發的規範。由于該規範不是 JavaScript 原始支援的,使用 AMD 規範進行開發的時候需要引入第三方的庫函數,也就是 RequireJS。
RequireJS 主要解決的問題
- 使 JS 異步加載,避免頁面失去響應
- 管理代碼之間的依賴性,有利于代碼的編寫和維護
下面來看看如何使用 require.js
要想使用 require.js,首先要 define
// ? 代表該參數可選
define(id?, dependencies?, factory);
- id:指的是定義的子產品的名字
- dependencies:是定義的子產品所依賴子產品的數組
-
factory:為子產品初始化要執行的函數或對象。如果為函數,它應該隻被執行一次。如果是對象,此對象應該為子產品的輸出值。
具體的規範說明可以參考 AMD (中文版)
舉個例子,建立一個名為 “alpha” 的子產品,使用了 require,exports,和名為 “beta” 的子產品:
define("alpha", ["require", "exports", "beta"], function (require, exports, beta) {
exports.verb = function() {
return beta.verb();
//Or:
return require("beta").verb();
}
});
一個傳回對象的匿名子產品:
define(["alpha"], function (alpha) {
return {
verb: function(){
return alpha.verb() + ;
}
};
});
一個沒有依賴性的子產品可以直接定義對象:
define({
add: function(x, y){
return x + y;
}
});
如何使用
AMD 采用 require 語句加載子產品
require([module],callback);
- module:是一個數組,裡面的成員是要加載的子產品
- callback:加載成功之後的回調函數
例如
require(['./a.js'],function(a){
var date = new Date()
a.printDate(date)
})
具體的使用方法如下
// util.js
define(function(){
return {
getFormatDate:function(date,type){
if(type === ){
return '2018-08-09'
}
if(type === ){
return '2018 年 8 月 9 日'
}
}
}
})
// a-util.js
define(['./util.js'],function(util){
return {
aGetFormatDate:function(date){
return util.getFormatDate(date,)
}
}
})
// a.js
define(['./a-util.js'],function(aUtil){
return {
printDate:function(date){
console.log(aUtil.aGetFormatDate(date))
}
}
})
// main.js
require(['./a.js'],function(a){
var date = new Date()
a.printDate(date)
})
// 使用
// <script src = "/require.min.js" data-main="./main.js"></script>
假設這裡有 4 個檔案,util.js,a-util.js 引用了 util.js,a.js 引用了 a-util.js,main.js 引用了 a.js。
其中,data-main 屬性的作用是加載網頁程式的主子產品。
上例示範了一個主子產品最簡單的寫法,預設情況下,require.js 假設依賴和主子產品在同一個目錄。
使用
require.config()
方法可以對子產品的加載行為進行自定義。
require.config()
就寫在主子產品(main.js)的頭部,參數是一個對象,這個對象的 paths 屬性指定各個子產品的加載路徑
require.config({
paths:{
"a":"src/a.js",
"b":"src/b.js"
}
})
還有一種方法是改變基礎目錄(baseUrl)
require.config({
baseUrl: "src",
paths: {
"a": "a.js",
"b": "b.js",
}
});
4. CommonJS
commonJS 是 nodejs 的子產品化規範,現在被大量用在前端,由于建構工具的高度自動化,使得使用 npm 的成本非常低。commonJS 不會異步加載 JS,而是同步一次性加載出來
在 commonJS 中,有一個全局性的方法 require(),用于加載子產品,例如
const util = require('util');
然後,就可以調用 util 提供的方法了
const util = require('util');
var date = new date();
util.getFormatDate(date,);
commonJS 對于子產品的定義分三種,子產品定義(exports),子產品引用(require)和子產品标示(module)
exports() 對象用于導出目前子產品的變量或方法,唯一的導出口。require() 用來引入外部子產品。module 對象代表子產品本身。
舉個栗子
// util.js
module.exports = {
getFormatDate:function(date, type){
if(type === ){
return '2017-06-15'
}
if(type === ){
return '2017 年 6 月 15 日'
}
}
}
// a-util.js
const util = require('util.js')
module.exports = {
aGetFormatDate:function(date){
return util.getFormatDate(date,)
}
}
或者下面這種方式
// foobar.js
// 定義行為
function foobar(){
this.foo = function(){
console.log('Hello foo');
}
this.bar = function(){
console.log('Hello bar');
}
}
// 把 foobar 暴露給其它子產品
exports.foobar = foobar;
// main.js
//使用檔案與子產品檔案在同一目錄
var foobar = require('./foobar').foobar,
test = new foobar();
test.bar(); // 'Hello bar'
5. ES6 Module
ES6 子產品的設計思想是盡量靜态化,使得編譯時就能确定子產品的依賴關系,以及輸入和輸出的變量,而 CommonJS 和 AMD 子產品都隻能在運作時确定這些關系。如 CommonJS 加載方式為 “運作時加載”,ES6 的加載方式為 “編譯時加載” 或者靜态加載,即 ES6 可以在編譯時就完成子產品加載,效率比 CommonJS 子產品的加載方式高。
ES6 子產品自動采用嚴格模式,不管有沒有在子產品頭部加上 “use strict”。
ES6 export 語句輸出的接口與其對應的值是動态綁定關系,即通過該接口可以取到子產品内部實時的值。而 CommonJS 子產品輸出的是值的緩存,不存在動态更新。
ES6 與 CommonJS 子產品的差異
- CommonJS 子產品輸出的是一個值的複制,ES6 子產品輸出的是值的引用。
- CommonJS 子產品是運作時加載,ES6 子產品是編譯時輸出接口。
- CommonJS 中的 this 指向目前子產品,ES6 子產品 this 為 undefined
第二個差異是因為 CommonJS 加載的是一個對象(即 Module.exports 屬性),該對象隻有在腳本運作結束時才會生成,而 ES6 子產品不是對象,它的對外接口隻是一種靜态定義,在代碼靜态解析階段就會生成。
總結
CommonJS 采用了伺服器優先的政策,使用同步方式加載子產品,而 AMD 采用異步加載的方式。是以如果需要使用異步加載 js 的話建議使用 AMD,而當項目使用了 npm 的情況下建議使用 CommonJS。