前言

因为比较菜,所以经常需要读一些别人的代码学习学习。

有源码的代码当然好,但是很多网站不开源。这些网站的 js 又都是打包压缩过的,学习起来很难受。

所以我做了一个小工具,通过修改抽象语法树,来处理这些打包压缩过的 js,增强代码可读性,让我们学习起来更容易。

如果再借助重定向线上 js 到本地 js,或者使用 chrome 自带的 override 源码能力,甚至可以轻松调试别人的线上代码。

有了这个工具,我 CV 界大师兄的名号可谓实至名归。

下面是这个工具的代码仓库:boompack

需求

在此之前,其实面对这些压缩过的 js 我是不太想做这个工具的。

通常使用 prettier 美化一下,然后慢慢磨就好了。

但是这次我面对的是一个 canvas 相关的 js,压缩后的核心代码使用 prettier 格式化之后有 2 万多行,看到这份代码之后人都麻了。

这里随便写个压缩代码示例来举例:

function f() {
var a = (c = 33, d = 12), b = 1, g = (e == 2 ? a === 1 && b == 1 || c == 1 && d == 1 && c == 4 : c = 2);
for (var i; i < 10; i++)if (s < 1) s++
return a = 2, d == 2, e = !1, e = !0
}

这份代码其实并不算特别复杂,因为没有十几个逻辑表达式和三元运算交杂在一起。

但是这份代码很典型,因为基本上比较影响阅读的点都有。

我们简单列一下:

  • 大量无意义的单字符变量,修改变量名也无法批量替换
  • 序列表达式很多,即大量语句以逗号分隔,调试时只算做一行代码
  • 多个逻辑表达式混杂不清,需要改为 if 表达式
  • if 或者 for 循环不加花括号
  • 大量三元运算,需要使用 if 和 else
  • !0 和!1 的表达反人类,需要使用 true 和 false
  • return 语句结合序列表达式,实际上只返回最后一个

这些就是主要的困难,特别是当它们各种互相嵌套,又和十几个逻辑运算和三元运算交杂在一起,光是拆解出来就得花个十几分钟。

如果人力拆解这些代码,也不是没有好处,至少你可以化身人肉低端编译器,反复巩固 js 基础,要是碰到一些奇葩公司让你手写代码你就是王者。

但是因为我需要留出时间打游戏的原因,所以还是写了这么个工具简化流程。

使用方法

  • 克隆仓库到本地后
  • yarn install 安装依赖包
  • 将需要转换地压缩代码,复制粘贴到test/from/index.js这个文件中
  • 终端运行脚本 yarn start
  • 最终会在test/to/这个文件夹下生成 index.js,也就是我们最后修改后的文件。

效果

使用工具转换后的 js 代码如下:

function func_f() {
let var_a, var_b, var_g;
c = 33;
var_a = d = 12;
var_b = 1; if (e == 2) {
if (var_a === 1 && var_b == 1) {
if (var_a === 1) {
var_g = var_b == 1;
} else {
var_g = var_a === 1;
}
} else {
if (c == 1 && d == 1) {
var_g = c == 4;
} else {
if (c == 1) {
var_g = d == 1;
} else {
var_g = c == 1;
}
}
}
} else {
var_g = c = 2;
} for (var var_i; var_i < 10; var_i++) {
if (s < 1) {
s++
}
}
let result;
var_a = 2;
d == 2;
e = false;
result = e = true;
return result;
}

可以看到相对于压缩后的代码,我们转换后的代码变长了很多。

这份代码相较于上一份,可读性大大增强了。

另外我已经使用 jQuery 压缩后的文件测试过了,转换没有任何问题。

然而,依然不保证转换后的代码一定正确,js 的 hack 玩法太多,只能说用这个转换肯定可控。

核心玩法:抽象语法树

想要解析修改这种压缩 js,需要用到我们的抽象语法树。

所谓抽象语法树,实际上就是一种树形结构来表示编程语句。

具体可以百度,这里不解释太多,总之你可以理解为可以将一串代码解析成一个树形结构,这个树形结构上面每个节点代表一种语法结构。

这里列一个必备网站:https://astexplorer.net/,用来查看 js 被转换为抽象语法树后的样子。

现在前端的基础库 babel 系列,就是通过抽象语法树将 es6 转换为 es5 的,当然也包括转换 reacttypescript

因为抽象语法树和代码之间是可以相互转换的。

所以我们的核心思路是将代码转换为抽象语法树,然后在这个树上做修改,修改完后再转换为代码。

应用 recast 去转换代码

js 代码和抽象语法树的转换有很多 js 库可以实现。

比如@babel/parserrecast,还有不少其他的库,这里我们使用 recast

我对这个研究也不深入,没怎么了解他们的优缺点,不过当时看到 recast满足需求就直接用了。

可以在 npmjs 上找到 recast,里面有简单的介绍文档:地址,也有仓库地址。

