webpack與模塊化

目錄

1.模塊化

2.模塊化的核心

3.ESM

3.1獨立模塊作用域

3.2導出模塊內部數據

3.3導入外部模塊數據

3.3.1靜態導入

 3.3.2ESM導入導出——示例:

3.3.3動態導入import()

 4.模塊化的向下兼容

5.CommonJS

5.1獨立模塊作用域

5.2導出模塊內部數據

5.3導入外部模塊數據

5.4CommonJS規範使用示例

6.AMD

7.AMD——requireJS

7.1獨立模塊作用域

7.2導出模塊內部數據

7.3導入外部模塊數據

7.4AMD——requireJS使用示例

8.requireJS 的CommonJS風格

8.1導出模塊內部數據

8.2導入外部模塊數據

8.3requireJS的CommonJS風格示例

9.UMD


1.模塊化

模塊化已經是現代前端開發中不可或缺的一部分了。也是後端必備。

把複雜的問題分解成相對獨立的模塊,這樣的設計可以降低程序複雜性,提高代碼的重用,也有利於團隊協作開發與後期的維護和擴展。

ECMAScript2015 開始引入了模塊的概念,我們稱爲:ECMAScript Module,簡稱:ESM。

2.模塊化的核心

  • 獨立的作用域——將代碼進行有效的隔離,各模塊之間代碼不會相互影響
  • 如何導出模塊內部數據——訪問模塊化的數據
  • 如果導入外部模塊數據

3.ESM

ECMAScript2015/ECMAScript6 開始,JavaScript 原生引入了模塊概念,而且現在主流瀏覽器也都有了很好的支持。

3.1獨立模塊作用域

一個文件就是模塊,擁有獨立的作用域,且導出的模塊都自動處於 嚴格模式,即:'use strict'

如果該文件是通過模塊化進行加載的,那麼:

  1. 該文件會產生一個獨立的作用域;
  2. 該文件內的代碼默認是運行在嚴格模式下即:'use strict'的。

嚴格模式('use strict'):

  • 變量必須先聲明才能使用;
  • 沒有變量提升(預解析機制)。

3.2導出模塊內部數據

使用 export 語句導出模塊內部數據。

// 導出單個特性
export let name1, name2, …, nameN;
export let name1 = …, name2 = …, …, nameN;
export function FunctionName(){...}
export class ClassName {...}

// 導出列表
export { name1, name2, …, nameN };

// 重命名導出
export { variable1 as name1, variable2 as name2, …, nameN };

// 默認導出
export default expression;
export default function (…) { … }
export default function name1(…) { … }
export { name1 as default, … };

// 模塊重定向導出
export * from …;
export { name1, name2, …, nameN } from …;
export { import1 as name1, import2 as name2, …, nameN } from …;
export { default } from …;

3.3導入外部模塊數據

導入分爲兩種模式

  • 靜態導入
  • 動態導入

3.3.1靜態導入

在瀏覽器中,import 語句只能在聲明瞭 type="module" 的 script 的標籤中使用

且import語句必須寫在JS文件最上面;

import defaultExport from "module-name";
import * as name from "module-name";
import { export } from "module-name";
import { export as alias } from "module-name";
import { export1 , export2 } from "module-name";
import { foo , bar } from "module-name/path/to/specific/un-exported/file";
import { export1 , export2 as alias2 , [...] } from "module-name";
import defaultExport, { export [ , [...] ] } from "module-name";
import defaultExport, * as name from "module-name";
import "module-name";

靜態導入方式不支持延遲加載,import 必須這模塊的最開始

document.onclick = function () {

    // import 必須放置在當前模塊最開始加載
    // import m1 from './m1.js'

    // console.log(m1);

}

 3.3.2ESM導入導出——示例:

項目路徑:

index.html:注意ESM中使用模塊化時,script標籤中必須要有type="module"屬性。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>
<body>
    <!-- 在瀏覽器中,import 語句只能在聲明瞭 type="module" 的 script 的標籤中使用。 -->
    <script type="module" src="./js/main.js"></script>
</body>
</html>

main.js:

注意點:

  • 文件路徑必須有'.js'結尾;
  • 導入列表時,所有的變量名必須和導出時的變量名一一對應,不想一一對象可使用別名;
  •  default導入導出時都不能加{};
  • 模塊化重定向導出(即從另一個模塊或腳本文件導出)時,from後表示從已經存在的模塊、腳本文件…導出

