模塊化導入導出方式 require,export 與 import,export 的較量

前言

????利用一些模塊化的工具進行模塊化開發已經有很長的時間了,特別最近一直在webpack下開發vue,在模塊化的開發方式中,最基本的莫過於模塊的導入和導出了,在實際使用過程中我們,可以使用require/export的方式進行模塊的導入和導出,也可以import/export的方式進行模塊的導入和導出,在使用層面上這兩個方式感覺上都是一樣的,所以在開發中我有時候用require/export,有時候用import/export,但是隨著時間的推移以及代碼量的增大,我開始思考代碼統一的問題,我應該選擇哪一種方式來進行模塊導入導出,然後就思索require/export和import/import兩種方式的差異以及優缺點,哪種情況用require更好哪種情況下用import更好,帶著這個知其然想知其所以然的困惑問題,就想將這個問題的答案總結一下整理成一篇文章,以便更好地理解並控制我們的代碼。

歷史簡覽

????要比較兩種事物的區別,有時離不開一個命題就是查看他們各自的發展歷史,所以這裡也一樣,兩種模塊化的導入導出方式的很多區別,可以從JavaScript的模塊化歷史進程中總結並看出端倪。

????JavaScript從誕生之初就只是定位為一個做網頁的前端腳本語言,做做簡單的動畫效果以及表單驗證,並沒有什麼模塊化的概念以及體系,使得它自身在開發大型複雜的項目時顯得捉襟見肘;後來隨著前端的應用的複雜度越來越高,js逐漸顯現出它的一些讓人痛苦的問題如全局變數污染的災難,函數命名衝突,依賴管理不好管理等等,隨著這些問題的出現,JavaScript的模塊化開始出現萌芽,我們之前最熟悉的自執行函數包裝代碼就是重要的標誌,特別是jQuery的自執行函數封裝,被後期很多人模仿並長期作為模塊化開發的一種方式,至今還在廣泛地使用當中

