详解小程序循环require之坑


Posted in Javascript onMarch 08, 2019

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 相关文章推荐
关于jquery.validate1.9.0前台验证的使用介绍
Apr 26 Javascript
JS自调用匿名函数具体实现
Feb 11 Javascript
ANGULARJS中使用JQUERY分页控件
Sep 16 Javascript
AngularJS中关于ng-class指令的几种实现方式详解
Sep 17 Javascript
jQuery自定义组件(导入组件)
Nov 08 Javascript
Ionic项目中Native Camera的使用方法
Jun 07 Javascript
JavaScript实现简单的树形菜单效果
Jun 23 Javascript
彻底理解js面向对象之继承
Feb 04 Javascript
koa上传excel文件并解析的实现方法
Aug 09 Javascript
关于JavaScript中高阶函数的魅力详解
Sep 07 Javascript
Vue封装全局过滤器Filters的步骤
Sep 16 Javascript
JS实现百度搜索框
Feb 25 Javascript
详解js 创建对象的几种方法
Mar 08 #Javascript
浅谈Javascript常用正则表达式应用
Mar 08 #Javascript
validform表单验证的实现方法
Mar 08 #Javascript
webpack4.x下babel的安装、配置及使用详解
Mar 07 #Javascript
Webpack4 使用Babel处理ES6语法的方法示例
Mar 07 #Javascript
深入理解react 组件类型及使用场景
Mar 07 #Javascript
如何优雅地在vue中添加权限控制示例详解
Mar 07 #Javascript
You might like
PHP把空格、换行符、中文逗号等替换成英文逗号的正则表达式
2014/05/04 PHP
PHP实现采集抓取淘宝网单个商品信息
2015/01/08 PHP
经典PHP加密解密函数Authcode()修复版代码
2015/04/05 PHP
javascript 写类方式之二
2009/07/05 Javascript
js写出遮罩层登陆框和对联广告并自动跟随滚动条滚动
2014/04/29 Javascript
jquery实现仿Flash的横向滑动菜单效果代码
2015/09/17 Javascript
基于replaceChild制作简单的吞噬特效
2015/09/21 Javascript
详解js运算符单竖杠“|”与“||”的用法和作用介绍
2016/11/04 Javascript
微信小程序开发的四十个技术窍门总结(推荐)
2017/01/23 Javascript
jQuery实现的鼠标滚轮控制图片缩放功能实例
2017/10/14 jQuery
实例教学如何写vue插件
2017/11/30 Javascript
JS简单判断是否在微信浏览器打开的方法示例
2019/01/08 Javascript
基于JS实现计算24点算法代码实例解析
2020/07/23 Javascript
Python smallseg分词用法实例分析
2015/05/28 Python
深入讲解Python函数中参数的使用及默认参数的陷阱
2016/03/13 Python
实例讲解Python中函数的调用与定义
2016/03/14 Python
windows下python之mysqldb模块安装方法
2017/09/07 Python
对python中Matplotlib的坐标轴的坐标区间的设定实例讲解
2018/05/25 Python
在python中实现调用可执行文件.exe的3种方法
2019/07/07 Python
opencv3/C++图像像素操作详解
2019/12/10 Python
python实现ftp文件传输系统(案例分析)
2020/03/20 Python
动态设置django的model field的默认值操作步骤
2020/03/30 Python
python openpyxl模块的使用详解
2021/02/25 Python
马来西亚时装购物网站:ZALORA马来西亚
2017/03/14 全球购物
Coach澳大利亚官方网站:美国著名时尚奢侈品牌
2017/05/24 全球购物
Hoover胡佛官网:美国吸尘器和洗地机品牌
2019/01/09 全球购物
PatPat阿根廷:妈妈们的购物平台
2019/05/30 全球购物
雅诗兰黛加拿大官网:Estee Lauder加拿大
2019/07/31 全球购物
LORAC官网:美国彩妆品牌
2019/08/27 全球购物
西班牙品牌鞋子、服装和配饰在线商店:Esdemarca
2021/02/17 全球购物
UNIX命令速查表
2012/03/10 面试题
中专生职业生涯规划书范文
2013/12/29 职场文书
整改报告怎么写
2014/11/06 职场文书
离职感谢信
2015/01/21 职场文书
在人间读书笔记
2015/06/30 职场文书
Spring Boot优化后启动速度快到飞起技巧示例
2022/07/23 Java/Android