浅谈Reflect

为什么是Reflect.get, 而不是target[key]?

vue3源码中看到下面这样一段代码. 创建代理时, Proxy的get钩子中返回数据使用了Reflect.get(target, key, receiver). 第一反应是这又是啥花里胡哨的玩意? 为什么不直接用target[key]? 仅仅是为了使用新语法么? 不用行不行?

答案当然是不行的.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
const shallowUnwrapHandlers = {
    get: (target, key, receiver) => unref(Reflect.get(target, key, receiver)),
    set: (target, key, value, receiver) => {
        const oldValue = target[key];
        if (isRef(oldValue) && !isRef(value)) {
            oldValue.value = value;
            return true;
        }
        else {
            return Reflect.set(target, key, value, receiver);
        }
    }
};
function proxyRefs(objectWithRefs) {
    return isReactive(objectWithRefs)
        ? objectWithRefs
        : new Proxy(objectWithRefs, shallowUnwrapHandlers);
}

什么是Reflect

Reflect是一个全局对象.其提供了很多方法, 并且任何在Proxy的拦截器中能够找到的方法, 都能够在Reflect中找到同名函数. 下面是所有方法及说明

方法 说明
Reflect.aply(target, thisArgument, argumentsList) 对一个函数进行调用操作, 与Function.prototype.apply功能类似
Reflect.construct(target, argumentsList[, newTarget]) 对构造函数进行new操作, 相当于执行new target(…args)
Reflect.defineProperty(target, propertyKey, attributes) 和Object.defineProperty类似, 如果设置成功就返回true
Reflect.deleteProperty(target, propertyKey) 作为函数的delete操作符, 相当于执行 delete target[key]
Reflect.get(target, propertyKey[, receiver]) 获取对象身上某个属性的值, 类似于target[name]
Reflect.getOwnPropertyDescriptor(target, propertyKey) 类似于Object.getOwnPropertyDescriptor, 如果对象中存在该属性, 则返回对应的属性描述符, 否则返回undefined
Reflect.getPrototypeOf(target) 类似于Object.getPrototypeOf()
Reflect.has(target, propertyKey) 判断一个对象是否存在某个属性, 和 in 运算符的功能完全相同
Reflect.isExtensible(target) 类似于Object.isExtensible()
Reflect.ownKeys(target) 返回一个包含所有自身属性(不包含继承属性)的数组. (类似于Object.keys(), 但不会受enumerable影响)
Reflect.preventExtensions(target) 类似于Object.preventExtensions(), 返回Boolean
Reflect.set(target, propertyKey, value[, receiver]) 将值分配给属性的函数. 返回Boolean. 更新成功返回true
Reflect.setPrototypeOf(target, prototype) 设置对象原型的函数. 返回Boolean, 更新成功返回true

target[key]会有什么问题

给需要监听的obj一个访问器属性, 返回this.foo的值. 然后在effect副作用函数中通过代理对象p来访问bar属性. 然后改变p.foo的值.

我们期望的输出当然是随着p.foo改变, 副作用函数再次执行. 但是实际输出与预期并不符.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
const obj = {
foo: 1,
get bar() {
return this.foo
}
};
const p = new Proxy(obj, {
get(target, key) {
track(target, key)
return target[key]
},
set(target, key, newVal) {
target[key] = newVal
trigger(target, key)
return true
}
});
effect(() => {
console.log(p.bar);
})
p.foo = 10

输出结果:

1
1

控制台只输出了一次1. 当p.foo=10执行时, 副作用函数并没有被执行. 为什么?

给访问属性添加输出日志

1
2
3
4
5
6
7
const obj = {
foo: 1,
get bar() {
console.log(this)
return this.foo
}
};

输出结果:

输出结果

此时bar函数的this指向原始对象本身. 所以在副作用函数中访问p.bar 等价于访问obj.foo. 自然也不会建立响应联系了.

那怎么做才能让bar函数中的this指向代理对象呢?

Reflect.get

Reflect.get(target, propertyKey[, receiver])接受三个参数, 其中receiver代表谁在读取属性

修改代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
const obj = {
foo: 1,
get bar() {
// 此时this为代理对象p
return this.foo
}
};
const p = new Proxy(obj, {
// 拦截读取操作, 接受第三个参数 receiver
get(target, key, receiver) {
track(target, key)
// 使用Reflect.get返回读取到的属性值
return Reflect.get(target, key, receiver)
},
set(target, key, newVal) {
target[key] = newVal
trigger(target, key)
return true
}
});
effect(() => {
console.log(p.bar);
})
p.foo = 10

// 输出结果

1
2
1
10