第五章-非原始值的响应式方案

57 阅读10分钟

一、理解proxy和Reflect

1.1、proxy

  • proxy: 使用proxy可以创建一个代理对象, 能够实现对其他对象的代理, 也就是proxy只能代理对象, 无法代理非对象值, 例如字符串、布尔值等

  • 代理: 指的是对一个对象的基本语义的代理, 允许拦截并重新定义一个对象的基本操作

  • **基本语义:**对于一个对象, 进行读取属性值, 设置属性值; 对于一个函数(函数也是对象), 调用函数等操作

    • 基本操作, 理解proxy所有内部方法都为基本语义
    let obj = {a: 1};
    const p = new Proxy(obj, {
      set(target, key, value, receiver){}
      get(target, key, receiver)
    })
    
    let fn = function(name) {console.log(name)}
    const p = new Proxy(fn, {
      apply(target, thisArg, argArray){
         target.call(this.Arg, ...argArray)
      }
    })
    

    图片转存失败,建议将图片保存下来直接上传

    • 复合操作: 由两个以上的基本语义组成

    第一个语义: get, obj获取属性fn;

    第二个语义: apply, 执行了 obj.fn();

    obj.fn()
    

    附加知识点:

当proxy handlers中没有进行配置的时候,代理会将所有应用到它的操作转发到这个对象上

let obj = {foo: 1}
let p = new Proxy(obj, {})
p.foo = 2;
console.log(obj.foo); // 2

当进行配置的时候, 按照配置来执行

let obj = {foo: 1}
let p = new Proxy(obj, {
   set(target, key, value) {
      target[key] = 11;
   }
})
console.log(obj.foo); // 1
p.foo = 2;
console.log(obj.foo); // 11

1.2、Reflect

reflect方法与proxy handlers方法相同, 用于被代理对象的实现 , reflect方法与被代理的实现差别在于reflect的参数receiver, receiver用于指定执行被代理对象target的上下文, 即是receiver被当成被代理对象的this

let obj = { 
  foo: 1,
  get bar() {
    return this.foo;
}}
console.log(obj.bar); // 1 
console.log(Reflect.get(obj, "bar", {foo: 2}) ) // 2

// 下面代码不会更改输出的值, 直接返回了值, 不存在上下文的指定
let obj = { foo: 1}
console.log(obj.foo) // 1
console.log(Reflect.get(obj, "foo", {foo: 2}) ) // 1

1、问题

下面是比对使用Reflect,通过原始对象 target 来完成对属性的读取和设置操作会造成的

const data = {
  foo: 1,
  get bar() {
    return this.foo;
  }
}
const obj = new Proxy(data, proxyOption);
effect(() => {
   console.log(obj.bar);
})
obj.foo++; // 这里更改obj.foo不会触发effect函数的执行

2、原因

const data = {
  foo: 1,
  get bar() {
    return this.foo;  // 这里的真正指向?
  }
}
let proxyOption = {
  get(target, key, receiver) {
    console.log(target === data) // true
    track(target, key);
     // 这里的target为原生的data, 所以当读取obj.bar,然后访问data.bar,对应的获取到data.foo, data为原生对象, 不会为foo建立响应联系
    return target[key]; 
  }
}

3、解决方法

原型发生修改为对应的Reflect.*

let proxyOption = {
  get(target, key, receiver) {
    track(target, key);
    return Reflect.get(target, key, receiver);
  },
  
  set(target, key, newVal, receiver) {
    Reflect.set(target, key, newVal, receiver)
    trigger(target, key);
  }
}

二、JavaScript对象及Proxy的工作原理

javaScript中有两种对象, 一种称之为常规对象, 一种称之为异质对象(proxy)

1、非函数对象含有内部方法

图片转存失败,建议将图片保存下来直接上传

2、函数含有的内部方法

图片转存失败,建议将图片保存下来直接上传

3、proxy对象中的内部方法对应的处理器函数

图片转存失败,建议将图片保存下来直接上传

内部方法具有多态性: 这就是说,不同类型的对象可能部署了相同的内部方法,却具有不同的逻辑。例如,普通对象和 Proxy 对象都部署了 [[Get]] 这个内部方法,但它们的逻辑是不同的

常规对象和异质对象的区别如下

● 对于除了 [[Call]]和 [[Construct]]的方法,必须使用 ECMA 规范 10.1.x 节给出的定义实现;

● 对于内部方法 [[Call]],必须使用 ECMA 规范 10.2.1 节给出的定义实现;

