简述js数据劫持
数据劫持,指的是在访问或者修改对象的某个属性时,通过一段代码拦截这个行为,进行额外的操作或者修改返回结果。
数据劫持经典应用
vue双向数据绑定
数据劫持常见实现思路
- 利用Object.defineProperty设置 setter及getter
- 利用ES6新增的proxy设置代理
具体实现
defineProperty方式
// 1 定义一个对象
let obj = {
name: 'Bill'
}
// 2 定义监听函数
function observer(obj) {
if(typeof obj === 'object') {
for (let key in obj) {
// defineReactive 方法设置get和set,见第三步
defineReactive(obj, key, obj[key]);
}
}
}
// 3 定义defineReactive函数处理每个属性
function defineReactive(obj, key, value) {
Object.defineProperty(obj, key, {
get() {
return value;
},
set(val) {
console.log('数据更新了')
value = val;
}
})
}
// 4 初步实现数据劫持,测试,控制台输出:数据更新了
observer(obj);
obj.name = 'haha'
// 5 以上已经实现设置obj的属性的时候,被监听到,并且可以去执行一些动作了。但是,如果对象里面嵌入了对象呢?如:
let obj = {
name: 'Bill',
age: {
old: 60
}
}
// 6 再次测试,控制台无输出,额外动作未被执行
observer(obj);
obj.age.old = '50'
// 7 为解决上述问题,对监控的obj进行递归迭代处理
function defineReactive(obj, key, value) {
// 如果对象的属性也是一个对象。迭代处理
observer(value);
Object.defineProperty(obj, key, {
//....
})
}
// 8 再次测试,输出 数据更新了
observer(obj);
obj.age.old = '50'
// 9 但是,到这一步,仍不完善,当obj为数组(也是对象)时将仍然无法在通过内置方法改变数组时触发行为
// 为obj新增一个属性,属性值为数组,为该数组增加元素,控制台无输出
obj.skill = [1, 2, 3];
obj.skill.push(4);
// 10 重写数组内置方法,实现改变数组元素时触发行为
let arr = ['push', 'pop', 'splice','shift', 'unshift'];
arr.forEach(method=> {
let oldPush = Array.prototype[method];
Array.prototype[method] = function(value) {
console.log('数据更新了')
oldPush.call(this, value)
}
})
// 11 再次测试 控制台输出 数据更新了。数据劫持基本实现
obj.skill = [1, 2, 3];
obj.skill.push(4);
以下为完整代码:
let obj = {
name: 'Bill',
age: {
old: 60
},
skill:[1,2,3]
}
// vue 数据劫持 Observer.defineProperty
function observer(obj) {
if(typeof obj === 'object') {
for (let key in obj) {
defineReactive(obj, key, obj[key]);
}
}
}
function defineReactive(obj, key, value) {
observer(value);
Object.defineProperty(obj, key, {
get() {
return value;
},
set(val) {
console.log('数据更新了')
value = val;
}
})
}
observer(obj);
// 重写数组相关方法
let arr = ['push', 'pop', 'splice','shift', 'unshift'];
arr.forEach(method=> {
let oldPush = Array.prototype[method];
Array.prototype[method] = function(value) {
console.log('数据更新了')
oldPush.call(this, value)
}
})
// 以下为测试 输出两次 数据更新了
obj.age.old = 50
obj.skill.push(40)
ES6 Proxy方式
// 判断传入的数据是否为数组
function isArray(o){
return Object.prototype.toString.call(o) === `[object Array]`
}
// 判断传入数据是否为普通对象
function isObject(o){
return Object.prototype.toString.call(o) === `[object Object]`
}
class Observer{
constructor(
target,
handler = {
set(target, key, value, receiver){
console.log('检测到了set的key为 -> ' + key);
return Reflect.set(target, key, value, receiver);
}
}
){
if( !isObject(target) && !isArray(target) ){
throw new TypeError('target 不是数组或对象')
}
this._target = JSON.parse(JSON.stringify(target)); // 避免引用修改 数组不考虑
this._handler = handler;
return new Proxy(this._observer(this._target), this._handler);
}
// 为每一项为Array或者Object类型数据变为代理
_observer(target){
// 遍历对象中的每一项
for( const key in target ){
// 如果对象为Object或者Array
if( isObject(target[key]) || isArray(target[key]) ){
// 递归遍历
this._observer(target[key]);
// 转为Proxy
target[key] = new Proxy(target[key], this._handler);
}
}
// 将转换好的target返回出去
return target;
}
}
// 利用以上封装好的Observer类 实现数据劫持
const o = {
a : [1, 2],
c : {
a : 1,
b : 2,
c : [
[1,2,{
d : 3
}]
]
},
b : 2
}
const ob = new Observer(o);
ob.a.push(3); // 检测到了set的key为 -> 2 检测到了set的key为 -> length
ob.c.a = 2; // 检测到了set的key为 -> a
ob.c.c[0][2].d = 6; // 检测到了set的key为 -> d
ob.b = 44; // 检测到了set的key为 -> b
总结
ES5 defineProperty方式及ES6 Proxy方式均能实现数据劫持,其中前者是Vue2数据劫持实现方式。
相比较而言,ES6 Proxy方式更优秀,能同时监听到对象及数组的变化,不需要重写数组方法,它也是Vue3实现数据劫持所采用方案。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· 从HTTP原因短语缺失研究HTTP/2和HTTP/3的设计差异
· 三行代码完成国际化适配,妙~啊~