在 JS 高级压缩中,函数名混淆是重要的保护手段。理解其原理不仅能帮助选择合适的工具,还能编写更易于混淆的代码。
混淆原理
1. 符号表替换
混淆器会建立变量/函数名到短名称的映射:
// 原始代码
function calculateTotalPrice(items) {
var totalPrice = 0;
for (var i = 0; i < items.length; i++) {
totalPrice += items[i].price;
}
return totalPrice;
}
// 混淆器内部映射
{
'calculateTotalPrice': 'a',
'items': 'b',
'totalPrice': 'c',
'i': 'd'
}
// 混淆结果
function a(b) {
var c = 0;
for (var d = 0; d < b.length; d++) {
c += b[d].price;
}
return c;
}
2. 作用域分析
混淆器需要理解作用域,避免名称冲突:
// 不同作用域的同名变量可以映射为相同短名
function outer() {
var name = 'outer'; // -> a
function inner() {
var name = 'inner'; // 也可以 -> a,因为作用域不同
return name;
}
return inner();
}
// 混淆后
function a() {
var a = 'outer';
function b() {
var a = 'inner';
return a;
}
return b();
}
3. 全局名称保护
全局变量和函数名通常不混淆,因为可能被外部引用:
// 全局函数,不混淆
window.myApp = {
init: function() { },
start: function() { }
};
// 内部函数,可以混淆
(function() {
function internalHelper() { } // -> a
internalHelper();
})();
作用域隔离技术
1. IIFE(立即调用函数表达式)
// 创建独立作用域
(function() {
var privateVar = 'secret';
function privateFunc() { }
// 只暴露必要的接口
window.myModule = {
publicMethod: function() { }
};
})();
// 外部无法访问 privateVar 和 privateFunc
2. 模块模式
var Module = (function() {
// 私有成员
var count = 0;
function increment() { count++; }
// 公开接口
return {
getCount: function() { return count; },
add: function() { increment(); }
};
})();
3. ES Module 天然隔离
// utils.js
const helper = function() { }; // 不暴露,外部无法访问
export function publicMethod() { }
// main.js
import { publicMethod } from './utils.js';
// helper 不可访问
混淆对作用域的影响
1. 闭包中的混淆
// 原始
function createCounter() {
var count = 0;
return {
increment: function() { count++; },
getCount: function() { return count; }
};
}
// 混淆后
function a() {
var b = 0;
return {
increment: function() { b++; },
getCount: function() { return b; }
};
}
// 闭包关系保持不变
2. this 绑定的影响
// this 引用的属性名不会被混淆
var obj = {
name: 'test',
greet: function() {
return this.name; // 'name' 不会被混淆
}
};
// 如果需要混淆属性名,需要使用特殊处理
var _0x1a2b = ['name'];
var obj = {};
obj[_0x1a2b[0]] = 'test';
3. eval 和 new Function 的限制
// 这些代码中的变量名无法被混淆
eval('var dynamicVar = 123');
var fn = new Function('return dynamicVar');
// 混淆器通常会跳过包含 eval 的函数
混淆策略
1. 保守策略
只混淆局部变量,保留函数名:
// 混淆前
function processData(data) {
var result = [];
for (var i = 0; i < data.length; i++) {
result.push(data[i] * 2);
}
return result;
}
// 保守混淆
function processData(data) {
var a = [];
for (var b = 0; b < data.length; b++) {
a.push(data[b] * 2);
}
return a;
}
2. 激进策略
混淆所有名称,包括函数名:
// 激进混淆
function a(b) {
var c = [];
for (var d = 0; d < b.length; d++) {
c.push(b[d] * 2);
}
return c;
}
3. 属性混淆
混淆对象属性名(需要特殊配置):
// 混淆前
var config = {
apiUrl: 'https://api.example.com',
timeout: 5000
};
// 属性混淆后
var a = { b: 'https://api.example.com', c: 5000 };
// 需要确保所有引用都使用 a.b 和 a.c
最佳实践
1. 代码编写建议
// 推荐:使用局部变量,易于混淆
function process() {
var data = fetchData();
var result = transform(data);
return result;
}
// 不推荐:依赖全局变量
function process() {
globalData = fetchData();
globalResult = transform(globalData);
return globalResult;
}
2. 避免动态属性访问
// 推荐:静态属性,可以混淆
obj.propertyName
// 不推荐:动态属性,难以混淆
obj['property' + 'Name']
obj[dynamicKey]
3. 使用模块化
// 模块化代码天然适合混淆
// utils.js
export function helper() { }
// main.js
import { helper } from './utils.js';
调试混淆代码
1. Source Map
// 构建时生成 Source Map
// webpack.config.js
module.exports = {
devtool: 'source-map',
optimization: {
minimize: true
}
};
2. 美化混淆代码
// 使用格式化工具美化混淆代码,便于调试
// 但变量名仍然是混淆后的
function a(b) {
var c = [];
for (var d = 0; d < b.length; d++) {
c.push(b[d] * 2);
}
return c;
}
3. 断点调试
在浏览器开发者工具中设置断点,观察变量值。
总结
函数名混淆通过符号表替换和作用域分析实现代码保护。理解其原理能帮助编写更易于混淆的代码,同时选择合适的混淆策略。作用域隔离是混淆的基础,模块化是现代 JS 的最佳实践。