原理:当触发数据读取操作时,执行副作用函数并存储到桶中当设置数据操作时,再将副作用函数从桶中取出并执行用一个全局变量activeEffect存储被注册过的副作用函数letactiveEffectconstbuketnewSet()weakMap为弱引用,不影响垃圾回收机制工作,当用户代码对一个对象没有引用关系时,垃圾会收器会回收该对象,避免引起栈堆的溢出constbucketnewWeakMap()consteffectStack〔〕定义一个宏任务队列constjobQueuenewSet()定义一个Promose,将一个任务添加到微任务队列constpPromise。resolve()是否正在刷新队列letisFlushingfalseSymbol唯一,可以作为对象属性标识符使用constITERATEKEYSymbol()constdata{foo:1,bar:2,gettep(){returnthis。foo}}constobj{}constproto{bar:1}constchildreactive(obj)constparentreactive(proto)使用parent作为child的原型Object。setPrototypeOf(child,parent)此处打印true,因为代理对象可以通过raw属性读取原始数据console。dir(child。rawobj)console。dir(parent。rawproto)effect((){会执行两次console。log(child。bar)})letarr〔1,2,3〕constobjreadonly(arr)constobjreactive(arr)重新建立副作用函数effect((){for(constkeyofobj){console。log(key)document。getElementById(test)。innerHTMLkey}})setTimeout(()obj〔3〕10,1000)封装代理对象isShallow浅响应,isReadonly,浅只读functioncreateReactive(obj,isShallowfalse,isReadonlyfalse){returnnewProxy(obj,{对原始数据的代理拦截读取操作get(target,key,receiver){代理对象可以通过raw属性访问原始数据if(keyraw){returntarget}因为数组的forof会读取symbol。iterator属性为避免错误和性能开销,要避免副作用函数与之建立响应如果key的类型是symbol则不进行追踪if(!isReadonlytypeofkey!symbol){track(target,key)}返回属性值如果对象自身不存在该属性,会从对象原型寻找对应属性,并调用原型get方法得到最终结果constresReflect。get(target,key,receiver)if(isShallow){returnres}深响应对于obj。foo。bar来说,当修改obj。foo。bar的值时,并不能触发响应为了解决这个问题,需要递归地调用reactive函数,直到能返回深响应地数据if(typeofresobjectres!null){实现深只读如果是只读对象,则调用readyonly对数据,返回只读对象returnisReadonly?readonly(res):reactive(res)}returnres},拦击in操作符读取属性值has(target,key){track(target,key)returnReflect。has(target,key)},拦截forin循环读取属性值ownKeys(target){将副作用函数和ITERATEKEY关联如果操作的是数组,则用length作为key并建立响应track(target,Array。isArray(target)?length:ITERATEKEY)returnReflect。ownKeys(target)},拦截设置操作set(target,key,newvalue,receiver){数据是只读的,则打印错误并返回if(isReadonly){console。warn(属性{key}是只读的)returntrue}获取旧值constoldvaltarget〔key〕如果属性不存在,说明是添加新属性,否则是设置已有属性Object。prototype。hasOwnProperty检查当前操作的属性是否已经存在对象身上如果代理目标是数组,检测被设置的索引是否小于数组长度如果是,为SET操作,否则ADD操作consttypeArray。isArray(target)?Number(key)target。length?SET:ADD:Object。prototype。hasOwnProperty。call(target,key)?SET:ADD设置属性值target〔key〕newvalue设置属性值constresReflect。set(target,key,newvalue,receiver)只有旧值与新值不同才会触发更新因为NaNNaN为false,而NaN!NaN为true判断target是否是recever的代理对象if(targetreceiver。raw){if(oldval!newvalue(oldvaloldvalnewvaluenewvalue)){trigger(target,key,type,newvalue)}}returnres},使用内部方法拦截删除对象属性操作deleteProperty(target,key){数据是只读的,则打印错误并返回if(isReadonly){console。warn(属性{key}是只读的)returntrue}检查属性是否存在对象身上consthadkeyObject。prototype。hasOwnProperty。call(target,key)使用Reflect。deleteProperty(target,key)完成属性删除constresReflect。deleteProperty(target,key)if(reshadkey){只有熟悉存在并且成功删除时才出发更新trigger(target,key,DELETE)}returnres}})}functiontrack(target,key){console。dir(target)if(!activeEffect)returntarget〔key〕letdepsMapbucket。get(target)if(!depsMap){bucket。set(target,(depsMapnewMap()))}letdepsdepsMap。get(key)if(!deps){depsMap。set(key,(depsnewSet()))}deps。add(activeEffect)activeEffect。deps。push(deps)}通过type来区分操作类型,避免性能开销functiontrigger(target,key,type,newValue){constdepsMapbucket。get(target)if(!depsMap)returnconsteffectsdepsMap。get(key)consteffectsToRunnewSet(effects)当操作为ADD并且是数组时取出与length相关联的副作用函数将副作用函数添加到待执行集合中if(typeADDArray。isArray(target)){constlengthEffectsdepsMap。get(length)lengthEffectslengthEffects。forEach(fn{if(fn!activeEffect){effectsToRun。add(fn)}})}当直接设置了数组的length属性时,只需要对大于新length的元组进行操作即可如果操作的是数组的length属性那么取出大于新length的所有元素对应的副作用函数执行if(Array。isArray(target)keylength){depsMap。forEach((effects,key){if(keynewValue){effects。forEach(fn{if(fn!activeEffect){effectsToRun。add(fn)}})}})}取得与INTERATEKEY相关的副作用函数constinterateEffectsdepsMap。get(ITERATEKEY)避免自增导致无限循环ECMA规范:再调用foreach遍历set集合时,如果一个值已经被访问过但这个值被删除并重新添加到集合,如果遍历没有结束,那么这个值又会重新被访问,解决办法是建立一个新的Set来遍历effectseffects。forEach(f{if(f!effectsToRun){effectsToRun。add(f)}})将ITERATEKEY相关联的副作用函数6添加到effectsToRun删除属性会导致ITERATEKEY减少,所以需要重新触发if(typeADDtypeDELETE){interateEffectsinterateEffects。forEach(effect{if(effect!activeEffect){effectsToRun。add(effect)}})}effectsToRun。forEach(fn{如果副作用函数存在调度函数,那么执行调度函数,否则执行原函数if(fn。options。scheduler){fn。options。scheduler(fn)}else{fn()}})}通过修改第二个参数来实现只读深浅,此处浅只读functionreadonly(obj){returncreateReactive(obj,false,true)}此处深只读functionshallowReadonly(obg){returncreateReactive(obj,true,true)}深响应functionreactive(obj){returncreateReactive(obj)}实现浅响应functionshallowReactive(obj){returncreateReactive(obj,true)}options对象动态调度副作用函数的执行时机functioneffect(fn,options{}){consteffectFn(){例如effet(functioneffectFn(){document。body。inntextobj。ok?obj。text:not})清除工作cleanup(effectFn)存储被注册过的副作用函数activeEffecteffectFn嵌套的副作用函数在调用副作用函数前将其压入栈中,首先压入的内层副作用函数effectStack。push(effectFn)letresfn()调用完之后,将其弹出栈,弹出内层的副作用函数effectStack。pop()activeEffecteffectStack〔effectStack。length1〕返回fn的结果returnres}存储与该副作用相关的依赖集合effectFn。deps〔〕将options挂在到副作用函数effectFn。optionsoptionsif(!options。lazy)effectFn()returneffectFn}functioncleanup(effectFn){遍历副作用函数的deps数组for(leti0;ieffectFn。length;i){constdepseffectFn。deps〔i〕从依赖集合删除deps。delete(effectFn)}effectFn。deps。length0}微任务队列何时执行?在options的调度函数中执行,例如effect((){console。log(obj。foo)},scheduler(fn){执行调度时,将其添加到微任务队列jobQueue。add(fn)刷新队列flushJob()})obj。fooobj。foo最终输出13微任务队列最终执行的只有一次,而此时obj。foo的值已经是3。functionflushJob(){如果正在刷新任务队列,什么都不做,否则isFlushingtrueif(isFlushing)returnisFlushingtrue将任务添加到微任务队列p。then((){jobQueue。forEach(jobjob())})。finally((){isFlushingfalse})}计算属性与懒执行functioncomputed(getter){letvalue是否需要重新计算值,true代表需要计算letdirtytrue只有调用value的时候才会执行consteffectFneffect(getter,{不执行lazy:true,当值发生变化时,在跳读器中重新设置diarty。scheduler(){if(!dirty){dirtytrue当计算属性依赖的响应数据发生变化时,手动调用函数触发响应trigger(obj,value)}}})constobj{getvalue(){if(dirty){执行副作用函数valueeffectFn()设置为false,下次访问时,直接使用原来的值dirtyfalse}当读取value时,手动调用track函数进行追踪track(obj,value)返回值为fn的值returnvalue}}returnobj}wach的实现原理当数据发生变化时,执行回调functionwatch(source,cb,options{}){letgetter如果source是函数,则执行函数,否则调用traverse函数递归地读取属性if(typeofsourcefunction){gettersource}else{getter()traverse(source)}旧值与新值letoldValue,newValueletcleanupfunctiononInvalidate(fn){cleanupfn}对scheduler函数的封装constjob(){newValueeffectFn()if(cleanup){cleanup()}返回旧值,新值,已经回调给用户使用cb(newValue,oldValue,onInvalidate)已经触发了回调函数,所以这里重新赋值oldValuenewValue}出发操作,建立回调consteffectFneffect(调用函数递归地读取数据()getter(),{lazy:true,调度函数scheduler:(){创建微任务队列,再DOM加载完成后再执行if(options。flushpost){constpPromise。resolve()p。then(job)}else{job()}}})if(options。immediate){job()}else{调用副作用函数,拿到旧值oldValueeffectFn()}}functiontraverse(value,seennewSet()){如果数据是原始值或者已经被读取过了,则什么都不做if(typeofvalue!objectvaluenullseen。has(value))returnseen。add(value)堆对象内部地属性,递归地读取数据for(constkinvalue){traverse(value〔k〕,seen)}returnvalue}watch(()obj。foo,(newValue,oldValue)alert(oldValue:newValue))setTimeout(()obj。foo,1000)constsumcomputed((){document。getElementById(test)。innerHTMLobj。tep})重新建立副作用函数effect(functioneffectFn(){sum。value})