//導入m1模塊
//1.導出單個特性:因爲不是默認導出,所以需要聲明變量接收,且文件必須有.js結尾
import {cssFunc1,cssFunc2,cssFunc3,M1Class} from './m1.js';

// 2.導入列表::此處a,b,c必須和導出中的變量名一一對應
import {a,b,c} from './m1.js';

//3.重命名導出:導入導出的變量名需一一對象,想在導入時使用不同名字可使用別名, 變量名 as 別名
// import {name,pw} from './m1.js'
import {name as name1,pw as pw1} from './m1.js';

//4.默認導入:注意:default導入導出都不需要加{}
// import aa from './m1.js';
// import defaultFunc from "./m1.js"
// import defaultFunc2 from './m1.js';
import bb from './m1.js';

// 5.模塊重定向導出
import * as obj from './m1.js'
import {v1,v2} from './m1.js';
import {value1,value2} from './m1.js';
import {default as defaultV} from './m1.js';


// 1.導出單個特性
let a1 = 10;
let m1Class = new M1Class();
m1Class.m1ClassFunc();

console.log("main.js",a1,cssFunc1,cssFunc2,cssFunc3);

//2.導入列表
console.log(a,b,c);//1 2 3

//3.重命名導出
// console.log(name,pw);//張三 1234
console.log(name1,pw1);//張三 1234

//4.默認導入
// defaultFunc();
// defaultFunc2();
// console.log(aa);
// console.log(bb);

//5.模塊重定向導出
console.log(obj);//Module {…}
console.log(v1,v2);//2 3
console.log(value1,value2);//2 3
console.log(defaultV);//ƒ m2Func(){ console.log(v1+v2); }

m1.js:

console.log("m1模塊...");

function css1(){
    console.log("m1模塊下的css1方法");
}


//1.導出單個特性
export let cssFunc1 = css1;

export let cssFunc2 = function css2(){
    console.log("m1模塊下的css2方法");
}

export function cssFunc3(){
    console.log("m1模塊下的cssFunc3方法");
}

export class M1Class{
    constructor(){

    }
    m1ClassFunc(){
        console.log("m1模塊下的m1ClassFunc");
    }
}

//2.導出列表
let a = 1,b=2,c=3;
export {a,b,c};

//3.重命名導出
let username = "張三";
let password = "1234";
export {username as name,password as pw};

//4.默認導出
let aa = 1;

// export default aa;
// export default function() { 
//     let defaultVal = 33;
//     console.log("defaultVal:"+defaultVal);
    
//  }
// export default function defaultFunc2() {
//     console.log("defaultFunc2方法");
// }
let bb = 2,cc = 3;
//不能同時導出多個。如export { bb as default,cc as default};
// export { bb as default};

// 5.模塊重定向導出: from-從已經存在的模塊、腳本文件…導出
export * from './m2.js';
export {v1,v2} from './m2.js';
export { v1 as value1, v2 as value2 } from './m2.js';
export { default } from './m2.js';

 m2.js:

let v1=2,v2=3;
export {v1,v2};
export default function m2Func(){
    console.log(v1+v2);
}

結果:

3.3.3動態導入import()

  1. 此外,還有一個類似函數的動態 import(),它不需要依賴 type="module" 的 script 標籤。
  2. 關鍵字 import 可以像調用函數一樣來動態的導入模塊。以這種方式調用,將返回一個 promise
  3. 使用async await 異步延遲加載是,要使用default()方法,必須導出時導出的是函數
  • 方式一:使用promise對象的then方法
import('./m.js')
  .then(m => {
    //...
});
// 也支持 await
let m = await import('./m.js');

 

通過 import() 方法導入返回的數據會被包裝在一個對象中,即使是 default 也是如此

示例:通過import()導出的是一個Promise對象。

不在頁面初始化加載時就加載m3.js文件,而是當點擊時加載。

m3.js:

let obj = {
    a:1,
    b:2
}

export default obj;

main.js:如果通過import語句導入

//6.動態導入:通過import()方法導入,返回一個promise對象進行異步延遲加載
document.onclick = function(){
    //直接通過import導入會報錯
    // import obj from 'm3.js';
    // console.log(obj);
    import('./m3.js').then(obj=>{
        console.log(obj);
        
    });
}

 結果:

  • 方式二:使用async await進行延遲加載

 使用async await 異步延遲加載是,要使用default()方法,必須導出時導出的是函數

