91超碰碰碰碰久久久久久综合_超碰av人澡人澡人澡人澡人掠_国产黄大片在线观看画质优化_txt小说免费全本

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

Nodejs中的模塊系統該如何使用

發布時間:2021-09-14 11:08:41 來源:億速云 閱讀:166 作者:柒染 欄目:web開發

本篇文章給大家分享的是有關Nodejs中的模塊系統該如何使用,小編覺得挺實用的,因此分享給大家學習,希望大家閱讀完這篇文章后可以有所收獲,話不多說,跟著小編一起來看看吧。

模塊化的背景

早期 JavaScript 是為了實現簡單的頁面交互邏輯, 但隨著時代發展, 瀏覽器不單單僅只能呈現簡單交互, 各種各樣的網站開始大放光彩。隨著網站開始變得復雜化,前端代碼日漸增多,相對比起其他靜態語言,JavaScript 缺少模塊化的弊端開始暴露出來,如命名沖突。因此為了方便前端代碼的維護和管理,社區開始了對模塊化規范的定義。在這過程中,出現了很多的模塊化規范,如CommonJS, AMD, CMD, ES modules,本文章主要講解Node中根據CommonJS實現的模塊化。

CommonJS 規范

首先,在 Node 世界里,模塊系統是遵守CommonJS規范的,CommonJS規范里定義,簡單講就是:

  • 每一個文件就是一個模塊

  • 通過module對象來代表一個模塊的信息

  • 通過exports用來導出模塊對外暴露的信息

  • 通過require來引用一個模塊

Node 模塊分類

  • 核心模塊: 如 fs,http,path 等模塊, 這些模塊不需要安裝, 在運行時已經加載在內存中。【推薦學習:《nodejs 教程》】

  • 第三方模塊: 通過安裝存放在 node_modules 中。

  • 自定義模塊: 主要是指 file 模塊,通過絕對路徑或者相對路徑進行引入。

Module 對象

我們上面說過,一個文件就是一個模塊,且通過一個 module 對象來描述當前模塊信息,一個 module 對象對應有以下屬性: - id: 當前模塊的id - path: 當前模塊對應的路徑 - exports: 當前模塊對外暴露的變量 - parent: 也是一個module對象,表示當前模塊的父模塊,即調用當前模塊的模塊 - filename: 當前模塊的文件名(絕對路徑), 可用于在模塊引入時將加載的模塊加入到全局模塊緩存中, 后續引入直接從緩存里進行取值 - loaded: 表示當前模塊是否加載完畢 - children: 是一個數組,存放著當前模塊調用的模塊 - paths: 是一個數組,記錄著從當前模塊開始查找node_modules目錄,遞歸向上查找到根目錄下的node_modules目錄下

module.exports 與 exports

說完CommonJS規范,我們先講下module.exportsexports的區別。

首先,我們用個新模塊進行一個簡單驗證

console.log(module.exports === exports); // true

可以發現,module.exportsepxorts實際上就是指向同一個引用變量。

demo1

// a模塊
module.exports.text = 'xxx';
exports.value = 2;

// b模塊代碼
let a = require('./a');

console.log(a); // {text: 'xxx', value: 2}

從而也就驗證了上面demo1中,為什么通過module.exportsexports添加屬性,在模塊引入時兩者都存在, 因為兩者最終都是往同一個引用變量上面進行屬性的添加.根據該 demo, 可以得出結論: module.exportsexports指向同一個引用變量

demo2

// a模塊
module.exports = {
  text: 'xxx'
}
exports.value = 2;

// b模塊代碼
let a = require('./a');

console.log(a); // {text: 'xxx'}

上面的 demo 例子中,對module.exports進行了重新賦值, exports進行了屬性的新增, 但是在引入模塊后最終導出的是module.exports定義的值, 可以得出結論: noed 的模塊最終導出的是module.exports, 而exports僅僅是對 module.exports 的引用, 類似于以下代碼:

exports = module.exports = {};
(function (exports, module) {
  // a模塊里面的代碼
  module.exports = {
    text: 'xxx'
  }
  exports.value = 2;

  console.log(module.exports === exports); // false
})(exports, module)

由于在函數執行中, exports 僅是對原module.exports對應變量的一個引用,當對module.exports進行賦值時,exports對應的變量和最新的module.exports并不是同一個變量

require 方法

