一、理解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;