在 node
环境中,有两个内置的全局变量无需引入即可直接使用,并且无处不见,它们构成了 nodejs
的模块体系: module
与 require
。以下是一个简单的示例
const fs = require('fs')
const add = (x, y) => x + y
module.exports = add
虽然它们在平常使用中仅仅是引入与导出模块,但稍稍深入,便可见乾坤之大。在业界可用它们做一些比较 trick 的事情,虽然我不大建议使用这些黑科技,但稍微了解还是很有必要。
require
一个 json 文件时会产生缓存,但是重写文件时如何 watch
当我们使用 node
中写一个模块时,实际上该模块被一个函数包裹,如下所示:
(function(exports, require, module, __filename, __dirname) {
// 所有的模块代码都被包裹在这个函数中
const fs = require('fs')
const add = (x, y) => x + y
module.exports = add
});
因此在一个模块中自动会注入以下变量:
exports
require
module
__filename
__dirname
调试最好的办法就是打印,我们想知道 module
是何方神圣,那就把它打印出来!
const fs = require('fs')
const add = (x, y) => x + y
module.exports = add
console.log(module)
module.id
: 如果是 .
代表是入口模块,否则是模块所在的文件名,可见如下的 koa
module.exports
: 模块的导出koa module
❝
module.exports
与exports
有什么关系?[1]❞
从以下源码中可以看到 module wrapper
的调用方 module._compile
是如何注入内置变量的,因此根据源码很容易理解一个模块中的变量:
exports
: 实际上是 module.exports
的引用require
: 大多情况下是 Module.prototype.require
module
__filename
__dirname
: path.dirname(__filename)
// <node_internals>/internal/modules/cjs/loader.js:1138
Module.prototype._compile = function(content, filename) {
// ...
const dirname = path.dirname(filename);
const require = makeRequireFunction(this, redirects);
let result;
// 从中可以看出:exports = module.exports
const exports = this.exports;
const thisValue = exports;
const module = this;
if (requireDepth === 0) statCache = new Map();
if (inspectorWrapper) {
result = inspectorWrapper(compiledWrapper, thisValue, exports,
require, module, filename, dirname);
} else {
result = compiledWrapper.call(thisValue, exports, require, module,
filename, dirname);
}
// ...
}
通过 node
的 REPL 控制台,或者在 VSCode
中输出 require
进行调试,可以发现 require
是一个极其复杂的对象
require
从以上 module wrapper
的源码中也可以看出 require
由 makeRequireFunction
函数生成,如下
// <node_internals>/internal/modules/cjs/helpers.js:33
function makeRequireFunction(mod, redirects) {
const Module = mod.constructor;
let require;
if (redirects) {
// ...
} else {
// require 实际上是 Module.prototype.require
require = function require(path) {
return mod.require(path);
};
}
function resolve(request, options) { // ... }
require.resolve = resolve;
function paths(request) {
validateString(request, 'request');
return Module._resolveLookupPaths(request, mod);
}
resolve.paths = paths;
require.main = process.mainModule;
// Enable support to add extra extension types.
require.extensions = Module._extensions;
require.cache = Module._cache;
return require;
}
❝关于
require
更详细的信息可以去参考官方文档: Node API: require[2]❞
require
函数被用作引入一个模块,也是平常最常见最常用到的函数
// <node_internals>/internal/modules/cjs/loader.js:1019
Module.prototype.require = function(id) {
validateString(id, 'id');
if (id === '') {
throw new ERR_INVALID_ARG_VALUE('id', id,
'must be a non-empty string');
}
requireDepth++;
try {
return Module._load(id, this, /* isMain */ false);
} finally {
requireDepth--;
}
}
而 require
引入一个模块时,实际上通过 Module._load
载入,大致的总结如下:
Module._cache
命中模块缓存,则直接取出 module.exports
,加载结束NativeModule
,则 loadNativeModule
加载模块,如 fs
、http
、path
等模块,加载结束Module.load
加载模块,当然这个步骤也很长,下一章节再细讲// <node_internals>/internal/modules/cjs/loader.js:879
Module._load = function(request, parent, isMain) {
let relResolveCacheIdentifier;
if (parent) {
// ...
}
const filename = Module._resolveFilename(request, parent, isMain);
const cachedModule = Module._cache[filename];
// 如果命中缓存,直接取缓存
if (cachedModule !== undefined) {
updateChildren(parent, cachedModule, true);
return cachedModule.exports;
}
// 如果是 NativeModule,加载它
const mod = loadNativeModule(filename, request);
if (mod && mod.canBeRequiredByUsers) return mod.exports;
// Don't call updateChildren(), Module constructor already does.
const module = new Module(filename, parent);
if (isMain) {
process.mainModule = module;
module.id = '.';
}
Module._cache[filename] = module;
if (parent !== undefined) { // ... }
let threw = true;
try {
if (enableSourceMaps) {
try {
// 如果不是 NativeModule,加载它
module.load(filename);
} catch (err) {
rekeySourceMap(Module._cache[filename], err);
throw err; /* node-do-not-add-exception-line */
}
} else {
module.load(filename);
}
threw = false;
} finally {
// ...
}
return module.exports;
};
「当代码执行 require(lib)
时,会执行 lib
模块中的内容,并作为一份缓存,下次引用时不再执行模块中内容」。
这里的缓存指的就是 require.cache
,也就是上一段指的 Module._cache
// <node_internals>/internal/modules/cjs/loader.js:899
require.cache = Module._cache;
这里有个小测试:
❝有两个文件:
index.js
与utils.js
。utils.js
中有一个打印操作,当index.js
引用utils.js
多次时,utils.js
中的打印操作会执行几次。代码示例如下❞
「index.js」
// index.js
// 此处引用两次
require('./utils')
require('./utils')
「utils.js」
// utils.js
console.log('被执行了一次')
「答案是只执行了一次」,因此 require.cache
,在 index.js
末尾打印 require
,此时会发现一个模块缓存
// index.js
require('./utils')
require('./utils')
console.log(require)
那回到本章刚开始的问题:
❝如何不重启应用热加载模块呢?
❞
答:「删掉 Module._cache
」,但同时会引发问题,如这种 一行 delete require.cache 引发的内存泄漏血案[3]
所以说嘛,这种黑魔法大幅修改核心代码的东西开发环境玩一玩就可以了,千万不要跑到生产环境中去,毕竟黑魔法是不可控的。
module wrapper
包裹,并注入全局变量 require
及 module
等module.exports
与 exports
的关系实际上是 exports = module.exports
require
实际上是 module.require
require.cache
会保证模块不会被执行多次delete require.cache
这种黑魔法本文由哈喽比特于4年以前收录,如有侵权请联系我们。
文章来源:https://mp.weixin.qq.com/s/MQve-AvSmwYI2y0NLoC0VQ
京东创始人刘强东和其妻子章泽天最近成为了互联网舆论关注的焦点。有关他们“移民美国”和在美国购买豪宅的传言在互联网上广泛传播。然而,京东官方通过微博发言人发布的消息澄清了这些传言,称这些言论纯属虚假信息和蓄意捏造。
日前,据博主“@超能数码君老周”爆料,国内三大运营商中国移动、中国电信和中国联通预计将集体采购百万台规模的华为Mate60系列手机。
据报道,荷兰半导体设备公司ASML正看到美国对华遏制政策的负面影响。阿斯麦(ASML)CEO彼得·温宁克在一档电视节目中分享了他对中国大陆问题以及该公司面临的出口管制和保护主义的看法。彼得曾在多个场合表达了他对出口管制以及中荷经济关系的担忧。
今年早些时候,抖音悄然上线了一款名为“青桃”的 App,Slogan 为“看见你的热爱”,根据应用介绍可知,“青桃”是一个属于年轻人的兴趣知识视频平台,由抖音官方出品的中长视频关联版本,整体风格有些类似B站。
日前,威马汽车首席数据官梅松林转发了一份“世界各国地区拥车率排行榜”,同时,他发文表示:中国汽车普及率低于非洲国家尼日利亚,每百户家庭仅17户有车。意大利世界排名第一,每十户中九户有车。
近日,一项新的研究发现,维生素 C 和 E 等抗氧化剂会激活一种机制,刺激癌症肿瘤中新血管的生长,帮助它们生长和扩散。
据媒体援引消息人士报道,苹果公司正在测试使用3D打印技术来生产其智能手表的钢质底盘。消息传出后,3D系统一度大涨超10%,不过截至周三收盘,该股涨幅回落至2%以内。
9月2日,坐拥千万粉丝的网红主播“秀才”账号被封禁,在社交媒体平台上引发热议。平台相关负责人表示,“秀才”账号违反平台相关规定,已封禁。据知情人士透露,秀才近期被举报存在违法行为,这可能是他被封禁的部分原因。据悉,“秀才”年龄39岁,是安徽省亳州市蒙城县人,抖音网红,粉丝数量超1200万。他曾被称为“中老年...
9月3日消息,亚马逊的一些股东,包括持有该公司股票的一家养老基金,日前对亚马逊、其创始人贝索斯和其董事会提起诉讼,指控他们在为 Project Kuiper 卫星星座项目购买发射服务时“违反了信义义务”。
据消息,为推广自家应用,苹果现推出了一个名为“Apps by Apple”的网站,展示了苹果为旗下产品(如 iPhone、iPad、Apple Watch、Mac 和 Apple TV)开发的各种应用程序。
特斯拉本周在美国大幅下调Model S和X售价,引发了该公司一些最坚定支持者的不满。知名特斯拉多头、未来基金(Future Fund)管理合伙人加里·布莱克发帖称,降价是一种“短期麻醉剂”,会让潜在客户等待进一步降价。
据外媒9月2日报道,荷兰半导体设备制造商阿斯麦称,尽管荷兰政府颁布的半导体设备出口管制新规9月正式生效,但该公司已获得在2023年底以前向中国运送受限制芯片制造机器的许可。
近日,根据美国证券交易委员会的文件显示,苹果卫星服务提供商 Globalstar 近期向马斯克旗下的 SpaceX 支付 6400 万美元(约 4.65 亿元人民币)。用于在 2023-2025 年期间,发射卫星,进一步扩展苹果 iPhone 系列的 SOS 卫星服务。
据报道,马斯克旗下社交平台𝕏(推特)日前调整了隐私政策,允许 𝕏 使用用户发布的信息来训练其人工智能(AI)模型。新的隐私政策将于 9 月 29 日生效。新政策规定,𝕏可能会使用所收集到的平台信息和公开可用的信息,来帮助训练 𝕏 的机器学习或人工智能模型。
9月2日,荣耀CEO赵明在采访中谈及华为手机回归时表示,替老同事们高兴,觉得手机行业,由于华为的回归,让竞争充满了更多的可能性和更多的魅力,对行业来说也是件好事。
《自然》30日发表的一篇论文报道了一个名为Swift的人工智能(AI)系统,该系统驾驶无人机的能力可在真实世界中一对一冠军赛里战胜人类对手。
近日,非营利组织纽约真菌学会(NYMS)发出警告,表示亚马逊为代表的电商平台上,充斥着各种AI生成的蘑菇觅食科普书籍,其中存在诸多错误。
社交媒体平台𝕏(原推特)新隐私政策提到:“在您同意的情况下,我们可能出于安全、安保和身份识别目的收集和使用您的生物识别信息。”
2023年德国柏林消费电子展上,各大企业都带来了最新的理念和产品,而高端化、本土化的中国产品正在不断吸引欧洲等国际市场的目光。
罗永浩日前在直播中吐槽苹果即将推出的 iPhone 新品,具体内容为:“以我对我‘子公司’的了解,我认为 iPhone 15 跟 iPhone 14 不会有什么区别的,除了序(列)号变了,这个‘不要脸’的东西,这个‘臭厨子’。