【ES5】前端模塊化

前言

JavaScript初期只是為了在前端頁面進行一些簡單的表單校驗,避免和后臺產(chǎn)生不必要多余的交互,寥寥數(shù)語即可。后來隨著互聯(lián)網(wǎng)的不斷發(fā)展,Ajax技術的廣泛應用,前端各種庫的出現(xiàn)(如jQuery),JavaScript不斷的更新迭代,大多數(shù)瀏覽器的支持,使得JavaScript的發(fā)展日益壯大。

最初編程人員寫JavaScript腳本時,一個js腳本寫在一個文件中,導致頁面代碼成百上千行的累加;后來使用多個文件進行分開,然后使用script標簽進行引入,這樣又會導致引用順序問題,引入的順序稍有不慎,就會出錯。所以為了解決這一問題,模塊化思想便油然而生。

前端模塊化規(guī)范

一、模塊化理解

1. 模塊化概念

所謂模塊化,就是各個模塊之間各司其職,做自己該做的事兒,需要用到誰,就引入哪個模塊,哪個模塊需要自己的一些功能,就暴露出去。就像一個手機一樣,有CPU、GPU、電池、攝像頭、傳感去、屏幕等模塊,每個模塊相互合作,組成了一個完整的手機,如果有哪個模塊失效了或者過時了,更換最新的模塊即可,沒有必要去更換整個手機,這樣更加節(jié)省成本,模塊化就是如此。

2. 模塊化的好處

  • 避免命名沖突(減少命名空間污染)
  • 更好的分離, 按需加載
  • 更高復用性
  • 高可維護性

3. 引入多個<script>后出現(xiàn)出現(xiàn)問題

  • 請求過多
    首先我們要依賴多個模塊,那樣就會發(fā)送多個請求,導致請求過多

  • 依賴模糊
    我們不知道他們的具體依賴關系是什么,也就是說很容易因為不了解他們之間的依賴關系導致加載先后順序出錯。

  • 難以維護
    以上兩種原因就導致了很難維護,很可能出現(xiàn)牽一發(fā)而動全身的情況導致項目出現(xiàn)嚴重的問題。

4. 模塊化的發(fā)展

4.1 函數(shù)封裝模式

最開始是將一個簡單的功能封裝成為一個函數(shù),掛載到全局作用域上,然后使用script標簽引入到頁面中進行使用

(1) 目錄結構

├─index.html
├─modules
|    ├─module1.js
|    └─module2.js

(2) 模塊代碼

module1代碼:

function test1() {
    console.log('我是module1中的test1');
}

function test2() {
    console.log('我是module1中的test2');
}

module2代碼:

function test1() {
    console.log('我是module2中的test1');
}

function test2() {
    console.log('我是module2中的test2');
}

(3)主頁面index

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>全局function模式</title>
</head>
<body>
    
    <script src="./modules/module1.js"></script>
    <script src="./modules/module2.js"></script>
    <script>
        test1()
        test2()
    </script>
</body>
</html>

(4)運行到瀏覽器結果

全局function結果

總結

  1. 同名的屬性或方法,后者會覆蓋前者。造成全局作用域污染
  2. 各個方法之間看不出有何聯(lián)系

4.2 namespace模式

將模塊的屬性和方法存放在一個變量上,該變量掛載在全局作用域window上

(1) 目錄結構

├─index.html
├─modules
|    ├─module1.js
|    └─module2.js

(2) 模塊代碼

module1

var module1 = {
    data: '-------------module1------------',
    say() {
        console.log(`我是${this.data}`);
    }
}

module2

var module2 = {
    data: '-------------module2------------',
    say() {
        console.log(`我是${this.data}`);
    }
}

(3) 主頁面index

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>namespace模式</title>
</head>
<body>
    <script src="./modules/module1.js"></script>
    <script src="./modules/module2.js"></script>

    <script>
        console.log(module1.data);
        module1.say()

        console.log(module2.data);
        module2.say()

        // 可以修改模塊內(nèi)的屬性和方法
        module1.data = 'module1被修改了'
        module1.say()

    </script>