require引入模塊的過程主要分為以下幾步:

  • 解析文件路徑成絕對路徑

  • 查看當前需要加載的模塊是否已經有緩存, 如果有緩存, 則直接使用緩存的即可

  • 查看是否是 node 自帶模塊, 如 http,fs 等, 是就直接返回

  • 根據文件路徑創建一個模塊對象

  • 將該模塊加入模塊緩存中

  • 通過對應的文件解析方式對文件進行解析編譯執行(node 默認僅支持解析.js,.json, .node后綴的文件)

  • 返回加載后的模塊 exports 對象

Nodejs中的模塊系統該如何使用

Module.prototype.require = function(id) {
  // ...
  try {
    // 主要通過Module的靜態方法_load加載模塊 
    return Module._load(id, this, /* isMain */ false);
  } finally {}
  // ...
};
// ...
Module._load = function(request, parent, isMain) {
  let relResolveCacheIdentifier;
  // ...
  // 解析文件路徑成絕對路徑
  const filename = Module._resolveFilename(request, parent, isMain);
  // 查看當前需要加載的模塊是否已經有緩存
  const cachedModule = Module._cache[filename];
  // 如果有緩存, 則直接使用緩存的即可
  if (cachedModule !== undefined) {
    // ...
    return cachedModule.exports;
  }

  // 查看是否是node自帶模塊, 如http,fs等, 是就直接返回
  const mod = loadNativeModule(filename, request);
  if (mod && mod.canBeRequiredByUsers) return mod.exports;

  // 根據文件路徑初始化一個模塊
  const module = cachedModule || new Module(filename, parent);

  // ...
  // 將該模塊加入模塊緩存中
  Module._cache[filename] = module;
  if (parent !== undefined) {
    relativeResolveCache[relResolveCacheIdentifier] = filename;
  }

  // ...
  // 進行模塊的加載
  module.load(filename);

  return module.exports;
};

至此, node 的模塊原理流程基本過完了。目前 node v13.2.0 版本起已經正式支持 ESM 特性。

__filename, __dirname

在接觸 node 中,你是否會困惑 __filename, __dirname是從哪里來的, 為什么會有這些變量呢? 仔細閱讀該章節,你會對這些有系統性的了解。

  • 順著上面的 require 源碼繼續走, 當一個模塊加載時, 會對模塊內容讀取

  • 將內容包裹成函數體

  • 將拼接的函數字符串編譯成函數

  • 執行編譯后的函數, 傳入對應的參數

Module.prototype._compile = function(content, filename) {
  // ...
  const compiledWrapper = wrapSafe(filename, content, this);
  // 
  result = compiledWrapper.call(thisValue, exports, require, module,
                                    filename, dirname);
  
  // ...
  return result;
};
function wrapSafe(filename, content, cjsModuleInstance) {
  // ...
  const wrapper = Module.wrap(content);
  // ...
}
let wrap = function(script) {
  return Module.wrapper[0] + script + Module.wrapper[1];
};

const wrapper = [
  '(function (exports, require, module, __filename, __dirname) { ',
  '\n});'
];

ObjectDefineProperty(Module, 'wrap', {
  get() {
    return wrap;
  },

  set(value) {
    patched = true;
    wrap = value;
  }
});

綜上, 也就是之所以模塊里面有__dirname,__filename, module, exports, require這些變量, 其實也就是 node 在執行過程傳入的, 看完是否解決了多年困惑的問題^_^

NodeJS 中使用 ES Modules

  • package.json增加"type": "module"配置

// test.mjs
export default {
	a: 'xxx'
}
// import.js
import a from './test.mjs';

console.log(a); // {a: 'xxx'}

import 與 require 兩種機制的區別

較明顯的區別是在于執行時機:

  • ES 模塊在執行時會將所有import導入的模塊會先進行預解析處理, 先于模塊內的其他模塊執行

// entry.js
console.log('execute entry');
let a = require('./a.js')

console.log(a);

// a.js
console.log('-----a--------');

module.exports = 'this is a';
// 最終輸出順序為:
// execute entry
// -----a--------
// this is a
// entry.js
console.log('execute entry');
import b from './b.mjs';

console.log(b);

// b.mjs
console.log('-----b--------');

export default 'this is b';
// 最終輸出順序為:
// -----b--------
// execute entry
// this is b
  • import 只能在模塊的頂層,不能在代碼塊之中(比如在if代碼塊中),如果需要動態引入, 需要使用import()動態加載;

