Skip to content

Proxy和Reflect

Proxy是es6中新增的代理对象,通过Proxy,我们可以监听对象的变化,实现响应式。

Reflect也是ES6新增的一个API,它是一个对象,字面的意思是反射。用于替换在Object构造函数上实现的一些方法,es6之后用Reflect会更加规范。

**捕获器不变式:**如果目标对象有一个不可写的数据属性,那么在捕获器返回一个与该属性不同的值时,会抛出TypeError。

可撤销代理:Proxy.Revocable(target,heandler); 定义可撤销的代理。

通过revoke()函数撤销

js
const obj = {
    name: "fewa",
};
const {proxy, revoke} = Proxy.revocable(obj, {
    get(target, key) {
        console.log("fe");
        return Reflect.get(target, key);
    },
});
console.log(proxy.name); //fewa
revoke();
console.log(proxy.name); //Uncaught TypeError: Cannot perform 'get' on a proxy that has been revoked

代理中this的问题:如果目标对象依赖于对象标识,那this可能会出错。

js
const wm = new WeakMap();
class User {
    constructor(userId) {
        wm.set(this, userId);
    }
    set id(userId) {
        wm.set(this, userId);
    }
    get id() {
        return wm.get(this);
    }
}

const user = new User(2);

console.log(user.id); //2
const proxy = new Proxy(User, {}); //User类
//const userProxy =  new Proxy(user,{}) //代理实例时,由于this指向代理对象,但是vm中找不到代理对象,因此userProxy.id会打印undefined
const userProxy = new proxy(2);
console.log(userProxy.id); //2

不能代理Date对象,因为Date类型方法的执行依赖this值上的内部槽位[[NumberDate]]

js
const target = new Date();
const proxy = new Proxy(target, {});
console.log(proxy instanceof Date);
proxy.getDate();// TypeError: this is not a Date object.

Proxy和Relect的基本使用:

js
 let obj = {
        name: "wag",
        age: 12,
      };

let objpro = new Proxy(obj, {
    //target为被被代理的obj对象,receiver为objpro代理对象
    set(target, key, value, receiver) {
        Reflect.set(target, key, value, receiver);
    },
    get(target, key, receiver) {
        return Reflect.get(target, key, receiver);
    },
    has(target, key) {
        return Reflect.has(target, key);
    },
    deleteProperty(target, key) {
        Reflect.deleteProperty(target, key);
    },
});

参数receiver的作用

receiver实际上是代理对象,当我们在Reflect的set() 和get()方法中传入receiver是,可以改变别代理对象obj中的this指向,让this指向objpro代理对象。从而实现访问objpro的_name,实现私有属性的监听。

js
const obj = {
    _name: "wang",
    set name(value) {
        //this指向obj
        this._name = value;
    },
    get name() {
        //this指向obj
        return this._name;
    },
};

const objpro = new Proxy(obj, {
    set(target, key, value, receiver) {
        console.log("----set");
        Reflect.set(target, key, value,receiver);
    },
    get(target, key, receiver) {
        console.log("----get");
        return Reflect.get(target, key,receiver);
    },
});
//没有传入receiver时,我们只能监听到name属性的访问和修改,监听不到_name
//传入receiver后,都可以监听到
console.log(objpro.name);
objpro.name = "sisi";

Reflect.constructor()

创建一个对象,可以指定对象的类型。

js
function student(name) {
    this.name = name;
}

function Animal() {}

const stu = Reflect.construct(student, ["name"], Animal);
console.log(stu); //原型指向Animal

响应式

vue3响应式

js
//正在执行的函数
let reactiveFn = null;
//封装Depend类,
class Depend {
    constructor() {
        //使用Set(),防止一个函数多次使用同一个key,
        //导致多次添加此函数到数组中,因此使用set可以防止重复
        this.reactiveFns = new Set();
    };
    // addFns(fn) {
    //     this.reactiveFns.push(fn);
    // };
    //不用传递参数,直接调用方法,就可以传入响应函数
    depend() {
        if (reactiveFn) {
            this.reactiveFns.add(reactiveFn);
        }
    }
    notify() {
        this.reactiveFns.forEach(fn => {
            fn();
        });
    }
}

