如何代理Object
对于对象的代理,不仅仅是get()和set()这两个简单的方法进行拦截,操作对象的方法有很多,比如in,delete,for...in这些,vue中充分利用Proxy和Reflect对这些操作进行拦截
in操作符
const obj = {foo:1}
const p= new Proxy(obj,{
has(target,key){
track(target,key)
return Reflect.has(target,key)
}
})
effect(()=>{
'foo' in p
})
in操作符对应拦截的方法是has,其他的操作基本都有对应的方法可以拦截
for...in操作符
const obj = {foo:1}
const ITERATE_KEY=Symbol()
const p= new Proxy(obj,{
ownKeys(target){
tarck(target,ITERATE_KEY)
return Reflect.ownKeys(target)
}
})
effect(()=>{
for(const key in p){
console.log(key)
}
})
这里为什么要定义一个ITERATE_KEY呢,因为,在Proxy中,ownKeys方法是获取对象所有属于自己的键值,这里没办法与某一个具体的键值产生联系,所以只能构造唯一的key作为标识. 上面的代码会和foo次数联系,当我们给p添加新属性
p.bar=2
由于对象只有一个foo,for...in只会循环一次,现在我们为它添加新属性,for...in会从一次变成2次.也就是说,添加新属性,也会对for...in循环产生影响,所以要触发与ITERATE_KEY相关联的副作用函数重新执行. 我们现在设置bar,执行set函数,set函数中的trigger函数只会触发与bar相关的副作用函数重新执行,但是for...in函数是与ITERATE_KEY产生联系的,所以并不能正确触发响应. 解决方法如下
function trigger(target, key) {
const depsMap = bucket.get(target)
if (!depsMap) return true
const effects = depsMap.get(key) //当值改变 从桶中取出与该属性对应的副作用函数
//取得与ITERATE_KEY相关联的函数
const iterateEffects = depsMap.get(ITERATE_KEY)
const effectsToRun = new Set()
effects && effects.forEach(effectFn=>{
if(effectFn!==activeEffect){
effectsToRun.add(effectFn)
}
})
//将与ITERATE_KEY相联系的副作用函数添加到effectsToRun中
iterateEffects && iterateEffects.forEach(effectFn=>{
if(effectFn !== activeEffect){
effectsToRun.add(effectFn)
}
})
effectsToRun.forEach(effectFn=>{
if(effectFn.options.scheduler){
effectFn.options.scheduler(effectFn)
}else{
effectFn()
}
})
}
其实就是把与ITERATE_KEY相联系的副作用函数取出来执行
当我们修改p.foo时,修改属性不会对for...in产生影响,我们无论修改多少次,for...in都只会执行一次,但是我们的代码仍然会再一次执行副作用函数,所以现在要解决这个问题 先对set函数进行改造
set(target, key, newVal,receiver) {
//如果属性不存在,则说明是添加属性,否则是已有属性
const type = Object.prototype.hasOwnProperty.call(target,key)?'SET':"ADD"
//设置属性值
const res = Reflect.set(target,key,newVal,receiver)
//将type作为第三个参数传给trigger
trigger(target, key,type)
return res
},
然后对trigger函数也要进行处理
function trigger(target, key,type) {
const depsMap = bucket.get(target)
if (!depsMap) return true
const effects = depsMap.get(key) //当值改变 从桶中取出与该属性对应的副作用函数
const effectsToRun = new Set()
effects && effects.forEach(effectFn=>{
if(effectFn!==activeEffect){
effectsToRun.add(effectFn)
}
})
//只有当type为ADD时,才触发与ITERATE_KEY副作用函数执行
if(type==='ADD'){
//取得与ITERATE_KEY相关联的函数
const iterateEffects = depsMap.get(ITERATE_KEY)
//将与ITERATE_KEY相联系的副作用函数添加到effectsToRun中
iterateEffects && iterateEffects.forEach(effectFn=>{
if(effectFn !== activeEffect){
effectsToRun.add(effectFn)
}
})
}
effectsToRun.forEach(effectFn=>{
if(effectFn.options.scheduler){
effectFn.options.scheduler(effectFn)
}else{
effectFn()
}
})
}
delete操作符
deleteProperty(target,key){
//检查被操作属性是否为对象自己的属性
const hadKey = Object.prototype.hasOwnProperty.call(target,key)
//使用Reflect.deleteProperty 完成属性的删除
const res = Reflect.deleteProperty(target,key)
if(hadKey&&res){
//只有被删除属性是对象自己属性且删除成功时,才触发更新
trigger(target,key,'DELETE')
}
return res
},
还有一点,当delete执行成功,对象属性会减少,会影响for...in循环次数,因此,与ITERATE_KEY相关的副作用函数应该重新执行
function trigger(target, key,type) {
const depsMap = bucket.get(target)
if (!depsMap) return true
const effects = depsMap.get(key) //当值改变 从桶中取出与该属性对应的副作用函数
const effectsToRun = new Set()
effects && effects.forEach(effectFn=>{
if(effectFn!==activeEffect){
effectsToRun.add(effectFn)
}
})
//只有当type为ADD或者DELETE时,才触发与ITERATE_KEY副作用函数执行
if(type==='ADD' || type==='DELETE'){
//取得与ITERATE_KEY相关联的函数
const iterateEffects = depsMap.get(ITERATE_KEY)
//将与ITERATE_KEY相联系的副作用函数添加到effectsToRun中
iterateEffects && iterateEffects.forEach(effectFn=>{
if(effectFn !== activeEffect){
effectsToRun.add(effectFn)
}
})
}
effectsToRun.forEach(effectFn=>{
if(effectFn.options.scheduler){
effectFn.options.scheduler(effectFn)
}else{
effectFn()
}
})
}
合理触发响应
从上面知道,我们需要知道操作类型是'SET','ADD','DELETE'或者是其他类型操作,从而触发响应.但是要合理触发响应,还有很多事情要做 第一个问题,当我们值没有发生变化时,不需要触发响应
const obj = {foo:1}
const p = new Proxy(obj,...)
effect(()=>{
console.log(p.foo)
})
//设置p.foo的值,但是值没有变化
p.foo=1
很简单看出,需要在set()操作里面进行处理
set(target, key, newVal,receiver) {
//获取旧值
const oldVal = trigger[key]
//如果属性不存在,则说明是添加属性,否则是已有属性
const type = Object.prototype.hasOwnProperty.call(target,key)?'SET':"ADD"
//设置属性值
const res = Reflect.set(target,key,newVal,receiver)
//与新值对比,只有不全等才会触发响应
if(oldVal !== newVal){
//将type作为第三个参数传给trigger
trigger(target, key,type)
}
return res
},
这样直接判断肯定是有问题的(不得不佩服vue设计者想的如此周全)
NaN ===NaN //false
NaN !==NaN //true
const obj = {foo:NaN}
const p = new Proxy(obj,...)
effect(()=>{
console.log(p.foo)
})
//仍然会触发响应,因为 NaN !== NaN为true
p.foo=NaN
这样就可以避免NaN问题的出现
set(target, key, newVal,receiver) {
//获取旧值
const oldVal = trigger[key]
//如果属性不存在,则说明是添加属性,否则是已有属性
const type = Object.prototype.hasOwnProperty.call(target,key)?'SET':"ADD"
//设置属性值
const res = Reflect.set(target,key,newVal,receiver)
//与新值对比,只有不全等才会触发响应
if(oldVal !== newVal && (oldVal===oldVal || newVal===newVal)){
//将type作为第三个参数传给trigger
trigger(target, key,type)
}
return res
},
处理完的NaN的问题,还有的问题就是原型链的问题,现在先封装一个reactive函数,这就是reactive的原理
function(obj){
return new Proxy(obj,{...})
}
下面就是基于原型链这个问题的例子
const child = reactive({})
const parent = reactive({bar:1})
//使用parent作为child原型
Reflect.setPrototypeOf(child,parent)
effect(()=>{
console.log(child.bar);//1
})
//修改child.bar的值,副作用函数会重新执行
child.bar=2
我们都知道,当对象身上找不到属性,会顺着原型链找,我们这里child本身没有bar属性,值是从原型链上继承的,因为child是响应式数据,因此在副作用函数中访问了parent.bar,又因为parent也是响应式数据,所以,child.bar和parent.bar都与副作用函数产生联系.
那为什么会执行两次呢.那是因为,如果设置的属性不在对象上,那么会取得原型,因为parent是代理对象,所以child和parent的set()函数都会被执行
看一下下面的代码
//child的set拦截函数
set(target,key,value,receiver){
//target是原始对象obj
//receiver是代理对象child
}
由于obj没有bar属性,所以会取得原型parent,并执行parent代理对象的set拦截函数
//parent的拦截函数
set(target,key,value,receiver){
//target是原始对象proto
//receiver仍然是代理对象child
}
根据这个特点,vue针对与这个问题的解决方法就是判断receiver是否是target代理对象 将原始对象通过"raw"赋值给代理对象 get方法
get(target, key,receiver) {
//代理对象可以通过raw访问原始数据
if(key==='raw') return target
tarck(target, key)
return Reflect.get(target,key,receiver)
},
这样我们就可以在set中判断receiver是不是tatget的代理对象
浅响应与深响应
vue也是考虑很周全,基本在开发中有的情况都想到了.比如下面这种情况
effect(()=>{
console.log(obj.foo.bar)
})
//修改obj.foo.bar的值,并不会触发响应
obj.foo.bar=2
副作用函数并不会执行,这是因为,我们zaiget中通过Reflect.get()得到的obj.foo是一个普通对象,并不是一个响应式数据,所以,要在Reflect.get返回的结果进行包装
get(target, key, receiver) {
//代理对象可以通过raw访问原始数据
if (key === 'raw') {
return target;
}
tarck(target, key);
//得到原始值结果
const res = Reflect.get(target, key, receiver);
if(typeof res ==="object" && res !==null){
//调用reactive函数包装为响应式数据返回
return reactive(res)
}
return res
},
这段代码就是判断这个Reflect.get()结果是不是一个对象,如果是,则递归调用reactive函数将这结果包装成响应式数据.
但是,并不是所有情况都希望深响应,vue就设计一个shallowReactive,即浅响应,意思就是只有对象第一层是响应的
const obj = shallowReactive({foo:{bar:1}})
effect(()=>{
console.log(obj.foo.bar);
})
//obj.foo是响应的,可以触发副作用函数
obj.foo={bar:2}
//obj.foo.bar不是响应的,不触发副作用函数
obj.foo.bar=3
这里我们要封装一个创建响应式数据的函数,同时reactive也要重新设计,vue底层也是差不多这么实现的
//封装createReactive函数,接受参数isShallow,代表浅响应,默认是false,为非浅响应
function createReactive(obj, isShallow = false) {
return new Proxy(obj, {
get(target, key, receiver) {
//代理对象可以通过raw访问原始数据
if (key === 'raw') {
return target;
}
//得到原始值结果
const res = Reflect.get(target, key, receiver);
tarck(target, key);
if (isShallow) {
return res;
} else {
if (typeof res === 'object' && res !== null) {
//调用reactive函数包装为响应式数据返回
return reactive(res);
}
return res;
}
},
//省略其他拦截操作
}
//封装reactive函数
function reactive(obj) {
return createReactive(obj);
}
//封装shallowReactive函数,浅响应数据
function shallowReactive(obj) {
return createReactive(obj, true);
}
我们封装一个createReactive函数,将isShallow作为识别浅响应和非浅响应的表示,分别创建浅响应和非浅响应数据
const obj = createReactive({ foo: { bar: 1 } }, true);
effect(() => {
console.log(obj.foo.bar);
});
//obj.foo是响应的,可以触发副作用函数
obj.foo = { bar: 2 };
//obj.foo.bar不是响应的,不触发副作用函数
obj.foo.bar = 3;
//1,2
只读与浅只读
在vue中,有一些数据是只读,例如组件接受一个props是一个只读数据,所以要用到readonly将这个数据变成只读数据
const obj = readonly({ foo: 1 });
//尝试修改数据,得到警告
obj.foo = 2;
修改createReactive函数,接受第三个参数,表示只读和非只读
function createReactive(obj, isShallow = false, isReadonly = false) {
return new Proxy(obj, {
set(target, key, newVal, receiver) {
//如果是只读,则打印警告信息并返回
if (isReadonly) {
console.warn(`属性${key}是只读的`);
return true;
}
//获取旧值
const oldVal = trigger[key];
//如果属性不存在,则说明是添加属性,否则是已有属性
const type = Object.prototype.hasOwnProperty.call(target, key)
? 'SET'
: 'ADD';
//设置属性值
const res = Reflect.set(target, key, newVal, receiver);
// target===receiver.raw 说明 receiver是target的代理对象
if (target === receiver.raw) {
//与新值对比,只有不全等才会触发响应
if (oldVal !== newVal && (oldVal === oldVal || newVal === newVal)) {
//将type作为第三个参数传给trigger
trigger(target, key, type);
}
}
return res;
},
deleteProperty(target, key) {
//如果是只读,则打印警告信息
if (isReadonly) {
console.warn(`属性${key}是只读的`);
return true;
}
//检查被操作属性是否为对象自己的属性
const hadKey = Object.prototype.hasOwnProperty.call(target, key);
//使用Reflect.deleteProperty 完成属性的删除
const res = Reflect.deleteProperty(target, key);
if (hadKey && res) {
//只有被删除属性是对象自己属性且删除成功时,才触发更新
trigger(target, key, 'DELETE');
}
return res;
},
//省略其他拦截函数
});
}
对于只读属性来说,是不可以设置属性值,也不可以删除属性,所以修改set和deleteProperty这两个拦截函数,当参数isReadonly为true时,阻止修改属性和删除属性
当一个数据是只读时,并不需要它与副作用函数产生联系,所以,也不需要调用track函数
get(target, key, receiver) {
//代理对象可以通过raw访问原始数据
if (key === 'raw') {
return target;
}
//得到原始值结果
const res = Reflect.get(target, key, receiver);
if (!isReadonly) tarck(target, key);
if (isShallow) {
return res;
} else {
if (typeof res === 'object' && res !== null) {
//调用reactive函数包装为响应式数据返回
return reactive(res);
}
return res;
}
},
//封装readonly函数,只读数据
function readonly(obj) {
return createReactive(obj, false, true);
}
还有一种情况就是对象嵌套,还是深和浅的问题
const obj = readonly({ foo: { bar: 1 } });
//还是可以修改
obj.foo.bar = 2;
这是因为我们只做到了浅只读,没有深只读,所以我们再继续修改我们的createReactive函数
get(target, key, receiver) {
//代理对象可以通过raw访问原始数据
if (key === 'raw') {
return target;
}
//得到原始值结果
const res = Reflect.get(target, key, receiver);
if (!isReadonly) tarck(target, key);
if (isShallow) return res;
if (typeof res === 'object' && res !== null) {
//调用reactive函数包装为响应式数据返回
//数据为只读,则调用readonly函数继续包装
return isReadonly? readonly(res): reactive(res);
}
return res;
},
//封装readonly函数,浅只读数据
function readonly(obj) {
return createReactive(obj, false, true);
}
//封装shallowReadonly函数,只读数据
function shallowReadonly(obj) {
return createReactive(obj, true, true);
}
我们在创建浅只读对象就只需要在createReactive函数第二个参数传true就行了
代理数组
在js中,数组有很多方法,我们需要知道有关于数组的操作,比如访问元素的值,访问数组长度,for...of遍历数组,还有一系列数组方法等.这个看似比对象复杂,实际上,数组也是对象的,很多方法我们通过拦截对象也可以做到,现在只需要去拦截数组的一些独特的操作
访问arr.length
const arr = reactive(['foo']);
effect(() => {
console.log(arr.length);
});
//设置索引1的值,会导致数组长度为2
arr[1] = 'bar';
因为在索引1赋值,数组长度发生变化,应该触发响应
set(target, key, newVal, receiver) {
//如果是只读,则打印警告信息并返回
if (isReadonly) {
console.warn(`属性${key}是只读的`);
return true;
}
//获取旧值
const oldVal = trigger[key];
//如果属性不存在,则说明是添加属性,否则是已有属性
const type = Array.isArray(target)
? //如果是数组,则检测设置的索引值是否小于数组长度
//如果是,则为SET操作,否则为ADD操作
Number[key] < target.length
? 'SET'
: 'ADD'
: Object.prototype.hasOwnProperty.call(target, key)
? 'SET'
: 'ADD';
//设置属性值
const res = Reflect.set(target, key, newVal, receiver);
// target===receiver.raw 说明 receiver是target的代理对象
if (target === receiver.raw) {
//与新值对比,只有不全等才会触发响应
if (oldVal !== newVal && (oldVal === oldVal || newVal === newVal)) {
//将type作为第三个参数传给trigger
trigger(target, key, type);
}
}
return res;
},
function trigger(target, key, type) {
const depsMap = bucket.get(target);
if (!depsMap) return true;
const effects = depsMap.get(key); //当值改变 从桶中取出与该属性对应的副作用函数
const effectsToRun = new Set();
effects &&
effects.forEach((effectFn) => {
if (effectFn !== activeEffect) {
effectsToRun.add(effectFn);
}
});
//只有当type为ADD或者DELETE时,才触发与ITERATE_KEY副作用函数执行
if (type === 'ADD' || type === 'DELETE') {
//取得与ITERATE_KEY相关联的函数
const iterateEffects = depsMap.get(ITERATE_KEY);
//将与ITERATE_KEY相联系的副作用函数添加到effectsToRun中
iterateEffects &&
iterateEffects.forEach((effectFn) => {
if (effectFn !== activeEffect) {
effectsToRun.add(effectFn);
}
});
}
//当操作类型为ADD切target为数组时,应该取出与length相关的副作用函数
if (type === 'ADD' && Array.isArray(target)) {
const lengthEffects = depsMap.get('length');
lengthEffects &&
lengthEffects.forEach((effectFn) => {
if (effectFn !== activeEffect) effectsToRun.add(effectFn);
});
}
effectsToRun.forEach((effectFn) => {
if (effectFn.options.scheduler) {
effectFn.options.scheduler(effectFn);
} else {
effectFn();
}
});
}
当修改数组长度为0,导致数组元素被删除,也应该触发响应
const arr = reactive(['foo']);
effect(() => {
console.log(arr[0]);
});
//数组长度设为0,导致第0个函数被删除,因此触发响应
arr.length = 0;
set(target, key, newVal, receiver) {
//如果是只读,则打印警告信息并返回
if (isReadonly) {
console.warn(`属性${key}是只读的`);
return true;
}
//获取旧值
const oldVal = trigger[key];
//如果属性不存在,则说明是添加属性,否则是已有属性
const type = Array.isArray(target)
? //如果是数组,则检测设置的索引值是否小于数组长度
//如果是,则为SET操作,否则为ADD操作
Number[key] < target.length
? 'SET'
: 'ADD'
: Object.prototype.hasOwnProperty.call(target, key)
? 'SET'
: 'ADD';
//设置属性值
const res = Reflect.set(target, key, newVal, receiver);
// target===receiver.raw 说明 receiver是target的代理对象
if (target === receiver.raw) {
//与新值对比,只有不全等才会触发响应
if (oldVal !== newVal && (oldVal === oldVal || newVal === newVal)) {
//将type作为第三个参数传给trigger
//增加第4个参数,功能:ex.数组长度为0,导致数组元素被删除
trigger(target, key, type,newVal);
}
}
return res;
},
修改trigger函数
function trigger(target, key, type, newVal) {
const depsMap = bucket.get(target);
if (!depsMap) return true;
const effects = depsMap.get(key); //当值改变 从桶中取出与该属性对应的副作用函数
const effectsToRun = new Set();
effects &&
effects.forEach((effectFn) => {
if (effectFn !== activeEffect) {
effectsToRun.add(effectFn);
}
});
//只有当type为ADD或者DELETE时,才触发与ITERATE_KEY副作用函数执行
if (type === 'ADD' || type === 'DELETE') {
//取得与ITERATE_KEY相关联的函数
const iterateEffects = depsMap.get(ITERATE_KEY);
//将与ITERATE_KEY相联系的副作用函数添加到effectsToRun中
iterateEffects &&
iterateEffects.forEach((effectFn) => {
if (effectFn !== activeEffect) {
effectsToRun.add(effectFn);
}
});
}
//当操作类型为ADD切target为数组时,应该取出与length相关的副作用函数
if (type === 'ADD' && Array.isArray(target)) {
const lengthEffects = depsMap.get('length');
lengthEffects &&
lengthEffects.forEach((effectFn) => {
if (effectFn !== activeEffect) effectsToRun.add(effectFn);
});
}
//如果操作目标为数组,并且修改length属性
if (Array.isArray(target) && key === 'length') {
//对于索引大于或者对于length值的元素
//需要把全部相关联的副作用函数取出来添加到effesToRun中等待执行
depsMap.forEach((effects, key) => {
if (key >= newVal) {
effects &&
effects.forEach((effectFn) => {
if (effectFn !== activeEffect) effectsToRun.add(effectFn);
});
}
});
}
effectsToRun.forEach((effectFn) => {
if (effectFn.options.scheduler) {
effectFn.options.scheduler(effectFn);
} else {
effectFn();
}
});
}
遍历数组
for...in操作
在js中使用for...in遍历数组和用它遍历对象是一样,都可以用ownKeys这个方法进行拦截.但是为数组添加新元素,修改数组长度都会影响for...in循环.我们要继续完善ownKeys这个拦截函数
ownKeys(target) {
//如果操作对象是数组,则使用length作为key建立响应关系
tarck(target, Array.isArray(target)?'length':ITERATE_KEY);
return Reflect.ownKeys(target);
},
const arr = reactive(['foo']);
effect(() => {
for (const key in arr) {
console.log(key);
}
});
arr[1] = 'bar'; //副作用函数重新执行
arr.length = 1; //副作用函数重新执行
for...of操作
const arr = reactive(['foo']);
effect(() => {
for (const key of arr) {
console.log(key);
}
});
arr[1] = 'bar'; //副作用函数重新执行
arr.length = 1; //副作用函数重新执行
不用添加任何方法就可以实现,因为for...of执行的操作我们在前面已经全部拦截完了
数组查找方法
我们知道数组大多数依赖对象基本语义,所以很多情况,我们不需要做特殊处理
const arr = reactive([1, 2]);
effect(() => {
console.log(arr.includes(1));
});
arr[0] = 3; //正常输出false
没有问题的情况下肯定要出问题
const obj = {};
const arr = [obj];
console.log(arr.includes(arr[0])); //正常输出true
const obj = {};
const arr = reactive([obj]);
console.log(arr.includes(arr[0])); 输出false
这是因为arr.includes(arr[0]) ,includes执行是由arr这个代理对象调用的,然而,includes会通过索引访问数组的值,如果元素可代理,则返回代理对象,所以此时的arr[0]并不是原来的
//定义应该Map实例,存储原始对象到代理对象的映射
const reactiveMap = new Map();
//封装reactive函数
function reactive(obj) {
// 优先通过原始对象obj寻找之前的代理对象,如果找到则返回已有的代理对象
const existionProxy = reactiveMap.get(obj);
if (existionProxy) return existionProxy;
//否则,创建新代理对象
const proxy = createReactive(obj);
//存储到Map中,从而避免重复创建
reactiveMap.set(obj, proxy);
return proxy;
}
这还是有点问题的
const obj = {};
const arr = reactive([obj]);
console.log(arr.includes(obj));//false
这个问题很简单,我们访问了这个对象,这个对象就会被代理,使用原对象访问,自然是找不到的. 解决方法就是重写includes方法 首先,先更改get拦截函数
get(target, key, receiver) {
//代理对象可以通过raw访问原始数据
if (key === 'raw') {
return target;
}
//如果操作对象是数组,并且key存在arrayInstrumentations上
//那么返回定义在arrayInstrumentations上的值
if(Array.isArray(target) && arrayInstrumentations.hasOwnProperty(key)){
return Reflect.get(arrayInstrumentations,key,receiver)
}
//得到原始值结果
const res = Reflect.get(target, key, receiver);
//typeof key !=="symbol" => for...of代理
if (!isReadonly && typeof key !== 'symbol') tarck(target, key);
if (isShallow) return res;
if (typeof res === 'object' && res !== null) {
//调用reactive函数包装为响应式数据返回
//数据为只读,则调用readonly函数继续包装
return isReadonly ? readonly(res) : reactive(res);
}
return res;
},
然后自定义includes函数(vue太牛了)
const originMethod = Array.prototype.includes
const arrayInstrumentations={
includes:function(...args){
//this是代理对象,先在代理对象中找,将结果存储到res中
let res = originMethod.apply(this,args)
if(res===false){
//res为false为没找到,通过this.raw拿到原始数组,在去查找更新res值
res = originMethod.apply(this.raw,args)
}
return res
}
}
除了includes方法,还有其他方法也需要做类似处理,最终代码
const arrayInstrumentations = {};
['includes', 'indexOf', 'lastIndexOf'].forEach((method) => {
const originMethod = Array.prototype[method];
arrayInstrumentations[method] = function (...args) {
//this是代理对象,先在代理对象中找,将结果存储到res中
let res = originMethod.apply(this, args);
if (res === false || res === -1) {
//res为false为没找到,通过this.raw拿到原始数组,在去查找更新res值
res = originMethod.apply(this.raw, args);
}
return res;
};
});
隐式修改数组长度
数组的栈方法,比如push,pop,shift,unshift,还有splice等方法都会修改数组长度
let shouldTrack = true;
['push', 'pop', 'shift', 'unshift', 'splice'].forEach((method) => {
const originMethod = Array.prototype[method];
arrayInstrumentations[method] = function (...args) {
shouldTrack = false;
let res = originMethod.apply(this, args);
shouldTrack = true;
return res;
};
});