详解小程序循环require之坑

 更新时间:2019年03月08日 09:42:28   作者:没有角的牛  
这篇文章主要介绍了详解小程序循环require之坑,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

1. 循环require

在JavaScript中,模块之间可能出现相互引用的情况,例如现在有三个模块,他们之间的相互引用关系如下,大致的引用关系可以表示为 A -> B -> C -> A,要完成模块A,它依赖于模块C,但是模块C反过来又依赖于模块A,此时就出现了循环require。

// a.js
const B = require('./b.js');

console.log('B in A', B);
const A = {
  name: 'A',
  childName: B.name,
};
module.exports = A;

// b.js
const C = require('./c.js');

console.log('C in B', C);
const B = {
  name: 'B',
  childName: C.name,
}
module.exports = B;

// c.js
const A = require('./a.js');

console.log('A in C', A);
const C = {
  name: 'C',
  childName: A.name,
};
module.exports = C;

那JS引擎会一直循环require下去吗?答案是不会的,如果我们以a.js为入口执行程序,C在引用A时,a.js已经执行,不会再重新执行a.js,因此c.js获得的A对象是一个空对象(因为a.js还没执行完成)。

2. 小程序中的坑

在正常情况下,JS引擎是可以解析循环require的情形的。但是在一些低版本的小程序中,居然出现程序一直循环require的情况,最终导致栈溢出而报错,实在是天坑。

那如何解决呢,很遗憾,目前并未找到完美的方法来解决,只能找到程序中的循环require的代码,并进行修改。为了快速定位程序中的循环引用,写了一段NodeJs检测代码来检测进行检测。

const fs = require('fs');
const path = require('path');
const fileCache = {};
const requireLink = [];

if (process.argv.length !== 3) {
 console.log(`please run as: node ${__filename.split(path.sep).pop()} file/to/track`);
 return;
}

const filePath = process.argv[2];
const absFilePath = getFullFilePath(filePath);
if (absFilePath) {
 resolveRequires(absFilePath, 0);
} else {
 console.error('file not exist:', filePath);
}

/**
 * 递归函数,解析文件的依赖
 * @param {String} file 引用文件的路径
 * @param {Number} level 文件所在的引用层级
 */
function resolveRequires(file, level) {
 requireLink[level] = file;
 for (let i = 0; i < level; i ++) {
  if (requireLink[i] === file) {
   console.log('**** require circle detected ****');
   console.log(requireLink.slice(0, level + 1));
   console.log();
   return;
  }
 }
 const requireFiles = getRequireFiles(file);
 requireFiles.forEach(file => resolveRequires(file, level + 1));
}

/**
 * 获取文件依赖的文件
 * @param {String} filePath 引用文件的路径
 */
