南昌哪里做网站,网站报备,快速迁移网站,专业的企业级cms建站系统Proxy 只能拦截对一个对象的基本操作#xff08;例如读取、设置属性值#xff09;#xff0c;而无法拦截复合操作#xff08;例如#xff0c;obj.fun(),由两个基本操作组成#xff0c;1#xff09;get到fun这个属性#xff0c;2#xff09;函数调用#xff09;。
1 … Proxy 只能拦截对一个对象的基本操作例如读取、设置属性值而无法拦截复合操作例如obj.fun(),由两个基本操作组成1get到fun这个属性2函数调用。
1 代理对象 操作 跟踪方法 触发方法 访问属性obj.name get(traget,p) set、delete 判断对象或原型上是否存在给定的key, key in obj has(target,p) delete,set 使用for...in遍历对象 ownkeys(target) delete,set(添加新属性时)
表 普通对象所有可能的读取操作
ownkeys方法没有具体的属性这时我们指定一个唯一符号ITERATE_KEY。来将它与副作用函数关联。
const ITERATE_KEY Symbol(ITERATE_KEY)
return new Proxy(obj,{// 省略其他代码set(target, p, newValue, receiver) {const type Object.prototype.hasOwnProperty.call(target,p) ? SET : ADDconst res Reflect.set(target,p,newValue,receiver)trigger(target,p,type,newValue)return res},deleteProperty(target, p) {const res Reflect.deleteProperty(target,p)trigger(target,p,DELETE)return res },has(target, p) {track(target,p)return Reflect.has(target,p)},ownKeys(target) {track(target,ITERATE_KEY)return Reflect.ownKeys(target)}
})
function trigger(target,p,type,newValue) {const map effectMap.get(target)if (map) {const addSet new Set()// 省略其他代码if (type ADD) {const tempSet map.get(ITERATE_KEY)tempSet tempSet.forEach(fn {if (activeEffect ! fn) addSet.add(fn)})}addSet.forEach(fn fn())}
}
1.1 合理触发
为了提供性能及用户交互应当满足下列条件时才触发响应
值发生改变时。初始值和新值不都是NaN。响应对象的原型也说响应对象时访问原型上的属性时只触发一次。
return new Proxy(obj,{get(target, p, receiver) {if (p raw) return target// 省略其他代码},set(target, p, newValue, receiver) {// 省略其他代码if (receiver.raw target) {if (oldVal ! newValue (newValue newValue || oldVal oldVal)) {trigger(target,p,type)}}},deleteProperty(target, p) {const hadKey Object.prototype.hasOwnProperty.call(target,p)const res Reflect.deleteProperty(target,p)if (hadKey res) {trigger(target,p,DELETE)}},
})1.2 深响应与浅响应
目前的reactive函数创建的响应对象是浅响应即对象的首层才具有响应性。如果对象的某个属性值是个对象那么该对象不具备响应性。而深响应是指无论多少层对象都具有响应性。
function createReactive(obj,isShallow false) {if (obj.raw) return objreturn new Proxy(obj,{get(target, p, receiver) {// 省略其他代码const val Reflect.get(target,p,receiver)if (isShallow) return valreturn typeof val object val ! null ? createReactive(val) : val},// 省略其他代码})
}
1.2.1 深只读和浅只读
某些数据要是只读的例如props当用户尝试修改只读数据时会收到一条警告信息。浅只读是指对象的首层不可写但是其他层可写对象的一个属性值也是对象时那么这个属性值对象里的属性是可写的。
function createReactive(obj,isShallow false,isReadonly) {if (obj.raw) return objreturn new Proxy(obj,{get(target, p, receiver) {// 省略其他代码const val Reflect.get(target,p,receiver)if (isShallow) return valreturn typeof val object val ! null ? (isReadonly ? readonly(val) : createReactive(val)) : val},set(target, p, newValue, receiver) {if (isReadonly) {console.error(该属性不可写)return}// 省略其他代码},deleteProperty(target, p) {if (isReadonly) {console.error(该属性不可写)return}// 省略其他代码},// 省略其他代码})
}function readonly(obj,isShallow false) {return createReactive(obj,isShallow,true)
}
2 代理数组
数组也是一种对象但属于异质对象与常规对象相比它们的部分内部方法和常规对象的不同。因此用于代理普通对象的大部份代码可以继续使用。
2.1 索引与length
通过索引设置元素值时可能会影响到length属性即当设置索引值大等于数组长度时length属性会发生改变。设置length属性可能会影响到索引值。当length设置为更小值时索引大等于length的部分元素全部会被删除。
new Proxy(obj,{set(target, p, newValue, receiver) {// 省略其他代码const type Array.isArray(target) ? (Number(p) target.length ? SET : ADD) :(Object.prototype.hasOwnProperty.call(target,p) ? SET : ADD)// 省略其他代码},// 省略其他代码
})function trigger(target,p,type,newValue) {const map effectMap.get(target)if (map) {// 省略其他代码if (type ADD Array.isArray(target)) {const tempSet map.get(length)tempSet tempSet.forEach(fn {if (activeEffect ! fn) addSet.add(fn)})}if (p length Array.isArray(target)) {map.forEach((set,key) {if (key newValue) {set.forEach(fn {if (activeEffect ! fn) addSet.add(fn)})}})}addSet.forEach(fn fn())}
}
2.2 遍历数组
1for...in和普通对象一样内部会调用ownKeys方法但不同的是其触发条件是length的改变。
new Proxy(obj,{ownKeys(target) {track(target,Array.isArray(target) ? length : ITERATE_KEY)return Reflect.ownKeys(target)}// 省略其他代码
})2for...of索引值的设置及length的改变都会触发该迭代所以几乎不要添加额外的代码就能让for...for迭代具有响应性。
从性能上及为了避免发生意外的错误我们不应该使副作用函数与symbol值之间建立响应联系。
2.3 数组的查找方法
数组的方法内部其实都依赖了对象的基本语义。因此大多数情况下我们不需要做特殊处理即可让这些方法按预期工作。但某些场景执行结果会不符合我们预期
const proxyObj reactive([hi,js,{name: js}])console.log(proxyObj.includes(hi),proxyObj.includes(proxyObj[2])) // true、false
这里查找基本类型数据时结果是正确的。但是查找对象时结果错误。这是因为reactive函数会为属性中的对象也创建响应对象而且每次都会创建新的响应对象。而且这里还有两个问题
将响应体对象赋值给另一个响应体时reactive不应该为其再创建响应体了。无论查找对象还是其响应体对象返回的结果应该一致从业务的角度看它们属于同一对象。
const reactiveMap new WeakMap()new Proxy(obj,{get(target, p, receiver) {if (p raw) return target// 省略其他代码if (Array.isArray(target) arrayInstrumentation.hasOwnProperty(p)) {return Reflect.get(arrayInstrumentation,p,receiver)}// 省略其他代码},
})function reactive(obj) {const originalProxy reactiveMap.get(obj)if (originalProxy) return originalProxy;const proxyObj createReactive(obj)reactiveMap.set(obj,proxyObj)return proxyObj
}const arrayInstrumentation {};
[includes,indexOf,lastIndexOf].forEach(method {const originalMethod Array.prototype[method]arrayInstrumentation[method] function (...args) {let res originalMethod.apply(this,args)if (res false || res -1) {res originalMethod.apply(this.raw,args)}return res}
});
2.4 隐式修改数组长度的原型方法
push/pop/shift/unshift/splice这些方法会隐式地修改数组长度。例如push方法在往数组中添加元素时既会读取length也会设置数组的length。这会导致两个独立的副作用函数互相影响
effect(() {proxyObj.push(1)
})
effect(() {proxyObj.push(2)
})
运行上面这段代码会得到栈溢出的错误。因为副作用函数1执行时会修改及读取length会触发副作用函数2执行而副作用2也会修改和读取length。这样就好造成循环调用。
解决方法是“屏蔽”这些方法对length属性的读取。
[push,pop,shift,unshift,splice].forEach(method {const originalMethod Array.prototype[method]arrayInstrumentation[method] function (...args) {shouldTrack falseconst res originalMethod.apply(this,args)shouldTrack truereturn res}
})function track(target,p) {if (activeEffect shouldTrack) {// 跟踪代码}
}