</body>
</html>

(4) 運行到瀏覽器結果

namespace運行結果

總結

  1. 作用: 減少了全局變量,解決命名沖突
  2. 從結果可以看出,這種模式不安全,可以任意修改模型中的屬性和方法

4.3 IIFE 匿名函數(shù)自調(diào)用模式

通過作用域鏈的特性,外部作用域無法訪問內(nèi)部作用域的變量,可以做到保護局模塊內(nèi)的變量的作用,然后通過把需要公開的屬性和方法掛載到window對象上,就實現(xiàn)了一個模塊的封裝

(1) 目錄結構

├─index.html
├─modules
|    ├─module1.js
|    └─module2.js

(2) 模塊代碼

module1

(function (w) {
    let data = '---module1---'

    function say() {
        console.log(`我是${data}`);
    }

    // 暴露module1 給全局
    w.module1 = {
        say
    }
})(window)

module2

(function (w) {
    let data = '---module2---'

    function say() {
        console.log(`我是${data}`);
    }

    // 暴露module1 給全局
    w.module2 = {
        say
    }
})(window)

(3) 主頁面index

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>匿名函數(shù)自調(diào)用</title>
</head>
<body>
    <script src="./modules/module1.js"></script>
    <script src="./modules/module2.js"></script>
    <script>
        module1.say()
        module2.say()

        // 訪問不到內(nèi)部其他變量  因為暴露的module對象沒有該屬性
        console.log(module1.data); // undefined
        
    </script>
</body>
</html>

(4) 運行結果

IIFE運行結果

總結

優(yōu)點:數(shù)據(jù)私有,可選擇暴露的屬性的方法,而私有的屬性和方法不會被修改

缺點:如果需要引用另一個模塊,該怎么辦?

4.4 IIFE增強模式

基于IIFE模式,解決依賴其他module的問題

(1) 目錄結構

├─index.html
├─js
| ├─modules
| |    ├─module1.js
| |    └─module2.js
| ├─lib
| |  └─jquery.js

模塊中引入jQuery,所以需要新建一個lib文件夾存放jQuery

(2) 模塊代碼

module1.js

/**
 * module1 模塊就是一個正常的模塊
 */

(function (w) {
    let data = '---module1---'

    function say() {
        console.log(`我是${data}`);
    }

    // 
    w.module1 = {
        say
    }
})(window)

module2.js

/**
 * module2 引入第三方庫:jQuery
 */

(function (w, $) {
    let data = '---module2---'

    function say() {
        console.log(`我是${data}`);
    }

    /**
     * 使用jQuery修改body的樣式
     */
    function change() {
        $(document.body).css({
            background: 'green'
        })
    }

    w.module2 = {
        say,
        change
    }
})(window, jQuery)

module2.js中的匿名函數(shù)的實參是window和jQuery對象,所以需要先引入jQuery庫

(3) 主頁面index

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>匿名函數(shù)自調(diào)用(增強)</title>
</head>
<body>
    <!-- 如果想使用第三方庫,先引入進來 -->
    <script src="./js/lib/jquery.js"></script>
    <script src="./js/modules/module1.js"></script>
    <script src="./js/modules/module2.js"></script>

    <script>
        /**
         * 增強版是為了引入其他模塊
         */
        module1.say()

        module2.say()

        module2.change()

    </script>
</body>
</html>

(4) 運行結果

可以看到,module2中的jQuery也已經(jīng)起了作用

總結

這就是現(xiàn)代模塊實現(xiàn)的基石。這樣做除了保證模塊的獨立性,還使得模塊之間的依賴關系變得明顯。

二、模塊化規(guī)范

1. CommonJS

概述

Node 應用由模塊組成,采用 CommonJS 模塊規(guī)范。每個文件就是一個模塊,有自己的作用域。在一個文件里面定義的變量、函數(shù)、類,都是私有的,對其他文件不可見。在服務器端,模塊的加載是運行時同步加載的;在瀏覽器端,模塊需要提前編譯打包處理。

