眼尖的同学看完上面这段代码会发现一个问题,集合是没有 set 方法,集合赋值用的是 add 操作,那咋办呢?来看看那么 Vue3.0 是怎么处理的,上一段简化后的源码
function reactive(target: object) { return createReactiveObject( target, rawToReactive, reactiveToRaw, mutableHandlers, mutableCollectionHandlers ) } function createReactiveObject( target: any, toProxy: WeakMap<any, any>, toRaw: WeakMap<any, any>, baseHandlers: ProxyHandler<any>, collectionHandlers: ProxyHandler<any> ) { //collectionTypes = new Set<Function>([Set, Map, WeakMap, WeakSet]) const handlers = collectionTypes.has(target.constructor) ? collectionHandlers : baseHandlers //生成代理对象 observed = new Proxy(target, handlers) toProxy.set(target, observed) toRaw.set(observed, target) if (!targetMap.has(target)) { targetMap.set(target, new Map()) } return observed }
根据 target 类型适配不同的 handler,如果是集合 (Map、Set)就使用 collectionHandlers,是其他类型就使用 baseHandlers。接下来看看 collectionHandlers
export const mutableCollectionHandlers: ProxyHandler<any> = { get: createInstrumentationGetter(mutableInstrumentations) } export const readonlyCollectionHandlers: ProxyHandler<any> = { get: createInstrumentationGetter(readonlyInstrumentations) }
没有意外只有 get,骚就骚在这儿:
// 可变数据插桩对象,以及一系列相应的插桩方法 const mutableInstrumentations: any = { get(key: any) { return get(this, key, toReactive) }, get size() { return size(this) }, has, add, set, delete: deleteEntry, clear, forEach: createForEach(false) } // 迭代器相关的方法 const iteratorMethods = ['keys', 'values', 'entries', Symbol.iterator] iteratorMethods.forEach(method => { mutableInstrumentations[method] = createIterableMethod(method, false) readonlyInstrumentations[method] = createIterableMethod(method, true) }) // 创建getter的函数 function createInstrumentationGetter(instrumentations: any) { return function getInstrumented( target: any, key: string | symbol, receiver: any ) { target = hasOwn(instrumentations, key) && key in target ? instrumentations : target return Reflect.get(target, key, receiver) } }
由于 Proxy 的 traps 跟 Map|Set 集合的原生方法不一致,因此无法通过 Proxy 劫持 set,所以作者在在这里进行了"偷梁换柱",这里新创建了一个和集合对象具有相同属性和方法的普通对象,在集合对象 get 操作时将 target 对象换成新创建的普通对象。这样,当调用 get 操作时 Reflect 反射到这个新对象上,当调用 set 方法时就直接调用新对象上可以触发响应的方法,是不是很巧妙?所以多看源码好处多多,可以多学学人家的骚操作。
IE 怎么办?
这是个实在不想提但又绕不开的话题,IE 在前端开发者眼里和魔鬼没什么区别。在 Vue3.0 之前,响应式数据的实现是依赖 ES5 的 Object.defineProperty,因此只要支持 ES5 的浏览器都支持 Vue,也就是说 Vue2.x 能支持到 IE9。Vue3.0 依赖的是 Proxy 和 Reflect 这一对出生新时代的 CP,且无法被转译成 ES5,或者通过 Polyfill 提供兼容,这就尴尬了。开发者技术前线获悉的信息,官方在发布最终版本之前会做到兼容 IE11,至于更低版本的 IE 那就只有送上一曲凉凉了。
其实也不用太纠结IE的问题,因为连微软自己都已经放弃治疗 IE 拥抱 Chromium 了,我们又何必纠结呢?
结语
在使用开源框架时不要忘了,我们之所以能免费试用他,靠的维护者投入的大量精力。希望我们多去发现它带来的优点和作者想通过它传递的编程思想。最后期待 Vue3.0 正式版本的早日到来。