探討ES6的import export default 和CommonJS的require module.exports

今天來扒一扒在node和ES6中的module,主要是爲了區分node和ES6中的不一樣意義,避免概念上的混淆,同時也分享一下,本身在這個坑裏得到的心得。html

在ES6以前

模塊的概念是在ES6發佈以前就出現的,我感受主要是爲了適應大型應用開發的須要而引入了JavaScript世界。模塊化編程已經從噱頭上升爲必備,因此ES6也順應時代,把這個寫進了標準。前端

CommonJS和AMD都是JavaScript模塊化規範,在ES6以前,Node主要遵循CommonJS,而AMD則主要運用在瀏覽器端,好比requirejs。node

並且node發佈的時候,就天生具有module,因此從某種意義上講,是node促進了js世界裏面的模塊化編程。express

// module-file.js for node
module.exports = {
  a : function() {},
  b : 'xxx'
};
複製代碼

把上面這個js文件放在node環境中,咱們這樣去用它:編程

var myModule = require('./module-file');
var b = myModule.b;myModule.a();
複製代碼

從模塊化思想出發,requirejs和國內前端大牛發佈的seajs(遵循CMD)也容許前端猿們經過require去加載另外一個模塊。不過在模塊定義的時候,須要藉助一個define函數:數組

// module-file.js for requirejs
define(function(require, exports, module){
   module.exports = {};
});
複製代碼

requirejs和seajs都支持上面這種形式,在define的回調函數中提供了模擬的require, exports, module,這讓習慣了node環境中使用方法的猿類能夠順便在瀏覽器端寫基本相同的代碼。瀏覽器

node,requirejs,seajs也同時支持下面這種導出模塊的方式:bash

define(function(){
  return {}; // return的值就是導出的模塊
});
複製代碼

這就出現了UMD,即一個兼容多種環境的方案。babel

Node,requirejs中的exports

node中導出模塊接口就是用exports,你能夠這樣作:網絡

module.exports.a = function() {};
module.exports.b = 'xxx';
複製代碼

也能夠寫在一個對象中:

module.exports = {
  a : function() {},
  b : 'xxx'
}
複製代碼

在requirejs中,還提供了一個exports變量做爲module.exports的別名:

define(function(require, exports, module){
   exports.a = function(){};
   exports.b = 'xxx';
});
複製代碼

注意「別名」的含義:exports是module.exports的地址的引用。它的本質是:

var exrpots = module.exports;
複製代碼

所以,你必須注意兩個點,就是導出接口的時候: 1.不能直接用exports={}來導出整個接口; 2.若是使用了module.exports={},那麼exports.a等都會被覆蓋無效。

ES6中的import和export

在ES6以前,要使用一個模塊,必須使用require函數將一個模塊引入,但ES6並無採用這種模塊化方案,在ES6中使用import指令引入一個模塊或模塊中的部分接口,並無將require寫入標準,這也就是說require對於ES6代碼而言,只是一個普通函數。

同理,在ES6標準中,導出模塊的接口也只能使用export指令,而非exports對象,這也一樣意味着module.exports只是node,requirejs等模塊化庫的自定義變量,而非ES標準接口。

常見export方式

在ES6規定中,這樣導出一個模塊的接口:

export function fun() {};
export { name1, name2, …, nameN };
export { variable1 as name1, variable2 as name2, …, nameN };
export let name1, name2, …, nameN; // also var
export let name1 = …, name2 = …, …, nameN; // also var, const
export default expression;
export default function (…) { … } // also class, function*
export default function name1(…) { … } // also class, function*
export { name1 as default, … };
export * from …;export { name1, name2, …, nameN } from …;
export { import1 as name1, import2 as name2, …, nameN } from …;
複製代碼

ES6裏面,直接把要導出的變量、函數、對象、類等前面加一個export關鍵字。好比:

// module-file.js
export function a(){};
export var obj = {};
複製代碼

有一個點:export必須導出具備對應關係的變量,下面的接口輸出是錯誤的:

// 錯誤演示
export 1; // 這種導出的內容不是變量是絕對錯誤的,包括導出表達式,也是絕對錯誤的
var a = 1;
export a;
function b() {}
export b;
複製代碼

上面的這些方法都是錯誤的,不能這樣導出接口。導出接口僅限兩種:

1.聲明時導出 2.以對象的形式導出(和解構聯繫起來)

若是要導出某個變量,能夠用花括號括起來,像這樣:

var a = 1;
export {a}; // 等效於:{a:a}
function b() {}
export {b};
複製代碼

我有點疑惑的是,爲什麼export容許屢次export {}這種形式?看上去很奇怪。另外,ES6更厲害之處在於,能夠在export變量以後,繼續修改變量:

export var obj {};obj.a = 1;
複製代碼

在import以後,obj的值仍然能夠在模塊內繼續改變,這是CommonJS以往不可能作到的。

import基本用法

在另一個js文件裏面這樣使用這些接口:

import {a,obj} from './module-file';
a();
alert(obj.b);
複製代碼

和node以前的require不同,require只能把模塊放到一個變量中,而在ES6中,擁有對象解構賦值的能力,因此直接就把引入的模塊的接口賦值給變量了。內在機理也有不一樣,require須要去執行整個模塊,將整個模塊放到內存中(也就是咱們說的運行時),若是隻是使用到其中一個方法,性能上就差不少,而import...from則是隻加載須要的接口方法,其餘方法在程序啓動以後根本觸及不到,因此這種又被稱爲「編譯時」,性能上好不少。

as關鍵字

編程的同窗對as都容易理解,簡單的說就是取一個別名。上面export中能夠用,import中其實也能夠用:

