你是怎麼理解ES6中Module的?使用場景?

林恒發表於2024-03-12

這裡給大家分享我在網上總結出來的一些知識,希望對大家有所幫助

你是怎麼理解ES6中Module的?使用場景?

一、介紹

模組,(Module),是能夠單獨命名並獨立地完成一定功能的程式語句的集合(即程式程式碼和資料結構的集合體)。

兩個基本的特徵:外部特徵和內部特徵

  • 外部特徵是指模組跟外部環境聯絡的介面(即其他模組或程式呼叫該模組的方式,包括有輸入輸出引數、引用的全域性變數)和模組的功能

  • 內部特徵是指模組的內部環境具有的特點(即該模組的區域性資料和程式程式碼)

為什麼需要模組化

  • 程式碼抽象
  • 程式碼封裝
  • 程式碼複用
  • 依賴管理

如果沒有模組化,我們程式碼會怎樣?

  • 變數和方法不容易維護,容易汙染全域性作用域
  • 載入資源的方式透過script標籤從上到下。
  • 依賴的環境主觀邏輯偏重,程式碼較多就會比較複雜。
  • 大型專案資源難以維護,特別是多人合作的情況下,資源的引入會讓人奔潰

因此,需要一種將JavaScript程式模組化的機制,如

  • CommonJs (典型代表:node.js早期)
  • AMD (典型代表:require.js)
  • CMD (典型代表:sea.js)

AMD

Asynchronous ModuleDefinition(AMD),非同步模組定義,採用非同步方式載入模組。所有依賴模組的語句,都定義在一個回撥函式中,等到模組載入完成之後,這個回撥函式才會執行

代表庫為require.js

/** main.js 入口檔案/主模組 **/
// 首先用config()指定各模組路徑和引用名
require.config({
  baseUrl: "js/lib",
  paths: {
    "jquery": "jquery.min",  //實際路徑為js/lib/jquery.min.js
    "underscore": "underscore.min",
  }
});
// 執行基本操作
require(["jquery","underscore"],function($,_){
  // some code here
});

CommonJs

CommonJS 是一套 Javascript 模組規範,用於服務端

// a.js
module.exports={ foo , bar}

// b.js
const { foo,bar } = require('./a.js')

其有如下特點:

  • 所有程式碼都執行在模組作用域,不會汙染全域性作用域
  • 模組是同步載入的,即只有載入完成,才能執行後面的操作
  • 模組在首次執行後就會快取,再次載入只返回快取結果,如果想要再次執行,可清除快取
  • require返回的值是被輸出的值的複製,模組內部的變化也不會影響這個值

既然存在了AMD以及CommonJs機制,ES6Module又有什麼不一樣?

ES6 在語言標準的層面上,實現了Module,即模組功能,完全可以取代 CommonJSAMD規範,成為瀏覽器和伺服器通用的模組解決方案

CommonJSAMD 模組,都只能在執行時確定這些東西。比如,CommonJS模組就是物件,輸入時必須查詢物件屬性

// CommonJS模組
let { stat, exists, readfile } = require('fs');

// 等同於
let _fs = require('fs');
let stat = _fs.stat;
let exists = _fs.exists;
let readfile = _fs.readfile;

ES6設計思想是儘量的靜態化,使得編譯時就能確定模組的依賴關係,以及輸入和輸出的變數

// ES6模組
import { stat, exists, readFile } from 'fs';

述程式碼,只載入3個方法,其他方法不載入,即 ES6 可以在編譯時就完成模組載入

由於編譯載入,使得靜態分析成為可能。包括現在流行的typeScript也是依靠靜態分析實現功能

二、使用

ES6模組內部自動採用了嚴格模式,這裡就不展開嚴格模式的限制,畢竟這是ES5之前就已經規定好

模組功能主要由兩個命令構成:

  • export:用於規定模組的對外介面
  • import:用於輸入其他模組提供的功能

export

