JS数据劫持 之 Object.defineProperty

1、简介

数据劫持,指的是在访问或者修改对象的某个属性时,通过一段代码拦截这个行为,进行额外的操作或者修改返回结果。

经典的应用是Vue双向数据绑定。

常见的数据劫持实现方法有两种:

  • Object.defineProperty
  • proxy设置代理(ES6新增)

本篇文章介绍Object.defineProperty的使用。


2、defineProperty

2.1、介绍

Object.defineProperty(obj, prop, descriptor)

  • 参数:
    • obj:绑定的目标对象
    • prop:绑定的属性名
    • descriptor:配置,此参数为一个对象
  • descriptor可供定义的选项:
    • value:属性的值
    • writable:如果为false,属性的值就无法被重写。
    • enumerable:是否能在for...in循环中遍历或者在Object.keys中列举出来。
    • configurable:configurable默认值为false如果为false,任何尝试修改目标属性或者修改属性(writable、configurable,enumerable)的行为都会被无效化。
    • get:一旦目标属性被访问,就会触发此方法,并将该方法最后的返回值返回给用户。当设置get方法时,不能有value和writable方法,否则会报错。
    • set:一旦目标属性被赋值,就会触发此方法。当设置set方法时,不能有value和writable方法,否则会报错。

2.2、代码示例

2.2.1、通过value设置属性值

// 定义空对象
var target = {};
// 通过value赋值
Object.defineProperty(target, 'name', {
    value: 'cxk'
})
console.log(target); // {name: 'cxk'}

2.2.2、通过writable设置是否可写/修改

// 定义空对象
var target = {};

target.sex = '女';

Object.defineProperty(target, 'name', {
    value: 'cxk',
    writable: false, // 不可被重写
});
// 可以添加新的属性
target.age = 18;
console.log(target); // {sex: '女', age: 18, name: 'cxk'}

// 可以重写新的属性
target.age = 20;
console.log(target); // {sex: '女', age: 20, name: 'cxk'}

// 可以重写defineProperty绑定之前的属性
target.sex = '男';
console.log(target); // {sex: '男', age: 20, name: 'cxk'}

// 不可以重写通过defineProperty绑定的属性
target.name = 'wyf'; // Uncaught SyntaxError: Unexpected identifier 'l'

2.2.3、通过enumerable设置属性值是否允许遍历

// 定义空对象
var target = {};

target.sex = '女';

Object.defineProperty(target, 'name', {
    value: 'cxk',
    enumerable: false, // 不能在for...in循环中遍历或者在Object.keys中列举出来
});

target.age = '18';

// 可以看见,name属性并不能被遍历出来
for (key in target) {
    console.log(key); // sex age
    console.log(target[key]); // 女 18
}

console.log(Object.keys(target)); // (2) ['sex', 'age']

2.2.4、通过configurable设置属性是否可删

// 定义空对象
var target = {};

target.sex = '女';

Object.defineProperty(target, 'name', {
    value: 'cxk',
    configurable: true // 这个属性默认为false,也就是不可删除,因此开启测试
});

delete target.name;
console.log(target); // {sex: '女'}

注意点:configurable的默认值为false。


2.2.5、通过get方法获取属性的值

var obj = {
    name: 'cxk',
    sex: '女',
    age: 18
};

// 定义空对象
var target = {};

// 当设置get方法时,不能有value和writable方法,否则会报错。
for (let key in obj) {
    Object.defineProperty(target, key, {
        get: function() {
            return obj[key] + ',练习时长两年半。';
        }
    });
}

// 修改target对象的sex属性,target和obj都不变
target.sex = '男';
console.log(obj.sex); // 女
console.log(target.sex); // 女,练习时长两年半。

// 修改obj对象的sex属性,target和obj一起变化
obj.sex = '男';
console.log(obj.sex); // 男
console.log(target.sex); // 男,练习时长两年半。

// 对比两个对象的内存地址是否相同,结果为false
console.log(target == obj); // false

// 那可以得出模糊的结论,就是defineProperty方法将二者以某种方式连在了一起。

注意点:

  • 当设置get方法时,不能有value和writable方法,否则会报错。
  • 访问obj对象,不会触发get方法;访问target对象才会触发get方法。
  • target和obj对象通过defineProperty方法连接在一起,两者的内存地址并不相等,但是修改obj的属性,target属性也会跟着变化。

2.2.6、通过set方法设置属性的值

var obj = {
    name: 'cxk',
    sex: '女',
    age: 18
};

// 定义空对象
var target = {};

// 当设置set方法时,不能有value和writable方法,否则会报错。
// 因此,set方法要和get方法一起使用
for (let key in obj) {
    Object.defineProperty(target, key, {
        get: function() {
            return obj[key]
        },
        set: function(newValue) {
            console.log('触发了set方法---值为:' + newValue);
            return newValue;
        }
    });
}

// 修改target
target.sex = '男'; // 触发了set方法---值为:男(但没有修改成功)
console.log(target.sex); // 女
console.log(obj.sex); // 女

// 修改obj
obj.sex = '男'; // 没有触发set方法(但修改成功)
console.log(target.sex); // 男
console.log(obj.sex); // 男

注意点:

  • 当设置set方法时,不能有value和writable方法,否则会报错。
  • 修改obj对象不会触发set方法;修改target对象才会触发set方法。
posted @ 2022-11-28 15:37  笔下洛璃  阅读(339)  评论(0编辑  收藏  举报