背景我们都知道vue3重写了响应式代码,使用Proxy劫持数据操作,分离出一个单独的库@vue/reactivity,不局限于vue,可以在任何js代码中使用,但是由于使用了Proxy,Proxycannot使用polyfill为了兼容使得在不支持Proxy的环境下无法使用。这也是vue3不支持ie11的部分原因。部分内容:响应式原理@vue/reactivity与vue2响应式的区别使用Object.defineProperty重写遇到的问题及解决方案代码实现应用场景及限制源码地址:reactivity主要是defObserver。之前我们先简单了解下@vue/reactivity的响应。首先我们在获取数据的时候劫持了一个数据来收集依赖,记录下我们调用了哪个方法,假设是方法effect1调用的。set中设置数据时,get中get中记录的方法触发effect1函数达到监听的目的,effect是一个封装方法,将调用前后的执行栈设置为自身,收集函数执行时的依赖差异vue3与vue2相比,最大的区别是使用ProxyProxy可以比Object.defineProperty有更全面的代理拦截:(虽然Proxy带来了更全面的功能,但是也带来了性能,Proxy其实比Object.defineProperty慢很多)关于ES6Proxy性能的思考get/set劫持未知属性constobj=reactive({});effect(()=>{console.log(obj.name);});obj.name=111;this在Vue2中必须使用set方法来赋值数组元素下标的变化。可以直接使用下标操作数组,直接修改数组lengthconstarr=reactive([]);effect(()=>{console.log(arr[0]);});arr[0]=111;支持deleteobj[key]属性删除的constobj=reactive({name:111,});effect(()=>{console.log(obj.name);});deleteobj.name;obj属性中的key是否存在支持constobj=reactive({});effect(()=>{console.log("name"inobj);});obj.name=111;for(letkeyinobj){}属性遍历ownKeys支持constobj=reactive({});effect(()=>{for(constkeyinobj){console.log(key);}});obj.name=111;支持Map,Set,WeakMap,WeakSet这些是Proxy带来的功能,有一些新的概念或者用法上的变化。独立分包,Vue中不仅可以使用reactive/effect/computed等函数式方法,更灵活地将原始数据和响应数据隔离,还可以通过toRaw获取原始数据,在vue2中,直接在原始数据,功能比较全面reactive/readonly/shallowReactive/shallowReadonly/ref/effectScope,read-only,shallow,基本类型的劫持,function那么如果我们要使用Object.defineProperty,是否可以完成上面的功能呢?你会遇到什么问题?问题及解决方案我们先忽略Proxy和Object.defineProperty的功能差异,因为我们要写的是@vue/reactivity而不是vue2,所以我们需要先解决一些新的概念差异,比如原始数据和响应数据的隔离@vue/reactivity方法,原始数据和响应数据之间存在弱类型引用(WeakMap)。获取对象类型数据时,仍取原始数据。判断是否有对应的响应数据,然后去取。如果存在则生成相应的响应式数据保存并获取。这样,它就被控制在get级别。通过responsivedata得到的永远是responsivetype,通过originalobject得到的永远是originaldata(除非你直接直接赋值responsivetype)propertiesinanoriginalobject),则不能直接使用vue2的源码。根据上面提到的逻辑,写一个最低限度实现的代码来验证逻辑:constproxyMap=newWeakMap();functionreactive(target){//如果当前原始对象已经有对应的响应对象,则返回缓存constexistingProxy=proxyMap.get(target);如果(现有代理){返回现有代理;}常量代理={};for(constkeyintarget){proxyKey(proxy,target,key);}proxyMap.set(target,proxy);返回代理;}functionproxyKey(proxy,target,key){Object.defineProperty(proxy,key,{enumerable:true,configurable:true,get:function(){console.log("get",key);constres=target[key];if(typeofres==="object"){returnreactive(res);}returnres;},set:function(value){console.log("set",key,value);复制代码target[key]=value;},});tryitintheonlineexamplesowedo这里,原始数据和响应数据是隔离的,不管数据层次有多深,我们还面临一个问题,数组呢?数组是通过下标获取的,跟对象的属性不一样。如何隔离它们就是像对象一样劫持数组下标。consttarget=[{deep:{name:1}}];constproxy=[];for(letkeyintarget){proxyKey(proxy,target,key);}在网上的例子中,尝试在上面的代码中加入一个isArray判断,这也决定了我们会一直维护这个数组映射未来。其实也很简单。当数组push/unshift/pop/shift/splice的长度发生变化时,重新映射新增或删除的下标。常量仪器={};//存储重写的方法["push","pop","shift","unshift","splice"].forEach((key)=>{instruments[key]=function(...args){constoldLen=target.length;constres=target[key](...args);constnewLen=target.length;//添加/删除元素if(oldLen!==newLen){if(oldLen
