手把手写JavaScript压缩的核心原理

压缩的核心原理其实主要是三个

  1. 移除注释
  2. 缩短标识符
  3. 压缩空白符

一、移除注释

js 复制代码
// 1. 移除注释 
let result = code
        .replace(/\/\*[\s\S]*?\*\//g, '') // 多行注释 
        .replace(/\/\/.*$/gm, ''); // 单行注释

代码解释

这段代码是用来移除 JavaScript 代码中的注释的,使用了两个正则表达式替换:

  1. 第一个 .replace() 移除多行注释(/* ... */):

    • 正则表达式:/\/\*\s\S*?\*\//g
    • 解释:
      • \/\* 匹配 /* 开始符号
      • \s\S*? 非贪婪匹配任意字符(包括换行)
      • \*\/ 匹配 */ 结束符号
    • g 标志表示全局匹配
  2. 第二个 .replace() 移除单行注释(// ...):

    • 正则表达式:/\/\/.*$/gm
    • 解释:
      • \/\/ 匹配 //
      • .* 匹配后面的所有字符直到行尾
      • $ 匹配行尾
    • gm 标志表示全局和多行匹配

示例: javascript

js 复制代码
const code = `
// 单行注释
function test() {
  /* 多行
     注释 */
  return 1;
}
`;

const result = code
    .replace(/\/\*\s\S*?\*\//g, '')
    .replace(/\/\/.*$/gm, '');

console.log(result);
/* 输出:

function test() {
  
  return 1;
}
*/

/s和/S分别表示

在正则表达式中,\s\S 是两个常用的元字符,它们分别表示:

  1. \s(小写 s)
  • 含义:匹配任何空白字符(whitespace character)
  • 包括:
    • 空格
    • 制表符 \t
    • 换行符 \n
    • 回车符 \r
    • 垂直制表符 \v
    • 换页符 \f
  1. \S(大写 S)
  • 含义:匹配任何非空白字符(non-whitespace character)
  • 相当于:^\s(即不是 \s 的字符)

示例对比 javascript

js 复制代码
const str = "Hello\tWorld\n123";

// 匹配所有空白字符
console.log(str.match(/\s/g)); 
// 输出: "\t", "\n" (制表符和换行符)

// 匹配所有非空白字符
console.log(str.match(/\S/g)); 
// 输出: "H", "e", "l", "l", "o", "W", "o", "r", "l", "d", "1", "2", "3"

在原始问题中的应用

在最初的代码中: javascript

js 复制代码
.replace(/\/\*\s\S*?\*\//g, '')
- `\s\S` 是一个巧妙用法,表示匹配所有字符(包括换行符):
  - `\s` 匹配空白字符(如换行符)
  - `\S` 匹配非空白字符
  - 组合起来等价于 `.`(点号)但能匹配换行符(因为普通的 `.` 默认不匹配换行符)

这种写法比 /\/\*.*?\*\//gs(使用 s 标志让 . 匹配换行符)更兼容旧版 JavaScript 环境。

[\s\S]*? 非贪婪匹配是什么

非贪婪匹配(Lazy/Lenient Matching) 在正则表达式中,贪婪匹配(Greedy Matching) 和 非贪婪匹配(Lazy Matching) 是两种不同的匹配策略:

  • 贪婪匹配(默认):尽可能匹配最长的字符串。
  • 非贪婪匹配:尽可能匹配最短的字符串。

  1. 如何表示非贪婪匹配?

在量词(*, +, ?, {n,m})后面加上 ?,就变成了非贪婪模式:

量词 贪婪模式 非贪婪模式
* .* .*?
+ .+ .+?
? ??(极少用) ??(极少用)
{n,m} {n,m} {n,m}?

  1. 示例对比 示例 1:匹配 HTML 标签
js 复制代码
html
<div>Hello</div><div>World</div>
 (1) 贪婪匹配(`<div>.*</div>`)
javascript
"<div>Hello</div><div>World</div>".match(/<div>.*<\/div>/);
// 匹配整个字符串:
// "<div>Hello</div><div>World</div>"
 (2) 非贪婪匹配(`<div>.*?</div>`)
javascript
"<div>Hello</div><div>World</div>".match(/<div>.*?<\/div>/);
// 只匹配第一个 `<div>`:
// "<div>Hello</div>"

示例 2:移除多行注释 在最初的代码中: javascript

js 复制代码
.replace(/\/\*\s\S*?\*\//g, '')
- `\s\S*?` 表示非贪婪匹配任意字符(包括换行),直到遇到第一个 `*/`。
- 如果不加 `?`(贪婪模式),它会匹配从第一个 `/*` 到最后一个 `*/` 之间的所有内容,可能会错误地吃掉多个注释块。

  1. 何时使用非贪婪匹配?

✅ 适用场景:

  • 提取最短匹配的内容(如 HTML/XML 标签、注释块)。
  • 避免匹配过多内容(如 .* 默认会匹配到最后一个符合条件的字符)。

❌ 不适用场景:

  • 需要匹配到最后一个符合条件的字符时(如提取整个 JSON 字符串)。

  1. 总结 模式 写法 行为

贪婪匹配 .*, .+, {n,m} 尽可能匹配最长字符串 非贪婪匹配 .*?, .+?, {n,m}? 尽可能匹配最短字符串

在写正则表达式时,如果发现匹配了过多内容,可以尝试使用 ? 改成非贪婪模式。

二、缩短标识符(基本版)

js 复制代码
const varMap = new Map();
let varCount = 0; // 匹配变量声明 
result = result.replace( /\b(var|let|const|function)\s+([a-zA-Z_$][\w$]+)/g,
  (match, keyword, varName) => { 
    if (!varMap.has(varName)) { 
       varMap.set(varName, `v${varCount++}`); 
    }
    return `${keyword} ${varMap.get(varName)}`; 
   } );

代码解析

这段代码的作用是匹配 JavaScript 变量声明(var/let/const/function),并将变量名替换成一个简短的、自动生成的名称(如 v0, v1, v2...),通常用于代码混淆(Obfuscation)或缩小变量名的场景。


  1. 正则表达式 javascript
js 复制代码
/\b(varletconstfunction)\s+(a-zA-Z_$\w$+)/g
- `\b`:匹配单词边界(防止匹配到类似 `myvar` 这样的单词)。
- `(var let const function)`:匹配变量声明关键字(`var`、`let`、`const` 或 `function`)。
- `\s+`:匹配至少一个空白字符(如空格、制表符)。
- `(a-zA-Z_$\w$+)`:匹配变量名:
  - `a-zA-Z_$`:变量名必须以字母、`_` 或 `$` 开头。
  - `\w$+`:后续字符可以是字母、数字、`_` 或 `$`。
  1. 替换函数 javascript
js 复制代码
(match, keyword, varName) => {
  if (!varMap.has(varName)) {
    varMap.set(varName, `v${varCount++}`);
  }
  return `${keyword} ${varMap.get(varName)}`;
}
- 参数:
  - `match`:整个匹配到的字符串(如 `let myVar`)。
  - `keyword`:匹配到的关键字(如 `let`)。
  - `varName`:匹配到的变量名(如 `myVar`)。
- 逻辑:
  1. 检查 `varMap`(可能是一个 `Map` 或对象)是否已存储该变量名。
  2. 如果没有,则生成一个新名称(如 `v0`, `v1`),并存入 `varMap`。
  3. 返回替换后的字符串(如 `let v0`)。

示例 输入代码

js 复制代码
let myVar = 10;
const myConst = "Hello";
function myFunc() {}

 处理后
let v0 = 10;
const v1 = "Hello";
function v2() {}
(假设 `varCount` 从 `0` 开始)

三、压缩空白符

js 复制代码
// 3. 压缩空白符
  result = result
    .replace(/\s+/g, ' ')              // 多空格合并
    .replace(/\s*([={}[\](),;:])\s*/g, '$1') // 清除运算符周围空格
    .replace(/;}/g, '}');              // 结尾分号清除

  return result;
}

这段代码的作用是压缩 JavaScript 代码中的空白符,以减少文件大小并提高加载速度。它主要做了三件事:

  1. 合并多个连续空格为单个空格
  2. 清除运算符和符号周围不必要的空格
  3. 移除 ;} 中的分号(因为 } 前不需要分号)

代码解析

  1. replace(/\s+/g, ' ')
  • 正则表达式:/\s+/g
    • \s+:匹配一个或多个空白字符(包括空格、换行符、制表符等)。
  • 替换:' '
    • 将所有连续的空白符替换成单个空格。

示例: javascript

js 复制代码
"Hello    World\n\nGoodbye" → "Hello World Goodbye"

  1. replace(/\s*(,;:)\s*/g, '$1')
  • 正则表达式:/\s*(,;:)\s*/g
    • \s*:匹配零个或多个空白字符(可选)。
    • (,;:):匹配以下符号:
      • =(赋值)
      • {}(大括号、方括号、圆括号)
      • ,;:(逗号、分号、冒号)
    • \s*:再次匹配零个或多个空白字符(可选)。
  • 替换:'$1'
    • 只保留符号本身,删除其前后的所有空格。

示例: javascript

js 复制代码
"let x = 1 ;" → "let x=1;"
"if ( x === y ) {" → "if(x===y){"

  1. replace(/;}/g, '}')
  • 正则表达式:/;}/g
    • 匹配 ;}(分号后紧跟右大括号)。
  • 替换:'}'
    • 删除 } 前的分号(因为 JavaScript 允许 } 前不加分号)。

