網站首頁 編程語言 正文
ES6模塊與CommonJS模塊的差異
ES6 模塊與 CommonJS 模塊完全不同
它們有三個重大差異
- CommonJS 模塊輸出的是一個值的拷貝,ES6 模塊輸出的是值的引用
- CommonJS 模塊是運行時加載,ES6 模塊是編譯時輸出接口
- CommonJS 模塊的
require()
是同步加載模塊,ES6 模塊的import
命令是異步加載,有一個獨立的模塊依賴的解析階段
第二個差異是 CommonJS 加載的是一個對象(即module.exports
屬性),該對象只有在腳本運行完才會生成,而 ES6 模塊不是對象,它的對外接口只是一種靜態定義,在代碼靜態解析階段就會生成。
CommonJS 模塊是 Node.js 專用的,語法上面,與 ES6 模塊最明顯的差異是,CommonJS 模塊使用 require()
和 module.exports
,ES6 模塊使用 import
和 export
。
ES6 中 module 的語法
ES6 模塊的設計思想是盡量的靜態化,使得編譯時就能確定模塊的依賴關系,以及輸入和輸出的變量。
ES6模塊不是對象,而是通過 export
命令顯式指定輸出的代碼,再通過 import
命令輸入。
export 命令
模塊功能主要由兩個命令構成:export
和 import
。 export
命令用于規定模塊的對外接口,import
命令用于輸入其他模塊提供的功能。
一個模塊就是一個獨立的文件。該文件內部的所有變量,外部無法獲取。如果希望外部能夠讀取模塊內部的某個變量,就必須使用 export
關鍵字輸出該變量。
export 輸出變量
// export.js
let firstName = 'Mark'
let lastName = 'Dave'
export { firstName, lastName }
上面代碼在 export
命令后面,使用大括號指定所要輸出的一組變量。
export 輸出函數或類
export
命令除了輸出變量,還可以輸出函數或類(class)
export function multiply(x, y) {
return x * y
}
上面代碼對外輸出一個函數 `multiply
export使用as重命名
通常情況下,export
輸出的變量是本來的名字,但是可以使用 as
關鍵字重命名
function fun1() { ... }
function fun2() { ... }
export {
fun1 as streamFun1,
fun2 as streamFun2,
fun2 as streamLatestFun
}
上面代碼使用 as
關鍵字,重命名了函數 fun1
和 fun2
的對外接口,重命名后,fun2
可以用不同的名字輸出兩次
export 規定的對外接口,必須與模塊內部的變量一一對應
export
命令規定的是對外的接口,必須與模塊內部的變量建立一一對應關系
// 報錯
export 1;
// 報錯
let m = 1
export m;
上面兩種寫法都會報錯,因為沒有提供對外的接口。第一種寫法直接輸出1,第二種寫法通過變量 m,還是直接輸出1,1只是一個值,不是接口。正確的寫法如下:
// 寫法1
export let m = 1;
// 寫法2
let m = 1
export { m }
// 寫法3
let n = 2
export { n as m }
上面三種寫法都是正確的,規定了對外的接口 m。其他腳本可以通過這個接口,取到值 1.它們的實質是,在接口名和模塊內部變量之間,建立了一一對應的關系。
同樣,function
和 class
的輸出,也必須同樣遵守這樣的寫法
// 報錯
function f() {}
export f
// 正確
function f() {}
export { f }
// 正確
export function f() {}
export可以出現在模塊的任何位置,只要處于模塊頂層就可以
export
命令可以出現在模塊的任何位置,只要處于模塊頂層就可以。如果處于塊級作用域內,就會報錯
function foo() {
export default 'bar' // SyntaxError
}
foo()
上面代碼中,export
語句放在函數之中,結果報錯
export default 命令
使用 import
命令的時候,用戶需要知道所要加載的變量名或函數名,否則無法加載。但是,用戶肯定希望快速上手,未必愿意閱讀文檔,去了解模塊有哪些屬性和方法。
為了給用戶提供方便,讓它們不用閱讀文檔就能加載模塊,就要用到 export default
命令,為模塊指定默認輸出。
// export-default.js
export default function() {
console.log('foo')
}
上面代碼是一個模塊文件 export-default.js
,它默認輸出是一個函數。
其他模塊加載該模塊時,import
命令可以為該匿名函數指定任意名字。
// import-default.js
import customName from './export-default'
customName()
上面代碼的 import
命令,可以用任意名稱指向 export-default.js
輸出的方法,這時就不需要知道原模塊輸出的函數名。需要注意的是,這時 import
命令后面,不使用大括號。export default
命令用在非匿名函數前,也是可以的
export default function foo() {
console.log('foo')
}
// 或者寫成
function foo() {
console.log('foo')
}
export default foo
上面代碼中,foo
函數的函數名 foo
,在模塊外部是無效的。加載的時候,視同匿名函數加載。
下面比較一下默認輸出和正常輸出:
// 第一組
export default function crc32() {}
import crc32 from 'crc32'
// 第二組
export function crc32() {}
import { crc32 } from 'crc32'
上面兩組寫法,第一組是使用 export default
時,對應的 import
語句不需要使用大括號;第二組是不使用 export default
時,對應的 import
語句需要使用大括號。
export default
命令用于指定模塊的默認輸出。顯然,一個模塊只能有一個默認輸出,因此 export default
命令只能使用一次。所以,import 命令后面才不用加大括號,因為只可能唯一對應 export default
命令。
本質上,export default
就是輸出一個叫做 default
的變量或方法,然后系統允許你為它取任意名字。所以,下面的寫法是有效的。
// module.js
function add(x, y) {
return x + y
}
export { add as default }
// 等同于
export default add
// main.js
import { default as foo } from 'modules'
// 等同于
import foo from 'modules'
正是因為 export default
命令其實只是輸出一個叫做 default
的變量,所以它后面不能跟變量聲明語句。
// 正確
export let a = 1
// 正確
let a = 1
export default a
// 錯誤
export default let a = 1
上面代碼中,export default a
的含義是將變量 a 的值賦值給變量 default
。所以,最后一種寫法會報錯。
同樣地,因為 export default
命令的本質是將后面的值,賦給 default
變量,所以可以直接將一根值寫在 rcport default
之后
// 正確
export default 42
// 報錯
export 42
如果想在一條 import
語句中,同時輸入默認方法和其他接口,可以寫成下面這樣
import _, { each, forEach } from 'lodash'
export default
也可以用來輸出類
// MyClass.js
export default class { ... }
// main.js
import MyClass from 'MyClass'
let o = new MyClass()
import 命令
使用 export
命令定義了模塊的對外接口后,其他 js 文件就可以通過 import
命令加載這個模塊。
// import.js
import { firstName, lastName } from './export.js'
function setName(element) {
element.textContent = firstName + ' ' + lastName
}
上面代碼的 import
命令,用于加載 export.js
文件,并從中輸入變量。import
命令接受一對大括號,里面指定要從其他模塊導入的變量名。大括號里面的變量名,必須與被導入模塊 export.js
對外借款的名稱相同
import 使用 as 重命名變量
如果想為輸入的變量重新取一個名字,import
命令要使用 as
關鍵字,將輸入的變量重命名。
import { lastName } as surname from './export.js'
import`命令輸入的變量都是只讀的
import
命令輸入的變量都是只讀的,,因為它的本質是輸入接口。也就是說,不允許在加載模塊的腳本里面,改寫接口。
import { a } from './xxx.js
a = {} // Syntax Error: 'a' is read-only
上面代碼中,腳本加載了變量 a,對其重新賦值就會報錯,因為 a 是一個只讀的接口,但是,如果 a 是一個對象,改寫 a 的屬性是允許的
import { a } from './xxx.js'
a.foo = 'hello' // 合法操作
上面代碼中,a 的屬性可以改寫成功,并且其他模塊也可以讀到改寫后的值,不過,這種寫法很難查錯,建議凡是輸入的變量,丟完全當做只讀,不要輕易改變它的屬性。
import
后面的 from
指定模塊文件的位置,可以是相對路徑,也可以是絕對路徑。如果不帶有路徑,只是一個模塊名,那么必須有配置文件,告訴 javascript 引擎該模塊的位置。
import 具有提升效果
import
命令具有提升效果,會提升到整個模塊的頭部,首先執行
foo()
import { foo } from 'my_module'
上面的代碼不會報錯,因為 import
的執行早于 foo
的調用。這種行為的本質是,import
命令是編譯階段執行的,在代碼運行之前。
import 是靜態執行,不能使用表達式和變量
由于 import 是靜態執行,所以不能使用表達式和變量,這些只有在運行時才能得到結果的語法結構
// 報錯
import { 'f' + 'oo' } from ''my_module
// 報錯
let module = 'my_module'
import { foo } from module
// 報錯
if (x === 1) {
import { foo } from 'module1'
} else {
import { foo } from 'module2'
}
上面三種寫法都會報錯,因為它們用到了表達式、變量和 if 結構。在靜態分析階段,這些語法是沒法得到值的。
最后,import
語句會執行所加載的模塊,因此可以有下面的寫法:
import 'lodash'
上面代碼僅僅執行 lodash
模塊,但是不輸入任何值。
如果多次重復執行同一句 import
語句,那么只會執行一次,而不會執行多次。
import 'lodash'
import 'lodash'
上面代碼加載了兩次 lodash
,但是只會執行一次
import { foo } from 'my_module'
import { bar } from 'my_module'
// 等同于
import { foo, bar } from 'my_module'
上面代碼中,雖然 foo
和 bar
在兩個語句中加載,但是它們對應的是同一個 my_module
模塊,也就是說,import
語句是singleton
模式。
模塊的整體加載
除了指定加載某個輸出值,還可以使用整體加載,即用星號(*
)指定一個對象,所有輸出值都加載在這個對象上。
例如,下面是一個 circle.js
文件,它輸出兩個方法 area
和 circum
// circle.js
export function area(radius) {
return Math.PI * radius * radius
}
export function circum(radius) {
return @ * Math.PI * radius
}
現在,加載這個模塊
// main.js
import {area, circum} from './circle'
console.log(area(4))
console.log(circum(14))
上面寫法是逐一指定要加載的方法,整體加載的寫法如下。
import * as circle from './circle'
console.log(circle.area(4))
console.log(circle.circum(14))
注意:模塊整體加載所在的那個對象(上例是 circle),應該是可以靜態分析的,所以不允許運行時改變。下面的寫法都是不允許的。
import * as circle from './circle'
// 下面兩行都是不允許的
circle.foo = 'hello'
circle.area = function () {}
export 和 import 的復合寫法
如果在一根模塊之中,先輸入后輸出同一個模塊,import
語句可以與 export
語句寫在一起。
export { foo, bar } from 'my_module'
// 可以簡單理解為
import { foo, bar } from 'my_module'
export { foo, bar }
上面代碼中,export
和 import
語句可以結合在一起,寫成一行。但需要注意的是,寫成一行以后,foo
和 bar
實際上并沒有被導入當前模塊,只是相當于對外轉發了這兩個接口,導致當前模塊不能直接使用 foo
和 bar
。
模塊的接口改名和整體輸出,也可以采用這種寫法
// 接口改名
export { foo as myFoo } from 'my_module'
// 整體輸出
export * from 'my_module'
默認接口的寫法如下:
export { default } from 'foo'
具名接口改為默認接口的寫法如下
export { es6 as default } from './someModule'
// 等同于
import { es6 } from './someModule'
export default es6
同樣的,默認接口也可以改名為具名接口
export { default as es6 } from './someModule'
import()
import
和 export
命令只能在模塊的頂層,不能在代碼塊之中(比如,在 if 代碼塊之中,或在函數之中)
ES2020提案引入 import()
函數,支持動態加載模塊
import(specifier)
上面代碼中,import
函數的參數 specifier
,指定所要加載的模塊的位置,import
命令能夠接受上面參數,import()
函數就能接受上面參數,兩者區別主要是后者為動態加載。import()
返回一個 Promise 對象。如
const main = document.querySelector('main');
import(`./section-modules/${someVariable}.js`)
.then(module => {
module.loadPageInto(main);
})
.catch(err => {
main.textContent = err.message;
});
import()
函數可以用在任何地方,不僅僅是模塊,非模塊的腳本也可以使用。它是運行時執行,也就是說,什么時候運行到這一句,就會加載指定的模塊。另外,import()
函數與所加載的模塊沒有靜態連接關系,這點也是與 import
語句不相同。import()
類似于 Node 的 require
方法,區別主要是前者是異步加載,后者是同步加載。
適用場合
按需加載
import()
可以在需要的時候,再加載某個模塊
button.addEventListener('click', event => {
import('./dialogBox.js')
.then(dialogBox => {
dialogBox.open();
})
.catch(error => {
/* Error handling */
})
});
上面代碼中,import()
方法放在 click
事件的監聽函數中,只有用戶點擊了按鈕,才會加載這個模塊。
條件加載
import()
可以放在 if 代碼塊,根據不同的情況,加載不同的模塊
if (condition) {
import('moduleA').then(...);
} else {
import('moduleB').then(...);
}
上面代碼中,如果滿足條件,就加載模塊 A,否則加載模塊 B
動態的模塊路徑
import()
允許模塊路徑動態生成
import(f())
.then(...)
上面代碼中,根據函數 f 的返回結果,加載不同的模塊。
注意點
import()
加載模塊成功以后,這個模塊會作為一個對象,當作 then
方法的參數。因此,可以使用對象結構賦值的語法,獲取輸出接口
import('./myModule.js')
.then({export1, export2}) => {
// ...
})
上面代碼中,export1
和 export2
都是 myModule.js
的輸出接口,可以解構獲得
如果想同時加載多個模塊,可以
Promise.all([
import('./module1.js'),
import('./module2.js'),
import('./module3.js'),
])
.then(([module1, module2, module3]) => {
···
});
import()
也可以用在 async 函數中
async function main() {
const myModule = await import('./myModule.js');
const {export1, export2} = await import('./myModule.js');
const [module1, module2, module3] =
await Promise.all([
import('./module1.js'),
import('./module2.js'),
import('./module3.js'),
]);
}
main();
原文鏈接:https://blog.csdn.net/qq_38157825/article/details/119649597
- 上一篇:沒有了
- 下一篇:沒有了
相關推薦
- 2022-04-24 python?Django實現增刪改查實戰代碼_python
- 2022-09-22 git-lfs 離線安裝
- 2022-04-10 Blazor組件事件處理功能_基礎應用
- 2022-07-12 CSS樣式:行內元素 盒子模型
- 2022-04-01 Kubernetes中Nginx配置熱加載的全過程_nginx
- 2022-05-24 Django框架cookie和session方法及參數設置_python
- 2022-03-28 通過numba模塊給Python代碼提速的方法詳解_python
- 2023-04-01 Python關于維卷積的理解_python
- 欄目分類
-
- 最近更新
-
- window11 系統安裝 yarn
- 超詳細win安裝深度學習環境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權
- redisson分布式鎖中waittime的設
- maven:解決release錯誤:Artif
- restTemplate使用總結
- Spring Security之安全異常處理
- MybatisPlus優雅實現加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務發現-Nac
- Spring Security之基于HttpR
- Redis 底層數據結構-簡單動態字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應用詳解
- 聊聊消息隊列,發送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支