详解小程序循环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 相关文章推荐
收集的一些Array及String原型对象的扩展实现代码
Dec 05 Javascript
Javascript 倒计时源代码.(时.分.秒) 详细注释版
May 09 Javascript
如何使用JS获取IE上传文件路径(IE7,8)
Jul 08 Javascript
JQuery对表单元素的基本操作使用总结
Jul 18 Javascript
JavaScript检查某个function是否是原生代码的方法
Aug 20 Javascript
详解javascript高级定时器
Dec 31 Javascript
MUI顶部选项卡的用法(tab-top-webview-main)详解
Oct 08 Javascript
laypage+SpringMVC实现后端分页
Jul 27 Javascript
JavaScript常用进制转换及位运算实例解析
Oct 14 Javascript
JS实现购物车基本功能
Nov 08 Javascript
vue如何使用模拟的json数据查看效果
Mar 31 Vue.js
uniapp 微信小程序 自定义tabBar 导航
Apr 22 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
成本8450万,票房仅2亿,口碑两极分化,又一部DC电影扑街了
2020/04/09 欧美动漫
删除及到期域名的查看(抢域名必备哦)
2008/05/14 PHP
支持中文和其他编码的php截取字符串函数分享(截取中文字符串)
2014/03/13 PHP
教你在PHPStorm中配置Xdebug
2015/07/27 PHP
php微信公众平台交互与接口详解
2016/11/28 PHP
jQuery+jqmodal弹出窗口实现代码分明
2010/06/14 Javascript
javascript对select标签的控制(option选项/select)
2013/01/31 Javascript
使用JavaScript构建JSON格式字符串实现步骤
2013/03/22 Javascript
向左滚动文字 js代码效果
2013/08/17 Javascript
javaScript 动态访问JSon元素示例代码
2013/08/30 Javascript
客户端js性能优化小技巧整理
2013/11/05 Javascript
Javascript模拟加速运动与减速运动代码分享
2014/12/11 Javascript
jQuery事件委托之Safari
2016/07/05 Javascript
Zabbix添加Node.js监控的方法
2016/10/20 Javascript
整理关于Bootstrap过渡动画的慕课笔记
2017/03/29 Javascript
JavaScript 值类型和引用类型的初次研究(推荐)
2017/07/19 Javascript
浅谈ECMAScript6新特性之let、const
2017/08/02 Javascript
使用JS实现气泡跟随鼠标移动的动画效果
2017/09/16 Javascript
Angular弹出模态框的两种方式
2017/10/19 Javascript
Vue实现购物车的全选、单选、显示商品价格代码实例
2019/05/06 Javascript
深入学习js函数的隐式参数 arguments 和 this
2019/06/24 Javascript
Vue实战教程之仿肯德基宅急送App
2019/07/19 Javascript
[00:37]2016完美“圣”典风云人物:AMS宣传片
2016/12/06 DOTA
[01:20:05]DOTA2-DPC中国联赛 正赛 Ehome vs VG BO3 第二场 2月5日
2021/03/11 DOTA
python UNIX_TIMESTAMP时间处理方法分析
2016/04/18 Python
Python基于二分查找实现求整数平方根的方法
2016/05/12 Python
基于Python __dict__与dir()的区别详解
2017/10/30 Python
想学python 这5本书籍你必看!
2018/12/11 Python
Python qrcode 生成一个二维码的实例详解
2020/02/12 Python
自定义实现 PyQt5 下拉复选框 ComboCheckBox的完整代码
2020/03/30 Python
Bravofly德国:预订廉价航班和酒店
2019/09/22 全球购物
市场部规章制度
2014/01/24 职场文书
产品生产计划书
2014/05/07 职场文书
公司总经理任命书
2014/06/05 职场文书
小学生我的梦想演讲稿
2014/08/21 职场文书
2015年企业工作总结范文
2015/04/28 职场文书