前端面試題 - NodeJS能用ES6模塊嗎?CommonJS 和 ES6模塊的區別是什麼?

前端面試題 - 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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章