一、从一个需求开始

前几天接到一个新的需求,不是特别复杂如下图:

要求如下:

  1. 合并树结构下的所有事件与方法到同一个文本编辑器,动态生成方法名称与入参以及系统注释
  2. 文本编辑器内容变更,通知源数据变更
  3. 文本编辑器可以编辑当前入参与返参并需要解析出来
  4. 获取文本编辑器当前焦点,高亮显示当前编辑的树结构

对于一个有多年切图经验的cv仔,大致一设计,就有了实现思路:

  • function生成可以通过字符串拼接搞定
  • 变更通知在Vue下通过Watch可以监听到具体的数据变化,但是苦于前几天排查Watch造成的各种问题,尤其是通知的不确定性造成的性能问题,对Watch抱有一定的敬畏之心,顾此处没有采用,而是采用发布订阅模式,变更之后定向通知到订阅的function下
  • 后面两个字符串的解析问题必须是正则匹配去搞啊

既然有了实现方案,那就说干就干,首先递归解析树结构生成文本编辑器内容

实现订阅发布

 //发布变更notify(methods, data) {if (this.sub[methods]) {this.sub[methods].map(item => {item.notify(data);return item;});}}//订阅/**** @param {sub} 订阅方法* @param {notify} 通知callback*/subscribe(param) {if (typeof param.notify != "function") {return console.error("callback必须为function");}if (!this[param.sub]) {return console.error("不存在["+param.sub+"]订阅方法");}if (!this.sub[param.sub]) {this.sub[param.sub] = [];}this.sub[param.sub].push(param);// console.log(//   "事件【" + param.sub + "】订阅成功,通知到【" + param.notify + "】"// );}
}

在按照Vue模板解析语法实现一套Function解析,整体齐活

解析结果如下:

到目前为止,我们大致已经实现了当初的需求,可是这个解析模板真的能实现我们所有的语法解析么?

二、思考

  • JS语法过于活泛尤其是在function内部如下图,该如何去解析返回?
function test(name){()=>{()=>{(function(){return})()}}
}
  • function 名称被修改之后如何判断,原则上函数名称系统生成不可变更
function test(name){return name
}
//修改成function getName(name){return name
}

基于上面两个痛点问题,如果单纯使用正则匹配的方式去实现,需要实现及其复杂且繁琐的匹配规则,而且场景覆盖可能也没有那么的完善,需要不断在实际运行中不断去完善解析规则,那么我们应该如何去处理?

答案是可以通过AST去替代我们自主完成的语法解析。

三、AST实现 JS 语法解析

在计算机科学中,抽象语法树(Abstract Syntax Tree,AST),或简称语法树(Syntax tree),是源代码语法结构的一种抽象表示。它以树状的形式表现编程语言的语法结构,树上的每个节点都表示源代码中的一种结构。对前端开发而言AST你可能不熟悉但是这却是你日常开发必不可少的,如babel实现高版本JS原发转化为低版本语法,在这里我们就不详细介绍AST相关知识了,感兴趣可以参考陈栋老师的文章【JavaScript AST实现原理揭秘】。接下来我们一起看看如何通过AST去解析获取方法名称以及返回值。

1.引入AST解析依赖包,这里我们采用babel/parser