m3.js:

function css(){
    console.log("css");
}

export default css;

main.js:

//使用async  await進行異步延遲加載
document.onclick = async function(){
    let m1 = await import('./m3.js');
    console.log(m1);
    m1.default();
}

結果:

 

 4.模塊化的向下兼容

  • CommonJS
  • AMD
  • UMD
  • ESM

無論是那種模塊化規範,重點關注:

  • 獨立模塊作用域
  • 導出模塊內部數據
  • 導入外部模塊數據

5.CommonJS

在早起前端對於模塊化並沒有什麼規範,反而是偏向服務端的應用有更強烈的需求,CommonJS 規範就是一套偏向服務端的模塊化規範,NodeJS 就採用了這個規範

NodeJS和前端JS是同宗同源,NodeJS使用V8解析器,ECMAscript最爲底層語言,NodeJS基於此延伸出了操作瀏覽器之外的如操作文件系統,網絡,硬盤方法。

CommonJS 是後端模塊化規範,通過操作文件系統進行實現,但是前端不能操作文件系統,所以前端使用不了CommonJS 規範。

5.1獨立模塊作用域

一個文件就是模塊,擁有獨立的作用域。

CommonJS是使用同步加載方法夾雜模塊化文件。只有模塊化加載成功後纔會繼續往下執行。

5.2導出模塊內部數據

通過 module.exportsexports 對象導出模塊內部數據。

注意: module.exportsexports 兩種方式不能同時使用。

// a.js
let a = 1;
let b = 2;

module.exports = {
  x: a,
  y: b
}
// or
exports.x = a;
exports.y = b;

5.3導入外部模塊數據

通過 require 函數導入外部模塊數據

// b.js
let a = require('./a');
a.x;
a.y;

5.4CommonJS規範使用示例

通過nodeJS環境,使用nodemon main.js啓動main.js。

m1.js:

let a = 1, b =2;
// module.exports = {
//     x:a,
//     y:b
// }

exports.l = a;
exports.m = b;

main.js:

let obj = require('./m1');
// console.log(obj);//{ x: 1, y: 2 }
console.log(obj);//{ l: 1, m: 2 }

6.AMD

因爲 CommonJS 規範一些特性(基於文件系統,同步加載),它並不適用於瀏覽器端,所以另外定義了適用於瀏覽器端的規範

AMD(Asynchronous Module Definition)。AMD沒有辦法獲取本地文件,因此使用異步文件加載方式實現模塊化加載。

https://github.com/amdjs/amdjs-api/wiki/AMD

瀏覽器並沒有具體實現該規範的代碼,我們可以通過一些第三方庫來解決。如requireJS。

7.AMD——requireJS

官網:https://requirejs.org/

必須在頁面通過data-main指定入口文件;

// 1.html
<script data-main="scripts/main" src="https://cdn.bootcss.com/require.js/2.3.6/require.min.js"></script>

7.1獨立模塊作用域

通過一個 define 方法來定義一個模塊,並通過該方法的第二個回調函數參數來產生獨立作用域

// scripts/Cart.js
define(function() {
  // 模塊內部代碼
})

7.2導出模塊內部數據

兩種方式導出模塊:

  • return方式;
  • 使用CommonJS風格進行導出

通過 return 導出模塊內部數據:可以導出方法,對象等東西;

// scripts/Cart.js
define(function() {
  return class Cart {
    add(item) {
      console.log(`添加商品:${item}`)
    }
  }
})

7.3導入外部模塊數據

通過前置依賴列表導入外部模塊數據

// scripts/main.js
// 定義一個模塊,並導入 ./Cart 模塊
define(['./Cart'], function(Cart) {
  let cart = new Cart()
  cart.add({name: 'iphoneXX', price: 1000000})
})

7.4AMD——requireJS使用示例

  •  必須在頁面通過data-main指定入口文件
  • 當使用data-main指定入口文件後,會動態創建一個script標籤,然後將文件通過ajax的方式(網絡請求方法)去加載需要加載的JS文件(main.js),因爲頁面必須運行在服務器環境下;
  • 通過define 方法來定義一個模塊,並將模塊代碼進行隔離;
  • 導出模塊:方式一:再通過 return 導出模塊內部數據;方式二:CommonJS風格導出;

