1. 什么是变量名压缩
// 原始代码
function calc(parameterA, parameterB) {
var localVariableA = parameterA + 1;
var localVariableB = parameterB + 2;
return localVariableA + localVariableB;
}
// 压缩后
function calc(a,b) {
var c = a + 1;
var d = b + 2;
return c + d;
}
2. 核心原理
2.1 核心原理
变量名压缩主要依赖于作用域分析和标识符替换。
2.2 步骤
- 作用域分析
- 构建作用域树,识别变量定义和引用关系
- 分析变量的生命周期和可访问范围
- 检测命名冲突和变量遮蔽
- 变量重命名
- 按作用域从内到外遍历
- 使用短标识符池(如单字母 a,b,c...)
- 为每个变量分配最短可用名称
- 确保不与保留字冲突
- 冲突处理
- 当内层作用域变量可能与外层冲突时,选择下一个可用名称
- 维护一个已使用标识符集合避免重复
2.3 重要优化策略:
- 优先压缩使用频率高的长变量名
- 保持局部变量名在不同函数间的一致性
- 特殊处理 eval() 和 with 等动态作用域
- 考虑 source map 的生成需求
3. 代码实现
3.1 测试代码
var globalVar = 'global';
function outer(param) {
var x = 100;
var obj = { prop: 'value' };
function inner(y) {
eval('console.log(x, y)');
with(obj) {
console.log(prop);
}
var dynProp = 'prop';
console.log(obj[dynProp]);
}
return inner;
}
3.2 作用域
3.3 作用域分析
class ScopeAnalyzer {
constructor() {
this.scopes = new Map();
this.currentScope = null;
}
analyze(ast) {
// 创建全局作用域
const globalScope = {
type: 'global',
parent: null,
variables: new Map(),
hasEval: false,
hasWith: false,
hasEvalInChildren: false,
children: []
};
this.scopes.set('global', globalScope);
this.currentScope = globalScope;
// 构建作用域树
this.visit(ast);
return this.scopes;
}
visit(node) {
if (!node) return;
switch(node.type) {
case 'Program':
this.visitChildren(node.body);
break;
case 'FunctionDeclaration':
case 'FunctionExpression':
this.handleFunction(node);
break;
case 'BlockStatement':
this.handleBlock(node);
break;
case 'VariableDeclaration':
this.handleVariableDeclaration(node);
break;
case 'CallExpression':
this.handleCallExpression(node);
this.visitChildren(node.arguments);
break;
case 'WithStatement':
this.handleWithStatement(node);
this.visit(node.body);
break;
case 'Identifier':
this.handleIdentifier(node);
break;
default:
// 递归访问其他类型节点的子节点
if (node.type) {
this.visitNodeChildren(node);
}
}
}
visitChildren(nodes) {
if (Array.isArray(nodes)) {
nodes.forEach(node => this.visit(node));
}
}
visitNodeChildren(node) {
for (const key in node) {
if (node.hasOwnProperty(key)) {
const child = node[key];
if (Array.isArray(child)) {
this.visitChildren(child);
} else if (child && typeof child === 'object' && child.type) {
this.visit(child);
}
}
}
}
handleFunction(node) {
// 创建新的函数作用域
const scope = {
type: 'function',
parent: this.currentScope,
variables: new Map(),
hasEval: false,
hasWith: false,
hasEvalInChildren: false,
children: []
};
this.scopes.set(node, scope);
this.currentScope.children.push(scope);
const parentScope = this.currentScope;
this.currentScope = scope;
// 如果是函数声明,在父作用域中注册函数名
if (node.type === 'FunctionDeclaration' && node.id) {
this.addToScope(parentScope, node.id.name, 'function', node);
}
// 记录参数
node.params.forEach(param => {
this.addToScope(scope, param.name, 'parameter', param);
});
// 访问函数体
this.visit(node.body);
this.currentScope = parentScope;
}
handleBlock(node) {
// ES6块级作用域
const scope = {
type: 'block',
parent: this.currentScope,
variables: new Map(),
hasEval: false,
hasWith: false,
hasEvalInChildren: false,
children: []
};
this.scopes.set(node, scope);
this.currentScope.children.push(scope);
const parentScope = this.currentScope;
this.currentScope = scope;
this.visitChildren(node.body);
this.currentScope = parentScope;
}
handleVariableDeclaration(node) {
node.declarations.forEach(declarator => {
const name = declarator.id.name;
this.addToScope(
this.currentScope,
name,
node.kind, // 'var', 'let', 或 'const'
declarator
);
// 访问初始化器
if (declarator.init) {
this.visit(declarator.init);
}
});
}
handleCallExpression(node) {
if (node.callee.type === 'Identifier' && node.callee.name === 'eval') {
this.markEvalScope(this.currentScope);
}
this.visit(node.callee);
}
handleWithStatement(node) {
this.markWithScope(this.currentScope);
this.visit(node.object);
}
handleIdentifier(node) {
// 记录变量引用
let currentScope = this.currentScope;
while (currentScope) {
const variable = currentScope.variables.get(node.name);
if (variable) {
variable.references.push(node);
break;
}
currentScope = currentScope.parent;
}
}
addToScope(scope, name, type, node) {
scope.variables.set(name, {
type: type,
node: node,
references: []
});
}
markEvalScope(scope) {
scope.hasEval = true;
// 向上传播eval标记
let current = scope;
while (current) {
current.hasEvalInChildren = true;
current = current.parent;
}
}
markWithScope(scope) {
scope.hasWith = true;
}
}
4. 生成唯一变量名的工具类
class NameGenerator {
constructor() {
this.usedNames = new Set();
this.counter = 0;
}
next() {
let name;
do {
name = this.generateName(this.counter++);
} while (this.usedNames.has(name));
this.usedNames.add(name);
return name;
}
generateName(index) {
const chars = 'abcdefghijklmnopqrstuvwxyz';
let name = '';
do {
name = chars[index % chars.length] + name;
index = Math.floor(index / chars.length) - 1;
} while (index >= 0);
return name;
}
}
5. 变量重新命名
class Mangler {
constructor(scopeAnalysis) {
this.scopes = scopeAnalysis;
this.nameGenerator = new NameGenerator();
this.renamedMap = new Map();
this.reservedWords = new Set([
'break', 'case', 'catch', 'continue', 'debugger', 'default', 'delete',
'do', 'else', 'finally', 'for', 'function', 'if', 'in', 'instanceof',
'new', 'return', 'switch', 'this', 'throw', 'try', 'typeof', 'var',
'void', 'while', 'with', 'class', 'const', 'enum', 'export', 'extends',
'import', 'super', 'implements', 'interface', 'let', 'package', 'private',
'protected', 'public', 'static', 'yield', 'null', 'true', 'false'
]);
}
mangle() {
// 从全局作用域开始处理
const globalScope = this.scopes.get('global');
this.mangleScope(globalScope, new Set());
}
mangleScope(scope, parentUsedNames) {
const localUsedNames = new Set(parentUsedNames);
// 如果作用域包含 eval 或 with,跳过重命名
if (scope.hasEval || scope.hasWith) {
return localUsedNames;
}
// 重命名当前作用域的变量
for (const [name, varInfo] of scope.variables.entries()) {
// 跳过已经在父作用域中使用的名称
if (parentUsedNames.has(name)) continue;
// 跳过特殊标识符和保留字
if (this.reservedWords.has(name)) continue;
// 生成新名称
const newName = this.generateSafeName(localUsedNames);
// 记录使用的名称
localUsedNames.add(newName);
// 执行重命名
this.renameVariable(name, newName, varInfo, scope);
}
// 递归处理子作用域
for (const childScope of scope.children) {
this.mangleScope(childScope, localUsedNames);
}
return localUsedNames;
}
generateSafeName(usedNames) {
let name;
do {
name = this.nameGenerator.next();
} while (usedNames.has(name) || this.reservedWords.has(name));
return name;
}
renameVariable(oldName, newName, varInfo, scope) {
const scopeId = scope.type === 'global' ? 'global' : scope;
this.renamedMap.set(`${oldName}_${scopeId}`, newName);
if (varInfo.node) {
this.updateDeclaration(varInfo.node, newName);
}
if (varInfo.references) {
varInfo.references.forEach(ref => {
this.updateReference(ref, newName);
});
}
}
updateReference(reference, newName) {
if (!reference) return;
switch(reference.type) {
case 'Identifier':
reference.name = newName;
break;
case 'MemberExpression':
if (!reference.computed && reference.property.type === 'Identifier') {
reference.property.name = newName;
}
break;
case 'Property':
if (!reference.computed && reference.key.type === 'Identifier') {
reference.key.name = newName;
}
break;
}
}
updateDeclaration(declaration, newName) {
if (!declaration) return;
switch(declaration.type) {
case 'VariableDeclarator':
if (declaration.id && declaration.id.type === 'Identifier') {
declaration.id.name = newName;
}
break;
case 'FunctionDeclaration':
case 'FunctionExpression':
if (declaration.id && declaration.id.type === 'Identifier') {
declaration.id.name = newName;
}
break;
case 'Identifier':
declaration.name = newName;
break;
}
}
}
4. 完整代码
const esprima = require('esprima');
class ScopeAnalyzer {
constructor() {
this.scopes = new Map();
this.currentScope = null;
}
analyze(ast) {
// 创建全局作用域
const globalScope = {
type: 'global',
parent: null,
variables: new Map(),
hasEval: false,
hasWith: false,
hasEvalInChildren: false,
children: []
};
this.scopes.set('global', globalScope);
this.currentScope = globalScope;
// 构建作用域树
this.visit(ast);
return this.scopes;
}
visit(node) {
if (!node) return;
switch(node.type) {
case 'Program':
this.visitChildren(node.body);
break;
case 'FunctionDeclaration':
case 'FunctionExpression':
this.handleFunction(node);
break;
case 'BlockStatement':
this.handleBlock(node);
break;
case 'VariableDeclaration':
this.handleVariableDeclaration(node);
break;
case 'CallExpression':
this.handleCallExpression(node);
this.visitChildren(node.arguments);
break;
case 'WithStatement':
this.handleWithStatement(node);
this.visit(node.body);
break;
case 'Identifier':
this.handleIdentifier(node);
break;
default:
// 递归访问其他类型节点的子节点
if (node.type) {
this.visitNodeChildren(node);
}
}
}
visitChildren(nodes) {
if (Array.isArray(nodes)) {
nodes.forEach(node => this.visit(node));
}
}
visitNodeChildren(node) {
for (const key in node) {
if (node.hasOwnProperty(key)) {
const child = node[key];
if (Array.isArray(child)) {
this.visitChildren(child);
} else if (child && typeof child === 'object' && child.type) {
this.visit(child);
}
}
}
}
handleFunction(node) {
// 创建新的函数作用域
const scope = {
type: 'function',
parent: this.currentScope,
variables: new Map(),
hasEval: false,
hasWith: false,
hasEvalInChildren: false,
children: []
};
this.scopes.set(node, scope);
this.currentScope.children.push(scope);
const parentScope = this.currentScope;
this.currentScope = scope;
// 如果是函数声明,在父作用域中注册函数名
if (node.type === 'FunctionDeclaration' && node.id) {
this.addToScope(parentScope, node.id.name, 'function', node);
}
// 记录参数
node.params.forEach(param => {
this.addToScope(scope, param.name, 'parameter', param);
});
// 访问函数体
this.visit(node.body);
this.currentScope = parentScope;
}
handleBlock(node) {
// ES6块级作用域
const scope = {
type: 'block',
parent: this.currentScope,
variables: new Map(),
hasEval: false,
hasWith: false,
hasEvalInChildren: false,
children: []
};
this.scopes.set(node, scope);
this.currentScope.children.push(scope);
const parentScope = this.currentScope;
this.currentScope = scope;
this.visitChildren(node.body);
this.currentScope = parentScope;
}
handleVariableDeclaration(node) {
node.declarations.forEach(declarator => {
const name = declarator.id.name;
this.addToScope(
this.currentScope,
name,
node.kind, // 'var', 'let', 或 'const'
declarator
);
// 访问初始化器
if (declarator.init) {
this.visit(declarator.init);
}
});
}
handleCallExpression(node) {
if (node.callee.type === 'Identifier' && node.callee.name === 'eval') {
this.markEvalScope(this.currentScope);
}
this.visit(node.callee);
}
handleWithStatement(node) {
this.markWithScope(this.currentScope);
this.visit(node.object);
}
handleIdentifier(node) {
// 记录变量引用
let currentScope = this.currentScope;
while (currentScope) {
const variable = currentScope.variables.get(node.name);
if (variable) {
variable.references.push(node);
break;
}
currentScope = currentScope.parent;
}
}
addToScope(scope, name, type, node) {
scope.variables.set(name, {
type: type,
node: node,
references: []
});
}
markEvalScope(scope) {
scope.hasEval = true;
// 向上传播eval标记
let current = scope;
while (current) {
current.hasEvalInChildren = true;
current = current.parent;
}
}
markWithScope(scope) {
scope.hasWith = true;
}
}
class Mangler {
constructor(scopeAnalysis) {
this.scopes = scopeAnalysis;
this.nameGenerator = new NameGenerator();
this.renamedMap = new Map();
this.reservedWords = new Set([
'break', 'case', 'catch', 'continue', 'debugger', 'default', 'delete',
'do', 'else', 'finally', 'for', 'function', 'if', 'in', 'instanceof',
'new', 'return', 'switch', 'this', 'throw', 'try', 'typeof', 'var',
'void', 'while', 'with', 'class', 'const', 'enum', 'export', 'extends',
'import', 'super', 'implements', 'interface', 'let', 'package', 'private',
'protected', 'public', 'static', 'yield', 'null', 'true', 'false'
]);
}
mangle() {
// 从全局作用域开始处理
const globalScope = this.scopes.get('global');
this.mangleScope(globalScope, new Set());
}
mangleScope(scope, parentUsedNames) {
const localUsedNames = new Set(parentUsedNames);
// 如果作用域包含 eval 或 with,跳过重命名
if (scope.hasEval || scope.hasWith) {
return localUsedNames;
}
// 重命名当前作用域的变量
for (const [name, varInfo] of scope.variables.entries()) {
// 跳过已经在父作用域中使用的名称
if (parentUsedNames.has(name)) continue;
// 跳过特殊标识符和保留字
if (this.reservedWords.has(name)) continue;
// 生成新名称
const newName = this.generateSafeName(localUsedNames);
// 记录使用的名称
localUsedNames.add(newName);
// 执行重命名
this.renameVariable(name, newName, varInfo, scope);
}
// 递归处理子作用域
for (const childScope of scope.children) {
this.mangleScope(childScope, localUsedNames);
}
return localUsedNames;
}
generateSafeName(usedNames) {
let name;
do {
name = this.nameGenerator.next();
} while (usedNames.has(name) || this.reservedWords.has(name));
return name;
}
renameVariable(oldName, newName, varInfo, scope) {
const scopeId = scope.type === 'global' ? 'global' : scope;
this.renamedMap.set(`${oldName}_${scopeId}`, newName);
if (varInfo.node) {
this.updateDeclaration(varInfo.node, newName);
}
if (varInfo.references) {
varInfo.references.forEach(ref => {
this.updateReference(ref, newName);
});
}
}
updateReference(reference, newName) {
if (!reference) return;
switch(reference.type) {
case 'Identifier':
reference.name = newName;
break;
case 'MemberExpression':
if (!reference.computed && reference.property.type === 'Identifier') {
reference.property.name = newName;
}
break;
case 'Property':
if (!reference.computed && reference.key.type === 'Identifier') {
reference.key.name = newName;
}
break;
}
}
updateDeclaration(declaration, newName) {
if (!declaration) return;
switch(declaration.type) {
case 'VariableDeclarator':
if (declaration.id && declaration.id.type === 'Identifier') {
declaration.id.name = newName;
}
break;
case 'FunctionDeclaration':
case 'FunctionExpression':
if (declaration.id && declaration.id.type === 'Identifier') {
declaration.id.name = newName;
}
break;
case 'Identifier':
declaration.name = newName;
break;
}
}
}
// 生成唯一变量名的工具类
class NameGenerator {
constructor() {
this.usedNames = new Set();
this.counter = 0;
}
next() {
let name;
do {
name = this.generateName(this.counter++);
} while (this.usedNames.has(name));
this.usedNames.add(name);
return name;
}
generateName(index) {
const chars = 'abcdefghijklmnopqrstuvwxyz';
let name = '';
do {
name = chars[index % chars.length] + name;
index = Math.floor(index / chars.length) - 1;
} while (index >= 0);
return name;
}
}
// 创建完整的压缩处理流程
class Minifier {
constructor() {
this.scopeAnalyzer = new ScopeAnalyzer();
}
minify(code) {
// 1. 解析代码生成AST
const ast = esprima.parse(code);
// 2. 分析作用域
const scopeAnalysis = this.scopeAnalyzer.analyze(ast);
console.log('scopeAnalysis ===> ', scopeAnalysis)
// 3. 变量名压缩
const mangler = new Mangler(scopeAnalysis);
mangler.mangle();
// 4. 生成代码
return this.generate(ast);
}
generate(ast) {
const generator = new CodeGenerator();
return generator.generate(ast);
}
}
// Only showing the modified CodeGenerator class - other classes remain the same
class CodeGenerator {
generate(ast) {
return this.generateNode(ast);
}
generateNode(node) {
if (!node) return '';
switch(node.type) {
case 'Program':
return node.body.map(stmt => this.generateNode(stmt)).join(';\n');
case 'VariableDeclaration':
return `${node.kind} ${node.declarations.map(d => this.generateNode(d)).join(', ')};`;
case 'VariableDeclarator':
return `${this.generateNode(node.id)}${node.init ? ' = ' + this.generateNode(node.init) : ''}`;
case 'FunctionDeclaration':
return `function ${this.generateNode(node.id)}(${node.params.map(p => this.generateNode(p)).join(', ')}) ${this.generateNode(node.body)}`;
case 'BlockStatement':
const body = node.body.map(stmt => this.generateNode(stmt)).join(';\n');
return `{
${this.indent(body)}
}`;
case 'ExpressionStatement':
return `${this.generateNode(node.expression)};`;
case 'CallExpression':
return `${this.generateNode(node.callee)}(${node.arguments.map(arg => this.generateNode(arg)).join(', ')})`;
case 'MemberExpression':
const object = this.generateNode(node.object);
const property = node.computed
? `[${this.generateNode(node.property)}]`
: `.${this.generateNode(node.property)}`;
return `${object}${property}`;
case 'ObjectExpression':
const properties = node.properties.map(prop => this.generateNode(prop)).join(', ');
return `{ ${properties} }`;
case 'Property':
const key = node.computed
? `[${this.generateNode(node.key)}]`
: this.generateNode(node.key);
return `${key}: ${this.generateNode(node.value)}`;
case 'WithStatement':
return `with(${this.generateNode(node.object)}) ${this.generateNode(node.body)}`;
case 'Identifier':
return node.name;
case 'Literal':
if (typeof node.value === 'string') {
return `'${node.value}'`;
}
return String(node.value);
default:
return '';
}
}
indent(code) {
return code.split('\n').map(line => ` ${line}`).join('\n');
}
}
// 使用示例:
const code = `
var globalVar = 'global';
function outer(param) {
var x = 100;
var obj = { prop: 'value' };
function inner(y) {
eval('console.log(x, y)');
with(obj) {
console.log(prop);
}
var dynProp = 'prop';
console.log(obj[dynProp]);
}
return inner;
}
`;
// 使用示例
const minifier = new Minifier();
const result = minifier.minify(code);
console.log(result);
输出
var a = 'global';;
function b(c) {
var d = 100;;
var e = { prop: 'value' };;
function f(g) {
eval('console.log(x, y)');;
with(e) {
console.log(prop);
};
var dynProp = 'prop';;
console.log(e[dynProp]);
};
}