详解小程序循环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 绑定时间实现代码
May 03 Javascript
jquery必须知道的一些常用特效方法及使用示例(整理)
Jun 24 Javascript
jquery 检测元素是否存在的实例代码
Nov 19 Javascript
在javascript中如何得到中英文混合字符串的长度
Jan 17 Javascript
JavaScript声明变量时为什么要加var关键字
Sep 29 Javascript
JavaScript模拟重力状态下抛物运动的方法
Mar 03 Javascript
微信小程序 网络API Websocket详解
Nov 09 Javascript
Bootstrap中datetimepicker使用小结
Dec 28 Javascript
Vue + Vue-router 同名路由切换数据不更新的方法
Nov 20 Javascript
Vuex 入门教程
Jan 10 Javascript
Vue中实现权限控制的方法示例
Jun 07 Javascript
JS中比Switch...Case更优雅的多条件判断写法
Sep 05 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中浮点数计算比较及取整不准确的解决方法
2015/01/09 PHP
使用Thinkphp框架开发移动端接口
2015/08/05 PHP
PHP实现的贪婪算法实例
2017/10/17 PHP
Yii2框架类自动加载机制实例分析
2018/05/02 PHP
PHP中非常有用却鲜有人知的函数集锦
2019/08/17 PHP
Div Select挡住的解决办法
2008/08/07 Javascript
js操作label给label赋值及取label的值示例
2013/11/07 Javascript
javascript实时显示当天日期的方法
2015/05/20 Javascript
jQuery简单实现验证邮箱格式
2015/07/15 Javascript
程序员必知35个jQuery 代码片段
2015/11/05 Javascript
浅析JSONP技术原理及实现
2016/06/08 Javascript
JavaScript判断数组是否存在key的简单实例
2016/08/03 Javascript
angular-cli修改端口号【angular2】
2017/04/19 Javascript
微信小程序中post方法与get方法的封装
2017/09/26 Javascript
Vue组件创建和传值的方法
2018/08/17 Javascript
scrapyd schedule.json setting 传入多个值问题
2019/08/07 Javascript
layui-select动态选中值的例子
2019/09/23 Javascript
JS实现简单省市二级联动
2019/11/27 Javascript
[02:42]决战东方!DOTA2亚洲邀请赛重启荣耀之争
2017/03/17 DOTA
Python实现合并两个列表的方法分析
2018/05/28 Python
Python脚本完成post接口测试的实例
2018/12/17 Python
matlab中二维插值函数interp2的使用详解
2020/04/22 Python
CSS改变网页中鼠标选中文字背景颜色例子
2014/04/23 HTML / CSS
使用CSS3来制作消息提醒框
2015/07/12 HTML / CSS
几个Shell Script面试题
2014/04/18 面试题
如何掌握自荐信格式呢
2013/11/19 职场文书
邮政员工辞职信
2014/01/16 职场文书
2014自主招生自荐信策略
2014/01/27 职场文书
致标枪运动员加油稿
2014/02/15 职场文书
房产公证书范本
2014/04/10 职场文书
2014年十一国庆向国旗敬礼寄语
2014/04/11 职场文书
产品陈列协议书(标准版)
2014/09/17 职场文书
审计局班子四风对照检查材料思想汇报
2014/10/07 职场文书
海上钢琴师的观后感
2015/06/11 职场文书
Javascript使用integrity属性进行安全验证
2021/11/07 Javascript
【海涛教你打DOTA】剑圣第一人称视角解说
2022/04/01 DOTA