jsHook原理(二)
Object.defineProperty() 是 JavaScript 中精准定义 / 修改对象属性 的核心方法,它能控制属性的读写、枚举、修改、删除等行为,是 Vue2 响应式原理的核心 API。一、基础语法
Object.defineProperty(obj, prop, descriptor)
参数说明
obj:要定义属性的目标对象(必填)prop:要定义 / 修改的属性名(字符串或 Symbol,必填)descriptor:属性描述符(对象,控制属性行为,必填)
二、属性描述符(核心)
描述符分两类:数据描述符 和 存取描述符,不能混用。
1. 数据描述符(直接定义属性值)
包含 4 个配置项:
| 配置项 | 类型 | 默认值 | 作用 |
|---|---|---|---|
value |
任意 | undefined |
属性的值 |
writable |
布尔 | false |
是否允许修改值 |
enumerable |
布尔 | false |
是否允许被枚举(for...in/Object.keys) |
configurable |
布尔 | false |
是否允许删除属性 / 重新配置描述符 |
2. 存取描述符(通过 getter/setter 控制值)
包含 2 个配置项:
| 配置项 | 类型 | 默认值 | 作用 |
|---|---|---|---|
get |
函数 | undefined |
读取属性时自动执行,返回值作为属性值 |
set |
函数 | undefined |
修改属性时自动执行,接收新值作为参数 |
三、完整使用示例
示例 1:基础数据描述符
const person = {};
// 定义属性 name
Object.defineProperty(person, 'name', {
value: '张三', // 属性值
writable: true, // 允许修改
enumerable: true, // 允许枚举
configurable: true// 允许删除/重新配置
});
// 使用属性
console.log(person.name); // 张三
person.name = '李四'; // 可修改
console.log(person.name); // 李四
示例 2:只读属性(writable: false)
const person = {};
Object.defineProperty(person, 'age', {
value: 18,
writable: false, // 禁止修改
enumerable: true
});
person.age = 20; // 静默失败(严格模式下报错)
console.log(person.age); // 18(值不变)
示例 3:不可枚举属性(enumerable: false)
// const person = {};
// Object.defineProperty(person, 'age', {
// value: 18,
// writable: false, // 禁止修改
// enumerable: true
// });
//
// person.age = 20; // 静默失败(严格模式下报错)
// console.log(person.age); // 18(值不变)
const person = { name: '张三' };
Object.defineProperty(person, 'id', {
value: 1001,
enumerable: false // 不可枚举
});
// 遍历不到 id
for (let key in person) {
console.log(key); // 只输出 name
}
// 遍历不到id,但是单独输出还是OK的。
console.log(person.id)
console.log(Object.keys(person)); // ['name']
示例 4:不可删除属性(configurable: false)
const person = {};
Object.defineProperty(person, 'gender', {
value: '男',
configurable: true // 可以删除
});
delete person.gender; // 静默失败
console.log(person.gender); // 结果是:undefined

const person = {};
Object.defineProperty(person, 'gender', {
value: '男',
configurable: false // 禁止删除
});
delete person.gender; // 静默失败
console.log(person.gender); // 男

当定义为false的时候,无法删除,可以打印出“男”
示例 5:存取描述符(get/set,Vue2 核心)
最常用场景:监听属性的读写,实现响应式
const person = {};
let _name = '张三'; // 内部变量,存储真实值
// 定义存取器属性
Object.defineProperty(person, 'name', {
// 读取 person.name 时触发get()函数
get() {
console.log('读取了 name 属性');
return "返回值是:"+_name;
},
// 修改 person.name 时触发set()函数
set(newVal) {
console.log('修改了 name 属性,新值:', newVal);
_name = newVal;
},
enumerable: true,
configurable: true
});
// 测试读写
console.log(person.name);
// 输出:读取了 name 属性 → 张三
person.name = '李四';
// 输出:修改了 name 属性,新值:李四
console.log(person.name);
// 输出:读取了 name 属性 → 李四

示例6:批量定义属性
单个属性用 Object.defineProperty,批量定义用 Object.defineProperties:
const obj = {};
Object.defineProperties(obj, {
name: { value: '张三', enumerable: true },
age: { value: 18, writable: true }
});
四、使用 Object.defineProperty 实现数据劫持
在 JavaScript 逆向与爬虫开发中,
Object.defineProperty 是实现 Hook(劫持 / 挂钩) 最核心、最常用的 API。它主要用于两大场景:- Hook 属性访问:劫持对象属性的
get(读取)和set(赋值)。 - Hook 函数本身:重写
Object.defineProperty方法,监控所有属性定义行为。
场景 1:Hook 对象属性(Getter/Setter)
用途:监控 / 篡改敏感数据(如
token、cookie、__webpack_require__、加密变量)。示例 1:基础属性劫持(监控读写)
// 目标对象
const user = {
_token: 'original_123' // 下划线通常表示私有
};
// 1. 保存原始值(备份)
const originalToken = user._token;
// 2. Hook:重定义 token 属性
Object.defineProperty(user, 'token', {
get() {
console.log('[HOOK] 读取 token:', this._token);
// 可篡改返回值
return this._token + '_hacked';
},
set(newVal) {
console.log('[HOOK] 设置 token:', newVal);
// 可拦截赋值,不写入原始值
// this._token = newVal; // 注释则阻止赋值
},
enumerable: true,
configurable: true
});
// 测试
console.log(user.token); // [HOOK] 读取 -> original_123_hacked
user.token = 'fake_456'; // [HOOK] 设置 -> fake_456
console.log(user._token); // 仍为 original_123(被拦截)

