JS模塊的導(dǎo)出和導(dǎo)入之export和module.export的區(qū)別

轉(zhuǎn)載自 JS模塊的導(dǎo)出和引入

在js編程中經(jīng)常會(huì)有模塊的導(dǎo)出導(dǎo)入,涉及到一些導(dǎo)入導(dǎo)出關(guān)鍵字

  • 導(dǎo)出關(guān)鍵字
    • module.exports
    • exports
    • export
    • export default
  • 導(dǎo)入關(guān)鍵字
    • require
      • const xxx = require("模塊名")
    • import
      • import { xxx } from "模塊名"
      • import xxx from "模塊名"
      • import xxx1, {xxx2, xxx3,...} from "模塊名"
      • import * from "模塊名"

因?yàn)樵趯?shí)際開發(fā)中經(jīng)常會(huì)混淆這些用法,所以想要弄清楚這些的區(qū)別,讓自己明白自己到底在寫什么。本文作為學(xué)習(xí)筆記輸出。

模塊規(guī)范

JS模塊化編程分了兩種規(guī)范:CommonJS模塊規(guī)范和ES6模塊規(guī)范。

  • CommonJS模塊規(guī)范 —— CommonJS規(guī)范中,以module.exports導(dǎo)出接口,以require引入模塊
  • ES6模塊規(guī)范 —— ES6標(biāo)準(zhǔn)規(guī)范中,以export指令導(dǎo)出接口,以import引入模塊

在Node.js編程中,Node模塊系統(tǒng)遵循的是CommonJS規(guī)范。

CommonJS模塊規(guī)范

CommonJS規(guī)范規(guī)定: 每個(gè)js文件就是一個(gè)模塊,有自己的作用域。
在一個(gè)文件里面定義的變量、函數(shù)、類,都是私有的,對(duì)其他文件不可見。
如果要暴露給其他程序,需要以module.exports導(dǎo)出接口,以require引入模塊。

module.exports 和 exports

module.exports / exports: 只有 Node 支持的導(dǎo)出
模塊導(dǎo)出的時(shí)候,導(dǎo)出的是module.exports,不是exports
module.exports可以導(dǎo)出所有的類型。對(duì)象,函數(shù),字符串、數(shù)值等。

每一個(gè)js文件通過node執(zhí)行時(shí),都自動(dòng)創(chuàng)建一個(gè)module變量和一個(gè)exports變量。
module變量代表當(dāng)前模塊。這個(gè)變量是一個(gè)對(duì)象,同時(shí),module對(duì)象會(huì)創(chuàng)建一個(gè)叫exports的屬性(即module.exports),該屬性初始化的值是 {},是對(duì)外的接口。加載某個(gè)模塊,其實(shí)是加載該模塊的module.exports屬性。

// test1.js
console.log(module)
復(fù)制代碼

執(zhí)行node test1.js的打印結(jié)果:

Module {
  id: '.',
  path: '/Users/xxxjuan/學(xué)習(xí)資料/Nodejs學(xué)習(xí)/demo-模塊編程',
  exports: {},
  parent: null,
  filename: '/Users/xxxjuan/學(xué)習(xí)資料/Nodejs學(xué)習(xí)/demo-模塊編程/test1.js',
  loaded: false,
  children: [],
  paths: [
    '/Users/xxxjuan/學(xué)習(xí)資料/Nodejs學(xué)習(xí)/demo-模塊編程/node_modules',
    '/Users/xxxjuan/學(xué)習(xí)資料/Nodejs學(xué)習(xí)/node_modules',
    '/Users/xxxjuan/學(xué)習(xí)資料/node_modules',
    '/Users/xxxjuan/node_modules',
    '/Users/node_modules',
    '/node_modules'
  ]
}

// module.exports 表示模塊對(duì)外輸出的值。
// module.filename 模塊的文件名,帶有絕對(duì)路徑。
// module.loaded 返回一個(gè)布爾值,表示模塊是否已經(jīng)完成加載。
// module.children 返回一個(gè)數(shù)組,表示該模塊要用到的其他模塊。
// module.parent 返回一個(gè)對(duì)象,表示調(diào)用該模塊的模塊。
復(fù)制代碼