● 对于内部方法 [[Construct]],必须使用 ECMA 规范 10.2.2节给出的定义实现

**代理具有透明性:**同样是[[Get]]方法, 如果在创建代理对象时没有指定对应的拦截函数,例如没有指定 get() 拦截函数,那么当我们通过代理对象访问属性值时,代理对象的内部方法 [[Get]] 会调用原始对象的内部方法 [[Get]] 来获取属性值

三、如何代理Object

举例通过v-for的实现流程去看如何代理对象, 主要是通过查找语义确定内部方法来实现

步骤一、对于读取一个普通对象可能的操作

  • obj.foo
  • key in obj
  • for(let key in obj)

步骤二、通过读取规范查找对应的代理方法

  • obj.foo → get
  • key in obj → has
  • for(let key in obj) → ownKeys(target)
const ITERATE_KEY = Symbol(); // 唯一个的key, 只需要建一个, 因为每个对象下面只会挂载一个ITERATE_KEY,  trigger的时间, 会先寻找对象, 后再寻找ITERATE_KEY。 可以理解对象不同, 但ITERATE_KEY相同

let proxyOption = {
  // 对应obj.key
  get(target, key, receiver) {
    track(target, key);
    return Reflect.get(target, key, receiver);
  },
  
  // 对应obj.key = 11
  set(target, key, newVal, receiver) {
    let res = Reflect.set(target, key, newVal, receiver)
    trigger(target, key);
    return res
  },
  
  // 对应delete obj.key
  deleteProperty(target, key) {
    Reflect.deleteProperty(target, key);
    trigger(target, key);
  },
  
  // 对应key in obj
  has(target, key) {
    track(target, key)
    return Reflect.has(target, key)
  },
  
  // 对应for(ley key in obj)
  ownKeys(target) {
    // const ITERATE_KEY = new Symbol()
    // 因为for...in没有对应的key, 所以通过Symbol来创建唯一的key建立对应的响应关系
    track(target, ITERATE_KEY)
    return Reflect.ownKeys(target)
  }
}

步骤三、添加obj属性触发不了响应函数, 需要与ITERATE_KEY建立响应关系

effect(() => {
  for(let key in obj) {
    console.log(key);  // 这里与ITERATE_KEY建立了关联关系
  }
})
obj.bar = 2; // 但是这里触发的key是bar

故需要修改trigger函数

function trigger(target, key) {
  let depsMap =  bucket.get(target);
  if(!depsMap) return;
  let effects = depsMap.get(key);
  let iterateEffects = depsMap.get(ITERATE_KEY);
  let effectsToRun = new Set();
  effects && effects.forEach(effectFn => {
    if(effectFn !== activeEffect){
      effectsToRun.add(effectFn);
    }
  })
  iterateEffects && iterateEffects.forEach(effectFn => {
    if(effectFn !== activeEffect){
      effectsToRun.add(effectFn);
    }
  })
  
  effectsToRun.forEach(effectFn => {
    if(effectFn.options.scheduler){
      effectFn.options.scheduler(effectFn);
    } else {
      effectFn();
    }
  });
}

步骤四、何时触发响应函数

理论上修改属性个数(新增或者删除)会对应for...in造成印象, 而修改属性不会, 其中新增和修改都是通过set函数触发的, 这时候需要引入type进行区分

// 触发函数
function trigger(target, key, type) {
  let depsMap =  bucket.get(target);
  if(!depsMap) return;
  let effects = depsMap.get(key);
  let iterateEffects = depsMap.get(ITERATE_KEY);
  let effectsToRun = new Set();
  effects && effects.forEach(effectFn => {
    if(effectFn !== activeEffect){
      effectsToRun.add(effectFn);
    }
  })
  if(type === "ADD" || type === "DELETE")
  iterateEffects && iterateEffects.forEach(effectFn => {
    if(effectFn !== activeEffect){
      effectsToRun.add(effectFn);
    }
  })
  
  effectsToRun.forEach(effectFn => {
    if(effectFn.options.scheduler){
      effectFn.options.scheduler(effectFn);
    } else {
      effectFn();
    }
  });
}


// 代理配置
let proxyOption = {

  // 对应obj.key = 11
  set(target, key, newVal, receiver) {
    let type = Object.prototype.hasOwnProperty.call(target, key) ? "SET": "ADD";
    let res = Reflect.set(target, key, newVal, receiver)
    trigger(target, key, type);
    return res
  },
  
  // 对应delete obj.key
  deleteProperty(target, key) {
    Reflect.deleteProperty(target, key);
    trigger(target, key, "DELETE");
  },
 
}

