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
})
*/

展开阅读全文

页面更新:2024-05-18

标签:递归   原始   对象   队列   数组   副作用   函数   属性   完整   类型   操作   代码   方案   方法

1 2 3 4 5

上滑加载更多 ↓
推荐阅读:
友情链接:
更多:

本站资料均由网友自行发布提供,仅用于学习交流。如有版权问题,请与我联系,QQ:4156828  

© CopyRight 2020-2024 All Rights Reserved. Powered By 71396.com 闽ICP备11008920号-4
闽公网安备35020302034903号

Top