`
const parser = require("@babel/parser");
let configJsTree = parser.parse(`${StrFun}`,{sourceType: "module",plugins: ["jsx","flow",],});

通过@babel/parser解析之后我们得到如下的树结构

我们先来看看每个字段都代表了什么,才能继续向下分析

字段名称 解释
CommentBlock 块注释
CommentLine 单行注释
FunctionDeclaration 块状域,存放完整的代码块 如 { return a}
ReturnStatement 返回域 程序中的return
Identifier (标志)对象,用来作为函数的唯一标志
ExpressionStatement 表达式
VariableDeclarator 等同于var
BinaryExpression 表达式
ArrowFunctionExpression 箭头函数
本次我们分析能用到的有 Identifier,FunctionDeclaration,ReturnStatement等,接下来我们实现语法的解析
 let body = configJsTree.program.body;let functionList = {};body.forEach(item => {//如果是函数体if (item.type == "FunctionDeclaration") {//获取方法名称let functionName = item.id.name;//获取入参let param = [];if (item.params) {item.params.forEach(jj => {param.push(jj.name);});}if (param.length > 0) {param = param.join(",");} else {param = "";}let hasReturn = false;//获取返参,存在return 并且不是空 returnif (item.body.body.some(kk => kk.type == "ReturnStatement" && kk.argument)) {hasReturn = true;}//获取程序开始位置,以及代码行数let position = `${item.loc.start.line},${item.loc.end.line}`;//获取方法体,由于AST需要逐层解析过于复杂,且业务无需该操作,顾解析当前方法体采用正则匹配let ContentString = res.split("\n");ContentString = ContentString.slice(Number(position.split(",")[0]) - 1,Number(position.split(",")[1]));ContentString = ContentString.join("\n");let content = parseStrinToFunctionContent(ContentString);functionList[functionName] = {functionName,param,hasReturn,position,function: content};}});

解析结果

函数名称变更监听

    ...if(this.oldFunctionList){//如果老数据列表不存在当前函数名称,启动checkif(!this.oldFunctionList[functionName]){for(let i in this.oldFunctionList){if(this.oldFunctionList[i].position==position&&this.oldFunctionList[i].functionName!=functionName){functionName=this.oldFunctionList[i].functionNameitem.id.name=this.oldFunctionList[i].functionNamenameBeChanged=true}}}}....//AST重置回JavaScript,并更新视图if(nameBeChanged){const generate = require("@babel/generator").defaultconfigJsTree.program.body=bodyconst result = generate(configJsTree, {minified: true})this.notify("updateEditor",result.code)}

到目前为止我们已经实现了上面的大部分要求,包括函数名称变更检测以及还原,参数解析,函数名称解析等。

AST的一次简单尝试相关推荐

  1. 桌面应用程序员简单尝试Rich JavaScript Application

    虽然10年前搞过一段时间的Web应用开发,且为所在企业设计了一个基于ASP.NET WebForms(在.NET 1.1的基础上)的Web应用开发框架.但是,后来一直做的都是桌面类的应用,比如Smar ...

  2. Android PC投屏简单尝试—最终章2

    源码地址:https://github.com/deepsadness/AppRemote 上一章中,我们简单实现了PC的投屏功能. 但是还是存在这一些缺陷. 屏幕的尺寸数据是写死的 不能通过PC来对 ...

  3. Android PC投屏简单尝试—最终章1

    回顾之前的几遍文章,我们分别通过RMTP协议和简单的Socket 发送Bitmap图片的Base64编码来完成投屏. 回想这系列文章的想法来源-Vysor,它通过 USB来进行连接的.又看到了 scr ...

  4. Android PC投屏简单尝试(录屏直播)2—硬解章(MediaCodec+RMTP)

    代码地址 :https://github.com/deepsadness/MediaProjectionDemo 想法来源 上一边文章的最后说使用录制的Api进行录屏直播.本来这边文章是预计在5月份完 ...

  5. Android PC投屏简单尝试- 自定义协议章(Socket+Bitmap)

    代码地址 :https://github.com/deepsadness/MediaProjectionDemo 效果预览 投屏效果预览 简单说明: 使用Android MediaProjection ...

  6. 大数据的可视化:bigvis包的简单尝试

    转载自:http://site.douban.com/182577/widget/notes/10568279/note/273907035/ bigvis包是Hadley Wickham的一个新作品 ...

  7. 【Unity入门】软件Unity Hub和Unity的安装和简单尝试

    目录 软件准备: 下载Unity Hub和需要的Unity Unity Hub配置: Unity3D配置: 开始新建项目:​ 简单尝试: 简易Demo: ①先认识场景的创建工具: ​ ②创建场景对象 ...

  8. 对迅雷下载进行投毒的简单尝试

    对迅雷下载进行投毒的简单尝试 2015-9-22 17:52:14教程43条评论 示例: http://www.163点com/typcn233.zip (把 点 换成 . ) 如果你用浏览器直接访问 ...

  9. 用MapReduce实现WordCount(简单尝试MapReduce)

    前言 MapReduce不需要"分割",框架已经做好这一步了. 只需要进行"线程步骤"--Mapper,而且无需计算如何分割(这是Combine的工作) 和&q ...

最新文章

  1. SAP WM初阶之IM层面货物移动后WM层面自动完成TO创建和确认
  2. mysql数据库入门教程(11):视图讲解大全
  3. Linux查看当前http连接,51CTO博客-专业IT技术博客创作平台-技术成就梦想
  4. 国内最大最全的交换技术专区上线
  5. Spring 三种bean装配的方式
  6. 数值分析 有效数字计算
  7. 表格对角线两边打字_word单元格斜线 word表格中斜线表头制作过程及表头斜线两边打字怎么操作...
  8. 第22节 NAT(网络地址转换)—实现公网IP和私网IP之间的转换
  9. 学习Mybatis框架(一)
  10. 小黄鸡 php,小黄鸡simsimi接口
  11. 新版《鹿鼎记》史上最低分!今天用数据分析告诉你韦小宝跟哪个老婆最亲
  12. 常用函数式接口及Stream流
  13. 【软件测试】测试员vs测试工程师,你是测试员还是测试工程师?
  14. Excel公式与函数——每天学一个
  15. java cloassLoader大仙儿
  16. Vue项目实现web端第三方分享(qq、qq空间、微博、微信)
  17. js调试技巧(通过debugger调试)
  18. 放大电路基本性能指标
  19. JavaScript Function中你可能不知道的知识点
  20. spring-cloud-stream通道多线程并发消费

热门文章

  1. 鞋之语告诉你如何开好一个新式洗鞋店
  2. LG OnScreen Control 软件提示‘镜像显示器‘ BUG
  3. 木木的常用软件点评(1)------系统必备软件篇
  4. springboot毕设项目基于微服务的闪聚支付系统设计vwt4i(java+VUE+Mybatis+Maven+Mysql)
  5. [转载]我爱我妻----让男人看一遍哭一遍的文章
  6. c++ pair 用法详解
  7. python接口自动化测试框架实战从设计到开发_【B0753】[java视频教程]Python接口自动化测试框架设计到开发完整版视频教程 it教程...
  8. 关于我求是不是质数的一个错误,输入9判断是质数的原因
  9. VMware虚拟机(ubuntu)使用过程中的一些问题及解决方法
  10. php导出成word试卷,依据word模板批量生成试卷