四、合理地触发响应(reactive方法在备注上可以看)

1、当值没有发生变化时, 不需要触发响应(需要排除NaN的影响)

 set(target, key, newVal, receiver) {
    let oldValue = target[key];
    let type = Object.prototype.hasOwnProperty.call(target, key) ? "SET": "ADD";
    let res = Reflect.set(target, key, newVal, receiver)
    if(oldValue !== newVal && (oldValue === oldValue || newVal === newVal))
    trigger(target, key, type);
    return res
  },

2、修改了原型链的数据,只需要触发当前对象

// 测试示例
let obj = {};
let proto = { bar: 1};
let child = reactive(obj);
let parent = reactive(proto);
Object.setPrototypeOf(child, parent);
effect(() => {
   console.log(child.bar)
})
child.bar = 2;
// 执行结果
1
2
2

触发了两次的原因:

1、访问child.bar → Reflect.get(target, key, receiver) → parent.bar (parent也是代理对象)→ Reflect.get(target, key, receiver)

2、设置child.bar → Reflect.set(target, key, value, receiver) → set的原理, 如果设置的属性不存在对象上, 那么获取原型, 调用原型的set方法 → parent的Reflect.set(target, key, value, receiver) 方法

3、一次为child的set中trigger方法, 一次为parent的set的trigger方法

解决方法: 通过receiver, receiver其实就是target的的代理对象, 上面方法中, 每次的target都会随着变化, 但是receiver都为child

get(target, key, receiver) {
      if(key === "raw") return  target;
      track(target, key);
      return Reflect.get(target, key, receiver);
},
    
// 对应obj.key = 11
 set(target, key, newVal, receiver) {

  let oldValue = target[key];
  let type = Object.prototype.hasOwnProperty.call(target, key) ? "SET": "ADD";
  let res = Reflect.set(target, key, newVal, receiver)
  if(target === receiver.raw){
  if(oldValue !== newVal && (oldValue === oldValue || newVal === newVal)){
        trigger(target, key, type);
     }
  }
   return res
 },

reactive方法

 function reactive(data) {
  return new Proxy(data,  {
    // 对应obj.key
    get(target, key, receiver) {
      track(target, key);
      return Reflect.get(target, key, receiver);
    },
    
    // 对应obj.key = 11
    set(target, key, newVal, receiver) {
      let oldValue = target[key];
      let type = Object.prototype.hasOwnProperty.call(target, key) ? "SET": "ADD";
      let res = Reflect.set(target, key, newVal, receiver)
      if(oldValue !== newVal && (oldValue === oldValue || newVal === newVal)){
        trigger(target, key, type);
      }
      return res
    },
    
    // 对应delete obj.key
    deleteProperty(target, key) {
      Reflect.deleteProperty(target, key);
      trigger(target, key, "DELETE");
    },
    
    // 对应key in obj
    has(target, key) {
      track(target, key)
      return Reflect.has(target, key)
    },
    
    // 对应for(ley key in obj)
    ownKeys(target) {
      // const ITERATE_KEY = new Symbol()
      // 因为for...in没有对应的key, 所以通过Symbol来创建唯一的key建立对应的响应关系
      track(target, ITERATE_KEY)
      return Reflect.ownKeys(target)
    }
  });
}

五、浅响应和深响应