特點

  • 所有代碼都運行在模塊作用域,不會污染全局作用域。
  • 模塊可以多次加載,但是只會在第一次加載時運行一次,然后運行結果就被緩存了,以后再加載,就直接讀取緩存結果。要想讓模塊再次運行,必須清除緩存。
  • 模塊加載的順序,按照其在代碼中出現(xiàn)的順序。

基本使用

暴露/導出: 通過module.exports或者exports作為導出媒介,在其上面添加屬性和方法,就能將屬性和方法公開出去
引入/導入: 通過require(xxx)來引入要使用的組件。如果是自定義的組件,xxx為.././開頭的路徑地址;如果是通過npm包管理器下載的第三方包,則xxx直接寫為引用的包名,如:require('jquery')

關于module.exports和exports

為什么要理解module.exports和exports的關系?
因為理解了他倆的關系,這樣在寫模塊的時候就不會混亂

(1) 初始關系內(nèi)存圖


從圖中可以看出,module.exportsexports指向的內(nèi)存地址是同一個,是一個空對象,可以說module.exports === exportstrue

(2) 在module.exports / exports上添加屬性或方法

如果在一個模塊中這樣寫

module.exports.name = '哈哈哈'
exports.age = 20

則內(nèi)存圖如下:


可以看出,指向的還是同一個堆內(nèi)存,所以module.exports === exportstrue

(3) module.exports / exports 指向被修改

如果module.exports 或者 exports的指向被修改,如下代碼

exports.name = '哈哈哈'
module.exports = {
    name: 'hello'
}

則內(nèi)存關系圖如下:


可以看出,兩個屬性指向的內(nèi)存地址發(fā)生了改變,此時module.exports === exportsfalse

exports 和 module.exports 的指向都發(fā)生了改變,最終以最后一個 module.exports 的修改為主

Node環(huán)境中實現(xiàn)

由于在node環(huán)境中已經(jīng)預置好了CommonJS的環(huán)境,即有require()方法,所以可以直接運行寫好的代碼即可。前提是已經(jīng)安裝好了node環(huán)境。

(1) 安裝NodeJs

https://nodejs.org/en/

(2) 初始化node項目

因為項目中可能會依賴第三方的包,所以需要初始化node項目來安裝第三方包
在項目目錄下運行命令

npm init

一路確定即可,最后會創(chuàng)建一個package.json文件

(3) 下載第三方包

uniq第三方庫為例,在項目目錄下運行命令

npm install uniq --save

(4) 項目目錄

├─app.js
├─package-lock.json
├─package.json
├─node_modules
|      ├─uniq
|      |  ├─.npmignore
|      |  ├─LICENSE
|      |  ├─package.json
|      |  ├─README.md
|      |  ├─uniq.js
|      |  ├─test
|      |  |  └─test.js
├─modules
|    ├─module1.js
|    ├─module2.js
|    ├─module3.js
|    ├─module4.js
|    └─module5.js

package-lock.json、package.json文件和node_modules文件夾是使用npm命令是自動生成的。其他文件都是自己新建的

(5) 模塊內(nèi)容

module1.js

exports.name = '劉德華'
exports.age = '20'
exports.say = function () {
    console.log('我是劉德華');
}

module2.js

module.exports = function() {
    console.log('我是模塊2');
}

module3.js

exports.name = '張學友'

module.exports = function () {
    console.log('module.exports', module.exports);
    console.log('exports', exports);
    return module.exports === exports
}

module4.js

exports.name = '周潤發(fā)'
exports.age = 25
module.exports.uniq = function () {
    console.log(module.exports === exports);
}

module5.js

module.exports = function () {
    console.log('我是module.exports 111');
}

module.exports = function () {
    console.log('我是module.exports 222', module.exports);
}

exports = function() {
    console.log('我是exports');
}

(6) app.js

/**
 * 引入  require(xxx)
 *      1. 如果引入的是第三方模塊,即從npm包管理器下載的,xxx為模塊名
 *      2. 如果是自己定義的模塊,xxx為模塊的路徑
 */
// 引入第三方模塊 uniq
const uniq = require('uniq')

