僅需一篇,搞定前端模塊化
原創(chuàng)【51CTO.com原創(chuàng)稿件】前言
在JavaScript發(fā)展初期就是為了實(shí)現(xiàn)簡(jiǎn)單的頁(yè)面交互邏輯,寥寥數(shù)語(yǔ)即可;如今CPU、瀏覽器性能得到了極大的提升,很多頁(yè)面邏輯遷移到了客戶端(表單驗(yàn)證等),隨著web2.0時(shí)代的到來(lái),Ajax技術(shù)得到廣泛應(yīng)用,jQuery等前端庫(kù)層出不窮,前端代碼日益膨脹,此時(shí)在JS方面就會(huì)考慮使用模塊化規(guī)范去管理。
本文內(nèi)容主要有理解模塊化,為什么要模塊化,模塊化的優(yōu)缺點(diǎn)以及模塊化規(guī)范,并且介紹下開(kāi)發(fā)中***的CommonJS, AMD, ES6、CMD規(guī)范。本文試圖站在小白的角度,用通俗易懂的筆調(diào)介紹這些枯燥無(wú)味的概念,希望諸君閱讀后,對(duì)模塊化編程有個(gè)全新的認(rèn)識(shí)和理解!
如需本文的源代碼,請(qǐng)猛戳源代碼 。
一、模塊化的理解
1.什么是模塊?
- 將一個(gè)復(fù)雜的程序依據(jù)一定的規(guī)則(規(guī)范)封裝成幾個(gè)塊(文件), 并進(jìn)行組合在一起
- 塊的內(nèi)部數(shù)據(jù)與實(shí)現(xiàn)是私有的, 只是向外部暴露一些接口(方法)與外部其它模塊通信
2.模塊化的進(jìn)化過(guò)程
- 全局function模式 : 將不同的功能封裝成不同的全局函數(shù)
- 編碼: 將不同的功能封裝成不同的全局函數(shù)
- 問(wèn)題: 污染全局命名空間, 容易引起命名沖突或數(shù)據(jù)不安全,而且模塊成員之間看不出直接關(guān)系
- function m1(){
- //...
- }
- function m2(){
- //...
- }
- namespace模式 : 簡(jiǎn)單對(duì)象封裝
- 作用: 減少了全局變量,解決命名沖突
- 問(wèn)題: 數(shù)據(jù)不安全(外部可以直接修改模塊內(nèi)部的數(shù)據(jù))
- let myModule = {
- data: 'www.baidu.com',
- foo() {
- console.log(`foo() ${this.data}`)
- },
- bar() {
- console.log(`bar() ${this.data}`)
- }
- }
- myModule.data = 'other data' //能直接修改模塊內(nèi)部的數(shù)據(jù)
- myModule.foo() // foo() other data
這樣的寫(xiě)法會(huì)暴露所有模塊成員,內(nèi)部狀態(tài)可以被外部改寫(xiě)。
- IIFE模式:匿名函數(shù)自調(diào)用(閉包)
- 作用: 數(shù)據(jù)是私有的, 外部只能通過(guò)暴露的方法操作
- 編碼: 將數(shù)據(jù)和行為封裝到一個(gè)函數(shù)內(nèi)部, 通過(guò)給window添加屬性來(lái)向外暴露接口
- 問(wèn)題: 如果當(dāng)前這個(gè)模塊依賴(lài)另一個(gè)模塊怎么辦?
- // index.html文件
- <script type="text/javascript" src="module.js"></script>
- <script type="text/javascript">
- myModule.foo()
- myModule.bar()
- console.log(myModule.data) //undefined 不能訪問(wèn)模塊內(nèi)部數(shù)據(jù)
- myModule.data = 'xxxx' //不是修改的模塊內(nèi)部的data
- myModule.foo() //沒(méi)有改變
- </script>
- // module.js文件
- (function(window) {
- let data = 'www.baidu.com'
- //操作數(shù)據(jù)的函數(shù)
- function foo() {
- //用于暴露有函數(shù)
- console.log(`foo() ${data}`)
- }
- function bar() {
- //用于暴露有函數(shù)
- console.log(`bar() ${data}`)
- otherFun() //內(nèi)部調(diào)用
- }
- function otherFun() {
- //內(nèi)部私有的函數(shù)
- console.log('otherFun()')
- }
- //暴露行為
- window.myModule = { foo, bar } //ES6寫(xiě)法
- })(window)
***得到的結(jié)果:
- IIFE模式增強(qiáng) : 引入依賴(lài)
這就是現(xiàn)代模塊實(shí)現(xiàn)的基石。
- // module.js文件
- (function(window, $) {
- let data = 'www.baidu.com'
- //操作數(shù)據(jù)的函數(shù)
- function foo() {
- //用于暴露有函數(shù)
- console.log(`foo() ${data}`)
- $('body').css('background', 'red')
- }
- function bar() {
- //用于暴露有函數(shù)
- console.log(`bar() ${data}`)
- otherFun() //內(nèi)部調(diào)用
- }
- function otherFun() {
- //內(nèi)部私有的函數(shù)
- console.log('otherFun()')
- }
- //暴露行為
- window.myModule = { foo, bar }
- })(window, jQuery)
- // index.html文件
- <!-- 引入的js必須有一定順序 -->
- <script type="text/javascript" src="jquery-1.10.1.js"></script>
- <script type="text/javascript" src="module.js"></script>
- <script type="text/javascript">
- myModule.foo()
- </script>
上例子通過(guò)jquery方法將頁(yè)面的背景顏色改成紅色,所以必須先引入jQuery庫(kù),就把這個(gè)庫(kù)當(dāng)作參數(shù)傳入。這樣做除了保證模塊的獨(dú)立性,還使得模塊之間的依賴(lài)關(guān)系變得明顯。
3. 模塊化的好處
- 避免命名沖突(減少命名空間污染)
- 更好的分離, 按需加載
- 更高復(fù)用性
- 高可維護(hù)性
4. 引入<script>
多個(gè)后出現(xiàn)問(wèn)題
- 請(qǐng)求過(guò)多
首先我們要依賴(lài)多個(gè)模塊,那樣就會(huì)發(fā)送多個(gè)請(qǐng)求,導(dǎo)致請(qǐng)求過(guò)多
- 依賴(lài)模糊
我們不知道他們的具體依賴(lài)關(guān)系是什么,也就是說(shuō)很容易因?yàn)椴涣私馑麄冎g的依賴(lài)關(guān)系導(dǎo)致加載先后順序出錯(cuò)。
- 難以維護(hù)
以上兩種原因就導(dǎo)致了很難維護(hù),很可能出現(xiàn)牽一發(fā)而動(dòng)全身的情況導(dǎo)致項(xiàng)目出現(xiàn)嚴(yán)重的問(wèn)題。
模塊化固然有多個(gè)好處,然而一個(gè)頁(yè)面需要引入多個(gè)js文件,就會(huì)出現(xiàn)以上這些問(wèn)題。而這些問(wèn)題可以通過(guò)模塊化規(guī)范來(lái)解決,下面介紹開(kāi)發(fā)中***的commonjs, AMD, ES6, CMD規(guī)范。
二、模塊化規(guī)范
1.CommonJS
(1)概述
Node 應(yīng)用由模塊組成,采用 CommonJS 模塊規(guī)范。每個(gè)文件就是一個(gè)模塊,有自己的作用域。在一個(gè)文件里面定義的變量、函數(shù)、類(lèi),都是私有的,對(duì)其他文件不可見(jiàn)。在服務(wù)器端,模塊的加載是運(yùn)行時(shí)同步加載的;在瀏覽器端,模塊需要提前編譯打包處理。
(2)特點(diǎn)
- 所有代碼都運(yùn)行在模塊作用域,不會(huì)污染全局作用域。
- 模塊可以多次加載,但是只會(huì)在***次加載時(shí)運(yùn)行一次,然后運(yùn)行結(jié)果就被緩存了,以后再加載,就直接讀取緩存結(jié)果。要想讓模塊再次運(yùn)行,必須清除緩存。
- 模塊加載的順序,按照其在代碼中出現(xiàn)的順序。
(3)基本語(yǔ)法
- 暴露模塊:module.exports = value或exports.xxx = value
- 引入模塊:require(xxx),如果是第三方模塊,xxx為模塊名;如果是自定義模塊,xxx為模塊文件路徑
此處我們有個(gè)疑問(wèn):CommonJS暴露的模塊到底是什么? CommonJS規(guī)范規(guī)定,每個(gè)模塊內(nèi)部,module變量代表當(dāng)前模塊。這個(gè)變量是一個(gè)對(duì)象,它的exports屬性(即module.exports)是對(duì)外的接口。加載某個(gè)模塊,其實(shí)是加載該模塊的module.exports屬性。
- // example.js
- var x = 5;
- var addX = function (value) {
- return value + x;
- };
- module.exports.x = x;
- module.exports.addX = addX;
上面代碼通過(guò)module.exports輸出變量x和函數(shù)addX。
- var example = require('./example.js');//如果參數(shù)字符串以“./”開(kāi)頭,則表示加載的是一個(gè)位于相對(duì)路徑
- console.log(example.x); // 5
- console.log(example.addX(1)); // 6
require命令用于加載模塊文件。require命令的基本功能是,讀入并執(zhí)行一個(gè)JavaScript文件,然后返回該模塊的exports對(duì)象。如果沒(méi)有發(fā)現(xiàn)指定模塊,會(huì)報(bào)錯(cuò)。
(4)模塊的加載機(jī)制
CommonJS模塊的加載機(jī)制是,輸入的是被輸出的值的拷貝。也就是說(shuō),一旦輸出一個(gè)值,模塊內(nèi)部的變化就影響不到這個(gè)值。這點(diǎn)與ES6模塊化有重大差異(下文會(huì)介紹),請(qǐng)看下面這個(gè)例子:
- // lib.js
- var counter = 3;
- function incCounter() {
- counter++;
- }
- module.exports = {
- counter: counter,
- incCounter: incCounter,
- };
上面代碼輸出內(nèi)部變量counter和改寫(xiě)這個(gè)變量的內(nèi)部方法incCounter。
- // main.js
- var counter = require('./lib').counter;
- var incCounter = require('./lib').incCounter;
- console.log(counter); // 3
- incCounter();
- console.log(counter); // 3
上面代碼說(shuō)明,counter輸出以后,lib.js模塊內(nèi)部的變化就影響不到counter了。這是因?yàn)閏ounter是一個(gè)原始類(lèi)型的值,會(huì)被緩存。除非寫(xiě)成一個(gè)函數(shù),才能得到內(nèi)部變動(dòng)后的值。
(5)服務(wù)器端實(shí)現(xiàn)
①下載安裝node.js
②創(chuàng)建項(xiàng)目結(jié)構(gòu)
注意:用npm init 自動(dòng)生成package.json時(shí),package name(包名)不能有中文和大寫(xiě)。
- |-modules
- |-module1.js
- |-module2.js
- |-module3.js
- |-app.js
- |-package.json
- {
- "name": "commonJS-node",
- "version": "1.0.0"
- }
③下載第三方模塊
- npm install uniq --save // 用于數(shù)組去重
④定義模塊代碼
- //module1.js
- module.exports = {
- msg: 'module1',
- foo() {
- console.log(this.msg)
- }
- }
- //module2.js
- module.exports = function() {
- console.log('module2')
- }
- //module3.js
- exports.foo = function() {
- console.log('foo() module3')
- }
- exports.arr = [1, 2, 3, 3, 2]
- // app.js文件
- // 引入第三方庫(kù),應(yīng)該放置在最前面
- let uniq = require('uniq')
- let module1 = require('./modules/module1')
- let module2 = require('./modules/module2')
- let module3 = require('./modules/module3')
- module1.foo() //module1
- module2() //module2
- module3.foo() //foo() module3
- console.log(uniq(module3.arr)) //[ 1, 2, 3 ]
⑤通過(guò)node運(yùn)行app.js
命令行輸入node app.js,運(yùn)行JS文件
(6)瀏覽器端實(shí)現(xiàn)(借助Browserify)
①創(chuàng)建項(xiàng)目結(jié)構(gòu)
- |-js
- |-dist //打包生成文件的目錄
- |-src //源碼所在的目錄
- |-module1.js
- |-module2.js
- |-module3.js
- |-app.js //應(yīng)用主源文件
- |-index.html //運(yùn)行于瀏覽器上
- |-package.json
- {
- "name": "browserify-test",
- "version": "1.0.0"
- }
②下載browserify
- 全局: npm install browserify -g
- 局部: npm install browserify --save-dev
③定義模塊代碼(同服務(wù)器端)
注意:index.html文件要運(yùn)行在瀏覽器上,需要借助browserify將app.js文件打包編譯,如果直接在index.html引入app.js就會(huì)報(bào)錯(cuò)!
④打包處理js
根目錄下運(yùn)行browserify js/src/app.js -o js/dist/bundle.js
⑤頁(yè)面使用引入
在index.html文件中引入
2.AMD
CommonJS規(guī)范加載模塊是同步的,也就是說(shuō),只有加載完成,才能執(zhí)行后面的操作。AMD規(guī)范則是非同步加載模塊,允許指定回調(diào)函數(shù)。由于Node.js主要用于服務(wù)器編程,模塊文件一般都已經(jīng)存在于本地硬盤(pán),所以加載起來(lái)比較快,不用考慮非同步加載的方式,所以CommonJS規(guī)范比較適用。但是,如果是瀏覽器環(huán)境,要從服務(wù)器端加載模塊,這時(shí)就必須采用非同步模式,因此瀏覽器端一般采用AMD規(guī)范。此外AMD規(guī)范比CommonJS規(guī)范在瀏覽器端實(shí)現(xiàn)要來(lái)著早。
(1)AMD規(guī)范基本語(yǔ)法
定義暴露模塊:
- //定義沒(méi)有依賴(lài)的模塊
- define(function(){
- return 模塊
- })
- //定義有依賴(lài)的模塊
- define(['module1', 'module2'], function(m1, m2){
- return 模塊
- })
引入使用模塊:
- require(['module1', 'module2'], function(m1, m2){
- 使用m1/m2
- })
(2)未使用AMD規(guī)范與使用require.js
通過(guò)比較兩者的實(shí)現(xiàn)方法,來(lái)說(shuō)明使用AMD規(guī)范的好處。
未使用AMD規(guī)范
- // alerter.js文件
- (function (window) {
- let msg = 'www.baidu.com'
- function getMsg() {
- return msg.toUpperCase()
- }
- window.dataService = {getMsg}
- })(window)
- // dataService.js文件
- (function (window, dataService) {
- let name = 'Tom'
- function showMsg() {
- alert(dataService.getMsg() + ', ' + name)
- }
- window.alerter = {showMsg}
- })(window, dataService)
- // main.js文件
- (function (alerter) {
- alerter.showMsg()
- })(alerter)
- // index.html文件
- // index.html文件
- <div><h1>Modular Demo 1: 未使用AMD(require.js)</h1></div>
- <script type="text/javascript" src="js/modules/dataService.js"></script>
- <script type="text/javascript" src="js/modules/alerter.js"></script>
- <script type="text/javascript" src="js/main.js"></script>
***得到如下結(jié)果:
這種方式缺點(diǎn)很明顯:首先會(huì)發(fā)送多個(gè)請(qǐng)求,其次引入的js文件順序不能搞錯(cuò),否則會(huì)報(bào)錯(cuò)!
- 使用require.js
RequireJS是一個(gè)工具庫(kù),主要用于客戶端的模塊管理。它的模塊管理遵守AMD規(guī)范,RequireJS的基本思想是,通過(guò)define方法,將代碼定義為模塊;通過(guò)require方法,實(shí)現(xiàn)代碼的模塊加載。
接下來(lái)介紹AMD規(guī)范在瀏覽器實(shí)現(xiàn)的步驟:
①下載require.js, 并引入
- 官網(wǎng): http://www.requirejs.cn/
- github : https://github.com/requirejs/requirejs
然后將require.js導(dǎo)入項(xiàng)目: js/libs/require.js
②創(chuàng)建項(xiàng)目結(jié)構(gòu)
- |-js
- |-libs
- |-require.js
- |-modules
- |-alerter.js
- |-dataService.js
- |-main.js
- |-index.html
③定義require.js的模塊代碼
- // dataService.js文件
- // 定義沒(méi)有依賴(lài)的模塊
- define(function() {
- let msg = 'www.baidu.com'
- function getMsg() {
- return msg.toUpperCase()
- }
- return { getMsg } // 暴露模塊
- })
- //alerter.js文件
- // 定義有依賴(lài)的模塊
- define(['dataService'], function(dataService) {
- let name = 'Tom'
- function showMsg() {
- alert(dataService.getMsg() + ', ' + name)
- }
- // 暴露模塊
- return { showMsg }
- })
- // main.js文件
- (function() {
- require.config({
- baseUrl: 'js/', //基本路徑 出發(fā)點(diǎn)在根目錄下
- paths: {
- //映射: 模塊標(biāo)識(shí)名: 路徑
- alerter: './modules/alerter', //此處不能寫(xiě)成alerter.js,會(huì)報(bào)錯(cuò)
- dataService: './modules/dataService'
- }
- })
- require(['alerter'], function(alerter) {
- alerter.showMsg()
- })
- })()
- // index.html文件
- <!DOCTYPE html>
- <html>
- <head>
- <title>Modular Demo</title>
- </head>
- <body>
- <!-- 引入require.js并指定js主文件的入口 -->
- <script data-main="js/main" src="js/libs/require.js"></script>
- </body>
- </html>
④頁(yè)面引入require.js模塊:
在index.html引入
此外在項(xiàng)目中如何引入第三方庫(kù)?只需在上面代碼的基礎(chǔ)稍作修改:
- // alerter.js文件
- define(['dataService', 'jquery'], function(dataService, $) {
- let name = 'Tom'
- function showMsg() {
- alert(dataService.getMsg() + ', ' + name)
- }
- $('body').css('background', 'green')
- // 暴露模塊
- return { showMsg }
- })
- // main.js文件
- (function() {
- require.config({
- baseUrl: 'js/', //基本路徑 出發(fā)點(diǎn)在根目錄下
- paths: {
- //自定義模塊
- alerter: './modules/alerter', //此處不能寫(xiě)成alerter.js,會(huì)報(bào)錯(cuò)
- dataService: './modules/dataService',
- // 第三方庫(kù)模塊
- jquery: './libs/jquery-1.10.1' //注意:寫(xiě)成jQuery會(huì)報(bào)錯(cuò)
- }
- })
- require(['alerter'], function(alerter) {
- alerter.showMsg()
- })
- })()
上例是在alerter.js文件中引入jQuery第三方庫(kù),main.js文件也要有相應(yīng)的路徑配置。
小結(jié):通過(guò)兩者的比較,可以得出AMD模塊定義的方法非常清晰,不會(huì)污染全局環(huán)境,能夠清楚地顯示依賴(lài)關(guān)系。AMD模式可以用于瀏覽器環(huán)境,并且允許非同步加載模塊,也可以根據(jù)需要?jiǎng)討B(tài)加載模塊。
3.CMD
CMD規(guī)范專(zhuān)門(mén)用于瀏覽器端,模塊的加載是異步的,模塊使用時(shí)才會(huì)加載執(zhí)行。CMD規(guī)范整合了CommonJS和AMD規(guī)范的特點(diǎn)。在 Sea.js 中,所有 JavaScript 模塊都遵循 CMD模塊定義規(guī)范。
(1)CMD規(guī)范基本語(yǔ)法
定義暴露模塊:
- //定義沒(méi)有依賴(lài)的模塊
- define(function(require, exports, module){
- exports.xxx = value
- module.exports = value
- })
- //定義有依賴(lài)的模塊
- define(function(require, exports, module){
- //引入依賴(lài)模塊(同步)
- var module2 = require('./module2')
- //引入依賴(lài)模塊(異步)
- require.async('./module3', function (m3) {
- })
- //暴露模塊
- exports.xxx = value
- })
引入使用模塊:
- define(function (require) {
- var m1 = require('./module1')
- var m4 = require('./module4')
- m1.show()
- m4.show()
- })
(2)sea.js簡(jiǎn)單使用教程
①下載sea.js, 并引入
- 官網(wǎng): http://seajs.org/
- github : https://github.com/seajs/seajs
然后將sea.js導(dǎo)入項(xiàng)目: js/libs/sea.js
②創(chuàng)建項(xiàng)目結(jié)構(gòu)
- |-js
- |-libs
- |-sea.js
- |-modules
- |-module1.js
- |-module2.js
- |-module3.js
- |-module4.js
- |-main.js
- |-index.html
③定義sea.js的模塊代碼
- // module1.js文件
- define(function (require, exports, module) {
- //內(nèi)部變量數(shù)據(jù)
- var data = 'atguigu.com'
- //內(nèi)部函數(shù)
- function show() {
- console.log('module1 show() ' + data)
- }
- //向外暴露
- exports.show = show
- })
- // module2.js文件
- define(function (require, exports, module) {
- module.exports = {
- msg: 'I Will Back'
- }
- })
- // module3.js文件
- define(function(require, exports, module) {
- const API_KEY = 'abc123'
- exports.API_KEY = API_KEY
- })
- // module4.js文件
- define(function (require, exports, module) {
- //引入依賴(lài)模塊(同步)
- var module2 = require('./module2')
- function show() {
- console.log('module4 show() ' + module2.msg)
- }
- exports.show = show
- //引入依賴(lài)模塊(異步)
- require.async('./module3', function (m3) {
- console.log('異步引入依賴(lài)模塊3 ' + m3.API_KEY)
- })
- })
- // main.js文件
- define(function (require) {
- var m1 = require('./module1')
- var m4 = require('./module4')
- m1.show()
- m4.show()
- })
④在index.html中引入
- <script type="text/javascript" src="js/libs/sea.js"></script>
- <script type="text/javascript">
- seajs.use('./js/modules/main')
- </script>
***得到結(jié)果如下:
4.ES6模塊化
ES6 模塊的設(shè)計(jì)思想是盡量的靜態(tài)化,使得編譯時(shí)就能確定模塊的依賴(lài)關(guān)系,以及輸入和輸出的變量。CommonJS 和 AMD 模塊,都只能在運(yùn)行時(shí)確定這些東西。比如,CommonJS 模塊就是對(duì)象,輸入時(shí)必須查找對(duì)象屬性。
(1)ES6模塊化語(yǔ)法
export命令用于規(guī)定模塊的對(duì)外接口,import命令用于輸入其他模塊提供的功能。
- /** 定義模塊 math.js **/
- var basicNum = 0;
- var add = function (a, b) {
- return a + b;
- };
- export { basicNum, add };
- /** 引用模塊 **/
- import { basicNum, add } from './math';
- function test(ele) {
- ele.textContent = add(99 + basicNum);
- }
如上例所示,使用import命令的時(shí)候,用戶需要知道所要加載的變量名或函數(shù)名,否則無(wú)法加載。為了給用戶提供方便,讓他們不用閱讀文檔就能加載模塊,就要用到export default命令,為模塊指定默認(rèn)輸出。
- // export-default.js
- export default function () {
- console.log('foo');
- }
- // import-default.js
- import customName from './export-default';
- customName(); // 'foo'
模塊默認(rèn)輸出, 其他模塊加載該模塊時(shí),import命令可以為該匿名函數(shù)指定任意名字。
(2)ES6 模塊與 CommonJS 模塊的差異
它們有兩個(gè)重大差異:
① CommonJS 模塊輸出的是一個(gè)值的拷貝,ES6 模塊輸出的是值的引用。
② CommonJS 模塊是運(yùn)行時(shí)加載,ES6 模塊是編譯時(shí)輸出接口。
第二個(gè)差異是因?yàn)?CommonJS 加載的是一個(gè)對(duì)象(即module.exports屬性),該對(duì)象只有在腳本運(yùn)行完才會(huì)生成。而 ES6 模塊不是對(duì)象,它的對(duì)外接口只是一種靜態(tài)定義,在代碼靜態(tài)解析階段就會(huì)生成。
下面重點(diǎn)解釋***個(gè)差異,我們還是舉上面那個(gè)CommonJS模塊的加載機(jī)制例子:
- // lib.js
- export let counter = 3;
- export function incCounter() {
- counter++;
- }
- // main.js
- import { counter, incCounter } from './lib';
- console.log(counter); // 3
- incCounter();
- console.log(counter); // 4
ES6 模塊的運(yùn)行機(jī)制與 CommonJS 不一樣。ES6 模塊是動(dòng)態(tài)引用,并且不會(huì)緩存值,模塊里面的變量綁定其所在的模塊。
(3) ES6-Babel-Browserify使用教程
簡(jiǎn)單來(lái)說(shuō)就一句話:使用Babel將ES6編譯為ES5代碼,使用Browserify編譯打包js。
①定義package.json文件
- {
- "name" : "es6-babel-browserify",
- "version" : "1.0.0"
- }
②安裝babel-cli, babel-preset-es2015和browserify
- npm install babel-cli browserify -g
- npm install babel-preset-es2015 --save-dev
- preset 預(yù)設(shè)(將es6轉(zhuǎn)換成es5的所有插件打包)
③定義.babelrc文件
- {
- "presets": ["es2015"]
- }
④定義模塊代碼
- //module1.js文件
- // 分別暴露
- export function foo() {
- console.log('foo() module1')
- }
- export function bar() {
- console.log('bar() module1')
- }
- //module2.js文件
- // 統(tǒng)一暴露
- function fun1() {
- console.log('fun1() module2')
- }
- function fun2() {
- console.log('fun2() module2')
- }
- export { fun1, fun2 }
- //module3.js文件
- // 默認(rèn)暴露 可以暴露任意數(shù)據(jù)類(lèi)項(xiàng),暴露什么數(shù)據(jù),接收到就是什么數(shù)據(jù)
- export default () => {
- console.log('默認(rèn)暴露')
- }
- // app.js文件
- import { foo, bar } from './module1'
- import { fun1, fun2 } from './module2'
- import module3 from './module3'
- foo()
- bar()
- fun1()
- fun2()
- module3()
⑤ 編譯并在index.html中引入
- 使用Babel將ES6編譯為ES5代碼(但包含CommonJS語(yǔ)法) : babel js/src -d js/lib
- 使用Browserify編譯js : browserify js/lib/app.js -o js/lib/bundle.js
然后在index.html文件中引入:
- <script type="text/javascript" src="js/lib/bundle.js"></script>
***得到如下結(jié)果:
此外第三方庫(kù)(以jQuery為例)如何引入呢?
首先安裝依賴(lài)npm install jquery@1
然后在app.js文件中引入:
- //app.js文件
- import { foo, bar } from './module1'
- import { fun1, fun2 } from './module2'
- import module3 from './module3'
- import $ from 'jquery'
- foo()
- bar()
- fun1()
- fun2()
- module3()
- $('body').css('background', 'green')
三、總結(jié)
- CommonJS規(guī)范主要用于服務(wù)端編程,加載模塊是同步的,這并不適合在瀏覽器環(huán)境,因?yàn)橥揭馕吨枞虞d,瀏覽器資源是異步加載的,因此有了AMD CMD解決方案。
- AMD規(guī)范在瀏覽器環(huán)境中異步加載模塊,而且可以并行加載多個(gè)模塊。不過(guò),AMD規(guī)范開(kāi)發(fā)成本高,代碼的閱讀和書(shū)寫(xiě)比較困難,模塊定義方式的語(yǔ)義不順暢。
- CMD規(guī)范與AMD規(guī)范很相似,都用于瀏覽器編程,依賴(lài)就近,延遲執(zhí)行,可以很容易在Node.js中運(yùn)行。不過(guò),依賴(lài)SPM 打包,模塊的加載邏輯偏重
- ES6 在語(yǔ)言標(biāo)準(zhǔn)的層面上,實(shí)現(xiàn)了模塊功能,而且實(shí)現(xiàn)得相當(dāng)簡(jiǎn)單,完全可以取代 CommonJS 和 AMD 規(guī)范,成為瀏覽器和服務(wù)器通用的模塊解決方案。
參考文章
- 前端模塊化開(kāi)發(fā)那點(diǎn)歷史
- CommonJS,AMD,CMD區(qū)別
- AMD 和 CMD 的區(qū)別有哪些?
- Javascript模塊化編程
- Javascript標(biāo)準(zhǔn)參考教程
- CMD 模塊定義規(guī)范
- 理解CommonJS、AMD、CMD三種規(guī)范
作者:浪里行舟,慕課網(wǎng)認(rèn)證作者,前端愛(ài)好者,立志往全棧工程師發(fā)展,從事前端一年多,目前技術(shù)棧有vue全家桶、ES6以及l(fā)ess等,樂(lè)于分享,最近一年寫(xiě)了五六十篇原創(chuàng)技術(shù)文章,得到諸多好評(píng)!
【51CTO原創(chuàng)稿件,合作站點(diǎn)轉(zhuǎn)載請(qǐng)注明原文作者和出處為51CTO.com】