默認(rèn)exports變量是對(duì)module.exports的引用,即exports和module.exports指向同一個(gè)內(nèi)存塊。 這等同在每個(gè)模塊頭部,有一行這樣的命令。

var exports = module.exports;
復(fù)制代碼
  • 當(dāng)通過exports去改變內(nèi)存塊里內(nèi)容時(shí),module.exports的值也會(huì)改變
  • 當(dāng)通過module.exports去改變內(nèi)存塊里內(nèi)容時(shí),exports的值也會(huì)改變
  • 當(dāng)module.exports被改變的時(shí)候,exports不會(huì)被改變
  • 當(dāng)exports被改變的時(shí)候,module.exports不會(huì)被改變

所以,exports屬性的出現(xiàn)應(yīng)該可以直接向exports對(duì)象添加方法,從而方便對(duì)外輸出模塊接口。不過當(dāng)module.exports改變時(shí),exports與module.exports也就斷開了鏈接,所以最好不要采用這種方式,統(tǒng)一采用module.exports方式。

// module_export_demo.js
module.exports.a = 100
console.log("log1: " + exports.a)  // log1: 100

exports.a = 200; 
console.log("log2: " + module.exports.a)  // log2: 200

module.exports = "hello"
console.log("log3: " + JSON.stringify(exports)) // log3: {"a":200}
復(fù)制代碼

module.exports可以導(dǎo)出所有的類型。對(duì)象,函數(shù),字符串、數(shù)值等。 語法示例:

// module_export_demo2.js
var x = 5

var str = "hello"

var addX = function (value) {
  return value + x
};

class Point {
  constructor(x, y) {
    this.x = x;
    this.y = y;
  }

  toString() {
    return '(' + this.x + ', ' + this.y + ')';
  }
}

// module.exports.x = x
// module.exports.str = str
// module.exports.addX = addX
// module.exports.Point = Point

module.exports = {
  x: x,
  str: str,
  addX: addX,
  Point: Point
}

// require時(shí)對(duì)比下兩種方式x的值到底取哪個(gè)
exports = {
  x: 10,
}
復(fù)制代碼

require

requirer用于加載模塊,是node的一個(gè)全局方法,使用非常簡單

const xxx = require("模塊名")
復(fù)制代碼

讀入并執(zhí)行一個(gè)JavaScript文件,返回模塊的exports對(duì)象。如果沒有發(fā)現(xiàn)指定模塊,會(huì)報(bào)錯(cuò)。

require方法接受以下幾種參數(shù)的傳遞:

  • 原生模塊,如http、fs、path等
  • 相對(duì)路徑的文件模塊,如./mod或../mod
  • 絕對(duì)路徑的文件模塊,如 /pathtomodule/mod
  • 第三方模塊,如koa等

在模塊目錄中通常有一個(gè)package.json文件,并且將入口文件寫入main字段

    // package.json
    { "name" : "some-library",
      "main" : "./lib/some-library.js" }
復(fù)制代碼

require發(fā)現(xiàn)參數(shù)字符串指向一個(gè)目錄以后,會(huì)自動(dòng)查看該目錄的package.json文件,然后加載main字段指定的入口文件。
如果package.json文件沒有main字段,或者根本就沒有package.json文件,則會(huì)加載該目錄下的index.js文件或index.node文件

因?yàn)槟K導(dǎo)出的實(shí)際是module.exports,所以require只能看到通過 module.exports 導(dǎo)出的內(nèi)容,看不到通過exports導(dǎo)出的內(nèi)容。它相當(dāng)于module.exports的傳送門,module.exports后面的內(nèi)容是什么,require的結(jié)果就是什么,對(duì)象、數(shù)字、字符串、函數(shù)...再把require的結(jié)果賦值給某個(gè)變量。 針對(duì)上面的 module_export_domo2.js 文件,引入模塊示例

// node require_demo2.js
const demo2 = require("./module_export_demo2")

console.log(demo2.x)  // 5 也證明了引入的是module.export的內(nèi)容
console.log(demo2.str)  // hello
console.log(demo2.addX(15)) // 20  = 5 + 15 

let point = new demo2.Point(3, 4)
console.log(point.toString()) //  (3, 4)
復(fù)制代碼

require 是運(yùn)行時(shí)的,其參數(shù)可以是表達(dá)式

let value = 2
const demo2 = require("./module_export" + "_demo" + value)
復(fù)制代碼

