当前位置:   article > 正文

应用ast抽象语法树修改js函数_js recast

js recast

原理:AST抽象语法树
目标:在每一个函数里面插入一个console.log()把函数传入的全部参数输出出来
关于:本文章是在基于我的个人理解且怕忘记知识所记录下来的给自己看并且分享自己的一个心得,文章可能有不严谨的地方,若有发现,可告知,勿喷

目录

  1. 准备工作
  2. 环境安装
  3. 使用recast
    3.1. recast互转
    3.2. recast遍历
  4. 实战:给每个方法加一个console.log输出这个方法的全部参数

准备工作

  1. node.js
  2. vscode
  3. recast(一个可以把js拆来拆去的东西,这是我们这篇博客的主角)
  4. ast explorer(一个可以在线把代码转换成ast语法树的网站,支持多种编程语言多种转换框架),由于我们使用的是recast来进行拆解,我们需要把这个网站里面的一个类型选择为recast在这里插入图片描述

环境安装

初始化node.js环境

npm init -y
  • 1

使用npm安装recast

npm i recast
  • 1

然后创建一个index.js文件
直接引入这玩意

// module引入方式
import recast from 'recast'
  • 1
  • 2

由于上面的引入用的是module的引入方式,但是node.js默认使用的是commonjs的引入方式

// commonjs引入方式
const recast = require('recast')
  • 1
  • 2

我个人比较喜欢用module这种引入方式,需要在初始化完毕node.js环境后根目录下自动生成的一个package.json文件里面加入一句话,“type”: “module”。整篇文章,将会使用module的引入方式
在这里插入图片描述
接下来创建一个code.js文件,用于放置我们要准备解析的js代码。
在这里插入图片描述
然后我们利用node.js自带的fs(文件操作模块),对代码进行读取

const mCode = fs.readFileSync('code.js', { encoding: 'utf-8' })
  • 1

到这里,我们的基本拆解环境就好咯。

使用recast

recast互转的2个方法(recast.parse, recast.print)
// 把代码转换成ast语法树
const mAst = recast.parse(mCode)
  • 1
  • 2

由于在编辑器里输出查看不是特别方便,我后面将会全部选择使用ast explorer解析网站进行解析
在这里插入图片描述

// 把ast树转换成代码重新输出
console.log(recast.print(mAst).code)
  • 1
  • 2
recast遍历整个树的方法(recast.visit)
recast.visit(ast, methods)
  • 1

这一个参数没啥好说的,直接把解析出来的ast传进去就好了
第二个参数就比较重要了,需要传进去的参数就是遍历类型(这边翻到了一个人整理的常见ast类型,跳转地址: JavaScript 常见 AST 梳理),是recast把一堆ast类型给封装成自带的一些ast类型方法,在recast遍历ast树的时候,我们只需要拿一个这个ast类型然后在前面加上visit就完事了,那我们如何得到这种类型?
有两种方法
第一种:去在线解析网站里面拿
配合ast explorer进行遍历
直接把代码丢到左边的框框里面,然后要看什么地方直接鼠标选取就完事了,然后红色箭头指的地方就是类型,这个类型是Identifier类型,在recast里面,这个类型方法就叫做visitIdentifier

在这里插入图片描述
在这里插入图片描述

第二种:直接翻源代码
如果你英语好的话,也可以直接进入这个recast的源代码里面翻那个类型方法表,就是右边那个密密麻麻的那堆东西,看着挺烦的我还是选择老老实实的去那个在线解析网站里面拿
在这里插入图片描述
既然我们拿到这个ast类型,那我们就可以开始遍历这个树了,那我们就把,每个方法的方法名字遍历下来吧。先去查一下方法名字对应的ast类型。

recast.visit(mAst, {
  visitIdentifier({ node }) {
    console.log(node.name)
    return false
  }
})
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

效果如下
在这里插入图片描述

细心的小伙伴会发现,为什么我的类型方法里面,多了个return false,而且,我只有两个方法,为什么遍历出来了好多好多name呢?

return false是每个类型方法必带的,不然会报错
遍历出来好多的name是因为,你的方法类型选错了喔
通过在线解析网站,我们可以看到,这个方法是一个方法类型FunctionDeclaration
在这里插入图片描述

