详解小程序循环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 相关文章推荐
半角全角相互转换的js函数
Oct 16 Javascript
ExtJs 3.1 XmlTreeLoader Example Error
Feb 09 Javascript
jquery.messager.js插件导致页面抖动的解决方法
Jul 14 Javascript
详解springmvc 接收json对象的两种方式
Dec 06 Javascript
vue2滚动条加载更多数据实现代码
Jan 10 Javascript
jquery封装插件时匿名函数形参和实参的写法解释
Feb 14 Javascript
原生JS封装animate运动框架的实例
Oct 12 Javascript
详解微信小程序调起键盘性能优化
Jul 24 Javascript
jQuery操作元素的内容和样式完整实例分析
Jan 10 jQuery
JavaScript canvas仿代码流瀑布
Feb 10 Javascript
vue如何在用户要关闭当前网页时弹出提示的实现
May 31 Javascript
浅析 Vue 3.0 的组装式 API(一)
Aug 31 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实现执行外部程序的方法详解
2017/08/17 PHP
php session_decode函数用法讲解
2019/05/26 PHP
php策略模式简单示例分析【区别于工厂模式】
2019/09/25 PHP
Avengerls vs Newbee BO3 第二场2.18
2021/03/10 DOTA
驱动事件的addEvent.js代码
2007/03/27 Javascript
Cookie 小记
2010/04/01 Javascript
跨域请求之jQuery的ajax jsonp的使用解惑
2011/10/09 Javascript
jQuery性能优化28条建议你值得借鉴
2013/02/16 Javascript
在Node.js应用中使用Redis的方法简介
2015/06/24 Javascript
angularjs在ng-repeat中使用ng-model遇到的问题
2016/01/21 Javascript
分享js粘帖屏幕截图到web页面插件screenshot-paste
2020/08/21 Javascript
jQuery实现checkbox的简单操作
2017/11/18 jQuery
详解如何在React组件“外”使用父组件的Props
2018/01/12 Javascript
深入理解js 中async 函数的含义和用法
2018/05/13 Javascript
JavaScript+Canvas实现彩色图片转换成黑白图片的方法分析
2018/07/31 Javascript
使用VUE+iView+.Net Core上传图片的方法示例
2019/01/04 Javascript
vue学习笔记五:在vue项目里面使用引入公共方法详解
2019/04/04 Javascript
封装微信小程序http拦截器过程解析
2019/08/13 Javascript
关于vue利用postcss-pxtorem进行移动端适配的问题
2019/11/20 Javascript
Python查询阿里巴巴关键字排名的方法
2015/07/08 Python
Python实现的朴素贝叶斯分类器示例
2018/01/06 Python
一百多行python代码实现抢票助手
2018/09/25 Python
python对列进行平移变换的方法(shift)
2019/01/10 Python
浅谈python量化 双均线策略(金叉死叉)
2020/06/03 Python
python源文件的字符编码知识点详解
2021/03/04 Python
HTML5 常用语法一览(列举不支持的属性)
2010/01/26 HTML / CSS
html5 video标签屏蔽右键视频另存为的js代码
2013/11/12 HTML / CSS
不可轻视HTML5!App三年内将被html5顶替彻底消失
2015/11/18 HTML / CSS
String是最基本的数据类型吗?
2013/06/13 面试题
委托与事件是什么关系?为什么要使用委托
2014/04/18 面试题
给国外客户的邀请函
2014/01/30 职场文书
党员组织生活会发言材料
2014/10/17 职场文书
党的群众路线教育实践活动个人整改落实情况汇报
2014/10/28 职场文书
2015初中政治教学工作总结
2015/07/21 职场文书
单位病假条范文
2015/08/17 职场文书
postgresql之greenplum字符串去重拼接方式
2023/05/08 PostgreSQL