Object.defineProperty和Proxy对比

Object.defineProperty(obj, prop, descriptor)

MDN文档

第三个参数的解释:

configurable:属性是否可配置(重新defineProperty),是否可删除,默认false

enumerable:属性可否被遍历,默认false

writable:配合value使用,可否修改value,默认false

value:默认undefined

get:默认undefined

set:默认undefined

一般用get、set就好,不用writable和value

描述符同时存在的情况:

基于Object.defineProperty()和发布订阅实现的数据劫持

// 观察者(劫持数据)
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;
    }
}

new Proxy(data, handler)

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);

基于Proxy和发布订阅实现的数据劫持(代理)

// 观察者(数据代理)
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 对象插入的时机

defineProperty和Proxy的一些区别

1、Proxy 返回一个Proxy对象,我们只操作该对象即可。defineProperty 则是将原来的对象属性一一遍历重新定义,然后还是操作原来对象。

2、Proxy 有更多的拦截方法(具体看文档),而 defineProperty 只有 get/set 。

3、Proxy 能监测到新添加的属性,而 defineProperty 不能。

4、Proxy 作为新标准将受到浏览器厂商重点持续的性能优化,也就是传说中的新标准的性能红利。

5、Proxy 毕竟是新标准,不可避免存在兼容性问题。

发布了63 篇原创文章 · 获赞 18 · 访问量 4万+

猜你喜欢

转载自blog.csdn.net/samfung09/article/details/103758871
今日推荐