详解小程序循环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的Select选择框的华丽变身
Aug 23 Javascript
javascript设置连续两次点击按钮时间间隔的方法
Oct 28 Javascript
Jquery组件easyUi实现手风琴(折叠面板)示例
Aug 23 Javascript
原生JS获取元素的位置与尺寸实现方法
Oct 18 Javascript
VSCode配置react开发环境的步骤
Dec 27 Javascript
解决Angular.js中使用Swiper插件不能滑动的问题
Feb 26 Javascript
vue1.0和vue2.0的watch监听事件写法详解
Sep 11 Javascript
js贪心算法 钱币找零问题代码实例
Sep 11 Javascript
jQuery 函数实例分析【函数声明、函数表达式、匿名函数等】
May 19 jQuery
Postman环境变量全局变量使用方法详解
Aug 13 Javascript
antd-DatePicker组件获取时间值,及相关设置方式
Oct 27 Javascript
Javascript实现打鼓效果
Jan 29 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
基于mysql的论坛(1)
2006/10/09 PHP
用php来限制每个ip每天浏览页面数量的实现思路
2015/02/24 PHP
php递归遍历删除文件的方法
2015/04/17 PHP
php中pcntl_fork创建子进程的方法实例
2019/03/14 PHP
Javascript中正则表达式的全局匹配模式分析
2011/04/26 Javascript
使用js获取地址栏中传递的值
2013/07/02 Javascript
jQuery表格列宽可拖拽改变且兼容firfox
2014/09/03 Javascript
javascript制作的cookie封装及使用指南
2015/01/02 Javascript
JavaScript中String.match()方法的使用详解
2015/06/06 Javascript
Javascript表单特效之十大常用原理性样例代码大总结
2016/07/12 Javascript
js+html制作简单验证码
2017/02/16 Javascript
Angular 输入框实现自定义验证功能
2017/02/19 Javascript
CSS3结合jQuery实现动画效果及回调函数的实例
2017/12/27 jQuery
原生JS+HTML5实现的可调节写字板功能示例
2018/08/30 Javascript
详解使用webpack+electron+reactJs开发windows桌面应用
2019/02/01 Javascript
React中阻止事件冒泡的问题详析
2019/04/12 Javascript
JS实现在线ps功能详解
2019/07/31 Javascript
详解Vue中的Props与Data细微差别
2020/03/02 Javascript
简单了解JavaScript弹窗实现代码
2020/05/07 Javascript
JavaScript中arguments的使用方法详解
2020/12/20 Javascript
netbeans7安装python插件的方法图解
2013/12/24 Python
python元组操作实例解析
2014/09/23 Python
Python 字符串转换为整形和浮点类型的方法
2018/07/17 Python
判断python字典中key是否存在的两种方法
2018/08/10 Python
python实现顺序表的简单代码
2018/09/28 Python
python 线性回归分析模型检验标准--拟合优度详解
2020/02/24 Python
Python抖音快手代码舞(字符舞)的实现方法
2021/02/07 Python
tensorflow2.0教程之Keras快速入门
2021/02/20 Python
分享一个H5原生form表单的checkbox特效代码
2018/02/26 HTML / CSS
澳大利亚最便宜的网上药房:Chemist Warehouse
2020/01/30 全球购物
保险专业求职信
2014/07/07 职场文书
建筑工程材料员岗位职责
2015/04/11 职场文书
小学语文课《掌声》教学反思
2016/03/03 职场文书
Python带你从浅入深探究Tuple(基础篇)
2021/05/15 Python
JavaScript实现两个数组的交集
2022/03/25 Javascript
Ruby序列化和持久化存储 Marshal和Pstore介绍
2022/04/18 Ruby