// 引入自己的模塊
const module1 = require('./modules/module1')
const module2 = require('./modules/module2')
const module3 = require('./modules/module3')
const module4 = require('./modules/module4')
const module5 = require('./modules/module5')

console.log(uniq([1, 1, 2, 2, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 5])); // [ 1, 2, 3, 4, 5 ]

// 引入的module其實就是 module.exports = exports 所指向的那個對象

// module1中,在exports對象中增加了 name、age、say的屬性和方法
console.log(module1.name); // 劉德華
console.log(module1.age); // 20
module1.say() // 我是劉德華

// module2 中,exports直接指向了一個方法,所以exports是一個方法
module2() // 我是模塊2

// module3 中,本來 module.exports = exports = {} 指向的是同一個對象,現(xiàn)在改為exports為{ name: '張學友' },而module.exports指向的是一個函數(shù),所以二者不相等
console.log(module3());


// module4 中, module.exports 和 exports 的指向都沒有改變,所以在比較的時候二者是相等的
module4.uniq()


// module5 中,exports 和 module.exports 的指向都發(fā)生了改變,最終以最后一個 module.exports 的修改為主
module5() // 我是module.exports 222

(7) 運行

在vscode中,安裝Code Runner插件,如下圖:

Code Runner

然后在app.js中右鍵,如下圖:

點擊上圖按鈕就會運行了。

(8) 運行結果

[Running] node "c:\Users\YCWB0217\Desktop\Test\前端模塊化\02_CommonJS-Node\app.js"
[ 1, 2, 3, 4, 5 ]
劉德華
20
我是劉德華
我是模塊2
module.exports [Function (anonymous)]
exports { name: '張學友' }
false
true
我是module.exports 222 [Function (anonymous)]

[Done] exited with code=0 in 0.228 seconds

瀏覽器中實現(xiàn)

由于瀏覽器中,無法識別require()方法,并不能直接使用CommonJs的模塊化,所以需要借助一個轉換器,將源碼轉換為瀏覽器識別的源碼。

過程如下:


(1) 項目目錄

├─index.html
├─package-lock.json
├─package.json
├─js
| ├─src
| |  ├─app.js
| |  ├─module1.js
| |  ├─module2.js
| |  ├─module3.js
| |  ├─module4.js
| |  └module5.js
| ├─dist
| |  └bundle.js

其中package-lock.json package.json js/dist是自動生成的

(2) 安裝browserify

npm install -g browserify

browserify官網(wǎng)

(3) 模塊代碼

這里的模塊代碼個Node環(huán)境下的代碼相同,可以直接將app.js、module1.js、module2.js、module3.jsmdoule4.js、module5.js一同復制到src文件加下

(4) browserify編譯app.js

在項目目錄下輸入命令

browserify js/src/app.js -o js/dist/bundle.js

-o 表示輸出

(5) index頁面引用bundle.js

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>CommonJS-Browserify</title>
</head>
<body>
    
</body>
</html>
<!-- 引用dist下的bundle.js -->
<script src="./js/dist/bundle.js"></script>

運行結果

瀏覽器運行結果

2. AMD-RequireJS

CommonJS規(guī)范加載模塊是同步的,也就是說,只有加載完成,才能執(zhí)行后面的操作。AMD規(guī)范則是非同步加載模塊,允許指定回調(diào)函數(shù)。由于Node.js主要用于服務器編程,模塊文件一般都已經(jīng)存在于本地硬盤,所以加載起來比較快,不用考慮非同步加載的方式,所以CommonJS規(guī)范比較適用。但是,如果是瀏覽器環(huán)境,要從服務器端加載模塊,這時就必須采用非同步模式,因此瀏覽器端一般采用AMD規(guī)范。此外AMD規(guī)范比CommonJS規(guī)范在瀏覽器端實現(xiàn)要來著早。

(1) 目錄結構

├─index.html
├─js
| ├─main.js
| ├─modules
| |    ├─module1.js
| |    ├─module2.js
| |    └module3.js
| ├─lib
| |  ├─jquery.js
| |  └─require.js

其中,js/lib目錄下是AMD需要引入的庫和jQuery.js