//监听响应式函数
function WatchFns(fn) {
    //添加需要监听的函数
    reactiveFn = fn;
    fn();
    reactiveFn = null;
}

//封装获取depend的函数
const targetMap = new WeakMap();

function getDepend(target, key) {
    //从weakMap中通过target来获取Map对象
    let map = targetMap.get(target);
    if (!map) {
        map = new Map();
        targetMap.set(target, map);
    }

    //从Map中通过key来获取depend
    let depend = map.get(key);
    if (!depend) {
        depend = new Depend();
        map.set(key, depend);
    }
    return depend;
}

//封装创建代理对象函数
function newProxy(obj) {
    return new Proxy(obj, {
        get(target, key, receiver) {
            // console.log('get-----', target, key);
            //收集使用对象属性的函数(需要响应的函数)
            const dep = getDepend(target, key);
            dep.depend();
            return Reflect.get(target, key, receiver);
        },
        set(target, key, newValue, receiver) {
            // console.log('set-----', target, key);
            Reflect.set(target, key, newValue, receiver);
            let depend = getDepend(target, key);
            //执行需要响应的函数
            depend.notify();
        }
    });
}

let obj = {
    name: 'wag',
    age: 15
}

let info = {
    name: 'kkk',
    age: 19,
    address: '北京市'
}

let objproxy = newProxy(obj);
let infoproxy = newProxy(info);

WatchFns(function() {
    console.log(objproxy.name + '-----name1');
})

WatchFns(function() {
    console.log(objproxy.name + '-----name2');
    console.log(objproxy.name + '-----name2');
})
WatchFns(function() {
    console.log(objproxy.age + '-----age1');
})

WatchFns(function() {
    console.log(infoproxy.age + '-----info-age');
})


objproxy.age = 45;
infoproxy.age = 78

vue2响应式

js
// 保存当前需要收集的响应式函数
let activeReactiveFn = null

/**
 * Depend优化:
 *  1> depend方法
 *  2> 使用Set来保存依赖函数, 而不是数组[]
 */

class Depend {
    constructor() {
        this.reactiveFns = new Set()
    }


    depend() {
        if (activeReactiveFn) {
            this.reactiveFns.add(activeReactiveFn)
        }
    }

    notify() {
        this.reactiveFns.forEach(fn => {
            fn()
        })
    }
}

// 封装一个响应式的函数
function watchFn(fn) {
    activeReactiveFn = fn
    fn()
    activeReactiveFn = null
}

// 封装一个获取depend函数
const targetMap = new WeakMap()

function getDepend(target, key) {
    // 根据target对象获取map的过程
    let map = targetMap.get(target)
    if (!map) {
        map = new Map()
        targetMap.set(target, map)
    }

    // 根据key获取depend对象
    let depend = map.get(key)
    if (!depend) {
        depend = new Depend()
        map.set(key, depend)
    }
    return depend
}

function reactive(obj) {
    // {name: "why", age: 18}
    // ES6之前, 使用Object.defineProperty
    Object.keys(obj).forEach(key => {
        let value = obj[key]
        Object.defineProperty(obj, key, {
            get: function() {
                const depend = getDepend(obj, key)
                depend.depend()
                return value
            },
            set: function(newValue) {
                value = newValue
                const depend = getDepend(obj, key)
                depend.notify()
            }
        })
    })
    return obj
}

// 监听对象的属性变量: Proxy(vue3)/Object.defineProperty(vue2)
const objProxy = reactive({
    name: "why", // depend对象
    age: 18 // depend对象
})

const infoProxy = reactive({
    address: "广州市",
    height: 1.88
})

watchFn(() => {
    console.log(infoProxy.address)
})

infoProxy.address = "北京市"

const foo = reactive({
    name: "foo"
})

watchFn(() => {
    console.log(foo.name)
})

foo.name = "bar"

响应式原理图解析:

Denpend类中的set用于保存每一个对象的每一个属性需要相应的函数,通过weakMap来引用Obj对象(防止obj对象无法销毁),Map中保存key和相应Depnend的依赖关系。这样可以实现每一个Depend中只存放属于一个key的响应函数。