index.html:

必須使用data-main指定入口文件

<body>
    <script data-main="js/main.js" src="js/require.js"></script>
</body>
</html>

require.js:

由於官網加載很慢,將以下地址再網頁上執行後,將網頁上JS複製下來,生成require.js文件,放入指定目錄

https://cdn.bootcss.com/require.js/2.3.6/require.min.js

main.js:

導入文件時,文件必須使用中括號[ ] 進行應用

//導入模塊化文件時,通過方法的參數獲取到模塊中導出的數據
//注意導入時,文件名需要使用中括號
define(['./m1'],function(Cart){
    let cart = new Cart()
        return cart.add({name: 'iphoneXX', price: 1000000})
    }
);

 m1.js:

通過return方式導出模塊化數據

define(function () {
    return class Cart {
        add(item) {
            console.log("m1模塊");
            console.log(`添加商品:${item}`, item)
        }
    };
});

結果:

 

8.requireJS 的CommonJS風格

require.js 也支持 CommonJS 風格的語法

8.1導出模塊內部數據

// scripts/Cart.js
define(['require', 'exports', 'module'], function(require, exports, module) {
  class Cart {
    add(item) {
      console.log(`添加商品:${item}`)
    }
  }
  exports.Cart = Cart;
})
// 忽略不需要的依賴導入
define(['exports'], function(exports) {
  class Cart {
    add(item) {
      console.log(`添加商品:${item}`)
    }
  }
  exports.Cart = Cart;
})
// 如果是依賴的導入爲:require, exports, module,也可以省略依賴導入聲明
define(function(require, exports, module) {
  class Cart {
    add(item) {
      console.log(`添加商品:${item}`)
    }
  }
  exports.Cart = Cart;
})

8.2導入外部模塊數據

//CommonJS風格導入導出模塊化數據
define(function(require) {
    //注意如果導出的是類,則需要將對象中的類解構出來才能用
    let {Cart} = require('./m2');
    let cart = new Cart();
    
    cart.add({name: 'iphoneXX', price: 1000000})
  })

8.3requireJS的CommonJS風格示例

m2.js:

// define(['require', 'exports', 'module'], function(require, exports, module) {
//     class Cart {
//       add(item) {
//             console.log("m2模塊");
//             console.log(`添加商品:${item}`, item)
//       }
//     }
//     exports.Cart = Cart;
//   })

// // 忽略不需要的依賴導入
// define(['exports'], function(exports) {
//     class Cart {
//       add(item) {
//         console.log(`添加商品:${item}`,item)
//       }
//     }
//     exports.Cart = Cart;
//   })

  // 如果是依賴的導入爲:require, exports, module,也可以省略依賴導入聲明
  define(function(require, exports, module) {
    class Cart {
      add(item) {
        console.log(`添加商品:${item}`,item)
      }
    }
    exports.Cart = Cart;
  })

 main.js:

//CommonJS風格導入導出模塊化數據
define(function(require) {
    //注意如果導出的是類,則需要將對象中的類解構出來才能用
    let {Cart} = require('./m2');
    let cart = new Cart();
    
    cart.add({name: 'iphoneXX', price: 1000000})
  })

9.UMD

嚴格來說,UMD 並不屬於一套模塊規範,它主要用來處理 CommonJSAMDCMD 的差異兼容,是模塊代碼能在前面不同的模塊環境下都能正常運行

  • 判斷如果module是object對象,且導出類型時obejct,就是在nodeJS 環境下導出;
  • define爲function且define.amd爲真,則再AMD下使用;
  • 判斷完後,會執行函數自執行裏的導出;
  • 通過判斷就能在瀏覽器下執行AMD模塊化導出,而再後端nodeJS下就能使用CommonJS規範進行模塊化導出
(function (root, factory) {
  	if (typeof module === "object" && typeof module.exports === "object") {
        // Node, CommonJS-like
        module.exports = factory(require('jquery'));
    }
    else if (typeof define === "function" && define.amd) {
      	// AMD 模塊環境下
        define(['jquery'], factory);
    }
}(this, function ($) { // $ 要導入的外部依賴模塊
    $('div')
    // ...
    function b(){}
    function c(){}

    // 模塊導出數據
    return {
        b: b,
        c: c
    }
}));

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章