前端面試題 - NodeJS能用ES6模塊嗎?CommonJS 和 ES6模塊的區別是什么?
JS能寫前端web,也能寫NodeJS。
- Node.js 后端應用由模塊組成,其模塊系統采用 CommonJS 規范,它并不是 JavaScript 語言規范的正式組成部分。
- 前端的模塊系統則采用ES6模塊規范,這是 JavaScript 語言規范的正式組成部分。
但是現在技術進步了,后端也能用ES6模塊規范(NodeJS支持),前端也能用Common JS規范(Webpack支持)。
- Node.js 默認用CommonJS規范,但也可以用ES6模塊規范,但要求 ES6 模塊采用.mjs后綴文件名。
也就是說,只要腳本文件里面使用import或者export命令,那么就必須采用.mjs后綴名。
Node.js 遇到.mjs文件,就認為它是 ES6 模塊,默認啟用嚴格模式,不必在每個模塊文件頂部指定"use strict"。
如果不希望將后綴名改成.mjs,可以在項目的package.json文件中,指定type字段為module。
一旦設置了以后,該項目里面的.js文件,就被解釋用 ES6 模塊。
如果這時還要使用 CommonJS 模塊,那么需要將 CommonJS 腳本的后綴名都改成.cjs。 - 在使用webpack等打包工具的前端項目中,默認用ES6規范,但也可以使用CommonJS,通過在項目的package.json文件中,指定type字段為commonjs,具體細節與NodeJS后端略有差異。
無論在前端還是在后端,import/export和require/module.exports也是可以在一個項目中同時使用,甚至相互混用,這樣做需要一些配置,但是建議盡量不要混用。
一個模塊同時要支持 CommonJS 和 ES6 兩種格式,也很容易:
- 如果原始模塊是 ES6 格式,那么需要給出一個整體輸出接口,比如export default obj,使得 CommonJS 可以用import()進行加載。
- 如果原始模塊是 CommonJS 格式,那么可以加一個ES6模塊包裝層(import該CommonJS 模塊,然后再export出去)。
// CommonJS模塊的ES6模塊包裝層
import cjsModule from '../index.js'; // index.js是CommonJS規范的
export const foo = cjsModule.foo;
ES6模塊和Commonjs模塊的相同點就是,二者對于同一模塊多次加載都只會執行一次模塊內代碼,即首次加載執行,后面加載模塊不執行其內部代碼。
ES6模塊 和 CommonJS的區別在于用法,加載時機和方式不同:
- CommonJS 模塊使用require()加載和module.exports輸出,require()是代碼運行階段同步加載JS文件的(運行時加載),后面的代碼必須等待這個命令執行完(只加載JS文件,不執行JS文件內容)才會執行。
- ES6 模塊使用import加載和export輸出,為了不影響dom渲染異步加載JS文件,在JS代碼靜態解析階段分析依賴關系,在代碼運行前分析出export和import對應符號引用(同樣只加載JS文件,不執行JS文件內容)。
JS代碼在被JS引擎加載后,分為兩個階段:1、靜態分析階段(我們常說的編譯階段)2、運行階段。靜態分析階段的主要工作是解析源碼生成字節碼。
ES6模塊就是在靜態分析階段實現export和import的分析解析。
靜態分析 & 靜態作用域:如果一門語言的作用域是靜態作用域,那么符號之間的引用關系能夠根據程序代碼在編譯時就確定清楚,在運行時不會變。某個函數是在哪聲明的,就具有它所在位置的作用域。
它能夠訪問哪些變量,那么就跟這些變量綁定了,在運行時就一直能訪問這些變量,這是固定的,這是非常有利于編譯器做優化的。
因此export命令后面只能跟著聲明式語句,而不能跟表達式(如變量名,字面量)。因為變量只有在聲明時,才會產生一個變量引用的符號。
另外,ES6模塊的export {} 中 {} 不是一個對象簡寫形式,更不是一個對象,而是export {} 語法組成部分,用作收集符號用。
另外,CommonJS的module.exports不是模塊內部的變量,而是外部傳入模塊的變量,所以一旦模塊內部代碼對于exports變量做了修改,其實就是對于外部該變量做了修改,
因此模塊代碼未執行完,模塊的輸出module.exports也是有值的,因為這是外部值。
- ES6模塊是在靜態解析階段分析import/export的輸入輸出的常/變量或函數,將其解析為一個“符號引用”(既不是一個對象,也不是一個變量,只是一種靜態定義,一個簡單的引用符號),
外部可以通過符號引用直接獲取到對應模塊中輸出變量的實時數據。由于ES6輸入的模塊變量只是一個“符號連接”,所以這個變量是只讀的,對它進行重新賦值會報錯。 - CommonJS則在運行階段加載模塊輸出對象,由于只作用于運行時導致完全沒辦法在編譯時做“靜態優化”,掛載在該對象上數據都是拷貝數據(淺拷貝),和原模塊中的輸出變量沒有關系了。
外部獲取module.exports,其實獲取的是緩存數據(值都是初始化后的初始值),而不是原模塊內的實時數據。如果訪問原模塊內的實時數據,通過函數返回內部值仍然是可以的。
建議凡是輸入的變量,都當作完全只讀,不要輕易改變它的屬性。
但人們往往說ES6模塊是異步的,為什么呢?因為Common JS肯定是同步的,由于 Node.js 主要用于服務器編程,模塊文件一般都已經存在于本地硬盤,所以加載起來比較快,不用考慮非同步加載的方式,所以 CommonJS 規范比較適用。
但ES6模塊最初用于web,而傳統的瀏覽器引入JS文件的方式就是使用script標簽導入。
但是為了讓script標簽能夠區分 模塊JS文件 和 非模塊JS文件,所以給script標簽加入了 type = "module" 屬性,來告訴瀏覽器引入的是ES6模塊JS文件。
然后其他js文件內部就可以使用import xxx來引入該ES6模塊JS文件里的內容了,就這樣實現了模塊化。
而設置了 type="module"的script標簽,相當于帶了 defer屬性,即異步加載JS文件,不阻塞DOM構建,且會等DOM構建完成后,才執行JS模塊代碼(script標簽默認是同步加載的)。
通俗易懂的前端面試題網站: https://www.front-interview.com
posted on
浙公網安備 33010602011771號