相關推薦:《node js教學》
JavaScript 做為一門為網頁添加互動功能的簡單腳本語言問世,在開始並不包含模組系統,隨著JavaScript 解決問題越來越複雜,把所有程式碼寫在一個檔案內,用function 區分功能單元已經不能支撐複雜應用開發了,ES6 帶來了大部分高階語言都有的class 和module,方便開發者組織程式碼
import _ from 'lodash'; class Fun {} export default Fun;
上面三行程式碼展示了一個模組系統最重要的兩個要素import 和export
export
用於規定模組的對外介面
#import
用於輸入其他模組提供的功能
而在ES6 之前,社群出現了許多模組載入方案,最主要的有CommonJS 和AMD 兩種,Node.js 誕生早於ES6,模組系統使用的是類似CommonJS的實現,遵從幾個原則
一個檔案是一個模組,檔案內的變數作用域都在模組內
module.exports 物件導出模組對外介面
require 引入其它模組
# circle.js
const { PI } = Math; module.exports = function area(r) { PI * r ** 2; };上面程式碼就實作了Node.js 的一個模組,模組沒有依賴其它模組,導出了方法
area 計算圓的面積
#test.js
const area = require('./circle.js'); console.log(`半径为 4 的圆的面积是 ${area(4)}`);模組依賴了circle.js,使用其對外暴露的area 方法,計算圓的面積module.exports模組對外暴露介面使用module.exports,常見的有兩種用法:為其新增屬性或賦值到新物件
test.js
// 添加属性 module.exports.prop1 = xxx; module.exports.funA = xxx; module.exports.funB = xxx; // 赋值到全新对象 module.exports = { prop1, funA, funB, };兩種寫法是等價的,使用時候沒區別
const mod = require('./test.js'); console.log(mod.prop1); console.log(mod.funA());還有另外一種直接使用
exports 對象的方法,但是只能對其添加屬性,不能賦值到新對象,後面會介紹原因
// 正确的写法:添加属性 exports.prop1 = xxx; exports.funA = xxx; exports.funB = xxx; // 赋值到全新对象 module.exports = { prop1, funA, funB, };require('id')模組類型require 使用比較簡單,id 支援模組名稱與檔案路徑兩種型別模組名稱
const fs = require('fs'); const _ = require('lodash');
node_modules 下的第三方模組,如果出現重名,優先使用系統內建模組
module.paths 查看特定尋找路徑),直到根據
NODE_PATH 環境變數找出檔案系統根目錄,具體流程可以參考官方文件
$HOME 是使用者的主目錄,
$PREFIX 是Node.js 裡配置的
node_prefix。強烈建議將所有的依賴放在本地的 node_modules 目錄,這樣將會更快地加載,且更可靠
為前綴的模組是文件的絕對路徑,依照系統路徑尋找模組
為前綴的模組是相對於目前呼叫require 方法的文件,不受後續模組在哪裡被使用到影響
Module._cache ,如果每次都呼叫
require('foo') 都解析到相同文件,則傳回相同的對象,同時多次呼叫
require(foo) 不會導致模組的程式碼執行多次。 Node.js 根據實際的檔案名稱快取模組,因此從不同層級目錄引用相同模組不會重複載入。
a.js
console.log('a 开始'); exports.done = false; const b = require('./b.js'); console.log('在 a 中,b.done = %j', b.done); exports.done = true; console.log('a 结束');
b.js
console.log('b 开始'); exports.done = false; const a = require('./a.js'); console.log('在 b 中,a.done = %j', a.done); exports.done = true; console.log('b 结束');
main.js:
console.log('main 开始'); const a = require('./a.js'); const b = require('./b.js'); console.log('在 main 中,a.done=%j,b.done=%j', a.done, b.done);當 main.js 載入 a.js 時,a.js 又載入 b.js,此時,b.js 會試著去載入 a.js為了防止無限的迴圈會回傳一個 a.js 的 exports 物件的
未完成的副本 給 b.js 模組,然後 b.js 完成載入,並將 exports 物件提供給 a.js 模組
因此範例的輸出是main 开始 a 开始 b 开始 在 b 中,a.done = false b 结束 在 a 中,b.done = true a 结束 在 main 中,a.done=true,b.done=true
看不懂上面的过程也没关系,日常工作根本用不到,即使看懂了也不要在项目中使用循环依赖!
Node.js 每个文件都是一个模块,模块内的变量都是局部变量,不会污染全局变量,在执行模块代码之前,Node.js 会使用一个如下的函数封装器将模块封装
(function(exports, require, module, __filename, __dirname) { // 模块的代码实际上在这里 });
回头看看最开始的问题,为什么 exports 对象不支持赋值为其它对象?把上面函数添加一句 exports 对象来源就很简单了
const exports = module.exports; (function(exports, require, module, __filename, __dirname) { // 模块的代码实际上在这里 });
其它模块 require 到的肯定是模块的 module.exports 对象,如果吧 exports 对象赋值给其它对象,就和 module.exports 对象断开了连接,自然就没用了
随着 ES6 使用越来越广泛,Node.js 也支持了 ES6 Module,有几种方法
使用 babel 构建是在 v12 之前版本最简单、通用的方式,具体配置参考 @babel/preset-env(https://babeljs.io/docs/en/babel-preset-env)
.babelrc
{ "presets": [ ["@babel/preset-env", { "targets": { "node": "8.9.0", "esmodules": true } }] ] }
在 v12 后可以使用原生方式支持 ES Module
开启 --experimental-modules
模块名修改为 .mjs
(强烈不推荐使用)或者 package.json 中设置 "type": module
这样 Node.js 会把 js 文件都当做 ES Module 来处理,更多详情参考官方文档(https://nodejs.org/dist/latest-v13.x/docs/api/esm.html)
更多编程相关知识,请访问:编程视频!!
以上是了解Node.js中的模組系統的詳細內容。更多資訊請關注PHP中文網其他相關文章!