详解小程序循环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操作URL函数修改版
Nov 07 Javascript
JavaScript中继承用法实例分析
May 16 Javascript
基于JQuery和CSS3实现仿Apple TV海报背景视觉差特效源码分享
Sep 21 Javascript
JavaScript实现设计模式中的单例模式的一些技巧总结
May 17 Javascript
js实现砖头在页面拖拉效果
Nov 20 Javascript
vue双向数据绑定原理探究(附demo)
Jan 17 Javascript
详解如何在Angular中快速定位DOM元素
May 17 Javascript
JS基于设计模式中的单例模式(Singleton)实现封装对数据增删改查功能
Feb 06 Javascript
JavaScript继承定义与用法实践分析
May 28 Javascript
浅谈vuex actions和mutation的异曲同工
Dec 13 Javascript
如何使用 vue-cli 创建模板项目
Nov 19 Vue.js
前端框架ECharts dataset对数据可视化的高级管理
Dec 24 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
PHP4在WinXP下IIS和Apache2服务器上的安装实例
2006/10/09 PHP
php中get_defined_constants函数用法实例分析
2015/05/12 PHP
php获取英文姓名首字母的方法
2015/07/13 PHP
PHP中new static()与new self()的比较
2016/08/19 PHP
PHP无限极分类函数的实现方法详解
2017/04/15 PHP
Jquery+asp.net后台数据传到前台js进行解析的方法
2014/05/11 Javascript
JS完整获取IE浏览器信息包括类型、版本、语言等等
2014/05/22 Javascript
JavaScript实现点击文字切换登录窗口的方法
2015/05/11 Javascript
javascript数组常用方法汇总
2016/09/10 Javascript
基于jQuery实现的查看全文功能【实用】
2016/12/11 Javascript
ajax异步请求详解
2017/01/06 Javascript
js仿拉勾网首页穿墙广告效果
2017/03/08 Javascript
jQuery实现的动态文字变化输出效果示例【附演示与demo源码下载】
2017/03/24 jQuery
JavaScript 事件流、事件处理程序及事件对象总结
2017/04/01 Javascript
微信小程序组件之srcoll-view的详解
2017/10/19 Javascript
js jquery 获取某一元素到浏览器顶端的距离实现方法
2018/09/05 jQuery
VueJS 组件参数名命名与组件属性转化问题
2018/12/03 Javascript
vue.js使用v-model实现表单元素(input) 双向数据绑定功能示例
2019/03/08 Javascript
小程序的上传文件接口的注意要点解析
2019/09/17 Javascript
JS算法教程之字符串去重与字符串反转
2020/12/15 Javascript
vue-cli中实现响应式布局的方法
2021/03/02 Vue.js
python 生成不重复的随机数的代码
2011/05/15 Python
python访问纯真IP数据库的代码
2011/05/19 Python
深入浅析Python获取对象信息的函数type()、isinstance()、dir()
2018/09/17 Python
对python指数、幂数拟合curve_fit详解
2018/12/29 Python
python如何实现读取并显示图片(不需要图形界面)
2020/07/08 Python
python批量修改文件名的示例
2020/09/27 Python
纯CSS3实现图片无间断轮播效果
2016/08/25 HTML / CSS
皇家阿尔伯特英国官方商店:Royal Albert骨瓷
2019/03/25 全球购物
论文指导教师评语
2014/04/28 职场文书
教师读书活动总结
2014/05/07 职场文书
三八妇女节演讲稿
2014/05/27 职场文书
企业开业庆典答谢词
2015/01/20 职场文书
教师节感想
2015/08/11 职场文书
2019个人半年工作总结
2019/06/21 职场文书
ICOM R71E和R72E图文对比解说
2022/04/07 无线电