一個模組就是一個獨立的檔案,該檔案內部的所有變數,外部無法獲取。如果你希望外部能夠讀取模組內部的某個變數,就必須使用export關鍵字輸出該變數

// profile.js
export var firstName = 'Michael';
export var lastName = 'Jackson';
export var year = 1958;

或 
// 建議使用下面寫法,這樣能瞬間確定輸出了哪些變數
var firstName = 'Michael';
var lastName = 'Jackson';
var year = 1958;

export { firstName, lastName, year };

輸出函式或類

export function multiply(x, y) {
  return x * y;
};

透過as可以進行輸出變數的重新命名

function v1() { ... }
function v2() { ... }

export {
  v1 as streamV1,
  v2 as streamV2,
  v2 as streamLatestVersion
};

import

使用export命令定義了模組的對外介面以後,其他 JS 檔案就可以透過import命令載入這個模組

// main.js
import { firstName, lastName, year } from './profile.js';

function setName(element) {
  element.textContent = firstName + ' ' + lastName;
}

同樣如果想要輸入變數起別名,透過as關鍵字

import { lastName as surname } from './profile.js';

當載入整個模組的時候,需要用到星號*

// circle.js
export function area(radius) {
  return Math.PI * radius * radius;
}

export function circumference(radius) {
  return 2 * Math.PI * radius;
}

// main.js
import * as circle from './circle';
console.log(circle)   // {area:area,circumference:circumference}

輸入的變數都是隻讀的,不允許修改,但是如果是物件,允許修改屬性

import {a} from './xxx.js'

a.foo = 'hello'; // 合法操作
a = {}; // Syntax Error : 'a' is read-only;

不過建議即使能修改,但我們不建議。因為修改之後,我們很難差錯

import後面我們常接著from關鍵字,from指定模組檔案的位置,可以是相對路徑,也可以是絕對路徑

import { a } from './a';

如果只有一個模組名,需要有配置檔案,告訴引擎模組的位置

import { myMethod } from 'util';

在編譯階段,import會提升到整個模組的頭部,首先執行

foo();

import { foo } from 'my_module';

多次重複執行同樣的匯入,只會執行一次

import 'lodash';
import 'lodash';

上面的情況,大家都能看到使用者在匯入模組的時候,需要知道載入的變數名和函式,否則無法載入

如果不需要知道變數名或函式就完成載入,就要用到export default命令,為模組指定預設輸出

// export-default.js
export default function () {
    console.log('foo');
}

載入該模組的時候,import命令可以為該函式指定任意名字

// import-default.js
import customName from './export-default';
customName(); // 'foo'

動態載入

允許您僅在需要時動態載入模組,而不必預先載入所有模組,這存在明顯的效能優勢

這個新功能允許您將import()作為函式呼叫,將其作為引數傳遞給模組的路徑。 它返回一個 promise,它用一個模組物件來實現,讓你可以訪問該物件的匯出

import('/modules/myModule.mjs')
  .then((module) => {
    // Do something with the module.
  });

複合寫法

如果在一個模組之中,先輸入後輸出同一個模組,import語句可以與export語句寫在一起

export { foo, bar } from 'my_module';

// 可以簡單理解為
import { foo, bar } from 'my_module';
export { foo, bar };

同理能夠搭配as*搭配使用

三、使用場景

如今,ES6模組化已經深入我們日常專案開發中,像vuereact專案搭建專案,元件化開發處處可見,其也是依賴模組化實現

vue元件

<template>
  <div class="App">
      元件化開發 ---- 模組化
  </div>
</template>

<script>
export default {
  name: 'HelloWorld',
  props: {
    msg: String
  }
}
</script>

react元件

function App() {
  return (
    <div className="App">
		元件化開發 ---- 模組化
    </div>
  );
}

export default App;

包括完成一些複雜應用的時候,我們也可以拆分成各個模組

參考文獻

  • https://macsalvation.net/the-history-of-js-module/
  • https://es6.ruanyifeng.com/#docs/module

相關文章