这是我参与「第五届青训营 」伴学笔记创作活动的第 14 天
前端项目中大部分代码都是第三方库的代码,在一些复杂项目中,通过调包可以解决一些很复杂的逻辑,对于复杂的逻辑,通常需要用到数据结构与算法,这些通常都有前人写好了的第三方包。 如果不掌握数据结构与算法,那么就很难解决复杂的问题,特别是当这些复杂的问题无法使用调包来解决时,容易遇到职业天花板。
一、实战案例分享
1、遍历所有文件,用node.js扫描指定目录下所有的文件
使用递归调用。DFS深度优先算法,扫描文件目录下的子目录,直到扫到文件为止
function scanDirectory(directoryPath) {
let allFiles = [];
function readDirectory(path) {
let files = fs.readdirSync(path);
files.forEach((file)=> {
let fullPath = path + '/' + file;
let state = fs.statSync(fullPath);
if(state.isDirectory()) {
readDirectory(fullPath);
} else {
allFiles.push(fullPath);
}
})
}
readDirectory(directoryPath);
return allFiles;
}
2、AST解析器, 相关数据结构:栈
AST即抽象语法树,通过AST解析器,我们可以将代码解析为一棵对象树的结构。每个语法都对应树里面的某些节点。这样,我们可以很方便地进行语法分析和转换。HTML/CSS/JS背后的解析算法都是差不多的。
对于AST解析器而言,最重要的两步是 tokenize(词法分析)和parse(语法分析)。
let foo = function() {} 首先会把代码分成一个个词法单元,便于后续分析,即token数组 ['let','foo','=','function','(',')','{','}']
词法分析器本质上是对代码字符串进行逐个字符的扫描,然后根据一定的语法规则进行分组。
其中,涉及到几个关键的步骤:
(1)确定语法规则,包括语言内置的关键字、单词符'('、分隔符等;
(2)逐个代码字符扫描,根据语法规则进行token分组。 在语法解析阶段,我们会逐个遍历token,构出一个完整的对象结构
3、模块打包器(Bundler) 相关算法:拓扑排序
Bundler是前端工程化中非常核心的一环。知名的Bundler有webpack、rollup、esbuild、turbopack Bundler本身的工程复杂度很高,一部分环节有:
(1)依赖图的建立
(2)循环依赖分析 首先通过AST解析器从入口开始分析模块的依赖关系:
//main.js
import { a } from './utils';
console.log(a)
//util.js
export const a = 1;
export const b = 1;
如上,Bundler在内部会首先初始化main.js的模块对象,然后解析出AST,分析其中的import节点,通过import节点找到它引用的模块,然后对这些模块进行初始化,一次类推构造完整的模块依赖关系。 循环依赖的检查:有向图成环问题——从入口节点出发,对整张图进行后序遍历(拓扑排序)
拓扑排序在涉及到依赖顺序关系的场景下都非常实用: Monorepo一个git仓库下有许多子项目(许多子npm包),子项目之间有依赖关系,比如子项目A、B、C,B是A的依赖,C是B的依赖,那么Monorepo工具会在A构建前将B和C提前进行构建,也就是先构建依赖,再构建自己。这一步就需要用拓扑排序算法进行依赖排序。
4、缓存淘汰处理 相关算法:LRU算法
(1)SSR缓存:服务端将组件渲染为字符串后缓存html结果,在下次访问时直接读取内存中的缓存,跳过了CPU密集型操作。
(2)Vue的keepalive组件缓存了内部组件的实例,避免了再次渲染时重新创建组件、请求数据的开销。
(3)webpack中对loader的结果进行缓存,在二次启动时可以跳过对loader的处理,大幅度提升构建速度 由于内存有限,需要考虑缓存淘汰问题。 阈值:缓存大小超过阈值时将某些缓存节点移除、 LRU(最近最少使用)算法,把最近使用频率最低的节点移除,尽可能保留使用频率高的缓存。
5、相似命令提示 比如git工具中输错命令时,可以给用户提示最接近的命令是哪个 字符串的最小编辑距离:字符串A“增、删、改”多少次才能为字符串B
二、 总结:
编程的本质是用一定的算法来处理一定的数据结构来解决某些特定问题的过程。 前端不只是做做WEB页面,即使实现UI也就要一定的数据结构知识(文件数、嵌套侧边栏)。 前端有UI和交互开发,工程化的工具链(代码压缩的算法)、可视化、跨端等细化领域,各个细分领域有各自的算法。