示例2. 进阶:劫持嵌套对象
上面只能劫持一层,对象里包对象就监听不到,所以要递归劫持:
function observe(obj) {
if (typeof obj !== 'object' || obj === null) {
return;
}
for (let key in obj) {
let val = obj[key];
// 递归劫持子对象
observe(val);
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get() {
console.log('get: ' + key);
return val;
},
set(newVal) {
console.log('set: ' + key + ': ' + newVal);
if (newVal === val) return;
val = newVal;
// 新值可能是对象,也要劫持
observe(newVal);
}
});
}
}
// 测试嵌套对象
const data = {
user: {
name: '小红'
}
};
// 首先要运行observe函数
observe(data);
// 其次,触发对象的嵌套属性
data.user.name = '小兰';
示例 3:Hook 全局变量(如 window 上的关键变量)
常用于逆向时定位加密函数入口:
// 监控全局 encryptKey 变量
let _encryptKey;
Object.defineProperty(window, 'encryptKey', {
get() {
console.log('[HOOK] 获取 encryptKey', new Error().stack);
return _encryptKey;
},
set(val) {
console.log('[HOOK] 设置 encryptKey =', val);
// 断点:赋值时自动断住,查看调用栈
debugger;
_encryptKey = val;
},
enumerable: true,
configurable: true
});
// 测试1:设置值
console.log('--- 测试1: 设置值 ---');
window.encryptKey = 'my-secret-key-123';
// 测试2:获取值
console.log('--- 测试2: 获取值 ---');
console.log('当前 encryptKey:', window.encryptKey);
// 测试3:修改值
console.log('--- 测试3: 修改值 ---');
window.encryptKey = 'new-key-456';
console.log('修改后:', window.encryptKey);

场景 2:Hook Object.defineProperty 自身
用途:监控所有通过此 API 定义属性的行为(反爬常用、定位隐藏属性)。
实现:重写原生方法
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>Object.defineProperty Hook 测试</title>
</head>
<body>
<h1>请打开浏览器控制台 (F12) 查看结果</h1>
<script>
// ==========================================
// 1. 你的 Hook 代码 (已做微小调整以适应非严格模式下的 this 指向)
// ==========================================
// 保存原始方法
const _defineProperty = Object.defineProperty;
// 重写 Hook
Object.defineProperty = function(obj, prop, descriptor) {
console.log('====================================');
console.log('🚨 [HOOK] 捕获到 Object.defineProperty 调用!');
console.log('目标对象:', obj);
console.log('属性名:', prop);
console.log('原始描述符:', JSON.parse(JSON.stringify(descriptor))); // 序列化以便查看
// 打印调用栈,方便定位是哪行代码触发了定义
console.trace('调用堆栈追踪:');
// 篡改逻辑:强制 enumerable 为 true
if (descriptor && descriptor.enumerable === false) {
console.warn(`⚠️ [篡改] 检测到属性 "${prop}" 试图被隐藏 (enumerable: false),已强制改为 true!`);
descriptor.enumerable = true;
}
// 调用原始方法,保证功能正常
// 注意:这里使用 _defineProperty 直接调用,避免 this 指向问题
return _defineProperty(obj, prop, descriptor);
};
// ==========================================
// 2. 模拟业务代码 (被 Hook 的目标)
// ==========================================
console.log('--- 开始模拟业务逻辑 ---');
const user = { name: 'Alice', age: 25 };
// 场景 A: 正常定义属性 (Hook 会记录,但不篡改)
Object.defineProperty(user, 'city', {
value: 'Beijing',
writable: true,
enumerable: true
});
// 场景 B: 定义隐藏属性 (Hook 会记录,并触发篡改逻辑)
// 这通常用于某些库试图隐藏内部属性,你的 Hook 将其暴露出来
Object.defineProperty(user, '_secretToken', {
value: '12345-ABCDE',
writable: false,
enumerable: false // 这里设置为 false,会被你的 Hook 拦截
});
// 场景 C: 使用 value 定义普通数据属性
Object.defineProperty(user, 'role', {
value: 'Admin',
writable: true,
enumerable: true
});
console.log('--- 业务逻辑结束 ---');
console.log('最终对象状态:', user);
console.log('Object.keys(user):', Object.keys(user)); // 验证 _secretToken 是否被暴露
</script>
</body>
</html>

效果:
网站任何代码执行
Object.defineProperty 时,都会被你的日志捕获,并可看到完整调用栈,快速定位隐藏属性的定义位置。场景 3:Hook 原型链(通用 Hook 所有实例)
用途:劫持类的所有对象(如
Array、自定义类)。// Hook 所有 Person 实例的 name 属性
function Person() {}
// 先定义原始属性
Object.defineProperty(Person.prototype, 'name', {
value: 'default',
writable: true,
enumerable: true,
configurable: true
});
// 再 Hook 原型
let _name = Person.prototype.name;
Object.defineProperty(Person.prototype, 'name', {
get() {
console.log('[HOOK] 获取 name');
return _name;
},
set(val) {
console.log('[HOOK] 设置 name =', val);
_name = val;
}
});
// 测试
const p1 = new Person();
p1.name = 'Alice'; // 触发 Hook
console.log(p1.name); // 触发 Hook


浙公网安备 33010602011771号