整合营销服务商

电脑端+手机端+微信端=数据同步管理

免费咨询热线:

require.js 禁用缓存

require.js 禁用缓存

每次发版用户都要清除页面缓存

  • Require.js 模块缓存:Require.js 内部有一个简单的模块缓存机制。当通过 require 加载模块时,Require.js 会检查该模块是否已经缓存。如果已经缓存,Require.js 将直接返回缓存的模块,而不会再次加载。这样可以避免重复加载相同的模块,提高性能。

有时候我们禁用缓存,以便及时看到最新的修改。在这种情况下,可以通过给 Require.js 的配置对象添加一个随机的查询参数来防止缓存。例如:

require.config({
 urlArgs: "bust=" + (new Date()).getTime()
});

require(["module"], function(module) {
  // 模块逻辑
});

这会在每次加载模块时,给脚本的 URL 添加一个不同的时间戳参数,迫使浏览器重新下载和执行模块文件,以确保获取最新的代码。

加时间戳禁用缓存在生产环境中不建议使用,因为它会增加页面加载时间。只在开发过程中使用禁用缓存来方便调试和测试。

我们可以在生产环境中加个固定的版本号

、require 是一个函数,同时还挂载着四个属性

  • require.resolve 解析某个文件是否存在,存在就返回此文件的绝对路径,否则会报错
  • require.main 当前所在文件的模块信息
  • require.extensions 对象存放着能解析的文件类型,有 .js、.json、.node 三种类型
  • require.cache 对象存放着已经缓存的模块(一个文件绝对路径为键名,记载着这个文件的信息)

2、require 函数的参数说明

  • 只有一个参数
  • 参数类型都是字符串
  • 参数可以是:
  • 模块(原生内置fs/http/path/https等、第三方包模块express、koa等等)
  • 路径+文件基础名(./express 当前路径、../express 上级路径、/express 绝对路径)
  • 路径+文件基础名+文件扩展名(./express.js 当前路径、../express.json 上级路径、/express.node 绝对路径)

3、reqiure 调用的时候

- 如果引用的是 .js 文件
  - fs模块同步读取文件
  - 包装fs模块同步读取的内容
  - 调用 vm 的 runInThisContext 方法 将 包装字符串 解析为 javascript
  - 调用匿名函数,并传参,最后返回 module.exports 的赋值。

- 如果引用的是 .json 文件
  - 经过fs模块同步读取文件以后用 JSON.parse() 解析返回结果。

- 如果引用的是 .node 文件
  - 则经过 dlopen()方法加载最后编译生成的对象,并且返回结果。

4、require 加载规则

加载规则参考地址:

 https://www.jianshu.com/p/a309864c8357
 https://blog.csdn.net/u010476739/article/details/80925128
 https://www.cnblogs.com/zhoulixue/p/8757546.html
 http://www.javashuo.com/article/p-bywkbpzj-d.html
 https://juejin.cn/post/6844903704123211783