(2) 下載require.js和jQuery.js

require.js:https://github.com/requirejs/requirejs
jQuery.js:https://github.com/jquery/jquery
將下載好的js存放在lib目錄下

(3) 模塊代碼

module1.js

/**
 * 定義一個沒有依賴的模塊
 */
define(function () {
    let data = '我是數(shù)據(jù)數(shù)據(jù)'
    function getData() {
        return data
    }
    return { getData }
})

module2.js

/**
 * 定義一個有依賴的模塊
 *      module2 依賴 module1 中的數(shù)據(jù)
 */

define([
    'module1'
], function (m1) {
    let data = '我是module2中的數(shù)據(jù)'

    function show() {
        console.log('我是module2,module1中的數(shù)據(jù)是【' + m1.getData() + '】');
    }
    return { show, data }
});

module3.js

define([
    'module2',
    'jquery'
], function (module2, $) {
    function show() {
        console.log('我是module3', 'module2中的data為【' + module2.data + '】');
    }

    function each(arr, callback) {
        return $.each(arr, callback)
    }

    return { show, each }
});

其中,module2依賴了module1,module3依賴了module2jQuery

(4) main.js

(function () {
    requirejs.config({
        baseUrl: 'js/',
        paths: {
            // 隱射:模塊標識名:路徑
            module1: './modules/module1',
            module2: './modules/module2',
            module3: './modules/module3',
            jquery: './lib/jquery'
        }
    })

    // 引用module2
    requirejs(['module2'], function (m2) {
        m2.show()
    })

    // 引用module3 使用jQuery
    requirejs(['module3'], function (m3) {
        m3.show()

        m3.each(['red', 'green', 'blue'], function (index, item) {
            console.log('第' + index + '個', '值為:' + item);
        })
    })
})()
  1. 首先需要對requirejs進行配置,下面有幾個注意項
    baseUrl:基于當前根目錄,如果paths中的路徑有相同的部分,可以將相同部分的目錄提取到baseUrl中來
    paths:路徑需要以./或者../開頭,其中,路徑不要加.js文件名
    對于jQuery,鍵名必須是jquery,因為jQuery源碼中暴露就是這個鍵名
  2. 通過全局requirejs()方法進行加載模塊并調(diào)用

(5) 運行結果

AMD結果

3. CMD-SeaJS

CMD規(guī)范專門用于瀏覽器端,模塊的加載是異步的,模塊使用時才會加載執(zhí)行。CMD規(guī)范整合了CommonJS和AMD規(guī)范的特點。在 Sea.js 中,所有 JavaScript 模塊都遵循 CMD模塊定義規(guī)范。

(1) 目錄結構

├─index.html
├─js
| ├─modules
| |    ├─main.js
| |    ├─module1.js
| |    ├─module2.js
| |    └─module3.js
| ├─lib
| |  └─sea.js

其中sea.js是CMD解析模塊的核心

(2) 下載sea.js

sea.js:https://github.com/seajs/seajs

(3) 模塊代碼

module1.js

/**
 * 定義一個依賴模塊,module1
 */

define(function (require, exports, module) {
    let data = '----------module01----------'

    function getData() {
        console.log('module1 getData() ' + data);
    }

    module.exports.data = data
    module.exports.getData = getData
})

module2.js

/**
 * 定義一個依賴模塊,module2
 */

define(function (require, exports, module) {
    let data = '----------module02----------'

    function getData() {
        console.log('module2 getData() ' + data);
    }

    exports.data = data
    exports.getData = getData
})

module3.js

/**
 * 定義一個依賴模塊,module3 依賴于 module1 和 module2
 */

define(function (require, exports, module) {
    let data = '----------module03----------'
    function getData() {
        console.log('module3 getData() ' + data);
    }
    // 引入模塊1  模塊2
    // 同步引入
    let module1 = require('./module1')
    module1.getData()

    // 異步引入
    require.async('./module2', function (m2) {
        console.log('module2加載完畢');
        m2.getData()
    })

    module.exports = {
        data,
        getData
    }
})

(4) main.js