ES 模塊對比 CommonJS 模塊, 還有以下的區別:

  • 沒有 requireexportsmodule.exports

    在大多數情況下,可以使用 ES 模塊 import 加載 CommonJS 模塊。(CommonJS 模塊文件后綴為 cjs) 如果需要引入.js后綴的 CommonJS 模塊, 可以使用module.createRequire()在 ES 模塊中構造require函數

// test.cjs
export default {
a: 'xxx'
}
// import.js
import a from './test.cjs';

console.log(a); // {a: 'xxx'}
// test.cjs
export default {
a: 'xxx'
}
// import.js
import a from './test.cjs';

console.log(a); // {a: 'xxx'}
// test.cjs
export default {
a: 'xxx'
}
// import.mjs
import { createRequire } from 'module';
const require = createRequire(import.meta.url);

// test.js 是 CommonJS 模塊。
const siblingModule = require('./test');
console.log(siblingModule); // {a: 'xxx'}
  • 沒有 __filename 或 __dirname

    這些 CommonJS 變量在 ES 模塊中不可用。

  • 沒有 JSON 模塊加載

    JSON 導入仍處于實驗階段,僅通過 --experimental-json-modules 標志支持。

  • 沒有 require.resolve

  • 沒有 NODE_PATH

  • 沒有 require.extensions

  • 沒有 require.cache

ES 模塊和 CommonJS 的相互引用

在 CommonJS 中引入 ES 模塊

由于 ES Modules 的加載、解析和執行都是異步的,而 require() 的過程是同步的、所以不能通過 require() 來引用一個 ES6 模塊。

ES6 提議的 import() 函數將會返回一個 Promise,它在 ES Modules 加載后標記完成。借助于此,我們可以在 CommonJS 中使用異步的方式導入 ES Modules:

// b.mjs
export default 'esm b'
// entry.js
(async () => {
	let { default: b } = await import('./b.mjs');
	console.log(b); // esm b
})()

在 ES 模塊中引入 CommonJS

在 ES6 模塊里可以很方便地使用 import 來引用一個 CommonJS 模塊,因為在 ES6 模塊里異步加載并非是必須的:

// a.cjs
module.exports = 'commonjs a';
// entry.js
import a from './a.cjs';

console.log(a); // commonjs a

至此,提供 2 個 demo 給大家測試下上述知識點是否已經掌握,如果沒有掌握可以回頭再進行閱讀。

demo module.exports&exports

// a模塊
exports.value = 2;

// b模塊代碼
let a = require('./a');

console.log(a); // {value: 2}

demo module.exports&exports

// a模塊
exports = 2;

// b模塊代碼
let a = require('./a');

console.log(a); // {}

require&_cache 模塊緩存機制

// origin.js
let count = 0;

exports.addCount = function () {
	count++
}

exports.getCount = function () {
	return count;
}

// b.js
let { getCount } = require('./origin');
exports.getCount = getCount;

// a.js
let { addCount, getCount: getValue } = require('./origin');
addCount();
console.log(getValue()); // 1
let { getCount } = require('./b');
console.log(getCount()); // 1

require.cache

根據上述例子, 模塊在 require 引入時會加入緩存對象require.cache中。 如果需要刪除緩存, 可以考慮將該緩存內容清除,則下次require模塊將會重新加載模塊。

let count = 0;

exports.addCount = function () {
	count++
}

exports.getCount = function () {
	return count;
}

// b.js
let { getCount } = require('./origin');
exports.getCount = getCount;

// a.js
let { addCount, getCount: getValue } = require('./origin');
addCount();
console.log(getValue()); // 1
delete require.cache[require.resolve('./origin')];
let { getCount } = require('./b');
console.log(getCount()); // 0

以上就是Nodejs中的模塊系統該如何使用,小編相信有部分知識點可能是我們日常工作會見到或用到的。希望你能通過這篇文章學到更多知識。更多詳情敬請關注億速云行業資訊頻道。

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

张掖市| 永靖县| 乐至县| 佛冈县| 平罗县| 固始县| 祁东县| 河源市| 辽中县| 昌都县| 运城市| 沅江市| 贵德县| 正镶白旗| 安龙县| 汾西县| 罗田县| 安宁市| 张家川| 铜陵市| 赤壁市| 武夷山市| 襄城县| 县级市| 宾川县| 丽江市| 沙洋县| 南安市| 隆林| 蓬安县| 青岛市| 文水县| 文化| 祁阳县| 辽阳市| 德钦县| 常州市| 隆尧县| 西林县| 怀来县| 凤阳县|