深入理解module.exports、exports、require、export、export default、import


深入理解module.exports、exports、require、export、export default、import

 

前言:說到module.exports、exports、require、export、export default、import這些,有一點我們是必須要提一下的,就是模塊化編程方式。以上這些都是模塊之間的導入和導出。

什么是模塊化

當你的網站越來越復雜時,我們往往會遇到一下情況,導致我們生產效率低,可維護性差:

  • 惱人的命名沖突
  • 繁瑣的文件依賴   

歷史上,JavaScript一直沒有模塊(module)體系, 無法將一個大程序拆分成互相依賴的小文件,再用簡單的方法拼裝起來。 其他語言都有這項功能,比如Ruby的 require、Python的 import , 甚至就連CSS都有 @import ,但是JavaScript任何這方面的支持都沒有,這對開發大型的、復雜的項目形成了巨大障礙。 

由此,我們把模塊化的概念理解為將一個大程序拆分成互相依賴的小文件,再用簡單的方法拼接起來。那程序中的模塊到底該具有哪些特性就滿足我們的使用了呢?

  • 模塊作用域
    • 模塊之間不需要考慮全局命名空間沖突的問題。
  • 模塊之間的通訊規則
    • 首先,各個模塊之間是相互依賴,相互關聯的。例如 CPU 需要讀取內存中的數據來進行計算,然后把計算結果又交給了我們的操作系統。
    • 既然相互關聯,那么模塊之間肯定是可以通訊的。
    • 模塊之間的通訊,也就意味着存在輸入和輸出。

模塊通訊規則

ES6之前已經出現了js模塊加載的方案,最主要的是CommonJS和AMD規范。commonjs主要應用於服務器,實現同步加載,如nodejs。AMD規范應用於瀏覽器,如requirejs,為異步加載。同時還有CMD規范,為同步加載方案如seaJS。

1、CommonJS規范

CommonJS規范規定了每個文件就是一個模塊,有自己的作用域。在一個文件里面定義的變量、函數、類,都是私有的,對其他文件不可見。CommonJS 規范的主要適用場景是服務器端編程,所以采用同步加載模塊的策略。如果我們依賴3個模塊,代碼會一個一個依次加載它們。

require 模塊導入

// 核心模塊 var fs = require('fs') // 第三方模塊 // npm install jquery var marked = require('jquery') // 用戶模塊(自己寫的),正確的,正確的方式 // 注意:加載自己寫的模塊,相對路徑不能省略 ./ var foo = require('./foo.js') // 用戶模塊(自己寫的),正確的(推薦),可以省略后綴名 .js var foo = require('./foo')

node模塊分類

  1. 核心模塊
    1. 由 Node 本身提供,具名的,例如 fs 文件操作模塊、http 網絡操作模塊
  2. 第三發模塊
    1. 由第三方提供,使用的時候我們需要通過 npm 進行下載然后才可以加載使用,例如我們使用過的 mimeejsmarked
    2. 注意:不可能有第三方包的名字和核心模塊的名字是一樣的,否則會造成沖突
  3. 用戶自己寫的模塊 
    1. 我們在文件中寫的代碼很多的情況下不好編寫和維護,所以我們可以考慮把文件中的代碼拆分到多個文件中,那這些我們自己創建的文件就是用戶模塊  

核心模塊

  • 核心模塊就是 node 內置的模塊,需要通過唯一的標識名稱來進行獲取。
  • 每一個核心模塊基本上都是暴露了一個對象,里面包含一些方法供我們使用
  • 一般在加載核心模塊的時候,變量的起名最好就和核心模塊的標識名同名即可
    • 例如:const fs = require('fs')
  • 核心模塊本質上也是文件模塊
    • 核心模塊已經被編譯到了 node 的可執行程序,一般看不到
    • 可以通過查看 node 的源碼看到核心模塊文件
    • 核心模塊也是基於 CommonJS 模塊規范

Node 中都以具名的方式提供了不同功能的模塊,使用的時候都必須根據特定的核心模塊名稱來加載使用。

參考文檔:https://nodejs.org/dist/latest-v9.x/docs/api/

模塊名稱 作用
fs 文件操作
http 網絡操作
path 路徑操作
url url 地址操作
os 操作系統信息
net 一種更底層的網絡操作方式
querystring 解析查詢字符串
util 工具函數模塊
... ...

 文件模塊

以 ./ 或 ../ 開頭的模塊標識就是文件模塊,一般就是用戶編寫的。

第三方模塊

一般就是通過 npm install 安裝的模塊就是第三方模塊。

加載規則如下:

  • 如果不是文件模塊,也不是核心模塊
  • node 會去 node_modules 目錄中找(找跟你引用的名稱一樣的目錄),例如這里 require('underscore')
  • 如果在 node_modules 目錄中找到 underscore 目錄,則找該目錄下的 package.json 文件
  • 如果找到 package.json 文件,則找該文件中的 main 屬性,拿到 main 指定的入口模塊
  • 如果過程都找不到,node 則取上一級目錄下找 node_modules 目錄,規則同上。。。
  • 如果一直找到代碼文件的根路徑還找不到,則報錯。。。

