前端进阶之旅前端进阶之旅
基础篇
进阶篇
高频篇
精选篇
手写篇
原理篇
面经篇
自检篇
每日一题
  • 综合
    • 综合题型
    • 其他问题
    • 设计模式
    • 思维导图
    • 学习路线
  • 前端基础
    • HTTP
    • 浏览器
    • 计算机基础
  • 进阶学习
    • NPM工作流
    • Docker
    • Canvas
    • Node学习指南
    • 前端综合文章
  • 其他
    • Handbook
    • 职场话题
    • CSS可视化
小程序题库
公众号动态
博客动态
开发者导航
基础篇
进阶篇
高频篇
精选篇
手写篇
原理篇
面经篇
自检篇
每日一题
  • 综合
    • 综合题型
    • 其他问题
    • 设计模式
    • 思维导图
    • 学习路线
  • 前端基础
    • HTTP
    • 浏览器
    • 计算机基础
  • 进阶学习
    • NPM工作流
    • Docker
    • Canvas
    • Node学习指南
    • 前端综合文章
  • 其他
    • Handbook
    • 职场话题
    • CSS可视化
小程序题库
公众号动态
博客动态
开发者导航
  • React专栏

    • React组合式开发实战

      • 前端开发的四个时代
      • 企业管理系统的前世今生
      • 可视化页面搭建工具
      • 实战篇 01:开发前准备
      • 实战篇 02:项目脚手架
      • 实战篇 03:页面布局方案
      • 实战篇 04:权限管理机制
      • 实战篇 05:菜单匹配逻辑
      • 实战篇 06:消息通知设计
      • 实战篇 07:多语言支持
      • 继往开来:可视化页面搭建工具
    • React Hooks与Immutable实战

    • React SSR服务端渲染与同构实践

    • IM聊天系统前端开发实践

    • 微前端开发实战

    • React进阶实践

  • Vue专栏

  • 移动端专栏

  • Node专栏

  • 前端工程化专栏

  • 算法专栏

  • Typescript专栏

  • 其他专栏

完整面试题地址:
作者:程序员poetry
扫码关注作者公众号:「前端进阶之旅」 每天分享技术干货
前端进阶之旅公众号二维码

压缩混淆工具是前端必用的工具之一,代码在上线之间需要经过压缩来减小体积,并且会做一些简单的混淆来防止源码直接泄漏。前端工程师可能每天都在用这种工具,可你有想过它的实现原理么?

代码的压缩和混淆都是对代码做转换,但是转换前后要保持语义一致,就是不能转完之后代码逻辑改变了。之所以能做这些转换是因为计算机执行代码并不需要换行、也不需要变量名多么易懂,那都是给人看的,可以简化掉,而且有的不会被执行到的代码也可以删掉。压缩和混淆就是分析代码中的这种代码,进行分析和转换,达到转换前后执行逻辑一致,但是代码体积更小、可读性更差的目的。

我们分别来实现一下压缩和混淆。

# 混淆

# 思路分析

混淆就是把代码变得难以阅读,让怀有恶意目的的人很难通过代码理清逻辑,但是不能改变执行的结果。要做等价转换。

这种转换包括两方面:

  • 名字转换。变量名、函数名这些我们会注意命名要有含义,但是编译后的代码就不需要了,可以把各种 identifier 的 name 重命名为没有含义的 abcd,这个可以通过 path.scope.rename 的 api 来做到。
  • 逻辑转换。if 的逻辑可以用 switch 来代替,for 的逻辑可以用 while 来代替,这都是等价的,把一种方式实现的代码转成另一种等价的形式就可以达到混淆的目的。做混淆工具主要是要找到这种等价的变化,而且后者一定要特别复杂难以分析,然后实现这种转换,就达到了混淆的目的。

这里我们只实现下名字的混淆。

目的是为了找出所有的声明,那就要遍历所有会生成作用域的节点,包括 FunctionDeclaration、BlockStatement 等,而这些节点又一个别名,叫 Scopable(所有的别名可以在 这里查,详见第七节),然后对每一个声明(binding)都重命名为无意义的名字,并且更新所有引用这个声明的地方,这个逻辑在 path.scope.rename 已经实现了,直接调用这个 api 即可。

# 代码实现

依然先写好插件的结构:

const { declare } = require('@babel/helper-plugin-utils');

const mangle = declare((api, options, dirname) => {
    api.assertVersion(7);

    return {
        pre(file) {
            file.set('uid', 0);
        },
        visitor: {
            Scopable: {

            }
        }
    }
});

module.exports = mangle;
@前端进阶之旅: 代码已经复制到剪贴板

这里在 file 放了一个 uid 是为了获取唯一 id 的,后面会用到。

我们基于这个 uid 来获取唯一的名字,因为不能以数字开头,所以用 A-Z、a-z、$ 和 _ 这 54 个字符来生成。

根据传入的 num 来取对应下标的字符组成字符串:

const base54 = (function(){
    var DIGITS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ$_";
    return function(num) {
            var ret = "";
            do {
                    ret = DIGITS.charAt(num % 54) + ret;
                    num = Math.floor(num / 54);
            } while (num > 0);
            return ret;
    };
})();
fe
基础篇
进阶篇
高频篇
精选篇
手写篇
原理篇
面经篇
自检篇
每日一题
  • 综合
    • 综合题型
    • 其他问题
    • 设计模式
    • 思维导图
    • 学习路线
  • 前端基础
    • HTTP
    • 浏览器
    • 计算机基础
  • 进阶学习
    • NPM工作流
    • Docker
    • Canvas
    • Node学习指南
    • 前端综合文章
  • 其他
    • Handbook
    • 职场话题
    • CSS可视化
小程序题库
公众号动态
博客动态
开发者导航
  • React专栏

    • React组合式开发实战

      • 前端开发的四个时代
      • 企业管理系统的前世今生
      • 可视化页面搭建工具
      • 实战篇 01:开发前准备
      • 实战篇 02:项目脚手架
      • 实战篇 03:页面布局方案
      • 实战篇 04:权限管理机制
      • 实战篇 05:菜单匹配逻辑
      • 实战篇 06:消息通知设计
      • 实战篇 07:多语言支持
      • 继往开来:可视化页面搭建工具
    • React Hooks与Immutable实战

    • React SSR服务端渲染与同构实践

    • IM聊天系统前端开发实践

    • 微前端开发实战

    • React进阶实践

  • Vue专栏

  • 移动端专栏

  • Node专栏

  • 前端工程化专栏

  • 算法专栏

  • Typescript专栏

  • 其他专栏