所谓的数据劫持,说白了就是在数据变动的时候,可以执行一些自定义的操作,而通过Object.defineProperty
来设置对象属性的setter和getter方法刚好可以做到这一点,所以只要学会的Object.defineProperty
的使用,数据劫持是很简单的。
代码实践
先准备一个对象
1
2
3
4var data={
name:"daihaoxin",
job:"coding"
}获取对象的属性描述符
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17console.log(Object.getOwnPropertyDescriptors(data));
// 输出
{
"name": {
"value": "daihaoxin",
"writable": true,
"enumerable": true,
"configurable": true
},
"job": {
"value": "coding",
"writable": true,
"enumerable": true,
"configurable": true
}
}下面我们将data的属性转换为存取描述符
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22Object.keys(data).map((item,index)=>{
var initData = data[item];
Object.defineProperty(data, item, {
enumerable: true,
configurable: true,
set: function(val) {
console.log(`修改属性${item}值为${val}`);
// 注意这里不能直接使用 data[item]=val,这样会引发重复设置属性值的死循环
initData = val;
},
get: function() {
console.log(`获取属性${item}的值${initData}`);
// 注意这里不能使用data[item],同样会引发重复获取属性值的死循环
return initData;
}
});
});
// 尝试修改属性的值试一试
data.name="xingmu"; // 修改属性name值为xingmu
data.name; // 获取属性name的值xingmu
data.job="jser"; // 修改属性job值为jser
data.job; // 获取属性job的值jser
通过Object.getOwnPropertyDescriptors
对比前后两次的属性描述符,可以看到已经成功转换了
- 封装为函数,有两点需要注意
- 对参数做为空和类型验证
- 对复杂属性进行递归处理
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34function dataIntercept(data) {
if(!data || typeof data !== 'object'){
console.log(`参数必须为对象`);
return false;
}
Object.keys(data).map((item,index)=>{
var initData = data[item];
Object.defineProperty(data, item, {
enumerable: true,
configurable: true,
set: function(val) {
console.log(`修改属性${item}值为${val}`);
initData = val;
},
get: function() {
console.log(`获取属性${item}的值${initData}`);
return initData;
}
});
if(typeof initData === 'object'){
dataIntercept(initData)
}
});
}
var data = {
name: "daihaoxin",
job: "coding"
}
dataIntercept(data);
// 尝试修改属性的值试一试
data.name="xingmu"; // 修改属性name值为xingmu
data.name; // 获取属性name的值xingmu
data.job="jser"; // 修改属性job值为jser
data.job; // 获取属性job的值jser
上面的 dataIntercept 函数实现了一个数据监听,当监听某个对象后,我们可以在用户读取或者设置属性值的时候做个拦截,做我们想做的事
总结
这个dataIntercept能够实现预定的基本功能,但是对于属性值为数组类型的处理上还有些问题,无法监控数组的变化原因:
- 当你修改数组的长度时,例如:
arr.length = newLength
- 当你利用索引直接设置一个项时,例如:
arr[index] = newValue
- 无法监控通过Array.prototype上的push、pop、shift、unshift、splice、sort、reverse、fill和copyWithin对数组的改变