Skip to content

Latest commit

 

History

History
331 lines (279 loc) · 7.12 KB

第5章.md

File metadata and controls

331 lines (279 loc) · 7.12 KB

对对象读取属性值、设置属性值、apply 等

函数调用属于复合操作,如 obj.fn(),先读取 obj.fn,再调用它

Reflect 是全局对象,它的方法跟 Proxy 的拦截器方法名相同

// 等价
const obj = { foo: 1 };
obj.foo;
Reflect.get(obj, "foo");

Reflect 等三个参数是 receiver,可以理解为 this

const obj = { foo: 1 };
Reflect.get(obj, "foo", { foo: 2 }); //输出是2

修改原始对象是不会触发副作用的

const obj = {
  foo: 1,
  get bar() {
    return this.foo; // this.foo指的是obj.foo,期望应该是p.foo(代理后的对象)
  },
};
effect(() => {
  console.log(p.bar);
});

p.foo++;
// 副作用不会执行,原因同标题

解决

const p = new  Proxy(obj,{
  get(target,key,receiver){
    track(target,key)
    return Reflect.get(target,key,receiver)
  }
})

如何区分普通对象和函数

函数会部署内部方法[[Call]],普通对象不会

什么是普通对象,什么是异质对象

  • 表 5-1 11 个内置方法都必须使用 ECMA 规范实现
  • 内部方法[[Call]]必须使用 ECMA 规范实现
  • 内部方法[[Construct]]必须使用 ECMA 规范实现

任意一条不符合就是异质对象,proxy 就是异质对象

创建代理对象时指定拦截函数,实际上是用自定义代理对象本身的内部方法和行为,而不是被代理对象的

拦截 in 操作符

const obj = { foo: 1 };
const p = new Proxy(obj, {
  has(target, key) {
    track(target, key);
    return Reflect.has(target, key);
  },
});

EnumerateObjectProperties 实现

// obj就是被for...in的对象
function* EnumerateObjectProperties(obj) {
  const visited = new Set();
  for (const key of Reflect.ownKeys(obj)) {
    if (typeof key === "symbol") continue;
    const desc = Reflect.getOwnProprotyDescriptor(key);
    if (desc) {
      visited.add(key);
      if (desc.enumerable) yield key;
    }
  }
  const proto = Reflect.getPrototypeOf(obj);
  if (proto === null) {
    return;
  }
  for (const protoKey of EnumerateObjectProperties(proto)) {
    if (visited.has(protoKey)) continue;
    yield protoKey;
  }
}

通过 EnumerateObjectProperties 得知,拦截 ownKeys 可以实现 for...in 代理

const obj = { foo: 1 };
const ITERATE_KEY = Symbol()
const p = new Proxy(obj, {
  ownKeys(target, key) {
    track(target, ITERATE_KEY);
    return Reflect.ownKeys(target);
  },
});
const p = new Proxy(obj, {
  deleteProperty(target, key) {
    const hadKey = Object.prototype.hasOwnProperty.call(target, key);
    const res = Reflect.deleteProperty(obj, target);
    if (hadKey && res) {
      trigger(target, key, "DELETE");
    }
    return res;
  },
});

封装 Proxy

function reactive(obj) {
  return new Proxy(obj, {});
}

原型上执行 2 次?因为设置的属性不存在对象上,会取得其原型,并调用原型的 set 方法,也就是 parent 的 set 方法

var a = {
  bar: 1,
};
var b = {};
var x = new Proxy(a, {
  set(target, key, value, receiver) {
    console.log("a", target, receiver);
  },
  get(target, key, value, receiver) {
    return Reflect.get(target, key, value, receiver);
  },
});
var y = new Proxy(b, {
  set(target, key, value, receiver) {
    Reflect.set(target, key, value, receiver);
    console.log("b", target, receiver);
  },
  get(target, key, value, receiver) {
    return Reflect.get(target, key, value, receiver);
  },
});
Object.setPrototypeOf(y, x);
y.bar = 2;
// a ...receiver 是child
// b ...receiver 是child

新增 raw 属性来访问代理对象的原始对象

function reactive(obj) {
  return new Proxy(obj, {
    get(target, key, receiver) {
      if (key === "raw") {
        return target;
      }
    },
    set(target, key, newVal, receiver) {
      //过滤掉parent的set
      if (target === receiver.raw) {
        ///触发更新
      }
    },
  });
}

reactive 函数中 key 为对象时,不能触发响应

深响应、浅响应

function createReactive(obj,isShallow = false) {
     reutrn new Proxy(obj,   {
       get(target,key,receiver){
        if (key === "raw") {
          return target;
        }
        const res = Reflect.get(target,key,receiver )
        if(isShallow) {
          return res
        }
        track(target,key)
        if(typeof res === 'object' && res !== null) {
          return reactive(res)
        }
       }
     })
  }

数组是异质对象,因为数组的 defineOwnProperty 属性与常规对象不同

修改数组索引,可能会导致元素被添加,需响应

// 修改
arr[100] = "465654";
function set(target, key) {
  //...
  const type = Array.isArray(target)
    ? Number(key) < target.length
      ? "SET"
      : "ADD"
    : Object.prototype.hasOwnProperty.call(target, key)
    ? "SET"
    : "ADD";
}

function trigger(target, key, type) {
  if (type === "ADD" && Array.isArray(target)) {
    const lengthEffects = depsMap.get("length");
    lengthEffects &&
      lengthEffects.forEach((effect) => {
        if (effectFn !== activeEffect) {
          effectsToRun.add(effectFn);
        }
      });
  }
}

修改 length,元素会发生改变

// 访问索引大于等于新length,要执行副作用
function trigger(target, ke+y, type, newVal) {
  if (Array.isArray(target) && key === "length") {
    if (key >= newVal) {
      effects.forEach((effectFn) => {
        /*...*/
      });
    }
  }
}

拦截数组的 for...in

function createReactive(obj) {
  return new Proxy(obj, {
    ownKeys(target) {
      track(Array.isArray(target) ? "length" : ITERATE_KEY);
      return Reflect.ownKeys(target);
    },
  });
}

数组的 values 方法就是数组内建迭代器

Array.prototype.values === Array.prototype[Symbol.iterator];
let a = [1, 2, 3];
for (const i of a) {
}
for (const i of a.values()) {
}

只需建立副作用函数与数组长度的联系,for...of 就可以自动响应,因为 for...of 内部回读取 length 属性

为了性能,不需要 track key 为 symbol 类型的

存储代理对象

const reactiveMap = new Map();
function reactive(obj) {
  const existionProxy = reactiveMap.get(obj);
  if (existionProxy) {
    return existionProxy;
  }
  const proxy = createProxy(obj);
  existionProxy.set(obj, proxy);
  return proxy;
}

includes 重定义,解决原始值和代理对象 includes 返回 false 的问题

const arrayInstrumentations = {}
function get(target,key,receiver) {
  if(Array.isArray(target)   &&  arrayInstrumentations.hasOwnProperty(key)  ){
    Reflect.get(arrayInstrumentations,key,receiver)
  }
}

const originMethod = Array.prototype.includes
const arrayInstrumentations = {
  includes(...args) {
      let res = originMethod.apply(this,args)
      if(res === false) {
        res = originMethod.apply(this.raw,args)
      }
  }
}

解决数组响应 push 栈溢出

let shouldTrack = true["push"].forEach((method) => {
  const originMethod = Array.prototype[method];
  arrayInstrumentations[method] = function (...args) {
    shouldTrack = false;
    let res = originMethod.apply(this, args);
    shouldTrack = true;
    return res;
  };
});