/**
 * 主JS文件,用于匯總各個模塊
 */

define(function(require){
    let module1 = require('./module1')
    // let module2 = require('./module2')
    let module3 = require('./module3')

    console.log(module1.data);
    module1.getData()

    console.log(module3.data);
    module3.getData()
})

(5) index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>CMD-SeaJS</title>
</head>
<body>
    
</body>
</html>
<script src="./js/lib/sea.js"></script>
<script>
    seajs.use('./js/modules/main')
</script>

(6) 運行結果

CMD運行結果

可以看到,由于在module3中異步加載module2,所以他的回調(diào)往往總是在同步任務結束之后才會運行的。

4. ES6中的模塊化

歷史上,JavaScript 一直沒有模塊(module)體系,無法將一個大程序拆分成互相依賴的小文件,再用簡單的方法拼裝起來。其他語言都有這項功能,比如 Ruby 的require、Python 的import,甚至就連 CSS 都有@import,但是 JavaScript 任何這方面的支持都沒有,這對開發(fā)大型的、復雜的項目形成了巨大障礙。

在 ES6 之前,社區(qū)制定了一些模塊加載方案,最主要的有 CommonJS 和 AMD 兩種。前者用于服務器,后者用于瀏覽器。ES6 在語言標準的層面上,實現(xiàn)了模塊功能,而且實現(xiàn)得相當簡單,完全可以取代 CommonJS 和 AMD 規(guī)范,成為瀏覽器和服務器通用的模塊解決方案。

編譯運行過程

es6模塊化過程

(1) 準備

① 在項目根目錄下

npm init

② 安裝babel-cli, babel-preset-es2015和browserify

npm install babel-cli -g

此步安裝過可省略

npm install browserify -g

npm install babel-preset-es2015 --save-dev

安裝完畢后,在項目根目錄會多出一個node_modules目錄

③ 在根目錄下,新建一個.babelrc的文件。內(nèi)容如下

{
    "presets": ["es2015"]
}

④ 安裝一個第三方庫
為了演示如何引入第三方包,再安裝一個第三方的包

npm install uniq --save

(2) 項目目錄

├─.babelrc
├─index.html
├─package-lock.json
├─package.json
├─js
| ├─src
| |  ├─app.js
| |  ├─module1.js
| |  ├─module2.js
| |  ├─module3.js
| |  └module4.js
| ├─dist
| |  ├─app.js
| |  ├─bundle.js
| |  ├─module1.js
| |  ├─module2.js
| |  ├─module3.js
| |  └module4.js

其中dist文件夾下文件不用管,其余文件為自己新建的

(3) 模塊代碼(src目錄下)

module1.js

/**
 * 單個暴露
 */

export var name = '劉德華'
export var age = 20
export function say () {
    console.log('我是劉德華');
}

module2.js

/**
 * 統(tǒng)一暴露
 */

let arr = [1, 2, 3, 4, 5]
function demo2() {
    console.log('我是demo2', arr);
}

function test2() {
    console.log('我是test2', arr);
}

export {
    demo2,
    test2
}

module3.js

/**
 * 默認暴露,只能暴露一次
 */

//  export default {
//     name: '佩奇',
//     age: 18,
//     speak() {
//         console.log(`我的名字是${this.name},我今年${this.age}歲了!`);
//     }
//  }

 export default {
     name: '周杰倫',
     age: 20,
     song() {
         console.log('天青色等煙雨,而我在等你!');
     }
 }

module4.js

/**
 * 混合暴露
 */
// 分別暴露
export let name = '周星馳'
export let age = 30

// 統(tǒng)一暴露
let height = 176
let sex = '男'

export {
    height,
    sex
}

// 默認暴露
export default {
    job: ['演員', '主持人', '導演', '編劇'],
    hobby: ['唱歌', '跳舞', '搞笑', '配音', '無厘頭']
}

(4) app.js (src目錄下)

import { name, age, say } from './module1'
import { demo2, test2 } from './module2'
import module3 from './module3'

// module 1
console.log('------------------------------');
console.log(name);
console.log(age);
say()

