iT邦幫忙

第 12 屆 iT 邦幫忙鐵人賽

DAY 19
0
Modern Web

《你的地圖會說話? WebGIS與JavaScript的情感交織》系列 第 19

[7-2] 實現AMD的require.js 與 ES6 Import/Export 大比拚

本篇文章請搭配
[7-1] 3D地圖初探 - ArcGIS API for JavaScript


昨天介紹ArcGIS API時,講到dojo框架,並且用require語法來實現模組化管理,
究竟require發展的起源?是用來解決什麼問題?要如何使用?
今天就讓我們來一一解惑。

同步載入與非同步載入

同步載入

瀏覽器載入html頁面時,會由上到下依序載入,也就是同步載入

  • 缺點一: 通常我們為在head標籤結束前載入JS,要等第一支載完才會載入第二支JS,
    第二支JS載完才接續載入第三支,以此類推。假如某一支JS載入很慢,
    就會全部等它載完,造成頁面卡住當掉的現象。
  • 缺點二: 假如今天JS載入有優先順序,例如jquery載完才可載入jquery-ui,
    如果今天不小心在include的時候順序顛倒,會造成不必要的錯誤,且難以管理。

↓ html include為同步載入。

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>

    <script src="A.js"></script>
    <script src="B.js"></script>
    <script src="C.js"></script>
</head>

↓ A.js

    console.log('Hello A')

↓ B.js

    console.log('Hello B')

↓ C.js

    console.log('Hello C')

↓ 結果
https://ithelp.ithome.com.tw/upload/images/20201004/20130604C1W0B21a0r.jpg
可以看到載入順序必為A → B → C,為同步載入。
這也解釋了寫在head裡面的JS必須全部載入完,才會載入寫在body中的DOM元素,因此才會需要寫$(document).ready()或window.onload來等候頁面在入完畢才執行程式。
新式的寫法會把JS寫在body結束前,如此一來,就會先載入DOM元素最後才載入JS。

非同步載入

為了解決同步載入的困境,達到非同步模組化管理。
不但要非同步載入,還要方便每個JS的管理,而require.js就是一個實現它很好的例子。

↓ 引入require.js

    <script src="https://cdnjs.cloudflare.com/ajax/libs/require.js/2.3.6/require.js"></script>

↓ 設定require.config

        require.config({
            paths: {  // 每個模組的路徑
                "A": "A", 
                "B": "B",
                "C": "C"
            }
        });

require.config可以設定每個欲載入的模組的路徑以及它們的依賴關係。
paths可以設定每個模組的路徑,屬性名稱(key)為模組名稱,屬性值(value)為路徑(省略.js)。

↓ 載入模組

        require(["A", "B", "C"], function (A, B, C) {
            console.log('JS OK');
        });

↓ 結果
https://ithelp.ithome.com.tw/upload/images/20201004/20130604C1W0B21a0r.jpg
https://ithelp.ithome.com.tw/upload/images/20201004/20130604oCkCZv3tBn.jpg
https://ithelp.ithome.com.tw/upload/images/20201004/20130604bcf7WPm4BU.jpg
多試幾次就會發現,每次載入的順序不盡相同,可能是A → B → C,也有可能B → C → A。
非同步載入每個JS載入不會受其它JS所影響,載入完的順序也是隨機。

AMD與CommonJS

達到了非同步載入,並解決了同步載入卡頓問題之後,還要進行模組化管理。
然而,早期JavaScript並沒有模組化的標準規範,民間眾多高手研擬出來各種版本,
其中以AMD與CommonJS為最多人使用的兩套規範。它們之間差別在於,
AMD規範是針對於瀏覽器所設計,而CommonJS則是針對node.js環境所設計。

AMD

↓ 引入require.js

    <script src="https://cdnjs.cloudflare.com/ajax/libs/require.js/2.3.6/require.js"></script>

↓ 新增test.js,define為一個模組。

define(["jquery"], function (jquery) {
    var jq = {
        HelloWorld: function () {
            $(function () {
                $(document.body).append("Hello jQuery!");
            });
        }
    };
    return jq;
});

透過define()可以定義模組,以及利用return輸出模組內容。define的第一個參數為依賴關係,也就是說要先載入jquery模組才會載入此模組。

↓ 設定require.config,將根目錄下的jquery-3.5.1.js定義為'jquery'模組。

        require.config({
            paths: {
                'jquery': '/jquery-3.5.1'
            }
        });

↓ 載入test模組,並呼叫test模組輸出的物件/函式。

        require(["test"], function (jq) {
            jq.HelloWorld();
        });

↓ 結果
https://ithelp.ithome.com.tw/upload/images/20201004/20130604CBHeskt1Tx.jpg

