VUE3非原始值响应方案完整代码,所有类型对象的完整方案
/* *原理:当触发数据读取操作时,执行副作用函数并存储到桶中 *当设置数据操作时,再将副作用函数从桶中取出并执行 */ //用一个全局变量activeEffect存储被注册过的副作用函数 let activeEffect //const buket=new Set() /* *weakMap为弱引用,不影响垃圾回收机制工作,当用户代码对一个 *对象没有引用关系时,垃圾会收器会回收该对象,避免引起栈堆的溢出 */ const bucket=new WeakMap() const effectStack=[] //定义一个宏任务队列 const jobQueue=new Set() //定义一个Promose,将一个任务添加到微任务队列 const p=Promise.resolve() //是否正在刷新队列 let isFlushing=false //Symbol唯一,可以作为对象属性标识符使用 const ITERATE_KEY=Symbol() //map的键使用 const MAP_ITERATE_KEY=Symbol() //定义一个map实例,存储原始对象到代理对象的映射 const reactiveMap=new Map() //代表是否追踪 let shouldTrack=true //定义一个对象,将自定义add方法添加到下面 const mutableInstrumentations={ add(key){ //this指向代理对象,通过raw获取原始数据对象 const target=this.raw //是否元素已经存在集合中 const hadkey=target.has(key) //直接使用原始数据对象的方法 const res=target.add(key) if(!hadkey){ trigger(target,key,"ADD") } return res }, delete(key){ //this指向代理对象,通过raw获取原始数据对象 const target=this.raw //是否元素已经存在集合中 const hadkey=target.has(key) const res=target.delete(key) if(hadkey){ trigger(target,key,"DELETE") } return res }, //map数据类型拥有get和set两个方法,所以需要添加这两个方法 get(key){ //this指向代理对象,通过raw获取原始数据对象 const target=this.raw //是否元素已经存在集合中 const had=target.has(key) //追踪依赖建立响应 track(target,key) //直接使用原始数据对象的方法 const res=target.get(key) //如果存在,返回结果,递归使用reactive返回最终的原始数据而非对象 if(!had){ return typeof res==="object"? reactive(res):res } return res }, set(key,value){ //this指向代理对象,通过raw获取原始数据对象 const target=this.raw //是否元素已经存在集合中 const had=target.has(key) //获取旧值 const oldValue=target.get(key) //此处为避免污染原始数据,value必须是数据,而不能是代理对象 const rawValue=value.raw || value //设置新值 target.set(key,rawValue) //如果不存在,说明时add //只有旧值与新值不同才会触发更新 //因为NaN===NaN为false,而NaN!==NaN为true if(!had){ trigger(target,key,"ADD") }else if(oldValue!==value && (oldValue===oldValue || value===value)){ trigger(target,key,"SET") } }, forEach(callback,thisArg){ const wrap=(val)=>typeof val==="object" ? reactive(val) : val const target=this.raw //建立响应 track(target,ITERATE_KEY) //通过原始对象调用传递过去的函数 target.forEach((v,k)=>{ //手动实现深响应 callback.call(thisArg,wrap(v),wrap(k),this) }) }, [Symbol.iterator]:iterationMethod, entries:iterationMethod, values:valuesIterationMethod, keys:keysIterationMethod, } //抽离独立函数 function iterationMethod(){ const wrap=(val)=>typeof val==="object" && val!==null ? reactive(val) : val const target=this.raw //获取原始迭代器方法 const itr=target[Symbol.iterator]() //建立响应 track(target,ITERATE_KEY) return { //对象的next方法是迭代协议 //调用原始迭代器的next方法获取value和done next(){ const {value,done}=itr.next() return { //如果value不是undifined,进行包裹 value:value?[wrap(value[0]),wrap(value[1])]:value, done } }, //实现可迭代协议 [Symbol.iterator](){ return this } } } //抽离独立函数 function valuesIterationMethod(){ const wrap=(val)=>typeof val==="object" && val!==null ? reactive(val) : val const target=this.raw //获取原始迭代器方法 const itr=target.values() //建立响应 track(target,ITERATE_KEY) return { //对象的next方法是迭代协议 //调用原始迭代器的next方法获取value和done next(){ const {value,done}=itr.next() return { value:wrap(value), done } }, //实现可迭代协议 [Symbol.iterator](){ return this } } } function keysIterationMethod(){ const wrap=(val)=>typeof val==="object" && val!==null ? reactive(val) : val const target=this.raw //获取原始迭代器方法 const itr=target.keys() //建立响应 track(target,MAP_ITERATE_KEY) return { //对象的next方法是迭代协议 //调用原始迭代器的next方法获取value和done next(){ const {value,done}=itr.next() return { value:wrap(value), done } }, //实现可迭代协议 [Symbol.iterator](){ return this } } } //重写arr.includes,indexOf,lastIndexOf方法 const arrayInstumentations={} ;["includes","indexof","lastIndexof"].forEach(method=>{ const originMethod=Array.prototype[method] arrayInstumentations[method]=function(...args){ //先在代理对象中查找,this是代理对象,将结果保存到res中 let res=originMethod.apply(this,args) //如果res不存在,说明this.raw拿到了原始数组,再去其中查找,并跟新res的值 if(res===false){ res=originMethod.apply(this.raw,args) } return res } }) //重写push方法 ;["push","pop","shift","unshift","splice"].forEach(method=>{ const originMethod=Array.prototype[method] arrayInstumentations[method]=function(...args){ //在调用原始方法之前,禁止追踪 shouldTrack=false //调用原始防范 let res=originMethod.apply(this,args) //调用原始方法之后,允许追踪 shouldTrack=true return res } }) /* const data={ foo:1,bar:2, get tep(){ return this.foo } } */ /* const obj={} const proto={bar:1} const child=reactive(obj) const parent=reactive(proto) //使用parent作为child的原型 Object.setPrototypeOf(child,parent) //此处打印true,因为代理对象可以通过raw属性读取原始数据 console.dir(child.raw===obj) console.dir(parent.raw===proto) effect(()=>{ //会执行两次 console.log(child.bar) }) */ /* let arr=[1,2,3] //const obj=readonly(arr) const obj=reactive(arr) //重新建立副作用函数 effect( ()=>{ for(const key of obj){ console.log(key) document.getElementById("test").innerHTML=key } } ) setTimeout(()=>obj[3]=10,1000) */ /* //map 测试 const key={key:1} const value=new Set([1,2,3]) let map=new Map([[key,value]]) //const obj=readonly(arr) const pa=reactive(map) //重新建立副作用函数 effect( ()=>{ pa.forEach(function (value,key){ console.log(value) console.log(value.size) document.getElementById("test").innerHTML=value }) } ) setTimeout(()=>pa.set({key:1},1),1000) */ /* //如果使用pa.get(key)获取的是原始数据,并不能实现响应,所以这里也要用forEach setTimeout(()=>pa.forEach((v,k)=>{ v.add(4) }),1000) */ const pa=reactive(new Map([ ["key1","value1"], ["key2","value2"], ])) effect(()=>{ for(const [key,value] of pa.entries()){ console.log(key,value) } }) setTimeout(()=>{pa.set("key3","value3")},1000) //封装代理对象, //isShallow浅响,应,isReadonly,浅只读 function createReactive(obj,isShallow=false,isReadonly=false){ return new Proxy(obj,{ //对原始数据的代理 //拦截读取操作 get(target,key,receiver){ //代理对象可以通过raw属性访问原始数据 if(key==="raw"){ return target } if(key==="size"){ track(target,ITERATE_KEY) return Reflect.get(target,key,target) } //返回定义在对象mutableInstrumentations下的方法 return mutableInstrumentations[key] /* //1.为解决set的this指向问题,Set.prototype.size是访问器属性 //对于代理对象来说,this指向了代理对象,所以当调用代理对象.size的时候,就会出错 //因为代理对象不存在内部槽,而set存在 //为解决这个问题,需要将Reflect.get的第三个参数指定为原始对象 //2.当执行p.delete时,delete为方法,所以会报错因为this始终指向p代理对象。 //所以需要把delete方法与原始数据对象绑定 if(key==="size"){ track(target,ITERATE_KEY) return Reflect.get(target,key,target) } if(key==="delete"){ return target[key].bind(target) } //重写arr.includes方法-= //如果定义的目标是数组,并且key存在于arrayInstrumentations上 //那么返回定义再arrayInstrumentations上的值 //执行函数时,实际执行的是定义再arrayInstrumentations上的includes方法 //bind函数将this指向原始数据对象 if(Array.isArray(target) && arrayInstumentations.hasOwnProperty(key)){ return Reflect.get(arrayInstumentations,key,receiver) } //因为数组的for of会读取symbol.iterator属性 //为避免错误和性能开销,要避免副作用函数与之建立响应 //如果key的类型是symbol则不进行追踪 if(!isReadonly && typeof key!=="symbol"){ track(target,key) } //返回属性值 //如果对象自身不存在该属性,会从对象原型寻找对应属性,并调用原型get方法得到最终结果 const res=Reflect.get(target,key,receiver) if(isShallow){ return res } //深响应 //对于obj.foo.bar来说,当修改obj.foo.bar的值时,并不能触发响应 //为了解决这个问题,需要递归地调用reactive函数,直到能返回深响应地数据 if(typeof res==="object" && res!==null){ //实现深只读 //如果是只读对象,则调用readyonly对数据,返回只读对象 return isReadonly?readonly(res):reactive(res) } return res */ }, //拦击in操作符读取属性值 has(target,key){ track(target,key) return Reflect.has(target,key) }, //拦截for in 循环读取属性值 ownKeys(target){ //将副作用函数和ITERATE_KEY关联 //如果操作的是数组,则用length作为key并建立响应 track(target,Array.isArray(target)?"length":ITERATE_KEY) return Reflect.ownKeys(target) }, //拦截设置操作 set(target,key,newvalue,receiver){ //数据是只读的,则打印错误并返回 if(isReadonly){ console.warn(`属性${key}是只读的`) return true } //获取旧值 const oldval=target[key] //如果属性不存在,说明是添加新属性,否则是设置已有属性 //Object.prototype.hasOwnProperty检查当前操作的属性是否已经存在对象身上 //如果代理目标是数组,检测被设置的索引是否小于数组长度 //如果是,为SET操作,否则ADD操作 const type=Array.isArray(target) ?Number(key){ if(fn!==activeEffect){ effectsToRun.add(fn) } }) } /* *当直接设置了数组的length属性时,只需要对大于新length的元组进行操作即可 *如果操作的是数组的length属性 *那么取出大于新length的所有元素对应的副作用函数执行 */ if(Array.isArray(target) && key==="length"){ depsMap.forEach((effects,key)=>{ if(key>=newValue){ effects.forEach(fn=>{ if(fn!==activeEffect){ effectsToRun.add(fn) } }) } }) } //取得与INTERATE_KEY相关的副作用函数 const interateEffects=depsMap.get(ITERATE_KEY) //避免自增导致无限循环 //ECMA规范:再调用foreach遍历set集合时,如果一个值已经被访问过 //但这个值被删除并重新添加到集合,如果遍历没有结束,那么这个值 //又会重新被访问,解决办法是建立一个新的Set来遍历 effects && effects.forEach(f=>{ if(f!=effectsToRun){ effectsToRun.add(f) } }) //将ITERATE_KEY相关联的副作用函数6添加到effectsToRun //删除属性会导致ITERATE_KEY减少,所以需要重新触发 if(type==="ADD" || type==="DELETE" || //如果操作的是map,并且是set操作,也应当触发副作用函数执行 //虽然set不改变键的数量,但是set需要设置值 (type==="SET" && Object.prototype.toString.call(target)==="[object Map]" ) ){ interateEffects && interateEffects.forEach(effect=>{ if(effect!==activeEffect){ effectsToRun.add(effect) } }) } if((type==="ADD" || type==="DELETE") && Object.prototype.toString.call(target)==="[object Map]" ){ const iterateEffects=depsMap.get(MAP_ITERATE_KEY) iterateEffects && iterateEffects.forEach(effect=>{ if(effect!==activeEffect){ effectsToRun.add(effect) } }) } effectsToRun.forEach(fn=>{ //如果副作用函数存在调度函数,那么执行调度函数,否则执行原函数 if(fn.options.scheduler){ fn.options.scheduler(fn) }else{ fn() } }) } //通过修改第二个参数来实现只读深浅,此处浅只读 function readonly(obj){ return createReactive(obj,false,true) } //此处深只读 function shallowReadonly(obg){ return createReactive(obj,true,true) } //当如下代码运行时,因为reactive是深响应,所以会返回false //这是重复创建了代理对象的问题 //并且使用了map之后,includes(obj)还是false //因为includes内部的this指向的是代理对象arr /* //arr测试 const obj={} const arr=reactive([obj]) console.log(arr.includes(arr[0])) //false console.log(arr.includes(obj)) //false */ /* //obj.size测试 const set=new Set([1,2,3]) const obj=reactive(set) effect(()=>{ document.getElementById("test").innerHTML=obj.size }) setTimeout(()=>obj.add(4),1000) */ //深响应 function reactive(obj){ //从map中查找之前创建的代理对象。 const existionProxy=reactiveMap.get(obj) if(existionProxy) return existionProxy //创建新的代理对象 const proxy=createReactive(obj) //将代理对象存储到Map中 reactiveMap.set(obj,proxy) return proxy } //实现浅响应 function shallowReactive(obj){ return createReactive(obj,true) } //options对象动态调度副作用函数的执行时机 function effect(fn,options={}){ const effectFn=()=>{ //例如effet(function effectFn(){document.body.inntext=obj.ok?obj.text:"not"}) //清除工作 cleanup(effectFn) //存储被注册过的副作用函数 activeEffect=effectFn //嵌套的副作用函数 //在调用副作用函数前将其压入栈中,首先压入的内层副作用函数 effectStack.push(effectFn) let res=fn() //调用完之后,将其弹出栈,弹出内层的副作用函数 effectStack.pop() activeEffect=effectStack[effectStack.length-1] //返回fn的结果 return res } //存储与该副作用相关的依赖集合 effectFn.deps=[] //将options挂在到副作用函数 effectFn.options=options if(!options.lazy) effectFn() return effectFn } function cleanup(effectFn){ //遍历副作用函数的deps数组 for(let i=0;i{console.log(obj.foo)}, scheduler(fn){ //执行调度时,将其添加到微任务队列 jobQueue.add(fn) //刷新队列 flushJob() } ) obj.foo++ obj.foo++ *最终输出 1 3 *微任务队列最终执行的只有一次,而此时obj.foo的值已经是3. */ function flushJob(){ //如果正在刷新任务队列,什么都不做,否则isFlushing=true if(isFlushing) return isFlushing=true //将任务添加到微任务队列 p.then(()=>{ jobQueue.forEach(job=>job()) }).finally(()=>{isFlushing=false}) } /* *计算属性与懒执行 */ function computed(getter){ let value //是否需要重新计算值,true代表需要计算 let dirty=true //只有调用value的时候才会执行 const effectFn=effect(getter,{ //不执行 lazy:true, //当值发生变化时,在跳读器中重新设置diarty。 scheduler(){ if(!dirty){ dirty=true //当计算属性依赖的响应数据发生变化时,手动调用函数触发响应 trigger(obj, "value") } } }) const obj={ get value(){ if(dirty){ //执行副作用函数 value=effectFn() //设置为false,下次访问时,直接使用原来的值 dirty=false } //当读取value时,手动调用track函数进行追踪 track(obj, "value") //返回值为fn的值 return value } } return obj } /* *wach的实现原理 *当数据发生变化时,执行回调 */ function watch(source,cb,options={}){ let getter //如果source是函数,则执行函数,否则调用traverse函数递归地读取属性 if(typeof source==="function"){ getter=source }else{ getter=()=>traverse(source) } //旧值与新值 let oldValue,newValue let cleanup function onInvalidate(fn){ cleanup=fn } //对scheduler函数的封装 const job=()=>{ newValue=effectFn() if(cleanup){ cleanup() } //返回旧值,新值,已经回调给用户使用 cb(newValue,oldValue,onInvalidate) //已经触发了回调函数,所以这里重新赋值 oldValue=newValue } //出发操作,建立回调 const effectFn=effect( //调用函数递归地读取数据 ()=>getter() ,{ lazy:true, //调度函数 scheduler:()=>{ //创建微任务队列,再DOM加载完成后再执行 if(options.flush==="post"){ const p=Promise.resolve() p.then(job) }else{ job() } } }) if(options.immediate){ job() }else{ //调用副作用函数,拿到旧值 oldValue=effectFn() } } function traverse(value,seen=new Set()){ //如果数据是原始值或者已经被读取过了,则什么都不做 if(typeof value!=="object" || value===null || seen.has(value)) return seen.add(value) //堆对象内部地属性,递归地读取数据 for(const k in value){ traverse(value[k],seen) } return value } /* watch(()=>obj.foo,(newValue,oldValue)=>alert(oldValue+":"+newValue)) setTimeout(()=>obj.foo++,1000) const sum=computed(()=>{ document.getElementById("test").innerHTML=obj.tep }) //重新建立副作用函数 effect(function effectFn(){ sum.value }) */