做爲一個前端工程師,咱們在項目中常常看到下面這樣的代碼:html
import {JSEncrypt} from 'jsencrypt'; import formatDate from '@/const/filter'; export const merchants = `${basicUrl}/merchants`;
const path \= require('path'); module.exports \= {};
大部分人可能都大概瞭解以上代碼的意思是 導入(引用) or 導出一些代碼塊。可是你們有沒有想過,一樣是導入、導出功能,爲何一個項目中經常能同時看到import
和 require
呢?他們又有什麼區別的呢?
要回答這些問題,咱們首先要對JavaScript模塊化有一個大致的瞭解。阮一峯老師的JavaScript模塊化編程系列文章對JavaScript模塊化的起源和目前主流的模塊化規範都作了介紹,建議你們先去了解這部份內容。前端
隨着網頁的功能愈來愈豐富,JavaScript代碼的開發工做量確定會愈來愈多。用最原始的方法,把全部不一樣功能的js代碼放在一個文件內管理是不現實的。爲了方便咱們專一開發業務代碼,引用一些庫或者組件也是咱們在平常開發中常常遇到的事情。在這個背景下,JavaScript模塊化編程應運而生。
實現特定功能的代碼組合在一塊兒,就是一個模塊。
在ES6以前,主流的模塊化規範有CommonJS和AMD。CommonJS是服務端的規範,AMD是瀏覽器端的規範。之因此要做服務端和瀏覽器端的區分,是有緣由的。node
Node.js的模塊化系統,就是參照CommonJS規範實現的。在CommonJS中,有一個全局性方法require(),用於加載模塊。webpack
const path \= require('path'); function resolve(dir) { return path.join(\_\_dirname, dir); }
像上面這樣,加載模塊後,就能夠直接調用模塊的方法。下面簡單介紹CommonJS規範語法。git
Node應用由模塊組成,採用CommonJS模塊規範。
根據這個規範,每一個文件就是一個模塊,有本身的做用域。在一個文件裏面定義的變量、函數、類,都是私有的,對其餘文件不可見。es6
// example.js var x = 5; var addX = function (value) { return value + x; }
上面代碼中,變量x
和函數addX
,是當前文件example.js私有的,其餘文件不可見。
若是想在多個文件分享變量,必須定義爲global
對象的屬性。github
global.warning \= true;
上面代碼的warning
變量,能夠被全部文件讀取。固然,這樣的寫法是不推薦的。
CommonJS規範規定,每一個模塊內部,module
變量表明當前模塊。這個變量是一個對象,它的exports
屬性(即module.exports
)是對外的接口。加載某個模塊,其實就是加載該模塊的module.exports
屬性。web
//example.js var x = 5; var addX = function (value) { return value + x; }; module.exports.x = x; module.exports.addX = addX;
require
方法用於加載模塊。npm
var example = require('./example.js'); console.log(example.x); // 5 console.log(example.addX(1)); // 6
CommonJS模塊的特色以下。編程
> 全部代碼都運行在模塊做用域,不會污染全局做用域。
> 模塊能夠屢次加載,可是隻會在第一次加載時運行一次,而後運行結果就被緩存了,之後再加載,就直接讀取緩存結果。要想讓模塊再次運行,必須清除緩存。
> 模塊加載的順序,按照其在代碼中出現的順序。
Node內部提供一個Module構件函數。全部模塊都是Module的實例。
function Module(id, parent) { this.id = id; this.exports = {}; this.parent = parent; //... }
每一個模塊內部,都有一個module
對象,表明當前模塊。它有如下屬性。
> module.id 模塊的識別符,一般是帶有絕對路徑的模塊文件名。
> module.filename 模塊的文件名,帶有絕對路徑。
> module.loaded 返回一個布爾值,表示模塊是否已經完成加載。
> module.parent 返回一個對象,表示調用該模塊的模塊。
> module.children 返回一個數組,表示該模塊要用到的其餘模塊。
> module.exports 表示模塊對外輸出的值。
module.exports
屬性表示當前模塊對外輸出的接口,其餘文件加載該模塊,實際上就是讀取module.exports
變量。
爲了方便,Node爲每一個模塊提供了一個exports變量,指向module.exports,這等同在每一個模塊頭部,有一行這樣的命令。
var exports = module.exports;
形成的結果是,在對外輸出模塊接口時,能夠向exports對象添加方法。
exports.area = function (r) { return Math.PI * r * r; }; exports.circumference = function (r) { return 2 * Math.PI * r; };
爲了方便,Node爲每一個模塊提供了一個exports變量,指向module.exports,這等同在每一個模塊頭部,有一行這樣的命令。
var exports = module.exports;
形成的結果是,在對外輸出模塊接口時,能夠向exports對象添加方法。
exports.area = function (r) { return Math.PI * r * r; }; exports.circumference = function (r) { return 2 * Math.PI * r; };
注意,不能直接將exports變量指向一個值,由於這樣等於切斷了exports與module.exports的聯繫。
exports = function (x) { console.log(x); };
上面的寫法是無效的,由於exports
再也不指向 module.exports
了。
下面的寫法也是無效的。
exports.hello = function () { return 'hello'; }; module.exports = 'hello world';
上面代碼中,hello
函數是沒法對外輸出的,由於modules.exports
被從新賦值了。
爲了不混淆,推薦只用module.exports
。
Node使用CommonJS模塊規範,內置的require
命令用於加載模塊文件。
require命令的基本功能是,讀入並執行一個JavaScript文件,而後返回該模塊的exports對象。若是沒有發現指定模塊,會報錯。
// example.js const sayHello = function () { console.log('hello world'); } module.exports = sayHello;
// main.js const sayHello = require('./example.js'); sayhello(); //hello world
可是CommonJS規範不適用於瀏覽器端。
在上面代碼中,sayHello
方法再在第一行require('./example.js');
以後運行,所以必須等example.js加載完成。也就是說,若是加載時間很長,整個應用就會停在那裏等,形成瀏覽器假死現象。
這對服務器來講不是問題,由於全部的模塊都存放在本地硬盤,能夠同步加載完成,等待時間就是硬盤的讀取時間。但對瀏覽器來講就是一個大問題,由於模塊都放在服務器端,等待時間取決於網速的快慢,這可能會形成很是差的用戶體驗。AMD規範給瀏覽器端提供了模塊化解決方案。
AMD是"Asynchronous Module Definition"的縮寫,意思就是"異步模塊定義"。它採用異步方式加載模塊,模塊的加載不影響它後面語句的運行。全部依賴這個模塊的語句,都定義在一個回調函數中,等到加載完成以後,這個回調函數纔會運行。
AMD也採用require()語句加載模塊,可是不一樣於CommonJS,它要求兩個參數:
require([module], callback);
module是須要加載的模塊,callback是模塊加載完成執行的回調函數。因爲模塊的加載和回調函數的執行是不一樣步的,因此瀏覽器等待的時間不會太長。
module是ES6在推出的模塊規範。前面提到的CommonJS服務於服務器,AMD服務於瀏覽器,而ES6的實現的模塊功能是在語言層面上實現的,是瀏覽器和服務器通用的模塊解決方案。
摘抄自阮一峯老師的ECMAScript 6 入門 Module的語法一節(有刪改):
模塊功能主要由兩個命令構成:export
和import
。export
命令用於規定模塊的對外接口,import
命令用於輸入其餘模塊提供的功能。
一個模塊就是一個獨立文件。該文件內部的全部變量、方法(即函數),外部沒法獲取。若是你但願外部可以讀取模塊內部的某個變量,就必須使用export
關鍵字輸出該 變量 。注意,要導出的是 變量 or 方法。
須要特別注意的是,export
命令規定的是對外的接口,必須與模塊內部的變量創建一一對應關係。
我一開始不理解,查閱了一些資料,節選自MDN:
There are two types of exports:
- Named Exports (Zero or more exports per module)
- Default Exports (One per module)
Identifier to be exported (so that it can be imported via
import
in another script).
拙劣的翻譯:要導出的是有標識符的變量(以便其餘模塊經過import
語句進行導入)。
如今再分別看一下報錯和正確的語法。
// export 一個常量1,報錯 export 1; // 表面上看起來是定義了一個名爲m變量1,實際上導出的是m的值1 // 想象一下,在其餘文件import的時候,找不到對應的入口,報錯 var m = 1; export m; // 直接導出一個變量 m,正確 export var m = 1; // 先定義一個變量m,再將變量m導出,正確 var m = 1; export { m }; // 先定義一個變量n,再把n看成m導出 // 這樣其餘文件import {m} 就能找到這裏對應的n var n = 1; export {n as m};
爲何要強調導出變量 or 方法呢?比如咱們出去買便當,加入我點了一個手撕雞+白飯+青菜,若是店家不用飯盒把食物裝起來給我,那我付錢以後,難道要徒手拿店家準備好的食物嗎?MDN提到的Named Exports和飯盒同樣,都是起到載體、裝載的做用,導出的是變量 or 方法,其餘模塊在導入的時候纔會有跡可尋。
使用export
定義了變量和方法之後,其餘模塊就能夠經過import命令加載這個模塊的內容。
// export.js var m = 1; export { m };
// import.js import { m } from './export.js'; console.log(m); // 1
import
命令輸入的變量是隻讀的。但若是import的變量是一個對象,這個對象的屬性是能夠修改的(可是不建議這樣作,很差查錯)。
能夠結合as
關鍵字給import的變量重命名:
// import.js import { n as m } from './export.js'; console.log(n); // 1
能夠用*
指定一個對象,總體加載一個模塊。全部輸出值都加載在這個對象上面。
// export.js var m = 1; var n = 2 export { m, n };
import * as test from './export.js'; console.log(test.m); // 1 console.log(test.n); // 2
export default
命令爲特定模塊指定默認輸出。這樣,其餘模塊不用知道名稱的狀況下就能夠加載該模塊。
// export-default.js function foo () { console.log('export default'); } export default foo;
// import.js // foo能夠取任意命名 import foo from './export-default.js'; foo();// export default
在一個模塊內,exoprt default
命令只能使用一次。
瞭解過上面的知識,咱們應該不難知道,module.exports
& require
、export
& import
都是導出 or 導入 模塊內容的寫法,都屬於JavaScript模塊規範的一種。module.exports
& require
是CommonJS和AMD模塊規範,是ES6的module模塊規範。
Node.js有它本身的 CommonJS 模塊格式,與 ES6 模塊格式是不兼容的。目前的解決方案是,將二者分開,ES6 模塊和 CommonJS 採用各自的加載方案。
若是咱們更細心一點,應該就會發現,在前端工程化已是主流趨勢的今天,在前端項目中,用到module.exports
& require
的地方大都是webpack相關的配置文件,由於webpack是npm生態中的一個模塊。webpack的運行依賴於node環境。而在平常開發中運用ES6新特性,能夠說是每個前端工程師的必備技能,在因此業務代碼中export
& import
隨處可見。
參考連接
阮一峯ECMAScript 6 入門
JavaScript 標準參考教程(alpha)
JavaScript模塊化編程
MDN