MDN文档
第三个参数的解释:
configurable:属性是否可配置(重新defineProperty),是否可删除,默认false
enumerable:属性可否被遍历,默认false
writable:配合value使用,可否修改value,默认false
value:默认undefined
get:默认undefined
set:默认undefined
一般用get、set就好,不用writable和value。
描述符同时存在的情况:
// 观察者(劫持数据)
class Observer{
constructor(data){
this.data = data;
// 传入的是个数组或js对象才进行劫持
Observer.checkDataType(this.data) ? this.init(this.data) : null;
}
// 检测数据类型是否是数组或js对象
static checkDataType(data){
return Object.prototype.toString.call(data) === '[object Object]' || Object.prototype.toString.call(data) === '[object Array]';
}
init(data){
Object.keys(data).forEach(key => {
// 如果当前属性值是个数组或js对象,则继续深度劫持
Observer.checkDataType(data[key]) ? new Observer(data[key]) : null;
this.defineReactive(data, key, data[key]);
})
}
// 劫持数据的具体过程(Object.defineProperty)
defineReactive(data, key, value){
let dep = new Dep(); // 每条数据一个dep
Object.defineProperty(data, key, {
enumerable: true,
get(){
// watcher初次取值时将该watcher加入到dep中
Dep.target ? (console.log('watcher第一次来取值了'), dep.add(Dep.target)) : null;
return value;
},
set(newValue){
value = newValue;
// 数据改变了,对应的dep通知watcher
dep.notify();
}
})
}
}
// 订阅者的管理员
class Dep{
constructor(){
// 所有订阅者
this.watchers = [];
}
add(watcher){
// 添加订阅者
this.watchers.push(watcher);
}
notify(){
// 通知订阅者,订阅者做出响应
this.watchers.forEach(watcher => {
watcher.update();
})
}
}
// 订阅者
class Watcher{
constructor(data, exp, cb){
this.data = data; //订阅的数据源
this.exp = exp; //具体订阅那条数据
this.cb = cb; //数据改变后要做什么
this.value = this.getValueFirstTime();
}
// 订阅者初次取值时,在Dep中标记
getValueFirstTime(){
Dep.target = this;
let value = this.getValue();
Dep.target = null;
return value;
}
getValue(){
let value = this.data;
// 将如 c.d[2] 这样的表达式,用正则捕获成 [c, d, 2]
this.exp.match(/(((?![.\[\]]).)+)/g).forEach(key => {
value = value[key];
});
return value;
}
// 数据更新了,订阅者做出动作
update(){
// 新值旧值不一致时才做出动作
this.getValue() !== this.value ? (this.cb && this.cb(), this.value = this.getValue()) : null;
}
}
MDN文档 阮一峰文章
基本用法:
let data = {a:1, b:[1,2,3]};
let handler = {
get(target, propKey, proxy){
return target[propKey];
},
set(target, propKey, value, proxy){
target[propKey] = value;
}
}
let p1 = new Proxy(data, handler);
// 观察者(数据代理)
class Observer{
constructor(data){
this.data = data;
this.proxyData; //暴露出去的proxy对象
Observer.checkDataType(this.data) ? this.init() : null;
}
// 检测数据类型是否是数组或js对象
static checkDataType(data){
return Object.prototype.toString.call(data) === '[object Object]' || Object.prototype.toString.call(data) === '[object Array]';
}
init(){
let deps = {}; // 所有的dep
let handler = {
get(target, propKey, proxy){
// 看看当前数据的dep定义了没有,没有就定义
deps[propKey] ? null : deps[propKey] = new Dep();
// watcher初次取值时将该watcher加入到dep中
Dep.target ? (console.log('watcher第一次来取值了'), deps[propKey].add(Dep.target)) : null;
// 如果是数组或js对象,则继续深度代理
return Observer.checkDataType(target[propKey]) ? new Proxy(target[propKey], handler) : Reflect.get(target, propKey);
},
set(target, propKey, value, proxy){
Reflect.set(target, propKey, value);
// 数据改变了,对应的dep通知watcher
deps[propKey] ? deps[propKey].notify() : null;
}
}
this.proxyData = new Proxy(this.data, handler);
}
}
// 订阅者管理员
class Dep{
constructor(){
this.watchers = [];
}
add(watcher){
this.watchers.push(watcher);
}
notify(){
this.watchers.forEach(watcher => {
watcher.update();
})
}
}
// 订阅者
class Watcher{
constructor(proxyData, exp, cb){
this.proxyData = proxyData; // 数据被代理之后,只需操作该proxy对象即可
this.exp = exp;
this.cb = cb;
this.value = this.getValueFirstTime();
}
// 订阅者初次取值时,在Dep中标记
getValueFirstTime(){
Dep.target = this;
let value = this.getValue();
Dep.target = null;
return value;
}
getValue(){
let value = this.proxyData;
// 将如 c.d[2] 这样的表达式,用正则捕获成 [c, d, 2]
this.exp.match(/(((?![.\[\]]).)+)/g).forEach(key => {
value = value[key];
});
return value;
}
// 数据更新了,订阅者做出动作
update(){
// 新值旧值不一致时才做出动作
this.getValue() !== this.value ? (this.cb && this.cb(), this.value = this.getValue()) : null;
}
}
该方式和上面的 defineProperty 方式的不同在于,数据被代理后,数据的读写都在该 proxy 对象上操作,以及 Dep 和 Watcher 对象插入的时机。
1、Proxy 返回一个Proxy对象,我们只操作该对象即可。defineProperty 则是将原来的对象属性一一遍历重新定义,然后还是操作原来对象。
2、Proxy 有更多的拦截方法(具体看文档),而 defineProperty 只有 get/set 。
3、Proxy 能监测到新添加的属性,而 defineProperty 不能。
4、Proxy 作为新标准将受到浏览器厂商重点持续的性能优化,也就是传说中的新标准的性能红利。
5、Proxy 毕竟是新标准,不可避免存在兼容性问题。