理解import、require、export、module.export

理解import、require、export、module.export

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;

nodeJS 中模塊化使用的就是CommonJS的規範,實質就是整體加載fs模塊,生成fs_對象,在對象上讀取屬性和方法,
這種加載方式是“運行時加載”

ES6 模塊

ES6 模塊不是對象,而是通過export命令顯式指定輸出的代碼,再通過import命令輸入。
// ES6模塊
import { stat, exists, readFile } from 'fs';

上面代碼的實質是從fs模塊加載 3 個方法,其他方法不加載。這種加載稱爲“編譯時加載”或者靜態加載,即 ES6 可以在編譯時就完成模塊加載,效率要比 CommonJS 模塊的加載方式高。

export命令

一個模塊就是一個獨立的文件。該文件內部的所有變量,外部無法獲取。如果你希望外部能夠讀取模塊內部的某個變量,就必須使用export關鍵字輸出該變量。

// profile.js
export var firstName = 'Michael';
export var lastName = 'Jackson';
export var year = 1958;

// profile.js
var firstName = 'Michael';
var lastName = 'Jackson';
var year = 1958;

export {firstName, lastName, year};

export的語法,對外導出接口,在接口名與模塊內部變量之間,建立了一一對應的關係。

// 寫法一
export var m = 1;

// 寫法二
var m = 1;
export {m};

// 寫法三
var n = 1;
export {n as m};

import命令

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

目前階段,通過 Babel 轉碼,CommonJS 模塊的require命令和 ES6 模塊的import命令,可以寫在同一個模塊裏面,但是最好不要這樣做。因爲import在靜態解析階段執行,所以它是一個模塊之中最早執行的。下面的代碼可能不會得到預期結果。

require('core-js/modules/es6.symbol');
require('core-js/modules/es6.promise');
import React from 'React';

export default 命令

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

本質上,export default就是輸出一個叫做default的變量或方法,然後系統允許你爲它取任意名字。所以,下面的寫法是有效的。

// modules.js
function add(x, y) {
  return x * y;
}
export {add as default};
// 等同於
// export default add;

// app.js
import { default as foo } from 'modules';
// 等同於
// import foo from 'modules';

// 正確
export var a = 1;

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

// 錯誤
export default var a = 1;

CommonJS規範

每個文件就是一個模塊,有自己的作用域。在一個文件裏面定義的變量、函數、類,都是私有的,對其他文件不可見。

CommonJS規範規定,每個模塊內部,module變量代表當前模塊。這個變量是一個對象,它的exports屬性(即module.exports)是對外的接口。加載某個模塊,其實是加載該模塊的module.exports屬性。

var x = 5;
var addX = function (value) {
  return value + x;
};
module.exports.x = x;
module.exports.addX = addX;

// 使用
var example = require('./example.js');

console.log(example.x); // 5
console.log(example.addX(1)); // 6

CommonJS模塊的特點

  1. 所有代碼都運行在模塊作用域,不會污染全局作用域。
  2. 模塊可以多次加載,但是只會在第一次加載時運行一次,然後運行結果就被緩存了,以後再加載,就直接讀取緩存結果。要想讓模塊再次運行,必須清除緩存。
  3. 模塊加載的順序,按照其在代碼中出現的順序。
export var foo = 'bar';
setTimeout(() => foo = 'baz', 500);

ES6 模塊化上面代碼輸出變量foo,值爲bar,500 毫秒之後變成baz。

這一點與 CommonJS 規範完全不同。CommonJS 模塊輸出的是值的緩存,不存在動態更新

module對象

Node內部提供一個Module構建函數。所有模塊都是Module的實例

module.id 模塊的識別符,通常是帶有絕對路徑的模塊文件名。
module.filename 模塊的文件名,帶有絕對路徑。
module.loaded 返回一個布爾值,表示模塊是否已經完成加載。
module.parent 返回一個對象,表示調用該模塊的模塊。
module.children 返回一個數組,表示該模塊要用到的其他模塊。
module.exports 表示模塊對外輸出的值。

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

注意,不能直接將exports變量指向一個值,因爲這樣等於切斷了exports與module.exports的聯繫。

exports.hello = function() {
  return 'hello';
};

module.exports = 'Hello world';

面代碼中,hello函數是無法對外輸出的,因爲module.exports被重新賦值了。

這意味着,如果一個模塊的對外接口,就是一個單一的值,不能使用exports輸出,只能使用module.exports輸出。

目錄的加載規則

和node中模塊加載規則一致

通常,我們會把相關的文件會放在一個目錄裏面,便於組織。這時,最好爲該目錄設置一個入口文件,讓require方法可以通過這個入口文件,加載整個目錄。

在目錄中放置一個package.json文件,並且將入口文件寫入main字段。下面是一個例子。

// package.json
{ "name" : "some-library",
  "main" : "./lib/some-library.js" }

require發現參數字符串指向一個目錄以後,會自動查看該目錄的package.json文件,然後加載main字段指定的入口文件。如果package.json文件沒有main字段,或者根本就沒有package.json文件,則會加載該目錄下的index.js文件或index.node文件。

參考

CommonJS規範
Module 的語法

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章