示例: javascript

js 复制代码
"function test() { return 1; }" → "function test(){return 1}"

完整示例

js 复制代码
javascript
function test ( ) {
  let x  =  1 ;
  if ( x  ===  2 ) {
    return  "Hello" ;
  }
}

处理后

javascript

js 复制代码
function test(){let x=1;if(x===2){return "Hello"}}

相关推荐
2301_781668615 小时前
前端基础 JS Vue3 Ajax
前端
上单带刀不带妹6 小时前
前端安全问题怎么解决
前端·安全
Fly-ping6 小时前
【前端】JavaScript 的事件循环 (Event Loop)
开发语言·前端·javascript
SunTecTec6 小时前
IDEA 类上方注释 签名
服务器·前端·intellij-idea
大佐不会说日语~6 小时前
Redis高可用架构演进面试笔记
redis·面试·架构
在逃的吗喽7 小时前
黑马头条项目详解
前端·javascript·ajax
袁煦丞7 小时前
有Nextcloud家庭共享不求人:cpolar内网穿透实验室第471个成功挑战
前端·程序员·远程工作
小磊哥er7 小时前
【前端工程化】前端项目开发过程中如何做好通知管理?
前端
拾光拾趣录7 小时前
一次“秒开”变成“转菊花”的线上事故
前端
你我约定有三7 小时前
前端笔记:同源策略、跨域问题
前端·笔记