但是 recast 的文档不太够,有的关键点还得自己看下具体的示例和源码才能弄明白,不过也不难。

这里就不展开了,先上一段我自己写的简单代码:

import { parse, print } from "recast";
import { readFile, writeFile } from "fs";
import path from "path";
import modifyAst from "./utils/modifyAst.js"; const fromPath = path.join("./test/from/index.js");
const toPath = path.join("./test/to/index.js"); readFile(fromPath, { encoding: "utf8" }, (err, sourceCode) => {
// 通过recast的parse函数转换为ast语法树
const ast = parse(sourceCode);
modifyAst(ast);
writeFile(toPath, print(ast).code, () => {
console.info("搞完");
});
});

这段代码的用处是从 from 文件夹下的文件获取 js 代码后,通过 recastparse 函数转换为 ast语法树 ,再通过我自定义的函数 modifyAst 来修改语法树后,最后使用 recastprint 函数将 ast语法树 转换为 js 代码。

这段内容比较简单,主要就是借助 recast 将代码转成抽象语法树,再转回代码。

具体修改抽象语法树在 modifyAst 里面:

import addBlock from "./addBlock.js";
import modifyReturn from "./modifyReturn.js";
import modifyUnaryExpression from "./modifyUnaryExpression.js";
// 修改声明中的表达式
import replaceVarName from "./modifyVariableDeclaration/replaceVarName.js";
import modifyDeclarationInit from "./modifyVariableDeclaration/modifyDeclarationInit.js"; // 修改表达式
import modifyExpressionStatement from "./modifyExpressionStatement/index.js"; /**
* 修改抽象语法树
*/
const modifyAst = (ast) => {
modifyUnaryExpression(ast);
replaceVarName(ast);
addBlock(ast);
modifyReturn(ast);
modifyDeclarationInit(ast);
modifyExpressionStatement(ast);
}; export default modifyAst;

modifyAst 中,我将不同的语句修改按照功能进行了划分到,写在了不同的文件中。

本篇博客也不宜展开过多,我只挑一部分代码展示:

import { types, visit } from "recast";

const { blockStatement } = types.builders;

/**
* 找到所有的if和for语句,给他们增加花括号
* @param {抽象语法树} ast
*/
const addBlock = (ast) => {
visit(ast, {
// 找到所有的if语句给他们增加花括号
visitIfStatement: function (path) {
if (
path.node.consequent != null &&
path.node.consequent.type != "BlockStatement"
) {
path.node.consequent = blockStatement([path.node.consequent]);
}
if (
path.node.alternate != null &&
path.node.alternate.type != "BlockStatement"
) {
path.node.alternate = blockStatement([path.node.alternate]);
}
this.traverse(path);
},
});
}; export default addBlock;

上面这部分代码的作用是遍历抽象树中所有的 if 语句,给那些没加花括号的 if 语句加上花括号。

实际上就是使用 recastvisit 方法遍历抽象语法树。visitIfStatement 这个回调函数,就是在遍历到 if 语句后执行的函数。

在函数中有两个 if 语句,那就是判断以及修改的代码,这个不多讲。

需要注意的是,recast 遍历抽象语法树时,如果识别到 if 语句后,不会继续遍历这个 if 语句里包裹的 if 语句,所以这里使用

this.traverse(path);

这行代码是用来继续遍历当前节点的子节点的,继续往下找 if 语句。

如果你自己判断出不需要向下遍历,不能简单地删掉这段代码,需要用这行代码替换:

return false

返回 false 表示不再向下遍历。

另外如果此时想直接使用新语句替换当前语句,可以直接返回一个新语句,例如:

return literal(true);

总结

总的来说,做完这个小工具算是解放了我大把的时间。

但是它只是我遇到典型压缩代码后,针对性进行更改的结果。可能遇到一些其他压缩后的语法,效果不大好,您也可以针对相应语法自行修改。

当然,如果您有更好的方法和建议,也希望能不吝赐教。

