当前位置: 首页 > news >正文

【高频考点精讲】JavaScript中的访问者模式:从AST解析到数据转换的艺术

大家好呀!今天想和大家聊聊一个既实用又有点"高冷"的设计模式——访问者模式。这个模式在AST解析、Babel插件开发中无处不在,但很多同学可能一直没搞明白它到底妙在哪里。

一、生活中的访问者模式

想象一下你开了一家奶茶店,店里来了几位顾客:

  • 程序员小王:只看配料表里的咖啡因含量
  • 健身达人小李:只关心热量和糖分
  • 环保人士小张:只检查杯子是不是可降解材料

每个顾客都只"访问"他们关心的部分,这就是访问者模式的精髓——将操作与对象结构分离

二、什么是访问者模式?

官方定义太拗口,我用人话解释:访问者模式允许你在不修改对象结构的情况下,定义新的操作。

举个代码例子,假设我们有一个简单的DOM树:

class Element {constructor(name, children = []) {this.name = namethis.children = children}// 关键方法:接受访问者accept(visitor) {visitor.visit(this)this.children.forEach(child => child.accept(visitor))}
}class TextNode {constructor(content) {this.content = content}accept(visitor) {visitor.visit(this)}
}

现在我们可以创建不同的访问者来做不同的事情:

// 创建一个打印访问者
class Printer {visit(node) {if (node instanceof Element) {console.log(`元素节点: ${node.name}`)} else {console.log(`文本节点: ${node.content}`)}}
}// 使用示例
const domTree = new Element('div', [new Element('p', [new TextNode('Hello')]),new TextNode('World')
])domTree.accept(new Printer())
// 输出:
// 元素节点: div
// 元素节点: p
// 文本节点: Hello
// 文本节点: World

三、为什么需要访问者模式?

1. 场景分析

假设我们要实现一个Babel插件,需要对AST进行多种操作:

  • 代码压缩(删除注释、缩短变量名)
  • 代码转换(ES6转ES5)
  • 代码分析(计算复杂度)

没有访问者模式时,我们可能这样写:

function traverse(ast) {// 处理变量声明if (ast.type === 'VariableDeclaration') {// 压缩逻辑// 转换逻辑// 分析逻辑}// 处理函数声明...
}

所有逻辑耦合在一起,像一锅大杂烩!

2. 访问者模式的优势

使用访问者模式后:

// 压缩访问者
class Minifier {VariableDeclaration(node) {// 只关心压缩逻辑}
}// 转换访问者
class Transformer {VariableDeclaration(node) {// 只关心转换逻辑}
}// 分别应用
ast.visit(new Minifier())
ast.visit(new Transformer())

就像把瑞士军刀的不同工具拆分开,每个工具专注一件事。

四、实战:实现一个简易Babel插件

让我们用访问者模式实现一个真实案例:把所有console.log替换为alert。

1. 定义AST节点类型

const ast = {type: 'Program',body: [{type: 'ExpressionStatement',expression: {type: 'CallExpression',callee: {type: 'MemberExpression',object: { type: 'Identifier', name: 'console' },property: { type: 'Identifier', name: 'log' }},arguments: [{ type: 'Literal', value: 'Hello' }]}}]
}

2. 创建转换访问者

class ConsoleToAlertVisitor {// 处理成员表达式(console.log)MemberExpression(node) {if (node.object.name === 'console' && node.property.name === 'log') {node.object.name = ''  // 清空consolenode.property.name = 'alert' // 改为alert}}
}// 简单的遍历函数
function traverse(node, visitor) {if (typeof visitor[node.type] === 'function') {visitor[node.type](node)}// 递归遍历子节点for (const key in node) {if (typeof node[key] === 'object' && node[key] !== null) {traverse(node[key], visitor)}}
}// 应用访问者
traverse(ast, new ConsoleToAlertVisitor())
console.log(JSON.stringify(ast, null, 2))

转换后的AST中,console.log已经被替换成了alert!

五、访问者模式在流行库中的应用

1. Babel中的访问者

Babel的插件系统就是基于访问者模式:

export default function() {return {visitor: {Identifier(path) {// 处理所有标识符},FunctionDeclaration(path) {// 处理函数声明}}}
}

2. ESLint中的访问者

ESLint规则也是类似原理:

module.exports = {create(context) {return {VariableDeclarator(node) {if (node.id.name.length < 3) {context.report(node, '变量名太短啦!')}}}}
}

六、访问者模式的优缺点

👍 优点:

  • 符合开闭原则:新增操作不用修改原有结构
  • 职责分离:每种访问者只关注自己的逻辑
  • 集中管理:相关操作集中在同一个访问者中

👎 缺点:

  • 破坏封装:需要暴露对象内部结构
  • 增加复杂度:简单场景可能过度设计
  • 不适用于频繁变更的结构:每次结构变化都要改所有访问者

七、课后思考题

下面这段代码使用访问者模式实现了一个简单的计算器,但输出结果不符合预期,你能找出问题并修复吗?

class NumberNode {constructor(value) {this.value = value}accept(visitor) {return visitor.visitNumber(this)}
}class AddNode {constructor(left, right) {this.left = leftthis.right = right}accept(visitor) {return visitor.visitAdd(this)}
}class CalculatorVisitor {visitNumber(node) {return node.value}visitAdd(node) {return node.left.accept(this) + node.right.accept(this)}
}// 计算: 1 + 2 + 3
const expr = new AddNode(new NumberNode(1),new AddNode(new NumberNode(2), new NumberNode(3))
)console.log(expr.accept(new CalculatorVisitor())) // 预期输出6,实际输出?

把你的答案写在评论区吧!我会随机抽几位同学的答案进行点评哦~

八、最后总结

访问者模式就像是一个专业团队中的专家顾问:

  • AST是你要审计的公司
  • 每个访问者是不同领域的专家(财务、法律、技术)
  • 专家们轮流审计,各自出具专业报告

这下应该理解了吧!

掌握这个模式后,你再去看Babel插件、ESLint规则,会有种豁然开朗的感觉。下次遇到需要处理复杂对象结构的场景,不妨考虑下访问者模式这个神器!

往期精选

【初级】前端开发工程师面试100题(一)
【初级】前端开发工程师面试100题(二)
【初级】前端开发工程师的面试100题(速记版)

觉得有用的话别忘了【点赞】【收藏】【分享】给朋友,有什么问题在评论区留言,我们下期再见!👋

相关文章:

  • windos端远程控制ubuntu运行脚本程序并转发ubuntu端脚本输出的网页
  • 开发NESMA辅助工具版本之需求匹配
  • 【KWDB 创作者计划】_上位机知识篇---PlatformIO
  • 深入详解Java中的@PostConstruct注解:实现简洁而高效初始化操作
  • 量子计算浪潮下的安全应对之法
  • 一个关于相对速度的假想的故事-7
  • 迅为RK3562开发板ARM四核A53核心板多种系统适配全开源
  • 汽车免拆诊断案例 | 2013款大众辉腾车发动机抖动
  • PHP 反序列化CLI 框架类PHPGGC 生成器TPYiiLaravel 等利用
  • 设计模式之策略模式
  • nginx实现同一个端口监听多个服务
  • 用Python爬取B站热门视频并自动保存到本地
  • Java多线程的暗号密码:5分钟掌握wait/notify
  • AutoGPT超详细教程
  • 服务器数据恢复—双循环RAID5数据恢复揭秘
  • Java Web容器分类及对比
  • BSTREE(二叉搜索树)的介绍与模拟实现
  • 【Nova UI】八、打造组件库第一个组件-图标组件(上):图标组件开发实战攻略
  • 【Java后端】MyBatis 与 MyBatis-Plus 如何防止 SQL 注入?从原理到实战
  • 锁存器知识点详解
  • “家门口的图书馆”有多好?上海静安区居民给出答案
  • 继加州后,美国又有11州起诉特朗普政府滥用关税政策“违法”
  • 接棒路颖,国泰海通证券副总裁谢乐斌履新海富通基金董事长
  • 生态环境部谈拿手持式仪器到海边测辐射:不能测量水中放射性核素含量
  • 北大学者:过度依赖技术工具可能会削弱人类主动思考的能力
  • 稀土管制难倒特斯拉人形机器人“擎天柱”,马斯克:“正与中国协商”