ES6 module模組 import export

Free Joe發表於2020-12-11

一個模組就是一個獨立的檔案。該檔案內部的所有變數,外部無法獲取。ES6中如果你希望外部能夠讀取模組內部的某個變數,就必須使用export關鍵字輸出該變數,其他 JS 檔案就可以通過import命令載入這個模組。

//export.js
export var foo = 'bar';

瀏覽器載入 ES6 模組,<script>加入type="module"屬性。

//xx.html
<script type="module">
	import {foo} from './js/export.js'
	console.log(foo);
</script>

1️⃣ES6 模組與 CommonJS 模組的差異

CommonJS 模組是 Node.js 專用的,與 ES6 模組不相容。

  • CommonJS 模組輸出的是一個值的拷貝,ES6 模組輸出的是值的引用。
  • CommonJS 模組是執行時載入,ES6 模組是編譯時輸出介面。
  • CommonJS 模組的require()是同步載入模組,ES6 模組的import命令是非同步載入,有一個獨立的模組依賴的解析階段。

CommonJS 載入的是一個物件(即module.exports屬性),該物件只有在指令碼執行完才會生成。

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

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

載入fs模組(即載入fs的所有方法),生成一個物件(_fs),然後再從這個物件上面讀取 3 個方法。

這種載入稱為“執行時載入”,因為只有執行時才能得到這個物件,導致完全沒辦法在編譯時做“靜態優化”

?module.exports與require

// lib.js
var counter = 3;
function incCounter() {
  counter++;
}
module.exports = {
  counter: counter,
  incCounter: incCounter,
};
// main.js
var mod = require('./lib');

console.log(mod.counter);  // 3
mod.incCounter();
console.log(mod.counter); // 3

ES6 模組不是物件,它的對外介面只是一種靜態定義,在程式碼靜態解析階段就會生成。

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

從fs模組載入 3 個方法,其他方法不載入。這種載入稱為“編譯時載入”或者“靜態載入”。效率要比 CommonJS 模組的載入方式高。當然,這也導致了沒法引用 ES6 模組本身,因為它不是物件。

2️⃣ES6 module

瀏覽器對於帶有type="module"<script>,都是非同步載入,不會造成堵塞瀏覽器,即等到整個頁面渲染完,再執行模組指令碼,等同於開啟了<script>標籤的defer屬性。有多個type="module"<script>,按在頁面出現的順序執行。但若使用了async屬性,就會轉為async的行為特徵。

程式碼是在模組作用域之中執行,而不是在全域性作用域執行。

ES6 的模組自動採用嚴格模式,不管你有沒有在模組頭部加上"use strict"。比如:這就會讓this受到了限制,頂層的this指向undefined,更多關於嚴格模式知識點看下文。

export

1.兩種寫法

//export.js

//寫法一
export let token='xx';

let bar='bar';
function foo(name){console.log(name);}

//寫法二
export {bar,foo}

2.別名 as

export {bar as baz,bar as bac}

3.對外介面必須與模組內部的變數建立一對一對應關係
在這裡插入圖片描述

// 報錯
export 1;//沒有提供對外的介面 只是一個值

// 報錯
var m = 1;
export m;//沒有提供對外的介面 只是一個值

// 報錯
function f() {}
export f;//沒有提供對外的介面 只是一個值(函式體)

4.export語句輸出的介面,與其對應的值是動態繫結關係

export var foo = 'bar';
setTimeout(() => foo = 'baz', 500);
<script type="module">
	import {foo} from './js/export.js'
	console.log(foo);
	setTimeout(() => console.log(foo), 500);
</script>

在這裡插入圖片描述
5.export命令可以出現在模組的任何位置,只要處於模組頂層就可以

function foo() {
  export default 'bar' // SyntaxError
}
foo()

import

1.寫法

import {foo} from './js/export.js'

2.別名 as

import {foo as koo} from './js/export.js'

3.import命令輸入的變數都是隻讀的(本質是輸入介面)
值不可變,物件存的是引用,引用地址沒變即可。

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

a = {}; // Syntax Error : 'a' is read-only;

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

obj.foo = 'hello'; // 合法操作

4.import命令具有提升效果,會提升到整個模組的頭部,首先執行。
import命令是編譯階段執行的,在程式碼執行之前。

foo();

import { foo } from 'my_module';

5.import後面的from指定模組檔案的位置,若是一個模組名,需要配置檔案指定模組位置

import React from 'React';
import { myMethod } from 'util';

6.import語句會執行所載入的模組

import 'lodash';//上面程式碼僅僅執行lodash模組,但是不輸入任何值。

7.一次全部匯入,模組整體載入* as 別名

	import * as k from './js/export.js'
	console.log(k.foo);

export default

一個模組只能有一個預設輸出。import預設輸出時不需要大括號,且可隨意起名字。

//export.js
function foo() {
  console.log('foo');
}
export let bar=1
export default foo;

//xx.html
<script type="module">
	import customName,{bar}  from './js/export.js'
	customName(); // 'foo'
	console.log(bar);//1
</script>

本質上,export default就是輸出一個叫做default的變數或方法,然後系統允許你為它取任意名字。

function add(x, y) {
  return x * y;
}
export {add as default};
import { default as foo } from 'modules';

// 正確
export var a = 1;

// 正確
var a = 1;
export default a;//等價於 export default 1;

// 錯誤
export default var a = 1;

impot() 動態載入模組

import和export命令只能在模組的頂層,固然有利於編譯器提高效率,但也導致無法在執行時載入模組。
假如要判斷引入模組,難道要寫成require方法?不必!新推出了impot()方法支援動態載入模組。返回的是promise物件。

let a;
if(true){
	a=import('./js/export.js');
}	
console.log(a)//Promise {<pending>}


a.then(({foo, bar}) => {//foo和bar都是export.js的輸出介面
  // ...·
});

嚴格模式

模組預設是嚴格模式

  • 變數必須宣告後再使用
  • 函式的引數不能有同名屬性,否則報錯
  • 不能使用with語句
  • 不能對只讀屬性賦值,否則報錯
  • 不能使用字首 0 表示八進位制數,否則報錯
  • 不能刪除不可刪除的屬性,否則報錯
  • 不能刪除變數delete prop,會報錯,只能刪除屬性delete global[prop]
  • eval不會在它的外層作用域引入變數
  • eval和arguments不能被重新賦值
  • arguments不會自動反映函式引數的變化
  • 不能使用arguments.callee
  • 不能使用arguments.caller
  • 禁止this指向全域性物件
  • 不能使用fn.caller和fn.arguments獲取函式呼叫的堆疊
  • 增加了保留字(比如protected、static和interface)

參考文件:
Module 的語法

相關文章