详解小程序循环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 相关文章推荐
[原创]来自ImageSee官方 JavaScript图片浏览器
Jan 16 Javascript
JavaScript 对象模型 执行模型
Dec 06 Javascript
IE的fireEvent方法概述及应用
Feb 22 Javascript
jQuery获取浏览器类型和版本号的方法
Jul 05 Javascript
jQuery实现表格隔行及滑动,点击时变色的方法【测试可用】
Aug 20 Javascript
深入分析node.js的异步API和其局限性
Sep 05 Javascript
js时间查询插件使用详解
Apr 07 Javascript
JavaScript 基础表单验证示例(纯Js实现)
Jul 20 Javascript
Javascript网页抢红包外挂实现分享
Jan 11 Javascript
vue-cli项目使用mock数据的方法(借助express)
Apr 15 Javascript
jQuery Migrate 插件用法实例详解
May 22 jQuery
vue 封装 Adminlte3组件的实现
Mar 18 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
天使彦史上最神还原,性别曝光的那一刻,百万网友恋爱了
2020/03/02 国漫
php中将网址转换为超链接的函数
2011/09/02 PHP
php一个找二层目录的小东东
2012/08/02 PHP
PHP多态代码实例
2015/06/26 PHP
关于jQuery UI 使用心得及技巧
2012/10/10 Javascript
如何让页面在打开时自动刷新一次让图片全部显示
2012/12/17 Javascript
javascript对话框使用方法(警告框 javascript确认框 提示框)
2014/01/07 Javascript
javascript类型转换使用方法
2014/02/08 Javascript
Jquery选择器中使用变量实现动态选择例子
2014/07/25 Javascript
使用AngularJS和PHP的Laravel实现单页评论的方法
2015/06/19 Javascript
谈谈我对JavaScript原型和闭包系列理解(随手笔记6)
2015/12/20 Javascript
ES6新特性之Object的变化分析
2017/03/31 Javascript
jQuery实现对网页节点的增删改查功能示例
2017/09/18 jQuery
详解小程序之简单登录注册表单验证
2019/05/13 Javascript
微信小程序聊天功能的示例代码
2020/01/13 Javascript
vue中父子组件传值,解决钩子函数mounted只运行一次的操作
2020/07/27 Javascript
JavaScript实现表单验证功能
2020/12/09 Javascript
[42:24]完美世界DOTA2联赛PWL S2 LBZS vs FTD.C 第三场 11.27
2020/12/01 DOTA
[38:31]完美世界DOTA2联赛PWL S3 Magma vs GXR 第一场 12.13
2020/12/17 DOTA
详细探究Python中的字典容器
2015/04/14 Python
Python Nose框架编写测试用例方法
2017/10/26 Python
详解Python中pandas的安装操作说明(傻瓜版)
2019/04/08 Python
Python 实现微信防撤回功能
2019/04/29 Python
Anaconda和ipython环境适配的实现
2020/04/22 Python
Woolworth官网:澳洲第一大超市
2017/06/25 全球购物
英语系毕业生自荐信
2013/10/31 职场文书
前台接待的工作职责
2013/11/21 职场文书
工程管理专业毕业生自荐信
2014/01/24 职场文书
《大海那边》教学反思
2014/04/09 职场文书
教师中国梦演讲稿
2014/04/23 职场文书
派出所正风肃纪剖析材料
2014/10/10 职场文书
党员教师群众路线个人整改措施
2014/10/28 职场文书
行政主管岗位职责
2015/02/03 职场文书
《学会看病》教学反思
2016/02/17 职场文书
八年级作文之一起的走过日子
2019/09/17 职场文书
SQL Server表分区删除详情
2021/10/16 SQL Server