require函數(shù)加載模塊

  1. require函數(shù)加載模塊順序按照其在代碼中出現(xiàn)的順序

  2. require函數(shù)加載模塊是同步的,只有加載完成,才能執(zhí)行后面的操作

  3. require函數(shù)加載的模塊是被輸出的值的拷貝,不會(huì)受到模塊內(nèi)容變化影響

     // module_export_demo3.js
     var counter = 3;
     function incCounter() {
       counter++;
     }
     module.exports = {
       counter: counter,
       incCounter: incCounter,
     };
    復(fù)制代碼
    
     const demo3 = require("./module_export_demo3")
     console.log(demo3.counter);  // 3
     demo3.incCounter();
     console.log(demo3.counter); // 3
    復(fù)制代碼
    

    counter輸出結(jié)果說明module_export_demo3模塊內(nèi)部的變化就影響不到counter了

  4. 模塊第一次被加載時(shí)會(huì)執(zhí)行一次,后續(xù)被加載時(shí)不會(huì)再執(zhí)行,都是從緩存中獲取的

    // module_export_demo3.js
    console.log("hello")
    module.exports = "wrold"
    復(fù)制代碼
    
    const demo3 = require("./module_export_demo3")
    const demo3_1 = require("./module_export_demo3")
    const demo3_2 = require("./module_export_demo3")
    const demo3_3 = require("./module_export_demo3")
    const demo3_4 = require("./module_export_demo3")
    const demo3_5 = require("./module_export_demo3")
    console.log(demo3)
    console.log(demo3_1)
    
    // hello
    // wrold
    // wrold
    復(fù)制代碼
    

    hello只會(huì)被打印一次說明console.log("hello")語句只執(zhí)行了一次,module_export_demo3.js只被加載了一次。

ES6模塊規(guī)范

ES6發(fā)布的module并沒有直接采用CommonJS,甚至連require都沒有采用,也就是說require仍然只是node的一個(gè)私有的全局方法,module.exports也只是node私有的一個(gè)全局變量屬性,跟標(biāo)準(zhǔn)什么關(guān)系都沒有。
ES6模塊規(guī)范是,在創(chuàng)建JS模塊時(shí),export 語句用于從模塊中導(dǎo)出函數(shù)、對(duì)象或原始值,以便其他程序可以通過 import 語句使用它們。

export 和 export default

ES6模塊導(dǎo)出有兩種方式:export(命名導(dǎo)出) 和 export default(默認(rèn)導(dǎo)出)。 在導(dǎo)出多個(gè)值時(shí),命名導(dǎo)出非常有用。在導(dǎo)入時(shí),必須使用相應(yīng)對(duì)象的相同名稱。但是,可以使用任何名稱導(dǎo)入默認(rèn)導(dǎo)出。

export語法介紹

// 導(dǎo)出單個(gè)特性
export let name1, name2, …, nameN; // also var, const
export let name1 = …, name2 = …, …, nameN; // also var, const
export function FunctionName(){...}
export class ClassName {...}

// 導(dǎo)出列表
export { name1, name2, …, nameN };

// 重命名導(dǎo)出
export { variable1 as name1, variable2 as name2, …, nameN };

// 默認(rèn)導(dǎo)出
export default expression;
export default function (…) { … } // also class, function*
export default function name1(…) { … } // also class, function*
export { name1 as default, … };

// 導(dǎo)出"引入模塊的導(dǎo)出值"
export { name1, name2, …, nameN } from …;
export { import1 as name1, import2 as name2, …, nameN } from …;
export * from …; // 導(dǎo)出"引入模塊的所有導(dǎo)出值",不包括模塊的默認(rèn)導(dǎo)出值
export { default } from …; // 導(dǎo)出"引入模塊的默認(rèn)導(dǎo)出值"
復(fù)制代碼
  • export與export default均可用于導(dǎo)出常量、函數(shù)、文件、模塊等
  • 通過export方式導(dǎo)出,在導(dǎo)入時(shí)要加{ },export default則不需要
  • 在一個(gè)文件中,export可以有多個(gè),export default僅有一個(gè)
  • 大部分風(fēng)格建議,模塊中最好在末尾用一個(gè)export導(dǎo)出所有的接口
export 1 // 這種寫法錯(cuò)誤