反压缩 js ,我的万花筒写轮眼开了,CV 能力大幅提升的更多相关文章

  1. .net 运用YUI相关的dll压缩js (按照自己的规则,想想都觉得强大和有趣)

    写在前面 不管是做前端的还是做后台的,不管是懂javaScript的还是不太懂JavaScript的人,我想都或多或想的知道些许js压缩对于页面性能提升的效应吧. 之前老喜欢用在线压缩工具去压缩js, ...

  2. uglifyjs压缩JS的

    一.故事总有其背景 年末将至,很多闲适的时间,于是刷刷微博,接触各种纷杂的信息——美其名曰“学习”.运气不错,遇到了一个新名词,uglifyjs. 据说是用来压缩JS文件的,据说还能优化JS,据说是基 ...

  3. 亲身实践 yui-compressor压缩js和css

    最近很懒散,个人感情.家庭原因,没有动力去学东西,老是发誓要搞好前端工程化,老中断,唉!没有魄力! 最近老觉得这前端工程化有什么好的,东西那么多,还得学!直到前几天产品提了个优化,说搜索结果页跳商品详 ...

  4. 利用Node 搭配uglify-js压缩js文件,批量下载图片到本地

    Node的便民技巧-- 压缩代码 下载图片 压缩代码 相信很多前端的同学都会在上线前压缩JS代码,现在的Gulp Webpack Grunt......都能轻松实现.但问题来了,这些都不会,难道就要面 ...

  5. 快速开发Grunt插件----压缩js模板

    前言 Grunt是一款前端构建工具,帮助我们自动化搭建前端工程.它可以实现自动对js.css.html文件的合并.压缩等一些列操作.Grunt有很多插件,每一款插件实现某个功能,你可以通过npm命名去 ...

  6. 如何使用grunt压缩js文件

    jQuery在使用grunt,bootstrap在使用grunt,百度UEditor在使用grunt,你没有理由不学.不用! 1. 前言 各位web前端开发人员,如果你现在还不知道grunt或者听说过 ...

  7. gulp 压缩js,css

    最近做的前端项目中发现引用的js包太多,导致页面加载时反应很慢,所以首先想到的是将js和css压缩,提高加载速度. 我们先来看看抓到的当前页面响应时间: 页面异步加载,需要响应时间 7.41秒,这也太 ...

  8. iis7 压缩js文件和启用gzip压缩

    压缩js文件 打开IIS 7的配置文件:c:\windows\system32\inetsrv\config\applicationhost.config 在<staticContent loc ...

  9. js数组特定位置元素置空,非null和undefined,实现echarts现状图效果;谷歌格式化压缩js代码

    一.想要实现eCharts线状图表的断点效果,如图(后来又查到数据格式为data:['-', 2, 3,'-' , 5, 6, 7]:也可以断点显示) 这种效果,在设置数据的时候应该是这样: data ...

随机推荐

  1. python22day

    内容回顾 递归练习 sys os logging shutil 函数结束啦 今日内容 面向对象 楔子:做一个人狗大战的游戏 技能要有归属感,人是人,狗是狗,技能的函数要写在对应函数内部,闭包. 复杂的 ...

  2. Java多线程专题5: JUC, 锁

    合集目录 Java多线程专题5: JUC, 锁 什么是可重入锁.公平锁.非公平锁.独占锁.共享锁 可重入锁 ReentrantLock A ReentrantLock is owned by the ...

  3. 幸运转轮(Cakra)

    题目描述 lxx参加了某卫视举办的一场选秀节目,凭借曼妙的舞姿和动人的歌声,他在众多idol中脱颖而出.现在在他的面前,有四个大转轮,这四个转轮将决定他能否赢得最终大奖--出道,机会只有一次!   每 ...

  4. spring 整合shiro框架 模拟登录控制器。

    一.导入shiro  jar包.  我在maven项目中,将常用的jar包都放在里面. <?xml version="1.0" encoding="UTF-8&qu ...

  5. 计算机网络-5-9-TCP拥塞控制

    TCP拥塞控制 拥塞控制的一般原理 在计算机网络中的链路容量(带宽),交换节点中的缓存和处理机等,都是网络的资源,在某段时间,若对网络中某一资源的需求超过该资源所能提供的可用部分,网络性能就会变坏,这 ...

  6. ApacheCN 网络安全译文集 20211025 更新

    Android 渗透测试学习手册 中文版 第一章 Android 安全入门 第二章 准备实验环境 第三章 Android 应用的逆向和审计 第四章 对 Android 设备进行流量分析 第五章 And ...

  7. Idea 如何不通过模板创建支持Maven的JavaWeb项目

    手动与模板创建的区别,请自行体会. 1. 点击创建项目 2. 不勾选骨架 3.填写项目名称以及该Maven项目坐标(groupid.artifactid.version). 在仓库中,以坐标确定项目. ...

  8. android怎么做表格显示数据

    实现思路:最底层(父级)背景为黑色,最上层(子级)背景为白色,然后父子组件之间存在一丝间隔即可显示出类似边框的线. 本次主要利用Android中的TableRow等实现,其他类比也可以实现效果. &l ...

  9. axios取消接口请求

    axios取消请求 这里就是分析一下接口请求需要被取消时的一些操作 因为我是用vue写的项目,所以标配用的是axios,怎么在axios中取消已经发送的请求呢? 1.在这之前我们还是先介绍一下原生js ...

  10. DNS域名解析之正向解析

    DNS域名解析之正向解析 1.DNS介绍 2.DNS正向解析实验 1.DNS定义:DNS是"域名系统"的英文缩写.它作为将域名和IP地址相互映射的一个分布式数据库,能够使人更方便地 ...