详解小程序循环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 相关文章推荐
JavaScript 中的事件教程
Apr 05 Javascript
js变量以及其作用域详解
Jul 18 Javascript
到处都是jQuery选择器的年代 不了解它们的性能,行吗
Jun 18 Javascript
js怎么终止程序return不行换jfslk
May 30 Javascript
jQuery 获取浏览器所在的IP地址的小例子
Nov 08 Javascript
JQuery插件Marquee.js实现无缝滚动效果
Apr 26 Javascript
Bootstrap select下拉联动(jQuery cxselect)
Jan 04 Javascript
AngularJS中$injector、$rootScope和$scope的概念和关联关系深入分析
Jan 19 Javascript
VUE简单的定时器实时刷新的实现方法
Jan 20 Javascript
js 将多个对象合并成一个对象 assign方法的实现
Sep 24 Javascript
在react项目中使用antd的form组件,动态设置input框的值
Oct 24 Javascript
Vue如何实现组件间通信
May 15 Vue.js
详解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往windows中添加用户
2006/12/06 PHP
php正则修正符用法实例详解
2016/12/29 PHP
PHP的RSA加密解密方法以及开发接口使用
2018/02/11 PHP
JS实多级联动下拉菜单类,简单实现省市区联动菜单!
2007/05/03 Javascript
通过身份证号得到出生日期和性别的js代码
2009/11/23 Javascript
文本框中,回车键触发事件的js代码[多浏览器兼容]
2010/06/07 Javascript
IE6/7 and IE8/9/10(IE7模式)依次隐藏具有absolute或relative的父元素和子元素后再显示父元素
2011/07/31 Javascript
javascript中的toFixed固定小数位数 简单实例分享
2013/07/12 Javascript
JavaScript对数字的判断与处理实例分析
2015/02/02 Javascript
JS实现仿苹果底部任务栏菜单效果代码
2015/08/28 Javascript
js 弹出虚拟键盘修改密码的简单实例
2016/10/10 Javascript
xmlplus组件设计系列之图标(ICON)(1)
2017/05/05 Javascript
jQuery基于cookie实现换肤功能实例
2017/10/14 jQuery
详解vue 组件之间使用eventbus传值
2017/10/25 Javascript
解决angularjs中同步执行http请求的方法
2018/08/13 Javascript
JavaScript实现简单的弹窗效果
2020/05/19 Javascript
Jquery高级应用Deferred对象原理及使用实例
2020/05/28 jQuery
python读取html中指定元素生成excle文件示例
2014/04/03 Python
详细解读Python中的__init__()方法
2015/05/02 Python
python+matplotlib实现礼盒柱状图实例代码
2018/01/16 Python
python使用代理ip访问网站的实例
2018/05/07 Python
利用Python如何实现数据驱动的接口自动化测试
2018/05/11 Python
python判断列表的连续数字范围并分块的方法
2018/11/16 Python
Python解析json代码实例解析
2019/11/25 Python
Python re正则表达式元字符分组()用法分享
2020/02/10 Python
pytorch:model.train和model.eval用法及区别详解
2020/02/20 Python
Django框架静态文件处理、中间件、上传文件操作实例详解
2020/02/29 Python
利用纯CSS3实现动态的自行车特效源码
2017/01/20 HTML / CSS
css3中flex布局宽度不生效的解决
2020/12/09 HTML / CSS
如何用canvas实现在线签名的示例代码
2018/07/10 HTML / CSS
美国鞋类购物网站:Shiekh Shoes
2016/08/21 全球购物
铁路个人事迹材料
2014/01/30 职场文书
三下乡活动方案
2014/01/31 职场文书
班主任开场白
2015/06/01 职场文书
gtx1650怎么样 gtx1650显卡相当于什么级别
2022/04/08 数码科技
Docker容器harbor私有仓库部署和管理
2022/08/05 Servers