// 正確的寫法
const value = 1
export { value }

// 或者
export const value = 1

// 或者
const value = 1
export default value

// 或者
export default 1
復(fù)制代碼

export default是別名的語法糖,這個(gè)語法糖的好處在是

  • import的時(shí)候,可以省去花括號(hào){}。
  • import的時(shí)候, 可以起任何變量名表示引入變量

所以如果import的時(shí)候,你發(fā)現(xiàn)某個(gè)變量沒有花括號(hào)括起來(除了* 號(hào)),是因?yàn)樵撟兞渴峭ㄟ^export default 導(dǎo)出的。

// d.js
// 導(dǎo)出函數(shù)
export default function() {}

// 等效于:
// function a() {};
// export {a as default};

復(fù)制代碼
import a from "d.js" // a 是 {defalut as a}的替代寫法。
復(fù)制代碼

所以使用export default命令,為模塊指定默認(rèn)輸出,這樣就不需要知道所要加載模塊的變量名。

// a.js
let sex = "boy";
export default sex //sex不能加大括號(hào) 等價(jià)于 export {sex as default}
復(fù)制代碼

本質(zhì)上,a.js文件的export default輸出一個(gè)叫做default的變量,然后系統(tǒng)允許你為它取任意名字。 自然default只能有一個(gè)值,所以一個(gè)文件內(nèi)不能有多個(gè)export default。

// b.js
import any from "./a.js"
import any12 from "./a.js" 
console.log(any, any12)   // boy,boy
復(fù)制代碼

import

require 和 import是完全不同的兩種概念。require是賦值過程,import是解構(gòu)過程 const xxx = require("模塊名") import { xxx } from "模塊名"

  • import是編譯時(shí)的, 必須放在文件開頭,否則會(huì)報(bào)錯(cuò)
  • import后面跟上花括號(hào)的形式是最基本的用法,花括號(hào)里面的變量與export后面的變量一一對(duì)應(yīng)。
import {a} from ..
復(fù)制代碼
  • 支持給變量取別名。因?yàn)橛械臅r(shí)候不同的兩個(gè)模塊可能有相同的接口,可以給這個(gè)變量取一個(gè)別名,方便在當(dāng)前的文件里面使用。
import {a as a_a} from ..
復(fù)制代碼

import介紹語法

import defaultExport from "module-name";
import * as name from "module-name";
import { export1 } from "module-name";
import { export1 as alias1 } from "module-name";
import { export1 , export2 } from "module-name";
import { foo , bar } from "module-name/path/to/specific/un-exported/file";
import { export1 , export2 as alias2 , [...] } from "module-name";
import defaultExport, { export1 [ , [...] ] } from "module-name";
import defaultExport, * as name from "module-name";
import "module-name";
var promise = import("module-name"); // 動(dòng)態(tài)模塊加載,返回的是一個(gè)promise對(duì)象
復(fù)制代碼

Node為何支持export / import

我們經(jīng)常會(huì)看到在node中也會(huì)用export / import,這是什么呢? 我們?cè)趎ode中使用babel支持ES6,僅僅是將ES6轉(zhuǎn)碼為ES5再執(zhí)行,import語法會(huì)被轉(zhuǎn)碼為require。因?yàn)槟壳八械囊娑歼€沒有實(shí)現(xiàn)export / import。
如何讓Node.js支持ES6的語法具體參考在node環(huán)境中支持ES6代碼

// ES6語法
import {a} from "./demo.js"
// 轉(zhuǎn)碼ES5后
var _demo = require("./demo.js")
復(fù)制代碼

這也是為什么在使用module.exports模塊導(dǎo)出時(shí),在引入模塊時(shí)使用import仍然起效,因?yàn)楸举|(zhì)上,import會(huì)被轉(zhuǎn)碼為require去執(zhí)行。

總結(jié)

CommonJS規(guī)范中,建議盡量都用 module.exports 導(dǎo)出,然后用require導(dǎo)入 ES6規(guī)則中,大部分風(fēng)格建議,模塊中最好在末尾用一個(gè)export導(dǎo)出所有的接口

  • module.exports / exports: 只有 Node 支持的導(dǎo)出
  • require: Node 和 ES6 都支持的引入
  • export / import : 只有ES6 支持的導(dǎo)出引入
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容