(function(window){nt//代碼 ntwindow.jQuery = window.$ = jQuery;//通過給window添加屬性而暴漏到全局n})(window);n

雖然jQuery的風格靈活,寫法簡單,但是並未從根本上解決模塊化本身需要解決的問題

  1. 如何優雅封裝代碼不污染並優雅地將API暴露出去
  2. 如何唯一標誌一個模塊並優雅地導入進行使用

為了解決這些問題,Commonjs社區的很多大神們開始了他們的不懈地努力和探索(這裡應該向前輩們致敬)

????這段歷史的節點出現在了2009年的一個很平常的一天,nodejs橫空出世了,人們終於可以用JavaScript來寫伺服器代碼了,為了能使node更好地進行伺服器端的開發,隨之附帶的JavaScript伺服器端開發模塊化開發規範Commonjs也在社區大神們的研究下隨之誕生了,JavaScript進入了全棧語言以及模塊化開發的時代。從那時起在伺服器端或者本地化開發我們定義模塊以及導入模塊的方式開始變成了這樣

//a.jsn export.a = function(){n console.log(module a);n }n n //b.jsn var a = require(a);n export.b = function(){n a.a();n console.log(module b);n }n

????但是,這個規範只適用於nodejs伺服器端的模塊化開發,在瀏覽器端是無法直接使用的(主要是伺服器載入是同步的,瀏覽器載入是非同步的); 那瀏覽器端怎麼辦呢?社區大神們很快意識到這也是個急需待解決的問題,經過又一段規範研究制定的艱辛歷程,AMD/Requirejs異軍突起,逐漸被廣大開發者認可,但是因為Requirejs一開始存在的一些問題,如預先執行,模塊依賴寫法冗餘等問題,後期更好的瀏覽器模塊化方案CMD/Seajs成為後起之秀,也就是我們國內大神玉伯的產品,開始流行起來,後期RequireJs也進行優化,就造就了兩雄爭霸的局面,至此到2015前,JavaScript流行的瀏覽器端模塊化開發方式類似下面這樣

//Requirejs //依賴前置(後期也支持Seajs的寫法就近依賴)n // a.jsn define(function(){n console.log(a);n return {n iAma: function(){n console.log(I am a);n }n }n })n // main.jsn require([a], function(a){n console.log(main);n a.iAma();n })n//Seajs //就近依賴,按需載入n //a.jsn define(function(require, exports, module){n console.log(a);n return {n iAma: function(){n console.log(I am a);n }n }n });nn //main.jsn define(function(require, exports, module){n console.log(main);n var a = require(a);n a.iAma();n });n

????歷史的年輪總是向前推進,官網的EMAC看到社區大神們吵得如火如荼,也開始有所行動並制定了屬於官方的模塊化方案,並在2015年6月發布了ES6的正式版本,但是這個標準還只是雛形,只是定義了模塊的如何載入/執行的內容,所以我們現在使用過程中需要將其轉換為ES5才能進行運行,不過從此我們就模塊化開發的方式又多了一種選擇import/export。

// a.jsnexport const a = 1;nexport const obj = {name: aaa};nexport function run(){....}n//main.jsnimport {run as go} from anrun()n

????目前伺服器端,瀏覽器端的最新版本都已經無限接近完全支持ES6了,但是目前來說如果想達到最好的兼容性,還是最好用babel進行轉換,轉換為ES5代碼如果在伺服器端可以直接運行,而如果要在瀏覽器端運行則需要通過打包工具如ReaquireJS 中的r.js 或者 Browserify 或者現在最流行的webpack進行轉換打包成瀏覽器能夠識別的代碼形式再進行運行

簡單了解了JavaScript模塊化的歷史發展進程,下面就可以更好地理解import/export以及require/export之間的區別了。

"野生"規範 與 正統規範

????兩種方式的第一個區別就是規範的出處不同,其實這種區別我們從上面的歷史簡覽就可以看出「野生」規範以及正統規範的意義,require/exports從2010年前後nodejs開始發布之後,在JavaScript的Commonjs社區中的開發者自發草擬的規則,後期逐漸成為大家公認的事實上的標準,一直沿用至今,不過隨著ES6的發布,正統的模塊化規範被提出,前者的使用也是有在被漸漸替代的趨勢,隨著ES6模塊化標準的完善以及各大瀏覽器廠商對ES6的支持度的不斷攀升,相信不久將來作為正統的import/export就會替代「野生」的require/export,不過當前因為有webpack,babel等模塊化管理以及ES轉換工具,所以目前來說兩種方式的寫法同樣流行並可以並行存在,在使用感知上並不存在明顯的區別。

形式不一樣

require/export只有三種簡單的寫法:

const Vue = require(vue) //導入nexports.myvue = Vue //導出nmodule.exports = Vue //導出n

import/export的寫法則多種多樣,具體有下面幾種寫法(來自官方實例)

export 的導出有兩種不同的導出方式,每種方式對應於下列的一種語法:

//命名導出:對應的使用是下列導入的命名導入nexport { name1, name2, , nameN }; //導出多個模塊nexport { variable1 as name1, variable2 as name2, , nameN };//導出多個重命名模塊,對應導入時以重命名為主nexport let name1, name2, , nameN; // also var;導出多個聲明模塊nexport let name1 = , name2 = , , nameN; // also var, const ;導出多個表達式模塊nn//默認導出:一般只有有一個默認導出nexport default expression; //導出默認表達式,一般用於導出默認變數nexport default function () { } // also class, function* //導出默認函數。也可以導出默認對象等nexport default function name1() { } // also class, function* //導出默認命名函數nexport { name1 as default, };//導出默認nnexport * from ;nexport { name1, name2, , nameN } from ;nexport { import1 as name1, import2 as name2, , nameN } from n

對應export,import 的使用也是一一對應的使用

import defaultExport from "module-name"; // 默認導入:對應默認導出進行使用nimport * as name from "module-name"; //導出module-name所有模塊並用as關鍵字對導入的模塊加入到命名空間name中nimport { export } from "module-name"; //導出特定命名的模塊nimport { export as alias } from "module-name";//導出特定命名的模塊並重命名為aliasnimport { export1 , export2 } from "module-name";//導出多個特定命名的模塊nimport { export1 , export2 as alias2 , [...] } from "module-name"; //導出多個特定命名的模塊並重命名為alias:nimport defaultExport, { export [ , [...] ] } from "module-name";//導出默認模塊並且導出多個特定命名的模塊,這種情況下默認模塊必須首先聲明nimport defaultExport, * as name from "module-name";nimport "module-name";n

命名導出一般用於導出多個值,使用時需注意導入要跟導出使用對應的變數名如:

// a.jsnfunction a1(){n console.log(a1);n}nfunction a2(){n console.log(a2)n}nvar a3 = a3nnexport {a1,a2,a3 as aa3}nexport const a4 = a4n//b.jsnimport {a1 as aa1,a2,aa3} from ann 默認導出定義了引用該模塊時默認引用的模塊名引用默認導出的模塊時可以隨意指定引用名 nn//a.jsnexport default k = k defaultnn//b.jsnimport kk from an

注意:

不能使用var,let或const作為默認導出。

????import必須放在文件的最開始,且前面不允許有其他邏輯代碼,這是由於它靜態編譯載入的原理決定的,下面會講到

載入原理不同

import是屬於靜態編譯時載入

關鍵句:得到的是代碼段,然後在執行階段進行執行

//下列語句發生了什麼nimport {mapGetters, mapActions} from vuexn

????完全支持ES6的JS編譯器編譯執行到該文件

  • 靜態分析該文件含有的所有import語句(import 具有提升效果)
  • 逐個編譯解釋import語句
  • 判斷模塊緩存區是否已經有載入該模塊代碼
  • 有則載入緩存,無則載入硬碟(伺服器端)或者網路(瀏覽器端)
  • 靜態優化所載入的外部模塊代碼(靜態載入,無法使用變數,表達式以及邏輯語句)
  • 執行所載入的模塊代碼(遇到重複只執行一次)
  • 外部模塊代碼載入完成(2-7循環完成)
  • 執行本模塊邏輯代碼

require是屬於運行時載入

關鍵句: 直接得到的是導入的對象

//下列語句發生了什麼nvar vuex = require(vuex)n

當node 遇到require()時,將按下面的順序處理

  • 先按照路徑規則查找模塊,查找規則可以參考《Node使用手冊》
  • 查找到模塊之後就會對模塊進行載入,這裡載入本質上是為模塊代碼諸如exports,require,module三個全局變數
  • 載入完之後則立即執行模塊內代碼並將模塊額exports變數的值輸出

該用require還是import?

????就這個問題而言,目前無法提供很準確的答案,因為就現在來說,幾乎所有引擎都暫時未全面支持ES6的模塊化標準,也就是說,目前import/export 暫未被全面支持,而我們在node在nodejs能夠使用這個特性都是通過babel進行轉換成ES5的,也就是說目前我們編寫的 import/export 最終都是編譯為 require/exports 來執行的,這也是為什麼在模塊導出時使用module.exports,在引用模塊時使用import 仍然起效,因為本質上,import會被轉碼為require去執行 總結:

import 與 require的區別總結

  • 規範不同:名門正派的模塊化規範(ES6:)對決野生成長的模塊化規範(ES5:大部分瀏覽器以及nodejs已經完全支持)
  • 形式不同: require/export簡單醫用,import/export功能強大,可按需載入
  • 載入原理不同:靜態編譯時載入對決運行時非同步載入

import 與 require的共同點總結

  • 最後輸出的都是對象
  • 都使前端開發走向了模塊化

展望:

????import才是正道,但是現在基本都用ES6編寫代碼,再用babel(import)轉化為ES5(require)再node或者瀏覽器端運行;而對於未來或許明年ES7一發布,模塊的載入有了標準更加細緻的標準,而且瀏覽器也實現對ES6,ES7的支持,這些工具也就會慢慢被替換,未來的前端還是變數很大的,而模塊化是個必然的趨勢,從語言層面本身支持模塊化將讓JavaScript就可以更加自信的走進大規模企業級開發。

參考文獻:

Modules/1.0:wiki.commonjs.org/wiki/

AMD規範:github.com/amdjs/amdjs-

import: developer.mozilla.org/z

export : developer.mozilla.org/z

EMACScript: ecma-international.org/

阮一峰(module):es6.ruanyifeng.com/?


推薦閱讀:

Android 模塊化探索與實踐
無線模塊化耳機,實現 1260 種聽覺組合!
如何管理好10萬行代碼的前端單頁面應用
模塊化的意義何在?
webpackJsonp is not defined?

TAG:模块化 | 前端开发 |