CommonJS

↓ 新增test_CommonJS.js,透過module.exports輸出模組。

const $ = require("jquery");
const HelloWorld = () =>{
    $(document.body).append("Hello jQuery!");
}

module.exports = {
   Hello: HelloWorld
} 

↓ 載入test_CommonJS模組,並呼叫。

        const helloWorld = require("test_CommonJS.js");
        helloWorld.Hello();

↓ 結果會Error,原因是瀏覽器的執行環境並沒有node.js所擁有的這四個東西。

  • module
  • exports
  • require
  • global

想要在瀏覽器環境下使用CommonJS,可以使用Browserify來實現。可是並非今天討論的重點,大家可以自己玩玩看?!

ES6 Import/Export

ES6出了新的語法達到了模組化的效果,也就是Import及Export,並且結合了AMD與CommonJS的優點。

  • 類似CommonJS有相對簡單的語法,並且每個模組以檔案為基礎
  • 類似AMD,支援非同步載入

範例

↓ 新增test_export.js,透過export語法輸出模組。

export default function () {  // 預設匯出函式
    console.log('export');
}

↓ 載入test_export.js模組並呼叫

        import fn from './test_export.js';
        fn(); 

↓ Error
https://ithelp.ithome.com.tw/upload/images/20201004/20130604fjVKxDI4BA.jpg
竟然Error惹!!!
Cannot use import statement outside a module
解法:html的script標籤預設為type="text/javascript",要將它改為type="module"。

↓ 加入type="module"

    <script type="module">
        import fn from './test_export.js';  // 匯入 預設匯出 並命名為fn
        fn(); 
    </script>

↓ 成功載入
https://ithelp.ithome.com.tw/upload/images/20201004/20130604tegQu0Cf1L.jpg

預設匯出 & 具名匯出

  • 預設匯出

↓ 剛剛我們上面的範例為預設匯出,export後面接default。

export default function () {  // 預設匯出函式
    console.log('export');
}

↓ 也可以預設匯出String或Number等原始型別

export default "Hello";

↓ 匯入

        import msg from './test_export.js';
        console.log(msg);

↓ 結果
https://ithelp.ithome.com.tw/upload/images/20201004/20130604aj5k3JscBM.jpg
預設匯出可以匯出原始型別、物件型別、類別(class)或函式等等,但一個模組只能有一個預設匯出。

  • 具名匯出

具名匯出在匯出時帶有名稱,一個模組可以同時擁有多個具名匯出。

export const message = "Hello";
export const obj = { name: 'obj' };

↓ 匯入時用大括號寫欲匯入的內容

        import { message, obj } from './test_export.js';
        console.log(message);
        console.log(obj);

↓ 結果
https://ithelp.ithome.com.tw/upload/images/20201004/20130604unSVJvnL5H.jpg

↓ 在預設匯入時還可利用as語法重新命名

        import { message as msg, obj as object } from './test_export.js';
        console.log(msg);
        console.log(object);

↓ 結果不變
https://ithelp.ithome.com.tw/upload/images/20201004/20130604unSVJvnL5H.jpg

  • 同時預設及具名匯出

↓ 預設匯出function,具名匯出message為字串、obj為物件。

export default function () {
    console.log('export');
}
export const message = "Hello";
export const obj = { name: 'obj' };

↓ 也可將具名匯出寫在最下面,用大括號加入想要匯出的變數。

const message = "Hello";
const obj = { name: 'obj' };

export default function () {
    console.log('export');
}

export { message, obj };

↓ 預設匯入命名為defaultExport,具名匯出用*表示匯出全部,用as語法重新命名為namedExport。

        import defaultExport, * as namedExport from './test_export.js';
        defaultExport();
        console.log(namedExport.message);
        console.log(namedExport.obj);

↓ 結果
https://ithelp.ithome.com.tw/upload/images/20201004/20130604obGnVHIGjZ.jpg


今天講解重點如下

  • 同步載入及非同步載入
  • AMD (for瀏覽器),用define()定義模組、require()匯入模組
  • CommonJS (for node.js,在瀏覽器可以使用Browserify來實現),用module.exports匯出模組、require()匯入模組
  • ES6 Import/Export、預設匯出 & 具名匯出

明天邁入第20篇啦!依照慣例會來個番外篇!
大家期待嗎?!/images/emoticon/emoticon37.gif


上一篇
[7-1] 3D地圖初探 - ArcGIS API for JavaScript
下一篇
[番外篇] 從npm安裝到活用Webpack Babel - 十分鐘就上手
系列文
《你的地圖會說話? WebGIS與JavaScript的情感交織》30

尚未有邦友留言

立即登入留言