详解小程序循环require之坑
程序员文章站
2023-12-24 19:22:09
1. 循环require
在javascript中,模块之间可能出现相互引用的情况,例如现在有三个模块,他们之间的相互引用关系如下,大致的引用关系可以表示为 a -&...
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 ''; }
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。