Rumah  >  Artikel  >  hujung hadapan web  >  Fahami sistem modul dalam Nodejs dengan cepat dalam satu artikel

Fahami sistem modul dalam Nodejs dengan cepat dalam satu artikel

青灯夜游
青灯夜游ke hadapan
2021-09-14 10:32:102454semak imbas

Artikel ini akan membawa anda melalui sistem modul dalam Nodejs Saya harap ia akan membantu anda!

Fahami sistem modul dalam Nodejs dengan cepat dalam satu artikel

Latar belakang modular

JavaScript awal digunakan untuk melaksanakan logik interaksi halaman yang mudah, tetapi dengan perkembangan zaman, penyemak imbas Tidak hanya ia boleh menyajikan interaksi mudah, pelbagai laman web telah mula bersinar. Apabila tapak web mula menjadi lebih kompleks dan kod bahagian hadapan meningkat, berbanding bahasa statik lain, kekurangan modulariti JavaScript mula didedahkan, seperti konflik penamaan. Oleh itu, untuk memudahkan penyelenggaraan dan pengurusan kod front-end, komuniti mula menentukan spesifikasi modular. Dalam proses ini, banyak spesifikasi modular telah muncul, seperti CommonJS, AMD, CMD, ES modules Artikel ini menerangkan terutamanya modularisasi yang dilaksanakan dalam Node berdasarkan CommonJS.

Spesifikasi CommonJS

Pertama sekali, dalam dunia Nod, sistem modul mematuhi spesifikasi CommonJS, yang ditakrifkan dalam spesifikasi CommonJS. Ringkasnya:

  • Setiap fail ialah modul
  • Mewakili maklumat modul melalui objek module
  • Gunakan exports untuk mengeksport maklumat yang terdedah oleh modul
  • Rujuk modul melalui require

Klasifikasi modul nod

  • Modul teras: seperti fs, http, laluan dan modul lain , modul ini tidak perlu dipasang dan sudah dimuatkan dalam memori semasa masa jalan. [Pembelajaran yang disyorkan: "tutorial nodejs"]
  • Modul pihak ketiga: disimpan dalam node_modules melalui pemasangan.
  • Modul tersuai: terutamanya merujuk kepada modul fail, yang diperkenalkan melalui laluan mutlak atau laluan relatif.

Objek modul

Seperti yang kami katakan di atas, fail ialah modul dan objek modul digunakan untuk menerangkan maklumat modul semasa sepadan dengan sifat berikut: - id: id modul semasa - laluan: laluan yang sepadan dengan modul semasa - eksport: pembolehubah yang didedahkan oleh modul semasa kepada dunia luar - ibu bapa: juga merupakan objek modul, menunjukkan modul induk modul semasa, iaitu modul yang memanggil modul semasa - nama fail: nama fail modul semasa (laluan mutlak), yang boleh digunakan untuk menambah modul yang dimuatkan ke cache modul global apabila modul diperkenalkan. - dimuatkan: menunjukkan sama ada modul semasa telah dimuatkan - kanak-kanak: ialah tatasusunan yang menyimpan modul yang dipanggil oleh modul semasa - laluan: ialah tatasusunan, merekodkan carian untuk direktori node_modules bermula dari modul semasa, dan mencari secara rekursif ke atas ke direktori node_modules di bawah direktori root

module.exports and exports

Selepas bercakap tentang spesifikasi CommonJS, mari kita bincangkan dahulu tentang perbezaan antara module.exports dan exports.

Pertama sekali, kami menggunakan modul baharu untuk menjalankan pengesahan mudah

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

dan kami dapati bahawa module.exports dan epxorts sebenarnya menunjukkan pembolehubah rujukan yang sama.

demo1

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

// b模块代码
let a = require('./a');

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

Ini juga mengesahkan sebab sifat yang ditambahkan melalui demo1 dan module.exports di atas exports kedua-duanya berbeza apabila modul itu diperkenalkan. Kedua-duanya wujud, kerana kedua-duanya akhirnya menambah atribut kepada pembolehubah rujukan yang sama, boleh disimpulkan bahawa: module.exports dan exports menunjuk kepada pembolehubah rujukan yang sama

demo2.

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

// b模块代码
let a = require('./a');

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

Dalam contoh demo di atas, module.exports telah ditugaskan semula dan exports telah menambah atribut, tetapi selepas modul diperkenalkan, eksport terakhir ialah module.exportsnilai yang ditentukan, ia boleh disimpulkan bahawa modul noed akhirnya mengeksport module.exports, dan exports hanyalah rujukan kepada module.exports, serupa dengan kod berikut:

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

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

Memandangkan dalam Semasa pelaksanaan fungsi, eksport hanya rujukan kepada pembolehubah yang sepadan dengan module.exports asal Apabila memberikan nilai kepada module.exports, pembolehubah yang sepadan dengan exports dan yang terkini module.exports bukan pembolehubah yang sama

memerlukan kaedah

requireProses memperkenalkan modul terutamanya dibahagikan kepada langkah-langkah berikut:

  • 解析文件路径成绝对路径
  • 查看当前需要加载的模块是否已经有缓存, 如果有缓存, 则直接使用缓存的即可
  • 查看是否是 node 自带模块, 如 http,fs 等, 是就直接返回
  • 根据文件路径创建一个模块对象
  • 将该模块加入模块缓存中
  • 通过对应的文件解析方式对文件进行解析编译执行(node 默认仅支持解析.js,.json, .node后缀的文件)
  • 返回加载后的模块 exports 对象

Fahami sistem modul dalam Nodejs dengan cepat dalam satu artikel

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

结语

至此,本文主要介绍了 Node 中基于CommonJS实现的模块化机制,并且通过源码的方式对模块化的整个流程进行了分析,有关于模块的介绍可查看下面参考资料。有疑问的欢迎评论区留言,谢谢。

参考资料

CommonJS 模块

Modul ES

Alamat asal: https://juejin.cn/post/7007233910681632781

Pengarang: Slow Ah

Untuk lebih banyak pengetahuan berkaitan pengaturcaraan, sila lawati: Video Pengaturcaraan! !

Atas ialah kandungan terperinci Fahami sistem modul dalam Nodejs dengan cepat dalam satu artikel. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!

Kenyataan:
Artikel ini dikembalikan pada:juejin.cn. Jika ada pelanggaran, sila hubungi admin@php.cn Padam
Artikel sebelumnya:Cara menggunakan set es6Artikel seterusnya:Cara menggunakan set es6