下面笔者场景还原一下项目经历面试的过程,借助 STAR 法则来简单介绍一下自己之前在做浏览器API兼容性检查器的过程(通过口述将一件事情清楚描述在面试中也是非常重要的,以下均为口述方式,所以没有图)。
面试官:
我看到你在简历中提到实现了一个检查浏览器 API 兼容性的工具,可以介绍一下么?
我:
(Situation)好的,当时的情况实际上是一次线上的用户的舆情反馈说页面白屏/打不开,通过 JSError 日志的排查我发现最近出现大量类似 IntersectionObserver is not defined 的日志,同时和我最近一次发布的模块曝光需求时间线是差不多吻合的,所以很快定位到了是当时使用浏览器 IntersectionObserver API 做 DOM 曝光时没有考虑到兼容性的问题。
面试官:
那问题解决了么?
我:
是的,当时定位到问题后通过增加 polyfill 的方式很快解决了这个问题。**(Task)**后来我借着这个问题我自己也进行了思考,其实随着操作系统和浏览器的更新,越来越多的 JS/浏览器的新特性开始被支持。为前端开发带来便利的同时,也会带来一些不可避免的兼容性问题。兼容代码(polyfill)的忽视很容易造成不可预估的问题。但如果只依赖开发人员人工检查兼容性问题并不是最优雅的解决方案,毕竟人工的难免会有遗漏。所以我想是不是能够开发一个集成现有的兼容性检查规则的工具将这个过程自动化。
面试官:
不错,详细介绍一下具体过程吧。
我:
(Action)恩,这个想法诞生之后我就去了解了一下常用的前端兼容性检查网站:Caniuse 和 MDN 这两个是我比较常用的。后来发现这两个网站的检查数据实际上在 Github 上都对应维护了一份静态的检查规则(caniuse-db 和 mdn-browser-compat-data),这些数据都是具有特定结构的 JSON 文件,尽管这两者对浏览器支持程度描述的方式不太一样,但已经能满足得到兼容性数据的基本要求。接下来就是对代码的分析检查,将代码和这些规则进行比较。这个过程需要对代码进行语法逻辑分析,所以我想到了用 Babel 将代码转化成 AST 语法树进行特定遍历。同时我整理常规的 API 的调用方式我发现不外乎几种,比如:NewExpression(构造表达式) 和 CallExpression(调用表达式)。当这些信息都掌握清楚后我觉得这件事情是具备技术可行性的。
面试官:
恩,这个实现过程有没有遇到哪些问题?你是怎么解决的?
我:
(Action)恩有的,刚刚提到 Caniuse 和 MDN 维护的静态 JSON 数据,我在实现过程中将这两份数据进行了格式的统一,目的是将两块数据进行互补同时方便后续进行检查比较。最终事实上得到了接近 9w 条数据,如果直接拿来对比是很影响效率的,所以当时利用 browserlist 可以配置指定目标检查的浏览器范围,比如 iOS Safari 9 以上,通过这一层去过滤在该范围内没有兼容性问题的数据,从而减少对比提升效率,也为开发者提供灵活的配置能力。第二个问题同样也是检查的性能优化,是通过 isReferencedIdentifier 去检测标识符是否有被真正引用到。
最后是这个工具与如何接入发布流程的管控,由于公司的发布流程采用的是云构建的方式,所以我在发布之前先经过这个工具的校验,并且将检查的结果打通消息通知和邮件系统,**( Result )**帮助其他人在发布前得到项目代码的浏览器 API 兼容性检查报告,避免了这类问题的再次出现。这次的经验帮助我加深了对 Babel 和 AST 的理解。
面试官:
那你了解 Babel parse AST 的过程么?
我:
在解析成 AST 过程中有两个阶段:词法分析和语法分析。
词法分析阶段:字符串形式的代码转换为令牌(tokens)流,令牌类似于AST中的节点;
语法分析阶段:把一个令牌流转化为 AST 的形式,同时把令牌中的信息转化为AST的表述结构。
面试官:
你项目中说的 AST 遍历的过程能再详细说说么?
我:
Babel 在处理一个节点时,是以访问者的形式获取节点信息并进行相关操作。这种方式是通过 Visitor 对象来完成的,Visitor 对象中定义了对于各种节点的访问函数,这样就可以针对不同的节点做出不同的处理。比如我在项目过程中主要针对 NewExpression 和 CallExpression 进行处理,通过 path 参数对节点以及节点的父子节点以及进行判断筛选,balabala。