// module 2
console.log('------------------------------');
demo2()
test2()

// module 3
console.log('------------------------------');
console.log(module3.name);
console.log(module3.age);
module3.song()

(5) 編譯

① 使用babel命令,將es6語法翻譯為es5語法

babel js/src -d js/dist

解析:將js/src文件夾下的js文件,編譯成對應的es5 js文件。到js/dist文件夾中

② 使用browserify編譯require函數(shù)進行加載對應模塊

browserify js/dist/app.js -o js/dist/bundle.js

解析:將有require的app.js文件,編譯成瀏覽器識別的js文件bundle.js

(6) index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>ES6_Babel_Browserify</title>
</head>
<body>
    
</body>
</html>
<script src="./js/dist/bundle.js"></script>

將編譯好的bundle.js文件使用script標簽加載進來

(7) 運行結果

es6模塊化運行結果

ES6模塊化補充

1. 模塊的導出和導入

es6中模塊的導出有三種方式:

  1. 分別暴露:對應上述module1.js
  2. 統(tǒng)一暴露:對應上述module2.js
  3. 默認暴露:對應上述module3.js

其對應的導出方式也不同
分別暴露統(tǒng)一暴露使用import {} from '模塊路徑'的方式導入,默認暴露使用import x from '模塊路徑'的方式導入

2. import {} from '' 導入方式優(yōu)化

情況:有這樣一個情況,module1中 export let name = 'module1';module2中export let name='module2'。此時用import {} from ''方式導入,會出現(xiàn)命名重復的問題。

下面給出解決方案:
① 使用as關鍵字給變量或者方法設置別名,避免和其他模塊的屬性和方法重復

import {name as n1} from './module1'
import {name as n2} from './module2'
// 使用
console.log(n1)
console.log(n2)

② 使用 * 將模塊中導出的內(nèi)容收集稱為一個對象,然后使用該對象調(diào)用對應的屬性和方法。類似于默認暴露

import * as m1 from './module1'
import * as m2 from './module2'
// 使用
console.log(m1.name)
console.log(m2.name)

這種方式類似于將模塊內(nèi)暴露的內(nèi)容使用一個對象進行包裹了起來,該對象使用as關鍵字進行命名,這樣避免污染作用域

注意:如果模塊使用的是默認(export default)暴露的,則沒有必要使用* as 進行封裝一層
如果使用* as ,則被封裝的對象為

{
  default:{
    name: '哈哈哈'
  }
}

3. 如果模塊中定義兩個 export default;則使用babel編譯的時候會報錯 Only one default export allowed per module

4. export 和 export default 混合使用

針對module4.js中的暴露方案,使用混合的方式進行導入。

import { name, age } from './module4'
import { height, sex } from './module4'
import other from './module4'

也有一種簡單的寫法

import other, { name, age, height, sex } from './module4'

效果都是一樣的。其中第二種導入混合導出的方式很常見

三、總結

  • CommonJS規(guī)范主要用于服務端編程,加載模塊是同步的,這并不適合在瀏覽器環(huán)境,因為同步意味著阻塞加載,瀏覽器資源是異步加載的,因此有了AMD CMD解決方案。
  • AMD規(guī)范在瀏覽器環(huán)境中異步加載模塊,而且可以并行加載多個模塊。不過,AMD規(guī)范開發(fā)成本高,代碼的閱讀和書寫比較困難,模塊定義方式的語義不順暢。
  • CMD規(guī)范與AMD規(guī)范很相似,都用于瀏覽器編程,依賴就近,延遲執(zhí)行,可以很容易在Node.js中運行。不過,依賴SPM 打包,模塊的加載邏輯偏重
  • ES6 在語言標準的層面上,實現(xiàn)了模塊功能,而且實現(xiàn)得相當簡單,完全可以取代 CommonJS 和 AMD 規(guī)范,成為瀏覽器和服務器通用的模塊解決方案。

參考資料

前端模塊化詳解(完整版)

項目源碼

https://github.com/HyFun/JS-Sample-Modules

最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

友情鏈接更多精彩內(nèi)容