注意:對於第三方模塊,我們都是 npm install 命令進行下載的,就放到項目根目錄下的 node_modules 目錄。

深入模塊化加載機制

  1. 從module path數組中取出第一個目錄作為查找基准。
  2. 直接從目錄中查找該文件,如果存在,則結束查找。如果不存在,則進行下一條查找。
  3. 嘗試添加.js、.json、.node后綴后查找,如果存在文件,則結束查找。如果不存在,則進行下一條。
  4. 嘗試將require的參數作為一個包來進行查找,讀取目錄下的package.json文件,取得main參數指定的文件。
  5. 嘗試查找該文件,如果存在,則結束查找。如果不存在,則進行第3條查找。
  6. 如果繼續失敗,則取出module path數組中的下一個目錄作為基准查找,循環第1至5個步驟。
  7. 如果繼續失敗,循環第1至6個步驟,直到module path中的最后一個值。
  8. 如果仍然失敗,則拋出異常。

整個查找過程十分類似原型鏈的查找和作用域的查找。所幸Node.js對路徑查找實現了緩存機制,否則由於每次判斷路徑都是同步阻塞式進行,會導致嚴重的性能消耗。

 

exports 模塊導出 

在node中,每個模塊都有一個module 對象,在該module 對象中,有一個成員叫作exports,默認最后會return module.exports。也就是說當需要向外導出成員時,只需要將成員掛載到module.exports上。當require該模塊時,就會默認導入該模塊暴露出的module.exports對象,注意不是exports對象.

導出多個成員1

module.exports.a = 123; module.exports.b = 'abc'; module.exports.c = {}; 

導出多個成員2

module.exports = { a: 123, b: 'abc', c: {} } 

 導出多個成員3,使用exports掛載

// module.exports 提供了一個別名 exports,exports是module.exports的一個引用,它們共同指向一個地址。 console.log(exports === module.exports) true exports.a = 123; exports.b = 'abc'; exports.c = {};

導出單個成員,必須使用module.exports

module.exports = function(a, b){ return a + b } //錯誤寫法(因為exports 為module.exports的一個引用, //當直接給exports 賦值后,會斷開與module.exports的引用關系。而最終模塊導出的module.exports) exports = function(a, b) { return a + b } 

深入理解module.exports 與exports 的區別

混合導出

exports.foo = 123; //導出 {foo:123} module.exports.a = 'a'; //導出 {foo:123,a:'a'} 
exports.a = 123; //導出 {a: 123} exports = {}; //斷開與module.exports的引用關系 exports.b = 'b'; //因為引用關系已經斷開,干擾 module.exports.c = 233; //導出 {a: 123, c: 233}
//直接給exports賦值,會斷開與module.exports的引用關系。同理,直接給module.exports賦值,也會斷開與exports的引用關系。 module.exports = 'helllo'; //導出 {'hello'} exports.a = 'a'; //干擾
exports.foo = 'hello'; //{foo: 'hello'} module.exports.a = 'a'; //{foo: 'hello',a: 'a'} exports = { //斷開引用關系 a: 'b' }; module.exports.foo = 'world'; // {foo: 'world', a: 'a'} exports.c = 'c'; // 干擾 exports = module.exports; //重新建立引用關系 exports.a = 123; // {foo: 'world', a: 123} module.exports = function(){} // {function(){}}

一般導出單個模塊用 module.exports = 123 ,導出多個模塊使用 exports.a = 1;exports.b = 2;.....

要是實在分不清楚,建議直接使用module.exports對象導出成員。絕對不會出錯,哈哈! 

 

