原理:当触发数据读取操作时,执行副作用函数并存储到桶中当设置数据操作时,再将副作用函数从桶中取出并执行用一个全局变量activeEffect存储被注册过的副作用函数letactiveEffectconstbuketnewSet()weakMap为弱引用,不影响垃圾回收机制工作,当用户代码对一个对象没有引用关系时,垃圾会收器会回收该对象,避免引起栈堆的溢出constbucketnewWeakMap()consteffectStack〔〕定义一个宏任务队列constjobQueuenewSet()定义一个Promose,将一个任务添加到微任务队列constpPromise。resolve()是否正在刷新队列letisFlushingfalseSymbol唯一,可以作为对象属性标识符使用constITERATEKEYSymbol()map的键使用constMAPITERATEKEYSymbol()定义一个map实例,存储原始对象到代理对象的映射constreactiveMapnewMap()代表是否追踪letshouldTracktrue定义一个对象,将自定义add方法添加到下面constmutableInstrumentations{add(key){this指向代理对象,通过raw获取原始数据对象consttargetthis。raw是否元素已经存在集合中consthadkeytarget。has(key)直接使用原始数据对象的方法constrestarget。add(key)if(!hadkey){trigger(target,key,ADD)}returnres},delete(key){this指向代理对象,通过raw获取原始数据对象consttargetthis。raw是否元素已经存在集合中consthadkeytarget。has(key)constrestarget。delete(key)if(hadkey){trigger(target,key,DELETE)}returnres},map数据类型拥有get和set两个方法,所以需要添加这两个方法get(key){this指向代理对象,通过raw获取原始数据对象consttargetthis。raw是否元素已经存在集合中consthadtarget。has(key)追踪依赖建立响应track(target,key)直接使用原始数据对象的方法constrestarget。get(key)如果存在,返回结果,递归使用reactive返回最终的原始数据而非对象if(!had){returntypeofresobject?reactive(res):res}returnres},set(key,value){this指向代理对象,通过raw获取原始数据对象consttargetthis。raw是否元素已经存在集合中consthadtarget。has(key)获取旧值constoldValuetarget。get(key)此处为避免污染原始数据,value必须是数据,而不能是代理对象constrawValuevalue。rawvalue设置新值target。set(key,rawValue)如果不存在,说明时add只有旧值与新值不同才会触发更新因为NaNNaN为false,而NaN!NaN为trueif(!had){trigger(target,key,ADD)}elseif(oldValue!value(oldValueoldValuevaluevalue)){trigger(target,key,SET)}},forEach(callback,thisArg){constwrap(val)typeofvalobject?reactive(val):valconsttargetthis。raw建立响应track(target,ITERATEKEY)通过原始对象调用传递过去的函数target。forEach((v,k){手动实现深响应callback。call(thisArg,wrap(v),wrap(k),this)})},〔Symbol。iterator〕:iterationMethod,entries:iterationMethod,values:valuesIterationMethod,keys:keysIterationMethod,}抽离独立函数functioniterationMethod(){constwrap(val)typeofvalobjectval!null?reactive(val):valconsttargetthis。raw获取原始迭代器方法constitrtarget〔Symbol。iterator〕()建立响应track(target,ITERATEKEY)return{对象的next方法是迭代协议调用原始迭代器的next方法获取value和donenext(){const{value,done}itr。next()return{如果value不是undifined,进行包裹value:value?〔wrap(value〔0〕),wrap(value〔1〕)〕:value,done}},实现可迭代协议〔Symbol。iterator〕(){returnthis}}}抽离独立函数functionvaluesIterationMethod(){constwrap(val)typeofvalobjectval!null?reactive(val):valconsttargetthis。raw获取原始迭代器方法constitrtarget。values()建立响应track(target,ITERATEKEY)return{对象的next方法是迭代协议调用原始迭代器的next方法获取value和donenext(){const{value,done}itr。next()return{value:wrap(value),done}},实现可迭代协议〔Symbol。iterator〕(){returnthis}}}functionkeysIterationMethod(){constwrap(val)typeofvalobjectval!null?reactive(val):valconsttargetthis。raw获取原始迭代器方法constitrtarget。keys()建立响应track(target,MAPITERATEKEY)return{对象的next方法是迭代协议调用原始迭代器的next方法获取value和donenext(){const{value,done}itr。next()return{value:wrap(value),done}},实现可迭代协议〔Symbol。iterator〕(){returnthis}}}重写arr。includes,indexOf,lastIndexOf方法constarrayInstumentations{};〔includes,indexof,lastIndexof〕。forEach(method{constoriginMethodArray。prototype〔method〕arrayInstumentations〔method〕function(。。。args){先在代理对象中查找,this是代理对象,将结果保存到res中letresoriginMethod。apply(this,args)如果res不存在,说明this。raw拿到了原始数组,再去其中查找,并跟新res的值if(resfalse){resoriginMethod。apply(this。raw,args)}returnres}})重写push方法;〔push,pop,shift,unshift,splice〕。forEach(method{constoriginMethodArray。prototype〔method〕arrayInstumentations〔method〕function(。。。args){在调用原始方法之前,禁止追踪shouldTrackfalse调用原始防范letresoriginMethod。apply(this,args)调用原始方法之后,允许追踪shouldTracktruereturnres}})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)map测试constkey{key:1}constvaluenewSet(〔1,2,3〕)letmapnewMap(〔〔key,value〕〕)constobjreadonly(arr)constpareactive(map)重新建立副作用函数effect((){pa。forEach(function(value,key){console。log(value)console。log(value。size)document。getElementById(test)。innerHTMLvalue})})setTimeout(()pa。set({key:1},1),1000)如果使用pa。get(key)获取的是原始数据,并不能实现响应,所以这里也要用forEachsetTimeout(()pa。forEach((v,k){v。add(4)}),1000)constpareactive(newMap(〔〔key1,value1〕,〔key2,value2〕,〕))effect((){for(const〔key,value〕ofpa。entries()){console。log(key,value)}})setTimeout((){pa。set(key3,value3)},1000)封装代理对象,isShallow浅响,应,isReadonly,浅只读functioncreateReactive(obj,isShallowfalse,isReadonlyfalse){returnnewProxy(obj,{对原始数据的代理拦截读取操作get(target,key,receiver){代理对象可以通过raw属性访问原始数据if(keyraw){returntarget}if(keysize){track(target,ITERATEKEY)returnReflect。get(target,key,target)}返回定义在对象mutableInstrumentations下的方法returnmutableInstrumentations〔key〕1。为解决set的this指向问题,Set。prototype。size是访问器属性对于代理对象来说,this指向了代理对象,所以当调用代理对象。size的时候,就会出错因为代理对象不存在内部槽,而set存在为解决这个问题,需要将Reflect。get的第三个参数指定为原始对象2。当执行p。delete时,delete为方法,所以会报错因为this始终指向p代理对象。所以需要把delete方法与原始数据对象绑定if(keysize){track(target,ITERATEKEY)returnReflect。get(target,key,target)}if(keydelete){returntarget〔key〕。bind(target)}重写arr。includes方法如果定义的目标是数组,并且key存在于arrayInstrumentations上那么返回定义再arrayInstrumentations上的值执行函数时,实际执行的是定义再arrayInstrumentations上的includes方法bind函数将this指向原始数据对象if(Array。isArray(target)arrayInstumentations。hasOwnProperty(key)){returnReflect。get(arrayInstumentations,key,receiver)}因为数组的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!shouldTrack)returnif(!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如果操作的是map,并且是set操作,也应当触发副作用函数执行虽然set不改变键的数量,但是set需要设置值(typeSETObject。prototype。toString。call(target)〔objectMap〕)){interateEffectsinterateEffects。forEach(effect{if(effect!activeEffect){effectsToRun。add(effect)}})}if((typeADDtypeDELETE)Object。prototype。toString。call(target)〔objectMap〕){constiterateEffectsdepsMap。get(MAPITERATEKEY)iterateEffectsiterateEffects。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)}当如下代码运行时,因为reactive是深响应,所以会返回false这是重复创建了代理对象的问题并且使用了map之后,includes(obj)还是false因为includes内部的this指向的是代理对象arrarr测试constobj{}constarrreactive(〔obj〕)console。log(arr。includes(arr〔0〕))falseconsole。log(arr。includes(obj))falseobj。size测试constsetnewSet(〔1,2,3〕)constobjreactive(set)effect((){document。getElementById(test)。innerHTMLobj。size})setTimeout(()obj。add(4),1000)深响应functionreactive(obj){从map中查找之前创建的代理对象。constexistionProxyreactiveMap。get(obj)if(existionProxy)returnexistionProxy创建新的代理对象constproxycreateReactive(obj)将代理对象存储到Map中reactiveMap。set(obj,proxy)returnproxy}实现浅响应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})