當咱們想在一個a.js
中暴露一個常量a, 能夠經過exports.a = 10
或者module.exports = {a: 10}
, b.js
引入常量a的時候:node
b.js:
const { a } = require('./a'); ## 對應exports.a = 10暴露方式
console.log(a); ## 輸出10
const a = require('./a').a; ## 對應module.exports = {a: 10}
console.log(a); ## 輸出10
## 上面的 const a = require('./a').a 使用對象的解構賦值等價於 const { a } = require('./a');
複製代碼
一個標準化解析webpack
var module = { exports: {} };
var exports = module.exports;
return module.exports;
複製代碼
能夠看出:web
module.exports
exports
就是module.exports
的一個引用從上面的return module.exports
, 當exports
指向了一個新的變量, 已經和module.exports
斷開鏈接關係, module.exports
不受任何影響json
var module = { exports: {} };
var exports = module.exports;
exports.a = 1; ## module.exports和exports仍是同一個引用, 此時module = { exports: { a: 1} };
exports = 1; ## 此時module.exports仍是指向 { a:1 }, 並不受影響
return module.exports;
複製代碼
同理如過modules.exports = 1
, 也會斷開它和exports
之間的聯繫, 致使exports
失去意義, 因此一個模塊只能存在一個暴露方式, 意思是module.exports
會覆蓋exports
, 由於最終是返回module.exports
bash
a.js:
export.a = 10;
module.exports = 1;
b.js:
const b = require('./a.js')
console.log(b); ## 輸出 1;
複製代碼
來看看node對require函數的描述函數
require(id)測試
- id module name or path
- Returns: exported module content
Description: Used to import modules, JSON, and local files. Modules can be imported from node_modules. Local modules and JSON files can be imported using a relative path (e.g. ./, ./foo, ./bar/baz, ../foo) that will be resolved against the directory named by __dirname (if defined) or the current working directory.
require
函數接受一個參數, 模塊名稱或者路徑, 返回模塊的內容。require
函數用於導出模塊, JSON
和本地文件。模塊能夠從node_modules
中導入, 本地文件和JSON
能夠經過相對路徑(./, ./foo, ./bar/baz, ../foo)等導出, 相對路徑最終會被path
模塊resolve
爲文件所處位置的絕對路徑。ui
// 簡單模擬require的實現
_require.js
function _require(dir) {
## 定義一個module對象
var module = {
exports: {}
};
## 引入nodejs 文件模塊 下面是nodejs中原生的require方法
var fs = require('fs');
var path = require('path');
## 同步讀取該文件
var moduleContent = fs.readFileSync(path.resolve(__dirname, dir), 'utf8');
## 處理本地文件爲.json
var ext = path.extname(path.resolve(__dirname, dir));
var addModuleExports = ext === '.json' ? 'module.exports=' : '';
## 頭尾拼接包裝成新的字符串
var packFuncStr = '(function(module,exports){ ' + addModuleExports + moduleContent + '};)';
## 字符串轉換成包裝函數
var packFunc = eval(packFuncStr);
## 把上面聲明module和它內部的module.exports都做爲參數傳進去
packFunc.call(module.exports, module, module.exports);
## packFunc第二個參數已經指明瞭exports = module.exports
## packFunc執行後上面的聲明的modeule對象獲得 moduleContent 中掛載到module.exports 或 exports上的API
return module.exports;
# 返回module.exports, 最終咱們拿到了path表明的文件模塊暴露的API
}
module.exports = _require;
複製代碼
demo
|____ a.js
|____ b.js
|____ _require.js
|____ c.json
a.js:
export.a = 10;
c.json:
{
"name": 'tftoy'
}
b.js:
const _require = require('./_require.js');
const { a } = _require('./a.js');
const c = _require('./c.json');
console.log(a); ## 輸出10;
console.log(c); ## 輸出{ name: 'tftoy' };
// 測試結果, 符合預期
複製代碼
咱們來分析下整個過程 當_require('./a.js')
發生如下事情:spa
fs
模塊讀取文件a.jseval
轉換爲包裝函數module.exports
_require('./a.js')
module = {
exports: {}
};
## 文件內容
moduleContent = 'exports.a = 10;';
## 文件後綴
ext = '.js'
addModuleExports = '';
## 包裝函數字符串
packFuncStr = '(function (module,exports){ ' + '' + 'exports.a = 10;' + '};)'
## 包裝函數
packFunc = function (module,exports) { exports.a = 10;};
## 執行包裝函數
packFunc.call(module.exports, module, module.exports);
## 返回結果
return { a: 10 };
所以_require('./a.js') => { a: 10 };
複製代碼
同理_require('./c.json')
也是這麼一個過程。code
exports
和module.exports
的用法exports
和module.exports
的關係、區別require
函數上述_require只是實現了一些基本功能, 還有不少場景沒有考慮, 感興趣的能夠去看看webpack對require函數的實現, 它能夠處理CommonJs模塊和Es6模塊, 可是require函數的思想是差很少的, 模塊最後都是被裝在一個函數字符串中 eval("__webpack_require__(/*! ./index.js */)
中。