function getRequireFiles(filePath) {
 if (!fileCache[filePath]) {
  try {
   const fileBuffer = fs.readFileSync(filePath);
   fileCache[filePath] = fileBuffer.toString();
  } catch(err) {
   console.log('read file failed', filePath);
   return [];
  }
 }
 const fileContent = fileCache[filePath];

 // 引入模块的几种形式
 const requirePattern = /require\s*\(['"](.*?)['"]\)/g;
 const importPattern1 = /import\s+.*?\s+from\s+['"](.*?)['"]/g;
 const importPattern2 = /import\s+['"](.*?)['"]/g;

 const requireFilePaths = [];
 const baseDir = path.dirname(filePath);
 let match = null;
 while ((match = requirePattern.exec(fileContent)) !== null) {
  requireFilePaths.push(match[1]);
 }
 while ((match = importPattern1.exec(fileContent)) !== null) {
  requireFilePaths.push(match[1]);
 }
 while ((match = importPattern2.exec(fileContent)) !== null) {
  requireFilePaths.push(match[1]);
 }

 return requireFilePaths.map(fp => getFullFilePath(fp, baseDir)).filter(fp => !!fp);
}

/**
 * 获取文件的完整绝对路径
 * @param {String} filePath 文件路径
 * @param {String} baseDir 文件路径的相对路径
 */
function getFullFilePath(filePath, baseDir) {
 if (baseDir) {
  filePath = path.resolve(baseDir, filePath);
 } else {
  filePath = path.resolve(filePath);
 }

 if (fs.existsSync(filePath)) {
  const stat = fs.statSync(filePath);
  if (stat.isDirectory() && fs.existsSync(path.join(filePath, 'index.js'))) {
   return path.join(filePath, 'index.js');
  } else if (stat.isFile()){
   return filePath;
  }
 } else if (fs.existsSync(filePath + '.js')) {
  return filePath + '.js';
 }

 return '';
}

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持脚本之家。

相关文章

  • JavaScript实现HSL拾色器

    JavaScript实现HSL拾色器

    这篇文章主要为大家详细介绍了JavaScript实现HSL拾色器,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2020-05-05
  • javascript ES6中set集合、map集合使用方法详解与源码实例

    javascript ES6中set集合、map集合使用方法详解与源码实例

    这篇文章主要介绍了javascript ES6中set、map使用方法详解与源码实例,需要的朋友可以参考下
    2022-12-12
  • 深入分析javascript中console命令

    深入分析javascript中console命令

    console对象是JavaScript的原生对象,它有点像Unix系统的标准输出stdout和标准错误stderr,可以输出各种信息用来调试程序,而且还提供了很多额外的方法,供开发者调用。它的常见用途有两个。显示网页代码运行时的错误信息。提供了一个命令行接口,用来与网页代码互动。
    2016-08-08
  • 前端实现Excel文件导出功能的完整代码解析(vue实现excel文件导出)

    前端实现Excel文件导出功能的完整代码解析(vue实现excel文件导出)

    在Vue中实现导出Excel文件有多种方式,可以通过前端实现,也可以通过前后端配合实现,下面这篇文章主要给大家介绍了关于前端实现Excel文件导出功能(vue实现excel文件导出)的相关资料,需要的朋友可以参考下
    2024-05-05
  • js如何引入wasm文件

    js如何引入wasm文件

    这篇文章主要介绍了js如何引入wasm文件问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2024-04-04
  • javascript输入CD-KEY自动分割的代码

    javascript输入CD-KEY自动分割的代码

    开发过程中用写的一个脚本,记录下来以备后用与他用,其中attributes["max"].nodeValue是取HTML自定义的 max属性(兼容Firefox和IE)
    2010-10-10
  • input输入框的自动匹配(原生代码)

    input输入框的自动匹配(原生代码)

    功能要求:使用原生代码实现,不可使用任何框架、只针对英文字符进行匹配,并且匹配到的内容在菜单中加粗、通过键盘上的上下箭头可以对菜单进行选择等等,感兴趣的你可以参考下
    2013-03-03
  • 纯前端使用ffmpeg实现视频压缩的具体方法及踩坑

    纯前端使用ffmpeg实现视频压缩的具体方法及踩坑

    这篇文章主要给大家介绍了关于纯前端使用ffmpeg实现视频压缩的具体方法及踩坑,要在前端使用FFmpeg进行视频压缩,你可以使用FFmpeg的JavaScript,文中通过代码介绍的非常详细,需要的朋友可以参考下
    2023-12-12
  • JS正则表达式替换字符串replace()方法实例代码

    JS正则表达式替换字符串replace()方法实例代码

    正则表达式是用于匹配字符串中字符组合的模式,在js中正则表达式是对象,这篇文章主要给大家介绍了关于JS正则表达式替换字符串replace()方法的相关资料,文中通过示例代码介绍的非常详细,需要的朋友可以参考下
    2022-07-07
  • jsp网页搜索结果中实现选中一行使其高亮

    jsp网页搜索结果中实现选中一行使其高亮

    在做搜索结果时为了好看需要将选中的行变的高亮些,下面有个不错的示例,需要的朋友可以参考下
    2014-02-02

最新评论