小程序循环require之坑

栏目: JavaScript · 发布时间: 5年前

内容简介:在JavaScript中,模块之间可能出现相互引用的情况,例如现在有三个模块,他们之间的相互引用关系如下,大致的引用关系可以表示为 A -> B -> C -> A,要完成模块A,它依赖于模块C,但是模块C反过来又依赖于模块A,此时就出现了循环require。那JS引擎会一直循环require下去吗?答案是不会的,如果我们以在正常情况下,JS引擎是可以解析循环require的情形的。但是在一些低版本的小程序中,居然出现程序一直循环require的情况,最终导致栈溢出而报错,实在是天坑。

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 '';
}

以上所述就是小编给大家介绍的《小程序循环require之坑》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

机器学习基础教程

机器学习基础教程

(英)Simon Rogers,、Mark Girolami / 郭茂祖、王春宇 刘扬 刘晓燕、刘扬、刘晓燕 / 机械工业出版社 / 2014-1 / 45.00

本书是一本机器学习入门教程,包含了数学和统计学的核心技术,用于帮助理解一些常用的机器学习算法。书中展示的算法涵盖了机器学习的各个重要领域:分类、聚类和投影。本书对一小部分算法进行了详细描述和推导,而不是简单地将大量算法罗列出来。 本书通过大量的MATLAB/Octave脚本将算法和概念由抽象的等式转化为解决实际问题的工具,利用它们读者可以重新绘制书中的插图,并研究如何改变模型说明和参数取值。......一起来看看 《机器学习基础教程》 这本书的介绍吧!

CSS 压缩/解压工具
CSS 压缩/解压工具

在线压缩/解压 CSS 代码

RGB转16进制工具
RGB转16进制工具

RGB HEX 互转工具

MD5 加密
MD5 加密

MD5 加密工具