加载的模块类型:

 1、原生内置模块
     存在:原生内置模块缓冲区中,直接返回缓存结果,不执行原生内置模块代码
     不存在:原生内置模块缓冲区中,执行原生内置模块代码,把模块加入缓冲区,并返回执行结果
 2、路径文件模块
 
     (1)、构建文件绝对路径列表
         存在后缀名(.js | .json | .node)
             使用path.resolve("路径") 拼接一个绝对路径,推入文件绝对路径列表
         不存在后缀名
             使用 path.resolve("路径"+".js") 拼接一个绝对路径的 .js 文件,推入文件绝对路径列表
             使用 path.resolve("路径"+".json") 拼接一个绝对路径的 .json 文件,推入文件绝对路径列表
             使用 path.resolve("路径"+".node") 拼接一个绝对路径的 .node 文件,推入文件绝对路径列表
             是否存在 path.resolve("路径") 目录
                 存在
                     是否存在package.json文件
                         存在
                             是否存在main属性(若main没指定后缀名,那会使用 .js | .json | .node,按照顺序查找)
                                 存在
                                     使用path.resolve(path.jon("路径",'main属性值')) 拼接一个绝对路径的文件,推入 文件绝对路径列表
                                 不存在
                                     使用path.resolve(path.jon("路径",'index.js')) 拼接一个绝对路径的 .js 文件,推入 文件绝对路径列表
                                     使用path.resolve(path.jon("路径",'index.json')) 拼接一个绝对路径的 .json 文件,推入 文件绝对路径列表
                                     使用path.resolve(path.jon("路径",'index.node')) 拼接一个绝对路径的 .node 文件,推入 文件绝对路径列表
                         不存在
                             使用path.resolve(path.jon("路径",'index.js')) 拼接一个绝对路径的 .js 文件,推入 文件绝对路径列表
                             使用path.resolve(path.jon("路径",'index.json')) 拼接一个绝对路径的 .json 文件,推入 文件绝对路径列表
                             使用path.resolve(path.jon("路径",'index.node')) 拼接一个绝对路径的 .node 文件,推入 文件绝对路径列表
 
     (2)、遍历文件绝对路径列表 ,查看是否在require.cache['文件路径']缓冲区中
         存在
             读取缓冲区中的模块,不执行文件模块的代码,直接返回缓存的结果(require.cache['文件路径'].exports)
         不存在
             报错找不到模块


 3、第三方包模块
 
     (1)、获取模块可能存在的目录列表 module.paths 
 
     (2)、构建 文件绝对路径列表
 
         遍历 目录列表 module.paths 第一个元素
             使用(path.join(目录元素,包名+".js") 拼接一个绝对路径的 .js 文件,推入 文件绝对路径列表
             使用(path.join(目录元素,包名+".json") 拼接一个绝对路径的 .json 文件,推入 文件绝对路径列表
             使用(path.join(目录元素,包名+".node") 拼接一个绝对路径的 .node 文件,推入 文件绝对路径列表
             判断 path.join(目录元素,包名) 目录是否存
                 存在
                     判断目录下是否存在package.json文件
                         存在
                             是否存在main属性
                                 存在
                                     使用 path.jon(目录元素",包名,'main属性值')) 拼接一个绝对路径的文件,推入 文件绝对路径列表
                                 不存在
                                     使用 path.jon(目录元素",包名,'index.js')) 拼接一个绝对路径的文件,推入 文件绝对路径列表
                                     使用 path.jon(目录元素",包名,'index.json')) 拼接一个绝对路径的文件,推入 文件绝对路径列表
                                     使用 path.jon(目录元素",包名,'index.node)) 拼接一个绝对路径的文件,推入 文件绝对路径列表
                         不存在
                             继续遍历 目录列表 module.paths
                 不存在
                     继续遍历 目录列表 module.paths
 
     (3)、遍历文件绝对路径列表,查看是否在require.cache['文件路径']缓冲区中
         存在
             读取缓冲区中的模块,不执行文件模块的代码,直接返回缓存的结果(require.cache['文件路径'].exports)
         不存在
             报错找不到模块


5、require 3种文件的加载方式

谢 @晓之车 在我上一篇分享(模块化中的import与require及区别)中的提问,我也特定研究了一番,这里是一篇很早的博文,一起来看下作者的分析

Node.js也使用了CommonJS模块机制,最近在InfoQ上有一篇文章讨论了这方面的问题。这篇文章提到Node.js在载入模块时,如果之前该模块已经加载过则不会有重复开销,因为模块加载有缓存机制。这篇文章是我初审的,当时也正好在思考Jscex在Node.js使用时的模块化问题,但研究了它的规则之后,我发现在某些情况下还是可能加载多次。现在我们便来分析这个问题。

当我们使用require方法加载另一模块的时候,Node.js会去查询一系列的目录。我们可以从module.paths中得到这些路径,例如:

[ '/Users/jeffz/Projects/node-test/node_modules',
  '/Users/jeffz/Projects/node_modules',
  '/Users/jeffz/node_modules',
  '/Users/node_modules',
  '/node_modules']

这里是我在运行/User/jeffz/Projects/node-test目录下一个模块时得到的结果。可见,Node.js会从当前模块所在目录的node_modules(这里怎么不遵守Unix习惯,而使用了下划线呢?)开始找起,如果没找到再会去找上级目录的node_modules,直到根目录为止。当然,实际情况下还会有NODE_PATH环境变量标识的目录等等。当模块的位置确定之后,Node.js便会查看这个位置的模块是否已经被加载,如果已加载,则直接返回。

简单地说,Node.js是根据模块所在路径来缓存模块的。

这么看来,“相同模块是否会被加载多次”这个问题,其实就演变成了“相同模块是否会出现在不同路径里”。简单想来这似乎不太可能,因为如果我们要使用某个模块的时候,它的位置总是确定的。例如,使用npm安装的模块,总是会出现在当前目录的node_modules里,加载时总是会找到相同的路径。那么,在“间接”依赖相同模块的情况下呢?

例如我们想要使用Express框架,于是使用npm来安装,便会得到:

$ npm install express
express@2.5.2 ./node_modules/express 
├── mkdirp@0.0.7
├── qs@0.4.0
├── mime@1.2.4
└── connect@1.8.5

可见,Express依赖了其他一些模块,它们都存放在express模块自己的目录里面,例如./node_modules/express/node_modules/mime。好,假如我们项目自身也要使用mime项目,我们自然也可以使用npm来安装:

$ npm install mime
mime@1.2.4 ./node_modules/mime 

于是我们最终得到的是这样的结构:

./node_modules
├── mime
└── express
    └── node_modules
        ├── mkdirp
        ├── qs
        ├── mime
        └── connect

请注意,这里的mime模块便出现在两个位置上,它们名称版本都一致,完全是一个模块。那么试想,如果我们在自己的代码里加载的mime模块,以及express内部加载的mime模块是同一个吗?显然不是,可见,在这里相同的模块被重复加载了两次,产生了两个模块“实例”。

这种重复加载在一般情况下不会有太大问题,最多内存占用大一点而已,不会影响程序的正确性。但是,我们也可以轻易设想到一些意外的情况。例如,在Jscex中,每个Task对象我都会给定一个ID,不断增长。要实现这点我们需要维护一个“种子”,全局唯一。之前这个种子定义在闭包内部,但由于Jscex模块会被加载多次,这样从不同模块“实例”生成的Task对象,它们的ID便有可能重复。当然,解决这个问题也并不困难,只需要将种子定义在根对象上即可,不同的模块“实例”共享相同的根对象。

还有个问题可能就显得隐蔽些了,我们可以通过一个简单的实验来观察结果。我们先来定义一个jeffz-a模块,其中暴露出一个MyType类型:

module.exports.MyType=function () { }

然后将其发布到npm上。然后再写一个jeffz-b模块,依赖jeffz-a,并将jeffz-a中定义的MyType类型直接暴露出去:

module.exports.MyType=require("jeffz-a").MyType;

接着将jeffz-b也发布置npm上。再重新写一个测试模块,使用npm安装jeffz-a和jeffz-b,最终目录会是这样的:

./node_modules
├── jeffz-a
└── jeffz-b
    └── node_modules
        └── jeffz-a

在测试模块内,我们来测试实例与类型之间的关系:

var a=require("jeffz-a");
var b=require("jeffz-b");

console.log(new a.MyType() instanceof a.MyType); // true
console.log(new b.MyType() instanceof b.MyType); // true

console.log(new a.MyType() instanceof b.MyType); // false
console.log(new b.MyType() instanceof a.MyType); // false

从表面上看,jeffz-b和jeffz-a暴露出的应该是相同的MyType类型,它们的对象通过instanceof相互判断应该都返回true,但实际上由于jeffz-b中的jeffz-a,与我们直接加载的jeffz-a模块是不同的实例,因此MyType类型自然也不是同一个了。

这对于Jscex的影响在于,Jscex的异步模块在取消时,原本是通过判断异常对象是否为CanceledError类型来决定Task的状态为cancelled还是faulted。但由于Node.js可能会将相同的模块加载为多个实例,因此即便抛出的的确是某个实例的CancelledError,也无法通过另一个实例内部的判断。因此,目前Jscex的判断方式修改为检查异常对象的isCancellation字段,简单地解决了这个问题。

当然,Node.js这种“重复加载”的影响也并非完全是负面的,至少它天然的解决了多版本共存的问题。例如,express v2.5.2依赖mime v1.2.4,但我们程序自身又想使用mime v1.2.5。此时,express内部自然使用mime v1.2.4,而我们自己的程序使用的便是mime v1.2.5。

有些情况下您可能也想避免这种重复加载,这就必须手动地删除模块内部被间接依赖的模块,将其移动到模块查询路径的公用部分上了。就目前看来,这些操作必须手动进行,因为npm在安装模块时不会关心依赖的模块是否已经安装过了(例如在NODE_PATH环境变量标识的路径里),它一定会重新下载所有依赖的模块。可惜如果您使用的是托管形式的Node.js服务,则很有可能无法做到这一点。

原文地址:Node.js中相同模块是否会被加载多次?-CSDN博客