function createReactive(data, isShallow = false) {
  return new Proxy(data,  {
    // 对应obj.key
    get(target, key, receiver) {
      if(key === "raw") return  target;
      track(target, key);
      let res = Reflect.get(target, key, receiver);
      if(isShallow) return res;
      if(typeof res === "object" && res !== null) {
        return reactive(res);
      }
      return res;
    },
}
function reactive(data) {
   return createReactive(data);
}
function shallowReactive(data) {
   return createReactive(data, true)
}

// 测试示例
let obj = reactive({data: {foo: 1}})
effect(() => {
   console.log(obj.data.foo)
})
obj.data.foo = 2;
// 测试结果
1
2

// 测试示例
let obj = shallowReactive({data: {foo: 1}})
effect(() => {
   console.log(obj.data.foo)
})
obj.data.foo = 2;
// 测试结果
1

六、只读和浅只读

1、修改或者删除属性的时候需要进行提示

2、不需要为只读数据建立响应管理

function createReactive(data, isShallow = false, isReadOnly = false) {
  return new Proxy(data,  {
    // 对应obj.key
    get(target, key, receiver) {
      if(key === "raw") return  target;
      if(!isReadOnly) {
        track(target, key);
      }
      let res = Reflect.get(target, key, receiver);
      if(isShallow) return res;
      if(typeof res === "object" && res !== null) {
        return isReadOnly ? readonly(res) : reactive(res);
      }
      return res;
    },
    
    // 对应obj.key = 11
    set(target, key, newVal, receiver) {
      if(isReadOnly) {
        console.warn(`属性${key}是只读的`);
        return true;
      }
      let oldValue = target[key];
      let type = Object.prototype.hasOwnProperty.call(target, key) ? "SET": "ADD";
      let res = Reflect.set(target, key, newVal, receiver)
      if(target === receiver.raw){
        if(oldValue !== newVal && (oldValue === oldValue || newVal === newVal)){
          trigger(target, key, type);
        }
      }
      return res
    },
    
    // 对应delete obj.key
    deleteProperty(target, key) {
      if(isReadOnly) {
        console.warn(`属性${key}是只读的`);
        return true;
      }
      const hadKey = Object.prototype.hasOwnProperty.call(target, key);
      const res =  Reflect.deleteProperty(target, key);
      if(hadKey && res) {
        trigger(target, key, "DELETE");
      }
    },
    
  });
}
function readonly(data) {
  return createReactive(data, false, true);
}
function shallowReadonly(data){
  return createReactive(data, true, true)
}
// 测试示例
let obj = readonly({data: {foo: 1}})

effect(() => {
   console.log( obj.data.foo)
})
obj.data.foo = 1;
// 测试结果
1
属性foo是只读的


// 测试示例
let obj = shallowReactive({data: {foo: 1}})
effect(() => {
   console.log( obj.data.foo)
})
obj.data.foo = 1;
// 测试结果
1

readonly所有属性都不可以设置, shallowReadonly直联的属性不可以设置, 级联的属性可以设置

readonly不会触发响应函数, shallowReadonly也不会触发响应函数, 即使设置成功

迭代后得到的代码

const ITERATE_KEY = Symbol(); // 唯一个的key, 只需要建一个, 因为每个对象下面只会挂载一个ITERATE_KEY,  trigger的时间, 会先寻找对象, 后再寻找ITERATE_KEY。 可以理解对象不同, 但ITERATE_KEY相同

let activeEffect // 当前副作用函数
let effectStack = []; // 副作用函数栈
function effect(fn, options = {}) {
  const effectFn = () => {
    cleanup(effectFn);
    // 当调用effect注册副作用函数时,将副作用函数赋值给activeEffect
    activeEffect = effectFn;
    // 调用副作用函数之前将副作用函数压入栈
    effectStack.push(effectFn);
    let res =  fn()
    // 在当前副作用函数执行完成之后, 将当前副作用函弹出栈,并将activeEffect还原为之前的值
    effectStack.pop();
    activeEffect = effectStack[effectStack.length - 1];
    return res;
  }
  // 将options挂载到effectFn上
  effectFn.options = options;
  // 用来所有与该副作用函数相关的依赖集合
  effectFn.deps = [];
  if(!options.lazy){
    effectFn();
  }
  return effectFn;
}

// 副作用函数依赖合集的删除
function cleanup(effectFn) {
  for(let i = 0; i < effectFn.deps.length; i++){
    let deps = effectFn.deps[i];
    deps.delete(effectFn);
  }
  effectFn.deps.length = 0;
}

// 桶,储存代理对象属性的相关事件
const bucket = new WeakMap();

// 跟踪函数
function track(target, key) {
  if(!activeEffect)  return;
  let depsMap = bucket.get(target);
  if(!depsMap) bucket.set(target, depsMap = new Map());
  let deps = depsMap.get(key);
  if(!deps) depsMap.set(key, deps = new Set())
  deps.add(activeEffect);
  activeEffect.deps.push(deps);
}

// 触发函数
function trigger(target, key, type) {
  let depsMap =  bucket.get(target);
  if(!depsMap) return;
  let effects = depsMap.get(key);
  let iterateEffects = depsMap.get(ITERATE_KEY);
  let effectsToRun = new Set();
  effects && effects.forEach(effectFn => {
    if(effectFn !== activeEffect){
      effectsToRun.add(effectFn);
    }
  })
  if(type === "ADD" || type === "DELETE")
    iterateEffects && iterateEffects.forEach(effectFn => {
      if(effectFn !== activeEffect){
        effectsToRun.add(effectFn);
      }
    })
  
  effectsToRun.forEach(effectFn => {
    if(effectFn.options.scheduler){
      effectFn.options.scheduler(effectFn);
    } else {
      effectFn();
    }
  });
}




function computed(getter) {
  let effectFn = effect(getter, {
    lazy:  true,
    scheduler() {
      dirty = true;
      trigger(obj, "value")
    }
  });
  let obj;
  let value;
  let dirty = true;
  obj = {
    get value() {
      if(dirty) {
        track(obj, "value");
        value = effectFn();
        dirty = false;
      }
      return value
    }
  }
  return obj;
}

function traverse(value, seen = new Set()) {
  if(typeof value !== "object" || value === null || seen.has(value)) return value;
  // 将数据添加到seen中, 代表遍历读取过了,避免循环引用引起的死循环
  seen.add(value)
  // 这里只考虑到了对象, 没有考虑到数组等结构体
  for(let key in value) {
    traverse(value[key], seen)
  }
  return value;
}

function watch(source, cb, options = {}) {
  let getter;
  if(typeof source === "function") {
    getter = source;
  } else {
    // 如果改为getter = traverse(source), 那么一开始就已经遍历obj下面的所有属性, 返回一个obj对象,
    // 后面effect中调用() => getter的时候, 访问的只是obj对象, 所有当对obj的对象进行设置的时候, 不起作用
    getter = () => traverse(source);
  }
  let newValue, oldValue;
  let cleanUp;
  function onInvalidate(cb) {
    cleanUp = cb;
  }
  const job = () => {
    newValue = effectFn();
    if(cleanUp) {
      cleanUp();
    }
    cb(newValue, oldValue, onInvalidate)
    oldValue = newValue;
  }
  let effectFn = effect(() => getter(), {
    lazy: true,
    scheduler() {
      if(options.flush === "post") {
        Promise.resolve().then(() => {
          job();
        })
      } else {
        job();
      }
    }
  })
  if(options.immediate) {
    job();
  } else {
    oldValue = effectFn();
  }
}


function createReactive(data, isShallow = false, isReadOnly = false) {
  return new Proxy(data,  {
    // 对应obj.key
    get(target, key, receiver) {
      if(key === "raw") return  target;
      if(!isReadOnly) {
        track(target, key);
      }
      let res = Reflect.get(target, key, receiver);
      if(isShallow) return res;
      if(typeof res === "object" && res !== null) {
        return isReadOnly ? readonly(res) : reactive(res);
      }
      return res;
    },
    
    // 对应obj.key = 11
    set(target, key, newVal, receiver) {
      if(isReadOnly) {
        console.warn(`属性${key}是只读的`);
        return true;
      }
      let oldValue = target[key];
      let type = Object.prototype.hasOwnProperty.call(target, key) ? "SET": "ADD";
      let res = Reflect.set(target, key, newVal, receiver)
      if(target === receiver.raw){
        if(oldValue !== newVal && (oldValue === oldValue || newVal === newVal)){
          trigger(target, key, type);
        }
      }
      return res
    },
    
    // 对应delete obj.key
    deleteProperty(target, key) {
      if(isReadOnly) {
        console.warn(`属性${key}是只读的`);
        return true;
      }
      const hadKey = Object.prototype.hasOwnProperty.call(target, key);
      const res =  Reflect.deleteProperty(target, key);
      if(hadKey && res) {
        trigger(target, key, "DELETE");
      }
    },
    
    // 对应key in obj
    has(target, key) {
      track(target, key)
      return Reflect.has(target, key)
    },
    
    // 对应for(ley key in obj)
    ownKeys(target) {
      // const ITERATE_KEY = new Symbol()
      // 因为for...in没有对应的key, 所以通过Symbol来创建唯一的key建立对应的响应关系
      track(target, ITERATE_KEY)
      return Reflect.ownKeys(target)
    }
  });
}
function reactive(data) {
   return createReactive(data);
}
function shallowReactive(data) {
   return createReactive(data, true)
}

function readonly(data) {
  return createReactive(data, false, true);
}
function shallowReadonly(data){
  return createReactive(data, true, true)
}
// 测试示例
let obj = shallowReactive({data: {foo: 1}})
effect(() => {
   console.log( obj.data.foo)
})
obj.data.foo = 1;