2、AMD規范(https://github.com/amdjs/amdjs-api/wiki/AMD)

AMD 是 Asynchronous Module Definition 的簡稱,即“異步模塊定義”,是從 CommonJS 討論中誕生的。AMD 優先照顧瀏覽器的模塊加載場景,使用了異步加載和回調的方式。

服務器端加載方式為同步加載,因為所有的模塊都存在了本地硬盤,同步加載需要等待的時間就是硬盤的讀取時間。這對於服務端來說不是什么問題。但是對於瀏覽器,所有的模塊都存在於服務端,等待的時間多數取決於網速的快慢。網速慢的時候,瀏覽器就會處於“假死”狀態。因此,瀏覽器加載模塊應采用異步加載的方式,這也是AMD規范誕生的背景。

RequireJS

模塊定義

通過define方法定義模塊,但是按照2種情況進行書寫。

  1. 該模塊獨立存在,不依賴其他模塊(可以返回任何值):      
    define(function() { return { // 返回的接口 } })

     

  2. 該模塊依賴其他模塊:
    define(['module1','module2'], function(module1, module2) { return { // 返回的接口 } })

     

require模塊加載 

//方法1 var module2 =require('module1'); //方法2 require(['module1'], function (module1) { module1.module1Fun(1, 3); }); require方法可以進行配置: require.config({ paths: { //為模塊指定位置,可以為服務器上的地址,也可以為外部網址等等,也可以指定多個地址,防止模塊加載出錯。 jquery: 'module/libs/jquery-10.3', } }); require(['jquery'],function($){});

 模塊導出

在Requirejs中,模塊導出共有三種方式: 

  1. 通過return方式導出,優先級最高; 
  2. 通過module.exports對象賦值導出,優先級次之; 
  3. 通過exports對象賦值導出,優先級最低;

上面的三種優先級是絕對的優先級,無關代碼的順序,例如即使將exports導出放在最后,也會被module.exports覆蓋,另外導出的內容只能是優先級最高的那個,而且僅僅包含其內容,絕不會它們內容的組合或並集。

//通過 return 方式導出,優先級最高,官方推薦 define(['module1','module2'], function(module1, module2) { return { // 返回的接口 } })
//將導出的成員掛載到 module.exports 對象上,寫法繼承CommonJS,優先級低於return define(function(require, exports, module) { // 導出模塊內容 module.exports = { username : 'HJJ' } }); 
//將導出成員掛載到exports上(不可以直接給exports直接賦值),優先級最低,寫法繼承CommonJS define(function(require, exports, module) { exports. username = 'HJJ' }); //'exports' 僅僅是 'module.exports' 的一個引用。在 'factory' 內部給 'exports' 重新賦值時,並不會改變 'module.exports' 的值。因此給 'exports' 賦值是無效的,不能用來更改模塊接口。 //還有就是如果直接將導出成員掛載到exports上,會導致實參形參傻傻分不清楚

 

3、CMD規范(https://github.com/seajs/seajs/issues/242)

CMD(Common Module Definition)通用模塊定義。CMD是在AMD基礎上改進的一種規范,均適用於瀏覽器環境,和AMD不同在於對依賴模塊的執行時機處理不同,CMD是就近依賴,而AMD是前置依賴。不過 RequireJS 從 2.0 開始,也改成可以延遲執行(根據寫法不同,處理方式不同)。CMD 推崇 as lazy as possible.

// CMD define(function(require, exports, module) { var module1 = require('./module1') module1.doSomething() // 此處略去 100 行 var module2 = require('./module2') // 依賴可以就近書寫 module2.doSomething() // ... }) // AMD 默認推薦的是 define(['./module1', './module2'], function(module1, module2) { // 依賴必須一開始就寫好 module1.doSomething() // 此處略去 100 行 module2.doSomething() ... }) 

 CMD模塊定義

define({ "foo": "bar" });
define('I am a template. My name is {{name}}.');
define(function(require, exports, module) { // 模塊代碼 }); define('hello', ['jquery'], function(require, exports, module) { // 模塊代碼 });

 

CMD模塊的導入導出同AMD,請移步AMD規范。

4、ES6

import模塊導入 

1、import命令接受一對大括號,里面指定要從其他模塊導入的變量名。大括號里面的變量名,必須與被導入模塊(profile.js)對外接口的名稱相同。如果想為輸入的變量重新取一個名字,import命令要使用as關鍵字,將輸入的變量重命名。

import { firstName, lastName as surname, year } from './profile.js';

2、import命令輸入的變量都是只讀的,因為它的本質是輸入接口。也就是說,不允許在加載模塊的腳本里面,改寫接口。但是,如果a是一個對象,改寫a的屬性是允許的。

import {a} from './xxx.js' a = {}; // Syntax Error : 'a' is read-only; import {a} from './xxx.js' a.foo = 'hello'; // 合法操作,建議都當成只讀屬性,方便排錯

3、import后面的from指定模塊文件的位置,可以是相對路徑,也可以是絕對路徑,.js路徑可以省略。如果只是模塊名,不帶有路徑,那么必須有配置文件,告訴 JavaScript 引擎該模塊的位置。

注意,import命令具有提升效果,會提升到整個模塊的頭部,首先執行。 

 

import {myMethod} from 'util'; foo(); import { foo } from 'my_module';

4、由於import是靜態執行,所以不能使用表達式和變量,這些只有在運行時才能得到結果的語法結構。

// 報錯 import { 'f' + 'oo' } from 'my_module'; // 報錯 let module = 'my_module'; import { foo } from module; // 報錯 if (x === 1) { import { foo } from 'module1'; } else { import { foo } from 'module2'; }

 5、import語句會執行所加載的模塊,因此可以有下面的寫法。

import 'lodash'; //上面代碼僅僅執行lodash模塊,但是不輸入任何值。

6、import語句是 Singleton 模式。如果多次重復執行同一句import語句,那么只會執行一次,而不會執行多次。

import 'lodash'; import 'lodash'; //等同於 import 'lodash'; import { foo } from 'my_module'; import { bar } from 'my_module'; // 等同於 import { foo, bar } from 'my_module';

7、同一個模塊里面 ,通過 Babel 轉碼,CommonJS 模塊的require命令和 ES6 模塊的import命令可以同時使用。但是不建議。原因如下:

ES6 模塊的設計思想是盡量的靜態化,使得編譯時就能確定模塊的依賴關系,以及輸入和輸出的變量。CommonJS 和 AMD 模塊,都只能在運行時確定這些東西。比如,CommonJS 模塊就是對象,輸入時必須查找對象屬性。

// CommonJS模塊 let { stat, exists, readFile } = require('fs'); // 等同於 let _fs = require('fs'); let stat = _fs.stat; let exists = _fs.exists; let readfile = _fs.readfile;

上面代碼的實質是整體加載fs模塊(即加載fs的所有方法),生成一個對象(_fs),然后再從這個對象上面讀取 3 個方法。這種加載稱為“運行時加載”,因為只有運行時才能得到這個對象,導致完全沒辦法在編譯時做“靜態優化”。

ES6 模塊不是對象,而是通過export命令顯式指定輸出的代碼,再通過import命令輸入。

// ES6模塊 import { stat, exists, readFile } from 'fs';

上面代碼的實質是從fs模塊加載 3 個方法,其他方法不加載。這種加載稱為“編譯時加載”或者靜態加載,即 ES6 可以在編譯時就完成模塊加載,效率要比 CommonJS 模塊的加載方式高。當然,這也導致了沒法引用 ES6 模塊本身,因為它不是對象。

由於 ES6 模塊是編譯時加載,使得靜態分析成為可能。有了它,就能進一步拓寬 JavaScript 的語法,比如引入宏(macro)和類型檢驗(type system)這些只能靠靜態分析實現的功能。

//import在靜態解析階段執行,所以它是一個模塊之中最早執行的。下面的代碼可能不會得到預期結果。 require('core-js/modules/es6.symbol'); require('core-js/modules/es6.promise'); import React from 'React';

export導出模塊

寫法1、

export var firstName = 'Michael'; export var lastName = 'Jackson'; export var year = 1958; export function multiply(x, y) { return x * y; };

寫法2(可以使用as關鍵字重命名)、

var firstName = 'Michael'; var lastName = 'Jackson'; var year = 1958; function v1() { ... } export {firstName, lastName, year, v1 as streamV1}; //使用大括號指定所要輸出的一組變量,推薦使用這種方式,簡介明了

 需要特別注意的是,export命令規定的是對外的接口,必須與模塊內部的變量建立一一對應關系。

// 報錯 export 1; // 報錯 var m = 1; export m; // 報錯 function f() {} export f; ----------------- export var m = 1; var m = 1; export {m}; var n = 1; export {n as m}; export function f() {}; //正確 function f() {} export {f};

 最后,export命令可以出現在模塊的任何位置,只要處於模塊頂層就可以。

function foo() { export default 'bar' // SyntaxError } foo()

默認導出(export default) 

每個模塊支持我們導出一個沒有名字的變量,使用關鍵語句export default來實現.

// export-default.js export default function () { console.log('foo'); } // import-default.js import customName from './export-default'; customName(); // 'foo' 

下面比較一下默認輸出和正常輸出。

export default命令用於指定模塊的默認輸出。顯然,一個模塊只能有一個默認輸出,因此export default命令只能使用一次。所以,import命令后面才不用加大括號,因為只可能唯一對應export default命令。

// 第一組 export default function crc32() { // 輸出 // ... } import crc32 from 'crc32'; // 輸入 // 第二組 export function crc32() { // 輸出 // ... }; import {crc32} from 'crc32'; // 輸入

export default命令其實只是輸出一個叫做default的變量,所以它后面不能跟變量聲明語句。

// 正確 export var a = 1; // 正確 var a = 1; export default a; // 錯誤 export default var a = 1;

因為export default命令的本質是將后面的值,賦給default變量,所以可以直接將一個值寫在export default之后

// 正確 export default 42; // 報錯 export 42;

如果想在一條import語句中,同時輸入默認方法和其他變量,可以寫成下面這樣。

import _, { each } from 'lodash'; //對應上面代碼的export語句如下 export default function (){ //... } export function each (obj, iterator, context){ //... }

export 與 import 的復合寫法 

如果在一個模塊之中,先輸入后輸出同一個模塊,import語句可以與export語句寫在一起。

export { foo, bar } from 'my_module'; // 可以簡單理解為 import { foo, bar } from 'my_module'; export { foo, bar }; export { es6 as default } from './someModule'; // 等同於 import { es6 } from './someModule'; export default es6;

深入理解module.exports、exports、require、export、export default、import

時間:2019-02-27
本文章向大家介紹深入理解module.exports、exports、require、export、export default、import,主要包括深入理解module.exports、exports、require、export、export default、import使用實例、應用技巧、基本知識點總結和需要注意事項,具有一定的參考價值,需要的朋友可以參考一下。

前言:說到module.exports、exports、require、export、export default、import這些,有一點我們是必須要提一下的,就是模塊化編程方式。以上這些都是模塊之間的導入和導出。

什么是模塊化

當你的網站越來越復雜時,我們往往會遇到一下情況,導致我們生產效率低,可維護性差:

  • 惱人的命名沖突
  • 繁瑣的文件依賴   

歷史上,JavaScript一直沒有模塊(module)體系, 無法將一個大程序拆分成互相依賴的小文件,再用簡單的方法拼裝起來。 其他語言都有這項功能,比如Ruby的 require、Python的 import , 甚至就連CSS都有 @import ,但是JavaScript任何這方面的支持都沒有,這對開發大型的、復雜的項目形成了巨大障礙。 

由此,我們把模塊化的概念理解為將一個大程序拆分成互相依賴的小文件,再用簡單的方法拼接起來。那程序中的模塊到底該具有哪些特性就滿足我們的使用了呢?

  • 模塊作用域
    • 模塊之間不需要考慮全局命名空間沖突的問題。
  • 模塊之間的通訊規則
    • 首先,各個模塊之間是相互依賴,相互關聯的。例如 CPU 需要讀取內存中的數據來進行計算,然后把計算結果又交給了我們的操作系統。
    • 既然相互關聯,那么模塊之間肯定是可以通訊的。
    • 模塊之間的通訊,也就意味着存在輸入和輸出。

模塊通訊規則

ES6之前已經出現了js模塊加載的方案,最主要的是CommonJS和AMD規范。commonjs主要應用於服務器,實現同步加載,如nodejs。AMD規范應用於瀏覽器,如requirejs,為異步加載。同時還有CMD規范,為同步加載方案如seaJS。

1、CommonJS規范

CommonJS規范規定了每個文件就是一個模塊,有自己的作用域。在一個文件里面定義的變量、函數、類,都是私有的,對其他文件不可見。CommonJS 規范的主要適用場景是服務器端編程,所以采用同步加載模塊的策略。如果我們依賴3個模塊,代碼會一個一個依次加載它們。

require 模塊導入

// 核心模塊 var fs = require('fs') // 第三方模塊 // npm install jquery var marked = require('jquery') // 用戶模塊(自己寫的),正確的,正確的方式 // 注意:加載自己寫的模塊,相對路徑不能省略 ./ var foo = require('./foo.js') // 用戶模塊(自己寫的),正確的(推薦),可以省略后綴名 .js var foo = require('./foo')

node模塊分類

  1. 核心模塊
    1. 由 Node 本身提供,具名的,例如 fs 文件操作模塊、http 網絡操作模塊
  2. 第三發模塊
    1. 由第三方提供,使用的時候我們需要通過 npm 進行下載然后才可以加載使用,例如我們使用過的 mimeejsmarked
    2. 注意:不可能有第三方包的名字和核心模塊的名字是一樣的,否則會造成沖突
  3. 用戶自己寫的模塊 
    1. 我們在文件中寫的代碼很多的情況下不好編寫和維護,所以我們可以考慮把文件中的代碼拆分到多個文件中,那這些我們自己創建的文件就是用戶模塊  

核心模塊

  • 核心模塊就是 node 內置的模塊,需要通過唯一的標識名稱來進行獲取。
  • 每一個核心模塊基本上都是暴露了一個對象,里面包含一些方法供我們使用
  • 一般在加載核心模塊的時候,變量的起名最好就和核心模塊的標識名同名即可
    • 例如:const fs = require('fs')
  • 核心模塊本質上也是文件模塊
    • 核心模塊已經被編譯到了 node 的可執行程序,一般看不到
    • 可以通過查看 node 的源碼看到核心模塊文件
    • 核心模塊也是基於 CommonJS 模塊規范

Node 中都以具名的方式提供了不同功能的模塊,使用的時候都必須根據特定的核心模塊名稱來加載使用。

參考文檔:https://nodejs.org/dist/latest-v9.x/docs/api/

模塊名稱 作用
fs 文件操作
http 網絡操作
path 路徑操作
url url 地址操作
os 操作系統信息
net 一種更底層的網絡操作方式
querystring 解析查詢字符串
util 工具函數模塊
... ...

 文件模塊

以 ./ 或 ../ 開頭的模塊標識就是文件模塊,一般就是用戶編寫的。

第三方模塊

一般就是通過 npm install 安裝的模塊就是第三方模塊。

加載規則如下:

  • 如果不是文件模塊,也不是核心模塊
  • node 會去 node_modules 目錄中找(找跟你引用的名稱一樣的目錄),例如這里 require('underscore')
  • 如果在 node_modules 目錄中找到 underscore 目錄,則找該目錄下的 package.json 文件
  • 如果找到 package.json 文件,則找該文件中的 main 屬性,拿到 main 指定的入口模塊
  • 如果過程都找不到,node 則取上一級目錄下找 node_modules 目錄,規則同上。。。
  • 如果一直找到代碼文件的根路徑還找不到,則報錯。。。

注意:對於第三方模塊,我們都是 npm install 命令進行下載的,就放到項目根目錄下的 node_modules 目錄。

深入模塊化加載機制

  1. 從module path數組中取出第一個目錄作為查找基准。
  2. 直接從目錄中查找該文件,如果存在,則結束查找。如果不存在,則進行下一條查找。
  3. 嘗試添加.js、.json、.node后綴后查找,如果存在文件,則結束查找。如果不存在,則進行下一條。
  4. 嘗試將require的參數作為一個包來進行查找,讀取目錄下的package.json文件,取得main參數指定的文件。
  5. 嘗試查找該文件,如果存在,則結束查找。如果不存在,則進行第3條查找。
  6. 如果繼續失敗,則取出module path數組中的下一個目錄作為基准查找,循環第1至5個步驟。
  7. 如果繼續失敗,循環第1至6個步驟,直到module path中的最后一個值。
  8. 如果仍然失敗,則拋出異常。

整個查找過程十分類似原型鏈的查找和作用域的查找。所幸Node.js對路徑查找實現了緩存機制,否則由於每次判斷路徑都是同步阻塞式進行,會導致嚴重的性能消耗。

 

exports 模塊導出 

在node中,每個模塊都有一個module 對象,在該module 對象中,有一個成員叫作exports,默認最后會return module.exports。也就是說當需要向外導出成員時,只需要將成員掛載到module.exports上。當require該模塊時,就會默認導入該模塊暴露出的module.exports對象,注意不是exports對象.

導出多個成員1

module.exports.a = 123; module.exports.b = 'abc'; module.exports.c = {}; 

導出多個成員2

module.exports = { a: 123, b: 'abc', c: {} } 

 導出多個成員3,使用exports掛載

// module.exports 提供了一個別名 exports,exports是module.exports的一個引用,它們共同指向一個地址。 console.log(exports === module.exports) true exports.a = 123; exports.b = 'abc'; exports.c = {};

導出單個成員,必須使用module.exports

module.exports = function(a, b){ return a + b } //錯誤寫法(因為exports 為module.exports的一個引用, //當直接給exports 賦值后,會斷開與module.exports的引用關系。而最終模塊導出的module.exports) exports = function(a, b) { return a + b } 

深入理解module.exports 與exports 的區別

混合導出

exports.foo = 123; //導出 {foo:123} module.exports.a = 'a'; //導出 {foo:123,a:'a'} 
exports.a = 123; //導出 {a: 123} exports = {}; //斷開與module.exports的引用關系 exports.b = 'b'; //因為引用關系已經斷開,干擾 module.exports.c = 233; //導出 {a: 123, c: 233}
//直接給exports賦值,會斷開與module.exports的引用關系。同理,直接給module.exports賦值,也會斷開與exports的引用關系。 module.exports = 'helllo'; //導出 {'hello'} exports.a = 'a'; //干擾
exports.foo = 'hello'; //{foo: 'hello'} module.exports.a = 'a'; //{foo: 'hello',a: 'a'} exports = { //斷開引用關系 a: 'b' }; module.exports.foo = 'world'; // {foo: 'world', a: 'a'} exports.c = 'c'; // 干擾 exports = module.exports; //重新建立引用關系 exports.a = 123; // {foo: 'world', a: 123} module.exports = function(){} // {function(){}}

一般導出單個模塊用 module.exports = 123 ,導出多個模塊使用 exports.a = 1;exports.b = 2;.....

要是實在分不清楚,建議直接使用module.exports對象導出成員。絕對不會出錯,哈哈! 

 

2、AMD規范(https://github.com/amdjs/amdjs-api/wiki/AMD)

AMD 是 Asynchronous Module Definition 的簡稱,即“異步模塊定義”,是從 CommonJS 討論中誕生的。AMD 優先照顧瀏覽器的模塊加載場景,使用了異步加載和回調的方式。

服務器端加載方式為同步加載,因為所有的模塊都存在了本地硬盤,同步加載需要等待的時間就是硬盤的讀取時間。這對於服務端來說不是什么問題。但是對於瀏覽器,所有的模塊都存在於服務端,等待的時間多數取決於網速的快慢。網速慢的時候,瀏覽器就會處於“假死”狀態。因此,瀏覽器加載模塊應采用異步加載的方式,這也是AMD規范誕生的背景。

RequireJS

模塊定義

通過define方法定義模塊,但是按照2種情況進行書寫。

  1. 該模塊獨立存在,不依賴其他模塊(可以返回任何值):      
    define(function() { return { // 返回的接口 } })

     

  2. 該模塊依賴其他模塊:
    define(['module1','module2'], function(module1, module2) { return { // 返回的接口 } })

     

require模塊加載 

//方法1 var module2 =require('module1'); //方法2 require(['module1'], function (module1) { module1.module1Fun(1, 3); }); require方法可以進行配置: require.config({ paths: { //為模塊指定位置,可以為服務器上的地址,也可以為外部網址等等,也可以指定多個地址,防止模塊加載出錯。 jquery: 'module/libs/jquery-10.3', } }); require(['jquery'],function($){});

 模塊導出

在Requirejs中,模塊導出共有三種方式: 

  1. 通過return方式導出,優先級最高; 
  2. 通過module.exports對象賦值導出,優先級次之; 
  3. 通過exports對象賦值導出,優先級最低;

上面的三種優先級是絕對的優先級,無關代碼的順序,例如即使將exports導出放在最后,也會被module.exports覆蓋,另外導出的內容只能是優先級最高的那個,而且僅僅包含其內容,絕不會它們內容的組合或並集。

//通過 return 方式導出,優先級最高,官方推薦 define(['module1','module2'], function(module1, module2) { return { // 返回的接口 } })
//將導出的成員掛載到 module.exports 對象上,寫法繼承CommonJS,優先級低於return define(function(require, exports, module) { // 導出模塊內容 module.exports = { username : 'HJJ' } }); 
//將導出成員掛載到exports上(不可以直接給exports直接賦值),優先級最低,寫法繼承CommonJS define(function(require, exports, module) { exports. username = 'HJJ' }); //'exports' 僅僅是 'module.exports' 的一個引用。在 'factory' 內部給 'exports' 重新賦值時,並不會改變 'module.exports' 的值。因此給 'exports' 賦值是無效的,不能用來更改模塊接口。 //還有就是如果直接將導出成員掛載到exports上,會導致實參形參傻傻分不清楚

 

3、CMD規范(https://github.com/seajs/seajs/issues/242)

CMD(Common Module Definition)通用模塊定義。CMD是在AMD基礎上改進的一種規范,均適用於瀏覽器環境,和AMD不同在於對依賴模塊的執行時機處理不同,CMD是就近依賴,而AMD是前置依賴。不過 RequireJS 從 2.0 開始,也改成可以延遲執行(根據寫法不同,處理方式不同)。CMD 推崇 as lazy as possible.

// CMD define(function(require, exports, module) { var module1 = require('./module1') module1.doSomething() // 此處略去 100 行 var module2 = require('./module2') // 依賴可以就近書寫 module2.doSomething() // ... }) // AMD 默認推薦的是 define(['./module1', './module2'], function(module1, module2) { // 依賴必須一開始就寫好 module1.doSomething() // 此處略去 100 行 module2.doSomething() ... }) 

 CMD模塊定義

define({ "foo": "bar" });
define('I am a template. My name is {{name}}.');
define(function(require, exports, module) { // 模塊代碼 }); define('hello', ['jquery'], function(require, exports, module) { // 模塊代碼 });

 

CMD模塊的導入導出同AMD,請移步AMD規范。

4、ES6

import模塊導入 

1、import命令接受一對大括號,里面指定要從其他模塊導入的變量名。大括號里面的變量名,必須與被導入模塊(profile.js)對外接口的名稱相同。如果想為輸入的變量重新取一個名字,import命令要使用as關鍵字,將輸入的變量重命名。

import { firstName, lastName as surname, year } from './profile.js';

2、import命令輸入的變量都是只讀的,因為它的本質是輸入接口。也就是說,不允許在加載模塊的腳本里面,改寫接口。但是,如果a是一個對象,改寫a的屬性是允許的。

import {a} from './xxx.js' a = {}; // Syntax Error : 'a' is read-only; import {a} from './xxx.js' a.foo = 'hello'; // 合法操作,建議都當成只讀屬性,方便排錯

3、import后面的from指定模塊文件的位置,可以是相對路徑,也可以是絕對路徑,.js路徑可以省略。如果只是模塊名,不帶有路徑,那么必須有配置文件,告訴 JavaScript 引擎該模塊的位置。

注意,import命令具有提升效果,會提升到整個模塊的頭部,首先執行。 

 

import {myMethod} from 'util'; foo(); import { foo } from 'my_module';

4、由於import是靜態執行,所以不能使用表達式和變量,這些只有在運行時才能得到結果的語法結構。

// 報錯 import { 'f' + 'oo' } from 'my_module'; // 報錯 let module = 'my_module'; import { foo } from module; // 報錯 if (x === 1) { import { foo } from 'module1'; } else { import { foo } from 'module2'; }

 5、import語句會執行所加載的模塊,因此可以有下面的寫法。

import 'lodash'; //上面代碼僅僅執行lodash模塊,但是不輸入任何值。

6、import語句是 Singleton 模式。如果多次重復執行同一句import語句,那么只會執行一次,而不會執行多次。

import 'lodash'; import 'lodash'; //等同於 import 'lodash'; import { foo } from 'my_module'; import { bar } from 'my_module'; // 等同於 import { foo, bar } from 'my_module';

7、同一個模塊里面 ,通過 Babel 轉碼,CommonJS 模塊的require命令和 ES6 模塊的import命令可以同時使用。但是不建議。原因如下:

ES6 模塊的設計思想是盡量的靜態化,使得編譯時就能確定模塊的依賴關系,以及輸入和輸出的變量。CommonJS 和 AMD 模塊,都只能在運行時確定這些東西。比如,CommonJS 模塊就是對象,輸入時必須查找對象屬性。

// CommonJS模塊 let { stat, exists, readFile } = require('fs'); // 等同於 let _fs = require('fs'); let stat = _fs.stat; let exists = _fs.exists; let readfile = _fs.readfile;

上面代碼的實質是整體加載fs模塊(即加載fs的所有方法),生成一個對象(_fs),然后再從這個對象上面讀取 3 個方法。這種加載稱為“運行時加載”,因為只有運行時才能得到這個對象,導致完全沒辦法在編譯時做“靜態優化”。

ES6 模塊不是對象,而是通過export命令顯式指定輸出的代碼,再通過import命令輸入。

// ES6模塊 import { stat, exists, readFile } from 'fs';

上面代碼的實質是從fs模塊加載 3 個方法,其他方法不加載。這種加載稱為“編譯時加載”或者靜態加載,即 ES6 可以在編譯時就完成模塊加載,效率要比 CommonJS 模塊的加載方式高。當然,這也導致了沒法引用 ES6 模塊本身,因為它不是對象。

由於 ES6 模塊是編譯時加載,使得靜態分析成為可能。有了它,就能進一步拓寬 JavaScript 的語法,比如引入宏(macro)和類型檢驗(type system)這些只能靠靜態分析實現的功能。

//import在靜態解析階段執行,所以它是一個模塊之中最早執行的。下面的代碼可能不會得到預期結果。 require('core-js/modules/es6.symbol'); require('core-js/modules/es6.promise'); import React from 'React';

export導出模塊

寫法1、

export var firstName = 'Michael'; export var lastName = 'Jackson'; export var year = 1958; export function multiply(x, y) { return x * y; };

寫法2(可以使用as關鍵字重命名)、

var firstName = 'Michael'; var lastName = 'Jackson'; var year = 1958; function v1() { ... } export {firstName, lastName, year, v1 as streamV1}; //使用大括號指定所要輸出的一組變量,推薦使用這種方式,簡介明了

 需要特別注意的是,export命令規定的是對外的接口,必須與模塊內部的變量建立一一對應關系。

// 報錯 export 1; // 報錯 var m = 1; export m; // 報錯 function f() {} export f; ----------------- export var m = 1; var m = 1; export {m}; var n = 1; export {n as m}; export function f() {}; //正確 function f() {} export {f};

 最后,export命令可以出現在模塊的任何位置,只要處於模塊頂層就可以。

function foo() { export default 'bar' // SyntaxError } foo()

默認導出(export default) 

每個模塊支持我們導出一個沒有名字的變量,使用關鍵語句export default來實現.

// export-default.js export default function () { console.log('foo'); } // import-default.js import customName from './export-default'; customName(); // 'foo' 

下面比較一下默認輸出和正常輸出。

export default命令用於指定模塊的默認輸出。顯然,一個模塊只能有一個默認輸出,因此export default命令只能使用一次。所以,import命令后面才不用加大括號,因為只可能唯一對應export default命令。

// 第一組 export default function crc32() { // 輸出 // ... } import crc32 from 'crc32'; // 輸入 // 第二組 export function crc32() { // 輸出 // ... }; import {crc32} from 'crc32'; // 輸入

export default命令其實只是輸出一個叫做default的變量,所以它后面不能跟變量聲明語句。

// 正確 export var a = 1; // 正確 var a = 1; export default a; // 錯誤 export default var a = 1;

因為export default命令的本質是將后面的值,賦給default變量,所以可以直接將一個值寫在export default之后

// 正確 export default 42; // 報錯 export 42;

如果想在一條import語句中,同時輸入默認方法和其他變量,可以寫成下面這樣。

import _, { each } from 'lodash'; //對應上面代碼的export語句如下 export default function (){ //... } export function each (obj, iterator, context){ //... }

export 與 import 的復合寫法 

如果在一個模塊之中,先輸入后輸出同一個模塊,import語句可以與export語句寫在一起。

export { foo, bar } from 'my_module'; // 可以簡單理解為 import { foo, bar } from 'my_module'; export { foo, bar }; export { es6 as default } from './someModule'; // 等同於 import { es6 } from './someModule'; export default es6;

 

參考資料:

ECMAScript 6 入門 --阮一峰

CMD模塊定義規范

AMD規范

 

上一頁 下一頁

原文地址:http://www.manongjc.com/article/62801.html

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM