前言
先看下面一段常规的object赋值代码:
const people = {
name: 'Rookie',
age: 18
};
people.age = 20;// set
people.age;// get
现在我们想要自定义实现people的set和get,那么应该如何实现呢?
下面我们介绍两种实现方式:Object.defineProperty(),Proxy
Object.defineProperty()
Object.defineProperty() 方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象。
使用语法:
Object.defineProperty(obj, prop, descriptor)
在descriptor中允许我们自定义obj的set和get方式。
有关Object.defineProperty()的更多用法可以看官方文档:文档资料
那么我们接下来使用Object.defineProperty()来重构前言的代码:
function defineReactive(data, key, val) {
Object.defineProperty(data, key, {
enumerable: true,
configurable: true,
get: function defineGet() {
console.log(`get key: ${key} val: ${val}`);
return val;
},
set: function defineSet(newVal) {
console.log(`set key: ${key} val: ${newVal}`);
val = newVal;
}
})
}
function observe(data) {
Object.keys(data).forEach(function (key) {
defineReactive(data, key, data[key]);
})
}
let people = {
name: 'Rookie',
age: 18
};
observe(people);
people.age = 20;// set key: age val: 20
people.age;// get key: age val: 20
people.sex = 'man';
由于people中含有多个属性,所以这里需要一个observe函数将people中的属性逐一拆解并进行defineReactive。从结果中看出对于people中已有的属性可以进行自定义操作,但是对于people中没有的属性却无法进行自定义操作。那么有什么解决方案呢?细心的朋友应该发现,Vue2的数据双向绑定就是通过此方式实现的,对于此问题的解决方案,我们可以重新生成people属性然后再次进行observe即可。
升级后的代码:
function defineReactive(data, key, val) {
Object.defineProperty(data, key, {
enumerable: true,
configurable: true,
get: function defineGet() {
console.log(`get key: ${key} val: ${val}`);
return val;
},
set: function defineSet(newVal) {
console.log(`set key: ${key} val: ${newVal}`);
val = newVal;
}
})
}
function observe(data) {
Object.keys(data).forEach(function (key) {
defineReactive(data, key, data[key]);
})
}
let people = {
name: 'Rookie',
age: 18
};
observe(people);
people.age = 20;// set key: age val: 20
people.age;// get key: age val: 20
people.sex = 'man';
observe(people);
people.sex = 'woman';
不难发现,Object.defineProperty()是劫持对象的属性,新增元素需要再次 definedProperty。。而 Proxy 劫持的是整个对象,不需要做特殊处理,Proxy也就是Vue3实现数据双向绑定的实现原理。
Proxy
Proxy 对象用于创建一个对象的代理,从而实现基本操作的拦截和自定义(如属性查找、赋值、枚举、函数调用等)。
使用语法:
const p = new Proxy(target, handler)
在handler中允许我们自定义target的set和get方式。
有关Proxy的更多用法可以看官方文档:proxy文档
那么我们接下来使用Proxy来重构前言的代码:
let people = {
name: 'Rookie',
age: 18
};
let person = new Proxy(people, {
get: function (obj, prop) {
// 附加一个属性
console.log(`get key: ${prop} val: ${obj[prop]}`);
// 默认行为是返回属性值
return obj[prop];
},
set: function (obj, prop, value) {
// 附加属性
obj[prop] = value;
console.log(`set key: ${prop} val: ${value}`);
// 表示成功
return true;
}
});
person.age = 20;
person.age;
person.sex = 'man';
people; //{name: "Rookie", age: 20, sex: "man"}
Proxy校验器
下面是一个使用proxy实现的一个对象拦截校验器的案例:
const target = {
_id: '1024',
name: 'vuejs'
}
const validators = {
name(val) {
return typeof val === 'string';
},
_id(val) {
return typeof val === 'number' && val > 1024;
}
};
const createValidator = (target, validator) => {
return new Proxy(target, {
_validator: validator,
set(target, prop, value, proxy) {
let validator = this._validator[prop](value)
if (validator) {
console.log(`set key: ${prop} val: ${value}`);
return Reflect.set(target, prop, value, proxy)
} else {
console.error(`Cannot set ${prop} to ${value}. Invalid type.`);
}
}
});
}
const proxy = createValidator(target, validators);
proxy.name = 'vue-js.com' // vue-js.com
proxy.name = 10086 // Cannot set name to 10086. Invalid type.
proxy._id = 1025 // 1025
proxy._id = 22 // Cannot set _id to 22. Invalid type
结语
虽然Object.defineProperty()和proxy都能实现对象属性的自定义,但是proxy作为es6新出现的特性,它的功能比defineProperty 更加丰富使用起来也更加灵活。