// a.js
var a = function() {};
export {a as fun};
// b.js
import {fun as a} from './a';a();
複製代碼

上面這段代碼,export的時候,對外提供的接口是fun,它是a.js內部a這個函數的別名,可是在模塊外面,認不到a,只能認到fun。

import中的as就很簡單,就是你在使用模塊裏面的方法的時候,給這個方法取一個別名,好在當前的文件裏面使用。之因此是這樣,是由於有的時候不一樣的兩個模塊可能經過相同的接口,好比有一個c.js也經過了fun這個接口:

// c.js
export function fun() {};
複製代碼

若是在b.js中同時使用a和c這兩個模塊,就必須想辦法解決接口重名的問題,as就解決了。

default關鍵字

其餘人寫教程什麼的,都把default放到export那個部分,我以爲不利於理解。在export的時候,可能會用到default,說白了,它實際上是別名的語法糖:

// d.js
export default function() {}
// 等效於:function a() {}; export {a as default};
複製代碼

在import的時候,能夠這樣用:

import a from './d';
// 等效於,或者說就是下面這種寫法的簡寫,是同一個意思import {default as a} from './d';
複製代碼

這個語法糖的好處就是import的時候,能夠省去花括號{}。簡單的說,若是import的時候,你發現某個變量沒有花括號括起來,那麼你在腦海中應該把它還原成有花括號的as語法。

因此,下面這種寫法你也應該理解了吧:

import _,{each,map} from '_';
複製代碼

*符號

*就是表明全部,只用在import中,咱們看下兩個例子:

import * as underscore from '_';
複製代碼

在乎義上和import _ from '';是不一樣的,雖然實際上後面的使用方法是同樣的。它表示的是把''模塊中的全部接口掛載到underscore這個對象上,因此能夠用underscore.each調用某個接口。

export * from '_';
// 等效於:import * as all from '_';export all;
複製代碼

該用require仍是import?

接下來的問題,就是咱們在實操中,還有必要用require嗎?我感受ES6標準已經將以前的全部模塊化規範都給碾壓了,這在之前的標準發佈中極少見,ES6用更加簡單的方式,實現了更加有效的module,感受require能夠回家養老了。

標準與非標準

既然是標準,那麼就是全部引擎應該去實現的,node和瀏覽器將來都會直接支持這種模塊加載方式,require完成歷史使命回家本身玩兒。並且做爲node或瀏覽器,同時能夠利用import提供本身的API,好比手機端提供基於網絡的定位API,這都不用SDK了,直接內置在客戶端內部,import一下就能夠了。

不過如今import導入模塊還並非所有環境都支持,使用babel可讓node支持ES6,但在瀏覽器端,則毫無辦法,可能還得暫時依賴require。可是很是很差的消息是,require不是ES6標準,這也就是說若是未來瀏覽器支持import後,你想用它,就必須升級代碼,而不能直接被兼容。

import只能在文件開頭使用,在import以前,你不能有其餘的代碼,這和其餘語言是同樣的。可是require則不一樣,它至關於node的一個定義在全局的函數,你能夠在任意地方使用它,甚至使用變量表達式做爲它的參數,這樣有一個好處,就是能夠在循環中加載模塊。

有沒有兼容import和require的模塊?

可是很坑的是,node的模塊導出和ES6標準也不符,由於node的模塊體系遵循的是CommonJS規範,這就致使你寫的模塊文件,不可能同時支持require和import。

要強調的就是,不要把require和import兩種模塊加載方案混用,好比:

// module-file.js
module.exports = {};
// a.js
import a from './moule-file';

複製代碼

這種混搭感受不是很好(但能夠用,下面有解釋)。因此,其實我沒有任何建議,我只是以爲,躺在坑裏,挺自在的……畢竟node中require的使用更加靈活一點,它沒有必須放在哪裏的限制,因此能夠在任意位置使用,並且它的結果也很是形象,甚至能夠把require當作一個引用類型別名,能夠這樣使用:

require('./a')(); // a模塊是一個函數,當即執行a模塊函數
var data = require('./a').data; // a模塊導出的是一個對象
var a = require('./a')[0]; // a模塊導出的是一個數組
複製代碼

這樣的寫法感受像給模塊取了一個別名,使用的時候很是靈活。可是須要注意的是,若是你打算使用require來導入這個模塊,那麼請使用module.exports導出這個模塊。

(臨時)兼容方案

有沒有一種兼容方案呢?

function a() {}
class b {}
module.exports = {a,b}; // {a,b}是ES6的寫法
複製代碼

在實踐中發現,module.exports能夠兼容require和import,並且這個案例須要你的node環境配置好支持ES6語法。module.exports導出的模塊,若是使用import,那麼徹底就是一個對象賦值、解構的過程:

import mod,{a,b} from './a';
複製代碼

之因此這是成立的,是由於咱們使用babel對ES6代碼進行轉碼後執行,而實際上,目前爲止,沒有任何一個環境是支持ES6 module方案的,即便babel,也僅僅是將ES6的import,export轉碼爲require, module.exports後交給node去執行。

導出的模塊接口被賦值給mod,因此mod是一個對象,含有a,b兩個方法。這裏的mod並無經過default導出,因此和ES6有很是大的意義上的區別,這種非標準的寫法,牆裂建議永遠不要用。並且,因爲require和module.exports是非標準的東西,僅在Node環境中有效,因此當將來瀏覽器支持模塊導入時,並不會主動提供require,而是採用import,若是要使用require,仍是不得不使用requirejs等庫,藉助define來用。

因此,最終,若是你打算用CommonJS,就不要摻和進ES6.

轉載連接:www.tangshuang.net/2882.html

相關文章
相關標籤/搜索