JavaScript模塊化簡述

做爲一個前端工程師,咱們在項目中常常看到下面這樣的代碼:html

import {JSEncrypt} from 'jsencrypt';  
import formatDate from '@/const/filter';  
export const merchants = `${basicUrl}/merchants`;
const path \= require('path');  
module.exports \= {};

大部分人可能都大概瞭解以上代碼的意思是 導入(引用) or 導出一些代碼塊。可是你們有沒有想過,一樣是導入、導出功能,爲何一個項目中經常能同時看到importrequire呢?他們又有什麼區別的呢?
要回答這些問題,咱們首先要對JavaScript模塊化有一個大致的瞭解。阮一峯老師的JavaScript模塊化編程系列文章對JavaScript模塊化的起源和目前主流的模塊化規範都作了介紹,建議你們先去了解這部份內容。前端

模塊化規範

隨着網頁的功能愈來愈豐富,JavaScript代碼的開發工做量確定會愈來愈多。用最原始的方法,把全部不一樣功能的js代碼放在一個文件內管理是不現實的。爲了方便咱們專一開發業務代碼,引用一些庫或者組件也是咱們在平常開發中常常遇到的事情。在這個背景下,JavaScript模塊化編程應運而生。
實現特定功能的代碼組合在一塊兒,就是一個模塊。
在ES6以前,主流的模塊化規範有CommonJSAMD。CommonJS是服務端的規範,AMD是瀏覽器端的規範。之因此要做服務端和瀏覽器端的區分,是有緣由的。node

CommonJS規範

Node.js的模塊化系統,就是參照CommonJS規範實現的。在CommonJS中,有一個全局性方法require(),用於加載模塊。webpack

const path \= require('path');
function resolve(dir) {
 return path.join(\_\_dirname, dir);
}

像上面這樣,加載模塊後,就能夠直接調用模塊的方法。下面簡單介紹CommonJS規範語法。git

CommonJS規範語法

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模塊的特色以下。編程

> 全部代碼都運行在模塊做用域,不會污染全局做用域。
> 模塊能夠屢次加載,可是隻會在第一次加載時運行一次,而後運行結果就被緩存了,之後再加載,就直接讀取緩存結果。要想讓模塊再次運行,必須清除緩存。
> 模塊加載的順序,按照其在代碼中出現的順序。

module對象

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屬性表示當前模塊對外輸出的接口,其餘文件加載該模塊,實際上就是讀取module.exports變量。

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;
};

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;
};

注意,不能直接將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

require命令

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規範

AMD是"Asynchronous Module Definition"的縮寫,意思就是"異步模塊定義"。它採用異步方式加載模塊,模塊的加載不影響它後面語句的運行。全部依賴這個模塊的語句,都定義在一個回調函數中,等到加載完成以後,這個回調函數纔會運行。

AMD也採用require()語句加載模塊,可是不一樣於CommonJS,它要求兩個參數:

require([module], callback);

module是須要加載的模塊,callback是模塊加載完成執行的回調函數。因爲模塊的加載和回調函數的執行是不一樣步的,因此瀏覽器等待的時間不會太長。

module

module是ES6在推出的模塊規範。前面提到的CommonJS服務於服務器,AMD服務於瀏覽器,而ES6的實現的模塊功能是在語言層面上實現的,是瀏覽器和服務器通用的模塊解決方案。

Export 命令

摘抄自阮一峯老師的ECMAScript 6 入門 Module的語法一節(有刪改)

模塊功能主要由兩個命令構成: exportimportexport命令用於規定模塊的對外接口, import命令用於輸入其餘模塊提供的功能。
一個模塊就是一個獨立文件。該文件內部的全部變量、方法(即函數),外部沒法獲取。若是你但願外部可以讀取模塊內部的某個變量,就必須使用 export關鍵字輸出該 變量 。注意,要導出的是 變量 or 方法
須要特別注意的是, export命令規定的是對外的接口,必須與模塊內部的變量創建一一對應關係。

我一開始不理解,查閱了一些資料,節選自MDN:

There are two types of exports:

  1. Named Exports (Zero or more exports per module)
  2. 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 方法,其餘模塊在導入的時候纔會有跡可尋。

import命令

使用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命令爲特定模塊指定默認輸出。這樣,其餘模塊不用知道名稱的狀況下就能夠加載該模塊。

// 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 & requireexport & import 都是導出 or 導入 模塊內容的寫法,都屬於JavaScript模塊規範的一種。module.exports & requireCommonJSAMD模塊規範,是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

相關文章
相關標籤/搜索