CommonJs
有很多優秀的特性,下面我們再簡單的回顧一下:
模組程式碼只在加載後運行;
模組只能載入一次;
模組可以要求載入其他模組;
支援循環依賴;
模組可以定義公共介面,其他模組可以基於這個公共介面觀察與互動;
Es Module
的獨特之處在於,既可以透過瀏覽器原生載入,也可以與第三方載入器和建置工具一起載入.
支援 Es module
模組的瀏覽器可以從頂級模組載入整個依賴圖,且是非同步完成。瀏覽器會解析入口模組,確定依賴,並傳送對依賴模組的請求。這些檔案透過網路返回後,瀏覽器就會解析它們的依賴,,如果這些二級依賴還沒有載入,則會發送更多請求。
這個非同步遞歸載入過程會持續到整個應用程式的依賴圖都解析完成。解析完成依賴圖,引用程式就可以正式載入模組了。
Es Module
不僅借用了CommonJs
和AMD
的許多優秀特性,還增加了一些新行為:
#Es Module
預設在嚴格模式下執行;
#Es Module
不共享全域命名空;
Es Module
頂層的this
的值是undefined
(常規腳本是window
);
模組中的var
宣告不會加入到window
物件;
Es Module
是非同步載入和執行的;
和import
。
import
指令用於輸入其他模組提供的功能。
export的基本使用
#匯出的基本形式:export const nickname = "moment"; export const address = "广州"; export const age = 18;
const nickname = "moment"; const address = "广州"; const age = 18; export { nickname, address, age };
export function foo(x, y) { return x + y; } export const obj = { nickname: "moment", address: "广州", age: 18, }; // 也可以写成这样的方式 function foo(x, y) { return x + y; } const obj = { nickname: "moment", address: "广州", age: 18, }; export { foo, obj };
as
關鍵字重新命名。
const address = "广州"; const age = 18; export { nickname as name, address as where, age as old };
export default "foo"; export default { name: 'moment' } export default function foo(x,y) { return x+y } export { bar, foo as default };
export 的錯誤使用
匯出語句必須在模組頂層,不能嵌套在某個區塊中:if(true){ export {...}; }
<pre class="brush:js;toolbar:false;">// 1只是一个值,不是一个接口
export 1
// moment只是一个值为1的变量
const moment = 1
export moment
// function和class的输出,也必须遵守这样的写法
function foo(x, y) {
return x+y
}
export foo</pre>
##使用export
#指令定義了模組的對外介面以後,其他js檔就可以透過
import {foo,age,nickname} form '模块标识符'
模組識別碼可以是目前模組的相對路徑,也可以是絕對路徑,也可以是純字串,但不能是動態計算的結果,例如憑藉的字串。
從上圖可以看得出來,物件的屬性被重新賦值了,而變數的則報了Assignment to constant variable
的型別錯誤。import
語句中同時取得它們。可以依序列出特定的標識符來取得,也可以使用
#// foo.js export default function foo(x, y) { return x + y; } export const bar = 777; export const baz = "moment"; // main.js import { default as foo, bar, baz } from "./foo.js"; import foo, { bar, baz } from "./foo.js"; import foo, * as FOO from "./foo.js";
標準用法的import
導入的模組是靜態的,會使所有被導入的模組,在載入時就被編譯(無法做到按需編譯,降低首頁載入速度)。有些場景中,你可能會想要根據條件導入模組或按需導入模組,這時你可以使用動態導入來代替靜態導入。import
可以像呼叫函數一樣來動態的導入模組。以這種方式調用,將會傳回一個
import("./foo.js").then((module) => { const { default: foo, bar, baz } = module; console.log(foo); // [Function: foo] console.log(bar); // 777 console.log(baz); // moment });
import 的错误使用 在浏览器上,你可以通过将 默认情况下, 讨论 第二个差异是因为 在学习工作原理之前,我们不妨来认识一下相关的概念。 Module Record 模块记录( Module Environment Record 不可變綁定就是當前的模組引入其他的模組,引入的變數不能修改,這就是模組獨特的不可變綁定。 #在開始之前,我們先大概了解一下整個流程大概是怎麼樣的,先有一個大概的了解: 階段一:建構( 階段二:實例化( 階段三:運行( Construction 建置階段 ModuleSpecifier ModuleRequest ExportName: 此模組用於匯出時綁定的名稱。 下面這張表記錄了使用 回到主題 只有當解析完當前的 MOdule Map 是由一個 URL 記錄和一個字串組成的key/value的對應物件。 URL記錄是取得模組的請求URL,字串指示模組的類型(例如。「javascript」)。模組映射的值要么是模組腳本,null(用於表示失敗的獲取),要么是佔位符值“fetching(獲取中)”。 linking 連結階段 Evaluation 求值階段 #在 深度优先搜索算法(英语:Depth-First-Search,DFS)是一种用于遍历或搜索树或图的算法。这个算法会尽可能深地搜索树的分支。当节点v的所在边都己被探寻过,搜索将回溯到发现节点v的那条边的起始节点。这一过程一直进行到已发现从源节点可达的所有节点为止。如果还存在未被发现的节点,则选择其中一个作为源节点并重复以上过程,整个进程反复进行直到所有节点都被访问为止。 好了,这篇文章到这也就结束了。 原文地址:https://juejin.cn/post/7166046272300777508 【推荐学习:javascript高级教程】await
必须在带有 async
的异步函数中使用,否则会报错:const p = new Promise((resolve, reject) => {
resolve(111);
});
// SyntaxError: await is only valid in async functions and the top level bodies of modules
const result = await p;
console.log(result);
Top-level await
:const p = new Promise((resolve, reject) => {
resolve(777);
});
const result = await p;
console.log(result); // 777正常输出
import
是静态执行,所以不能使用表达式和变量,这些只有在运行时才能得到结果的语法结构。// 错误
import { 'b' + 'ar' } from './foo.js';
// 错误
let module = './foo.js';
import { bar } from module;
// 错误
if (x === 1) {
import { bar } from './foo.js';
} else {
import { foo } from './foo.js';
}
在浏览器中使用 Es Module
type
属性设置为 module
用来告知浏览器将 script
标签视为模块。<script type="module" src="./main.mjs"></script>
<script type="module"></script>
defer
的方式延迟你的 nomodule
脚本: <script type="module">
console.log("模块情况下的");
</script>
<script src="./main.js" type="module" defer></script>
<script>
console.log("正常 script标签");
</script>
nomodule
脚本会被执行多次,而模块只会被执行一次: <script src="./foo.js"></script>
<script src="./foo.js"></script>
<script type="module" src="./main.js"></script>
<script type="module" src="./main.js"></script>
<script type="module" src="./main.js"></script>
模块的默认延迟
nomodule
脚本会阻塞 HTML
解析。你可以通过添加 defer
属性来解决此问题,该属性是等到 HTML
解析完成之后才执行。
defer
和 async
是一个可选属性,他们只可以选择其中一个,在 nomodule
脚本下,defer
等到 HTML
解析完才会解析当前脚本,而 async
会和 HTML
并行解析,不会阻塞 HTML
的解析,模块脚本可以指定 async
属性,但对于 defer
无效,因为模块默认就是延迟的。async
属性,模块脚本及其所有依赖项将于解析并行获取,并且模块脚本将在它可用时进行立即执行。Es Module 和 Commonjs 的区别
Es Module
模块之前,必须先了解 Es Module
与 Commonjs
完全不同,它们有三个完全不同:
CommonJS
模块输出的是一个值的拷贝,Es Module
输出的是值的引用;CommonJS
模块是运行时加载,Es Module
是编译时输出接口。CommonJS
模块的 require()
是同步加载模块,ES6 模块的import
命令是异步加载,有一个独立的模块依赖的解析阶段。CommonJS
加载的是一个对象(即module.exports
属性),该对象只有在脚本运行完才会生成。而 Es Module
不是对象,它的对外接口只是一种静态定义,在代码静态解析阶段就会生成。Commonjs
输出的是值的拷贝,也就是说,一旦输出一个值,模块内部的变化就影响不到这个值。具体可以看上一篇写的文章。Es Module
的运行机制与 CommonJS
不一样。JS引擎
对脚本静态分析的时候,遇到模块加载命令import
,就会生成一个只读引用。等到脚本真正执行时,再根据这个只读引用,到被加载的那个模块里面去取值。换句话说,import
就是一个连接管道,原始值变了,import
加载的值也会跟着变。因此,Es Module
是动态引用,并且不会缓存值,模块里面的变量绑定其所在的模块。Es Module 工作原理的相关概念
Module Record
) 封装了关于单个模块(当前模块)的导入和导出的结构信息。此信息用于链接连接模块集的导入和导出。一个模块记录包括四个字段,它们只在执行模块时使用。其中这四个字段分别是:
Realm
: 创建当前模块的作用域;Environment
:模块的顶层绑定的环境记录,该字段在模块被链接时设置;Namespace
:模組命名空間物件是模組命名空間外來對象,它提供對模組導出綁定的基於執行時間屬性的存取。模組命名空間物件沒有建構子;HostDefined
:欄位保留,以按host environments
使用,需要將附加資訊與模組關聯。 import
綁定,這些綁定提供了對存在於另一個環境記錄中的目標綁定的間接存取。 Es Module 的解析流程
Construction
),根據位址尋找js
檔案,透過網路下載,並且解析模組檔案為Module Record
;Instantiation
),對模組進行實例化,並且分配記憶體空間,解析模組的導入與導出語句,把模組指向對應的記憶體位址;Evaluation
),運行程式碼,計算值,並且將值填入記憶體位址;負責對模組進行尋址及下載。首先我們修改一個入口檔,這在
HTML 中通常是一個
的標籤來表示模組檔。
模組繼續透過
語句宣告,在
import宣告語句中有一個模組宣告標識符(
ModuleSpecifier),這告訴
loader 怎麼找出下一個模組的位址。
每一個模組標識號對應一個
,而每一個
模組記錄 包含了
JavaScript程式碼、
執行上下文、
ImportEntries、
LocalExportEntries、
IndirectExportEntries、
StarExportEntries 。其中
ImportEntries 值是一個
ImportEntry Records 類型,而
LocalExportEntries、
IndirectExportEntries、
StarExportEntries 是一個
ExportEntry Records 類型。
ImportEntry Records
一個
ModuleRequest: 一個模組識別碼( 包含三個欄位
ModuleRequest#、
ImportName、
LocalName;
);
模組識別碼的模組導出所需綁定的名稱。值
namespace-object 表示導入請求是針對目標模組的命名空間物件的;
詳情可參考下圖:下面這張表記錄了使用
匯入的
ImportEntry Records 欄位的實例:
導入宣告(Import Statement Form)
模組識別碼(ModuleRequest) 導入名(ImportName) 本地名(LocalName)
import React from "react";
"react" "default " "React"
import * as Moment from "react";
"react" namespace-obj "Moment"
import {useEffect} from "react";
"react" "useEffect" "useEffect"
import {useEffect as effect } from "react";
"react" "useEffect" "effect"
ExportEntry Records
ExportEntry Records
包含四個欄位ExportName
、ModuleRequest
、ImportName
、LocalName
,和ImportEntry Records
不同的是多了一個ExportName
。 export
匯出的ExportEntry Records
欄位的實例:
匯出宣告
匯出名稱
模組識別碼
匯入名稱
本機名稱
export var v;
"v"
null
null
"v"
export default function f() {}
"default"
null
null
"f"
#export default function () {}
"default"
null
null
"default"
export default 42;
"default"
null
null
"default"
export {x};
"x"
#null
#null "x"
export {v as x};
#"x"
null
null
"v"
export {x} from "mod";
"x"
"mod"
"x"
null
export {v as x} from "mod";
"x"
"mod"
"v"
null
#export * 來自 "mod";
null
"mod"
all-but-default
null
##export * as ns from "mod"; ##" ns"mod" all null
Module Record
之後,才能知道當前模組依賴的是那些子模組,然後你需要resolve
子模組,取得子模組,再解析子模組,不斷的循環這個流程resolving -> fetching -> parsing,結果如下圖所示:
靜態分析
,不會執行JavaScript程式碼,只會辨識export
和import
關鍵字,所以說不能在非全域作用域下使用import
,動態導入除外。 loader
使用Module Map
對全域的MOdule Record
進行追蹤、快取這樣就可以保證模組只被fetch
一次,每個全域作用域中會有一個獨立的Module Map。 Module Record
被解析完後,接下來JS 引擎需要把所有模組進行連結。 JS 引擎以入口檔案的Module Record
作為起點,以深度優先的順序去遞歸連結模組,為每個Module Record
建立一個Module Environment Record
,用於管理Module Record
中的變數。
Module Environment Record
中有一個Binding
,這個是用來存放#Module Record
導出的變數,如上圖所示,在該模組main.js
處導出了一個count
的變數,在Module Environment Record
中的Binding
就會有一個count
,在這個時候,就相當於V8
的編譯階段,創建一個模組實例物件,添加相對應的屬性和方法,此時值為undefined
或null
,為其分配記憶體空間。 count.js
中使用了import
關鍵字對main.js
進行導入,而count. js
的import
和main.js
的export
的變數指向的記憶體位置是一致的,這樣就把父子模組之間的關係鏈接起來了。如下圖所示:export
導出的為父模組,import
引入的為子模組,父模組可以對變數進行修改,具有讀寫權限,而子模組只有讀取權限。 Es module 是如何解決循環引用的
Es Module
中有5種狀態,分別為unlinked
、linking
、linked
、evaluating
和evaluated
,以循環模組記錄( Cyclic Module Records
)的Status
欄位來表示,正是透過這個欄位來判斷模組是否被執行過,每個模組只執行一次。這也是為什麼會使用Module Map
來進行全域快取Module Record
的原因了,如果一個模組的狀態為evaluated
,那麼下次執行則會自動跳過,從而包裝一個模組只會執行一次。 Es Module
採用 深度優先
的方法對模組圖進行遍歷,每個模組只執行一次,這也就避免了死循環的情況了。 // main.js
import { bar } from "./bar.js";
export const main = "main";
console.log("main");
// foo.js
import { main } from "./main.js";
export const foo = "foo";
console.log("foo");
// bar.js
import { foo } from "./foo.js";
export const bar = "bar";
console.log("bar");
node
运行 main.js
,得出以下结果:
以上是一文詳解es6中的模組化的詳細內容。更多資訊請關注PHP中文網其他相關文章!