他有多少个方法,就有多少个FunctionDeclaration,而这个里面就包含着方法的名字,那我们不就是直接遍历这个方法类型,然后取里面的name就好了喔
在这里插入图片描述
诶,这不就完美的输出了两个方法的名字了吗
在这里插入图片描述
但是,我们发现,有一个方法的名字,非常的粗鲁,我们需要把这个名字,变得健康,诶,那直接改对象就可以咯,先判断一下这个方法的名字,然后把这个名字改成hello,最后组合上去就好咯
诶,是不是很方便呢,嘿嘿,感受到ast语法树的快乐了吧,接下来开始我们的正文时间。
在这里插入图片描述

实战

在上面,我们已经把两个方法的名字全部输出来了,同时,我们还实现了修改了某个方法的名字
那我们要更骚一点,我们要直接给这个方法多加一行代码
老样子,上解析网站,为了更方便解析,我把code代码改成

function abc(a, b) {
  alert(1)
}

function cnm(sda,asdas) {
  return 1
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

在这里插入图片描述

在解析网站中,我们发现,一个调用方法他是一个小快快,是在方法的body里面的另一个以数组形式存在的,对应的成员的ast类型是ExpressionStatement,我们不难发现,我们每多一行调用方法,他都会出现一个ExpressionStatement类型的成员,不难看出,我们只需要构造一个这个东西就能造一行调用代码了,那就废话不多说,直接开造
在这里插入图片描述
首先我们分析一下一个调用函数是由啥玩意构成的

{
	type: 'ExpressionStatement', // ast表达式类型
	expression: {
		type: 'CallExpression', // ast引用函数类型
		callee: { // 该函数的一些信息,比如名字啥的都在里面
			type: 'Identifier', // 函数的标志符
			name: '函数名字'
		}
	},
	arguments: [  // 函数参数,这玩意是一个数组
		{
			type: 'Literal', // 直接输入值的类型,也是一个ast类型,还有一个就是Identifier类型
			value: '参数名称'
		}
		... //以此类推的加
	]
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

分析完毕,我们把这个东西,封装成一个方法,方便我们直接调用

function astFunction(options) {
  return {
    type: 'ExpressionStatement',
    expression: {
      type: 'CallExpression',
      callee: {
        type: 'Identifier',
        name: options.name || 'astFunction'
      },
      arguments: options.params || []
    }
  }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

效果如下,我是直接把函数给封装成一个js文件来直接使用,这样子看着代码不会太过于臃肿,我们想要的效果是不是已经实现了,嘿嘿,但是,你发现吧,我们还差一个条件,就是传入参数
在这里插入图片描述
那我们就继续观察解析网站,这不,在函数类型里面,就看到了这个a和b的参数数组,那我们不就直接仿造就好了嘿嘿
在这里插入图片描述
我把这个也封装成了一个方法,方便调用

function astParams(options) {
  return {
    type: 'Identifier',
    name: options.name || 'astParam'
  }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

测试成功
在这里插入图片描述
诶,但是我们这个hello方法没有这个a和b参数呀,那我们要怎么办,嘿嘿,不慌,既然我们知道了这个参数的构成,那我们直接去取不就好了
这回,我们就需要用到一个es6语法的运算符了

... // 三点运算符,能把一个数组或者对象展开
  • 1

由于params参数是一个数组形式,那我们直接用三点运算符整进去就好啦
在这里插入图片描述
在这里插入图片描述
诶,大功告成喔
index.js的代码

import recast from 'recast'
import {
  astFunction,
  astParams
} from './src/astAdd/index.js'
import fs from 'fs'
const mCode = fs.readFileSync('code.js', { encoding: 'utf-8' })
const mAst = recast.parse(mCode)
console.log(mAst)
recast.visit(mAst, {
  visitFunctionDeclaration({ node }) {
    // 给粗鲁的代码改名字
    if (node.id.name === 'cnm') {
      node.id.name = 'hello'
    }
    // 给每个方法加一个console.log并且输出参数
    node.body.body.unshift(astFunction({
      name: 'console.log',
      params: [...node.params]
    }))
    // node.body.push()
    return false
  }
})
// 组合代码
console.log(recast.print(mAst).code)

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27

简单封装的几个函数代码

export function astFunction(options) {
  return {
    type: 'ExpressionStatement',
    expression: {
      type: 'CallExpression',
      callee: {
        type: 'Identifier',
        name: options.name || 'astFunction'
      },
      arguments: options.params || []
    }
  }
}

export function astValueParams(options) {
  return {
    type: 'Literal',
    value: options.name || 'astParam'
  }
}

export function astParams(options) {
  return {
    type: 'Identifier',
    name: options.name || 'astParam'
  }
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28

路过的点个赞吗
ヽ(✿゚▽゚)ノ

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/花生_TL007/article/detail/597720
推荐阅读
相关标签
  

闽ICP备14008679号