CommonJs模塊中爲什麼exports = 1導出是一個空對象

exports 和 module.exports的經常使用方式

當咱們想在一個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');
複製代碼

exports 和 module.exports的區別

一個標準化解析webpack

var module = { exports: {} };
var exports = module.exports;
return module.exports;
複製代碼

能夠看出:web

  • 最終導出的是module.exports
  • exports就是module.exports的一個引用

exports = 1 暴露出來是一個空對象?

從上面的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.exportsbash

a.js:
export.a = 10;
module.exports = 1;

b.js:
const b = require('./a.js')
console.log(b); ## 輸出 1;
複製代碼

模擬node的require函數實現加深理解exports 和 module.exports

來看看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;
複製代碼

測試 _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

  1. fs模塊讀取文件a.js
  2. 拼接成包裝函數字符串
  3. eval轉換爲包裝函數
  4. 執行包裝函數
  5. 返回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

總結

  • 介紹了exportsmodule.exports的用法
  • exportsmodule.exports的關係、區別
  • 簡單實現了require函數

上述_require只是實現了一些基本功能, 還有不少場景沒有考慮, 感興趣的能夠去看看webpack對require函數的實現, 它能夠處理CommonJs模塊和Es6模塊, 可是require函數的思想是差很少的, 模塊最後都是被裝在一個函數字符串中 eval("__webpack_require__(/*! ./index.js */)中。

相關文章
相關標籤/搜索