工做中,隨着前端項目的擴張,不管從代碼精簡,仍是團隊合做,上線部署等各個方面來講,模塊化已經不可或缺。javascript
模塊化的最大做用就是提升代碼的複用率,解耦,減小衝突。html
前端模塊化的規範,比較著名的CommonJs
AMD
CMD
ES6模塊化
。這也是模塊化演變的進程。前端
CommonJs
CommonJS
規範規定,每一個模塊內部,module
變量表明當前模塊。這個變量是一個對象,它的exports
屬性(即module.exports
)是對外的接口。java
CommonJs
定義的模塊能夠分爲三個部分:es6
require
用來引入模塊exports
對象用於導出當前模塊的方法或變量,是惟一的導出口module
這個對象就表明模塊自己Node
應用就是採用CommonJS
模塊規範的典型。設計模式
咱們來看一個簡單的例子,定義一個methods
對象,而後暴露出去數組
var methods = {};
<!--能夠這樣輸出-->
module.exports.methods = methods;
<!--也能夠這樣輸出,至關於隱藏一行 -->
<!--var exports = module.exports; -->
exports.methods = methods;
<!--也能夠包裝一層再輸出-->
module.exports = { methods: methods };
複製代碼
引用:瀏覽器
var obj = require('./methods.js');
console.log(obj.methods);
複製代碼
可是,前面咱們寫到CommonJS
的一個重要特性是加載模塊是同步的,也就是說,只有加載完成,才能執行後面的操做。
這種規範在用於服務器變成時,沒什麼毛病,由於模塊文件通常都存於本地硬盤中,因此加載起來也比較快。
可是放在瀏覽器環境中呢?
這個時候的資源大都放在服務器上,這個時候再採起同步加載,頗有可能由於網速等緣由形成瀏覽器「假死」的狀況,用戶體驗十分很差。
這就引伸出了AMD
規範。bash
AMD
規範AMD
是"Asynchronous Module Definition
"的縮寫,意思就是"異步模塊定義"。服務器
比較著名的AMD
規範的典型就是Require.js
,咱們能夠經過requireJS
一窺一二
Require.js
Require.js
定義了兩個顯性API:
define(id, [depends], callback)
定義模塊require([module], callback)
加載模塊基本思想是,經過define
方法,將代碼定義爲模塊;經過require
方法,實現代碼的模塊加載。
咱們直接看代碼:
define
能夠定義獨立模塊和非獨立模塊(也就是含有依賴)
<!--獨立模塊-->
define(function(){
return {
m: 0,
methods: function() {}
}
})
<!--非獨立模塊-->
<!--m1, m2的參數與前面的依賴數組是相對應的-->
define(['module1', 'module2'], function(m1, m2){
return {
m: 0,
methods: function() {}
}
})
複製代碼
require
引用:
require(['module1', 'module2'], function ( m1, m2 ) {
m1.doSomething();
m2.doSomething();
});
複製代碼
require
也能夠實現動態加載以及第三方加載,詳細可參考阮一峯老師的《RequireJS和AMD規範》,這裏不詳細舉例了。
咱們瞭解了AMD
規範是異步加載模塊,不影響頁面構建進程,可是AMD
規範的一個顯著特性是預加載,就是遇到的引入包都要提早執行加載(2.0以後也能夠改爲延遲執行,此處按下不表)好比:
define(function(require, exports, module) {
console.log('start');
var a = require('./a');
a.doSomething();
var b = require('./b');
b.doSomething();
return {
c: function() {
console.log('this is c');
}
};
});
複製代碼
這個執行順序是什麼呢?
加載a =>
加載b =>
console.log('start'); =>
a.doSomething(); =>
b.doSomething(); =>
console.log('this is c');
複製代碼
其實就至關於:
define(['a', 'b'], function(require, exports, module) {
console.log('start');
a.doSomething();
b.doSomething();
return {
c: function() {
console.log('this is c');
}
};
});
複製代碼
這種就是依賴前置,也是AMD
規範的一個顯著特徵。
那到這可能會想爲何不能用到的時候再加載呢?
這個時候就能夠引伸出CMD
規範。
CMD
規範CMD
規範,全稱是Common Module Definition
。
實現CMD
規範的典範是Sea.js
,雖然已通過時,但你們不妨瞭解一下。
CMD
規範是延遲執行,就是說等執行到這了,我再加載,也就是懶加載。而且推崇依賴就近。
就是用到了,你再寫,再執行加載。
define(function(require, exports, module) {
console.log('start');
var a = require('./a');
a.doSomething();
var b = require('./b');
b.doSomething();
return {
c: function() {
console.log('this is c');
}
};
});
複製代碼
那麼上面那段代碼拿到這裏,執行順序就會改變:
console.log('start'); =>
加載a =>
a.doSomething(); =>
加載b =>
b.doSomething(); =>
console.log('this is c');
複製代碼
這個能夠說是兩種規範最直觀的差異了,可是兩種規範的差異不只僅只是這些。
AMD
和CMD
的不一樣之處AMD
是提早執行,CMD
是延遲執行。不過 RequireJS
從 2.0
開始,也改爲能夠延遲執行(根據寫法不一樣,處理方式不一樣)。CMD
推崇 as lazy as possible。CMD
推崇依賴就近,AMD
推崇依賴前置AMD
的 API 默認是一個當多個用,CMD
的 API 嚴格區分,推崇職責單一。ES6模塊化
由於模塊化愈發重要,ES6
在語言標準的層面上,實現了模塊功能,並且實現得至關簡單,徹底能夠取代 CommonJS
和 AMD
規範,成爲瀏覽器和服務器通用的模塊解決方案。
ES6
模塊的設計思想是儘可能的靜態化,使得編譯時就能肯定模塊的依賴關係,以及輸入和輸出的變量。
這和CommonJS
AMD
規範是有根本區別的,由於CommonJS
AMD
二者都是運行時加載。
模塊功能主要由兩個命令構成:export
和import
。
export
export
命令用於規定模塊的對外接口。
一個模塊就是一個獨立的文件。該文件內部的全部變量,外部沒法獲取。若是你但願外部可以讀取模塊內部的某個變量,就必須使用export
關鍵字輸出該變量。
exports
的使用:
<!--暴露變量-->
const a = 1;
const b = 2;
export {a, b};
<!--輸出函數-->
function a() {...}
export { a }
<!--也能夠設置別名-->
export { a as b }
複製代碼
import
import
命令用於輸入其餘模塊提供的功能。
特性:
import
命令輸入的變量都是隻讀,不可修改,由於它的本質是輸入接口。可是若是是對象,能夠修改對象的屬性。import
會提高到頭部,在編譯時執行。mport
是靜態執行,因此不能使用表達式和變量,這些只有在運行時才能獲得結果的語法結構。import
語句是 Singleton
模式,只有一個實例,因此屢次調用同一個包的不一樣方法,只會生成一個實例對象。import
命令的使用:
import { a, b } from './main'
<!--取別名-->
import { a as b } from './main'
<!--也能夠直接加載模塊-->
import 'lodash';
<!--模塊總體加載-->
<!--此時的掛在對象a,應該是能夠靜態分析的,不容許運行時改變其任何屬性-->
import * as a from 'lodash'
複製代碼
export default
從前面的例子能夠看出,使用import命令的時候,用戶須要知道所要加載的變量名或函數名,不然沒法加載。可是,用戶確定但願快速上手,未必願意閱讀文檔,去了解模塊有哪些屬性和方法。
爲了給用戶提供方便,讓他們不用閱讀文檔就能加載模塊,就要用到export default
命令,爲模塊指定默認輸出。
特性:
export default
默認輸出,輸出變量名或方法爲default
。export default
加載時,不管是匿名函數仍是有名函數,統一默認爲匿名函數,須要在引用的時候重命名。export default
的輸出,在import
的時候,不須要使用{}
export default
能夠輸出變量,函數,還有類代碼展現一下:
<!--export-default.js-->
export default function () {
console.log('foo');
}
<!--也能夠寫成有名函數-->
export default function foo() {
console.log('foo');
}
<!--import-default.js-->
import customName from './export-default';
customName(); // 'foo'
複製代碼
也能夠自定義default
<!--modules.js-->
function add(x, y) {
return x * y;
}
export {add as default};
<!--等同於-->
export default add;
<!--app.js-->
import { default as foo } from 'modules';
// 等同於
import foo from 'modules 複製代碼
輸出類:
<!--MyClass.js-->
export default class { ... }
<!--main.js-->
import MyClass from 'MyClass';
let o = new MyClass();
複製代碼
export
與 import
的複合寫法若是在一個模塊之中,先輸入後輸出同一個模塊,import語句能夠與export語句寫在一塊兒。
export { a, b } from 'xxx';
<!--等同於-->
import { a, b } from 'xxx';
export { a, b }
<!--默認接口改成具名接口-->
export { default as es6 } from './someModule';
複製代碼
這裏至關於一箇中轉,實際上並無導入到當前模塊,因此當前模塊不能直接使用轉發的接口。
import()
import()
函數能夠說是import的補全,由於import是靜態加載,會先於模塊內其餘語句先執行,這樣的設計當然有利於編譯器提升效率,但也致使沒法在運行時加載模塊。
所以出現了提案,引入import()
函數,實現動態加載。例如條件加載:
if (condition) {
import('moduleA').then(...);
} else {
import('moduleB').then(...);
}
複製代碼
目前import()
函數提案已到stage4階段,你們其實能夠放心用了,兼容性很樂觀。
import()
返回一個Promise
對象,下面是一個例子:
const main = document.querySelector('main');
import(`./section-modules/${someVariable}.js`)
.then(module => {
module.loadPageInto(main);
})
.catch(err => {
main.textContent = err.message;
});
複製代碼
特性:
import()
函數能夠用在任何地方,不只僅是模塊,非模塊的腳本也可使用。它是運行時執行,也就是說,何時運行到這一句,就會加載指定的模塊。import()
函數與所加載的模塊沒有靜態鏈接關係,這點也是與import
語句不相同。import()
相似於 Node
的require
方法,區別主要是前者是異步加載,後者是同步加載。require
和import
的區別總結require
是同步導入,import
是異步導入require
是值拷貝,導出值變化不會影響導入值;import
指向 內存地址,導入值會隨導出值而變化require
是運行時加載,import
是編譯時加載(靜態加載)require
能夠實現第三方加載,import
只能導入本地存在文件I am moving forward.