目錄html
隨着js技術的不斷髮展,途中會遇到各類問題,好比模塊化。node
那什麼是模塊化呢,他們的目的是什麼?webpack
定義:如何把一段代碼封裝成一個有用的單元,以及如何註冊此模塊的能力、輸出的值
依賴引用:如何引用其它代碼單元git
到目前爲止,大概分爲如下幾個里程碑式節點。es6
原始的開發方式 ---> CommonJS ---> AMD ---> CMD ---> UMD ---> ES6Module
最開始的時候,JS自身是沒有模塊機制的。項目有多個js文件。github
// a.js function foo() {} // b.js function bar() {} // c.js foo()
HTML加載web
<script src="a.js"></script> <script src="b.js"></script> <script src="c.js"></script>
原始的開發方式,隨着項目的複雜度,代碼量愈來愈多,所須要加載的文件也愈來愈多,這個時候,就要考慮幾個問題了:api
global
上,會污染全局環境,而且須要考慮命名衝突問題。script
是順序加載的,若是各文件之間有依賴,那咱們得考慮加載.js
文件的書寫順序。CommonJS規範 CommonJS使用例子
CommonJS
規範,主要運行於服務器端,同步加載模塊,而加載的文件資源大多數在本地服務器,因此執行速度或時間沒問題。Node.js
很好的實現了該規範。
該規範指出,一個單獨的文件就是一個模塊。
模塊功能主要的幾個命令:require
和module.exports
。require
命令用於輸入其餘模塊提供的功能,module.exports
命令用於規範模塊的對外接口,輸出的是一個值的拷貝,輸出以後就不能改變了,會緩存起來。瀏覽器
// moduleA.js var name = 'weiqinl' function foo() {} module.exports = exports = { name, foo } // moduleB.js var ma = require('./moduleA') // 能夠省略後綴.js exports.bar = function() { ma.name === 'weiqinl' // true ma.foo() // 執行foo方法 } // moduleC.js var mb = require('./moduleB') mb.bar()
經過例子,咱們能夠看出require(moduleId)
來加載其餘模塊的內容,其返回值就是其引用的外部模塊所暴露的API,以後再經過module.exports
或者exports
來爲當前模塊的方法和變量提供輸出接口。緩存
最後經過node
來執行模塊。
AMD規範文檔 AMD使用例子
AMD(Asynchronous Module Definition - 異步加載模塊定義)規範,制定了定義模塊的規則,一個單獨的文件就是一個模塊,模塊和模塊的依賴能夠被異步加載。主要運行於瀏覽器端,這和瀏覽器的異步加載模塊的環境恰好適應,它不會影響後面語句的運行。該規範是在RequireJs的推廣過程當中逐漸完善的。
模塊功能主要的幾個命令:define
、require
、return
和define.amd
。define
是全局函數,用來定義模塊,define(id?, dependencies?, factory)
。require
命令用於輸入其餘模塊提供的功能,return
命令用於規範模塊的對外接口,define.amd
屬性是一個對象,此屬性的存在來代表函數遵循AMD
規範。
// moduleA.js define(['jQuery','lodash'], function($, _) { var name = 'weiqinl', function foo() {} return { name, foo } }) // index.js require(['moduleA'], function(a) { a.name === 'weiqinl' // true a.foo() // 執行A模塊中的foo函數 // do sth... }) // index.html <script src="js/require.js" data-main="js/index"></script>
在這裏,咱們使用define
來定義模塊,return
來輸出接口, require
來加載模塊,這是AMD官方推薦用法。固然也可使用其餘兼容性的寫法,好比對 Simplified CommonJS Wrapper 格式的支持,但背後仍是原始AMD
的運行邏輯。
AMD
的運行邏輯是:提早加載,提早執行。在Requirejs
中,申明依賴模塊時,會第一時間加載並執行模塊內的代碼,使後面的回調函數能在所需的環境中運行。
爲了更好地優化請求,同時推出了打包工具r.js
,使所需加載的文件數減小。require.js模塊化開發,並用r.js打包例子
CMD規範文檔 CMD使用例子
CMD(Common Module Definition - 通用模塊定義)規範主要是Sea.js
推廣中造成的,一個文件就是一個模塊,能夠像Node.js
通常書寫模塊代碼。主要在瀏覽器中運行,固然也能夠在Node.js
中運行。
// moduleA.js // 定義模塊 define(function(require, exports, module) { var func = function() { var a = require('./a') // 到此纔會加載a模塊 a.func() if(false) { var b = require('./b') // 到此纔會加載b模塊 b.func() } } // do sth... exports.func = func; }) // index.js // 加載使用模塊 seajs.use('moduleA.js', function(ma) { var ma = math.func() }) // HTML,須要在頁面中引入sea.js文件。 <script src="./js/sea.js"></script> <script src="./js/index.js"></script>
這裏define
是一個全局函數,用來定義模塊,並經過exports
向外提供接口。以後,若是要使用某模塊,能夠經過require
來獲取該模塊提供的接口。最後使用某個組件的時候,經過seajs.use()
來調用。
- 經過
exports
暴露接口。這意味着不須要命名空間了,更不須要全局變量。- 經過
require
引入依賴。這可讓依賴內置,咱們只須要關心當前模塊的依賴。關注度分離
CMD推崇依賴就近,延遲執行。在上面例子中,經過require
引入的模塊,只有當程序運行到此處的時候,模塊纔會自動加載執行。
同時推出了spm(static package manager)
的打包方式,據說支付寶的項目在使用。
UMD文檔
UMD(Universal Module Definition - 通用模塊定義)模式,該模式主要用來解決CommonJS
模式和AMD
模式代碼不能通用的問題,並同時還支持老式的全局
變量規範。
// 使用Node, AMD 或 browser globals 模式建立模塊 (function (root, factory) { if (typeof define === 'function' && define.amd) { // AMD模式. 註冊爲一個匿名函數 define(['b'], factory); } else if (typeof module === 'object' && module.exports) { // Node等類CommonJS的環境 module.exports = factory(require('b')); } else { // 瀏覽器全局變量 (root is window) root.returnExports = factory(root.b); } }(typeof self !== 'undefined' ? self : this, function (b) { // 以某種方式使用 b //返回一個值來定義模塊導出。(便可以返回對象,也能夠返回函數) return {}; }));
define
爲函數,而且是否存在define.amd
,來判斷是否爲AMD規範
,module
是否爲一個對象,而且是否存在module.exports
來判斷是否爲CommonJS規範
這種模式,一般會在webpack
打包的時候用到。output.libraryTarget
將模塊以哪一種規範的文件輸出。
ES6模塊加載文檔 ES6 Module使用例子
在ECMAScript 2015版本出來以後,肯定了一種新的模塊加載方式,咱們稱之爲ES6 Module。它和前幾種方式有區別和相同點。
node
環境下運行。import
和export
來肯定。Commonjs
模塊混合使用。CommonJS
輸出的是一個值的拷貝。ES6模塊輸出的是值的引用,加載的時候會作靜態優化。CommonJS
模塊是運行時加載肯定輸出接口,ES6模塊是編譯時肯定輸出接口。ES6模塊功能主要由兩個命令構成:import
和export
。import
命令用於輸入其餘模塊提供的功能。export
命令用於規範模塊的對外接口。
export
的幾種用法
// 輸出變量 export var name = 'weiqinl' export var year = '2018' // 輸出一個對象(推薦) var name = 'weiqinl' var year = '2018' export { name, year} // 輸出函數或類 export function add(a, b) { return a + b; } // export default 命令 export default function() { console.log('foo') }
import
導入其餘模塊
// 正常命令 import { name, year } from './module.js' //後綴.js不能省略 // 若是遇到export default命令導出的模塊 import ed from './export-default.js'
模塊編輯好以後,它能夠以多種形式加載。
瀏覽器加載ES6模塊,使用<script>
標籤,可是要加入type="module"
屬性
外鏈js文件
<script type="module" src="index.js"></script>
也能夠內嵌在網頁中
<script type="module"> import utils from './utils.js'; // other code </script>
對於加載外部模塊,須要注意:
use strict
。import
命令加載其餘模塊(.js後綴不可省略,須要提供絕對 URL 或相對 URL),也可使用export
命令輸出對外接口。this
關鍵字返回undefined
,而不是指向window
。也就是說,在模塊頂層使用this
關鍵字,是無心義的。Node要求 ES6 模塊採用.mjs
後綴文件名。也就是說,只要腳本文件裏面使用import
或者export
命令,就必須採用.mjs
後綴名。
這個功能還在試驗階段。安裝Node V8.5.0
或以上版本,要用--experimental-modules
參數才能打開該功能。
$ node --experimental-modules my-app.mjs
Node
的import
命令只支持異步加載本地模塊(file:
協議),不支持加載遠程模塊。
以上便是,我對js模塊化概念,最主要的仍是他們輸入輸出的區別,理論結合實踐完成的簡單梳理。經過閱讀前輩的文章或者各類Issue
,也大概瞭解了JS的歷史進程。以史爲鑑,能夠知興替。此時不斷成爲歷史,歷史終將定論。
[完]