相关面试题
1.vue是如何避免收集到重复的副作用函数
2.使用proxy代理相比defineProperty 性能好
我们都知道vue3的双向数据绑定是用proxy来实现;那么proxy 是如何跟track 函数,trigger函数跟effect副作用函数进行关联呢
首先我们来理清代码执行过程
1.代码从effect开始执行, 执行副作用函数,里面包含获取代理对象的属性;
2.因为获取是代理对象的属性,会进入reactive里面的 proxy进行执行get方法, 从而触发里面的track方法进行收集
3.track方法里面如何获取当effect作用函数呢,需要将副作用赋值给全局变量 activeEffect
1.effect函数是将副作用赋者给activeEffect
相关代码以及解说
reactive.js
reactive实现proxy 代理,对对象的属性进行拦截,同时使用track来收集依赖副作用函数, trigger来触发收集的依赖副作用函数
import { track, trigger } from './effect'
import { isObject, hasChanged, isArray } from '../utils';
const proxyMap = new WeakMap();
export const reactive = (obj) => {
// 传入的值不是对象
if (!isObject(obj)) return obj
if (isReactive(obj)) return obj; // 解决嵌套 reactive(reactive(obj))
if(proxyMap.has(obj)) return proxyMap.get(obj) // 解决 已经代理过了 const a= reactive(obj) const b= reactive(obj)
const proxy = new Proxy(obj, {
get: (target, key, receiver) => {
if (key === '__isReactive') {
return true
}
const res = Reflect.get(target, key, receiver)
track(target, key)
// 面试题解决 职业访问才对对象进行双向数据绑定,既是 收集依赖
return isObject(res) ? reactive(res) : res;
},
set(target, key, value, receiver) {
const oldValue = target[key];
const oldLength = target.length;
const res = Reflect.set(target, key, value, receiver);
if (hasChanged(value, oldValue)) {
trigger(target, key);
if (isArray(target) && target.length !== oldLength) {
trigger(target, 'length');
}
}
return res;
},
})
proxyMap.set(obj, proxy)
return proxy
}
export const isReactive = (target) => {
return !!(target && target.__isReactive)
}
effect.js
该文件实现effect副作用函数,track函数跟trigger函数
需要关注问题是
1.track收集依赖的数据结构是什么
2.effect的副作用函数如何保存起来
let activeEffect // 保存副作用
let activeEffectStack = []; // 处理嵌套的effect 比如 effect(() => {effect(() => {})})
export const effect = (fn) => {
const effectFn = () => {
try {
// 将副作用函数保存起来,未了在tack函数获取到
activeEffect = effectFn
activeEffectStack.push(activeEffect)
return fn()
} finally {
activeEffectStack.pop()
activeEffect = activeEffectStack[activeEffectStack.length - 1]
}
}
effectFn();
return effectFn
}
// track如何跟副作用进行关联;首先从代码的执行顺序进行思考,
// 1.代码从effect开始执行, 执行副作用函数,里面包含获取代理对象的属性;
// 2.因为获取是代理对象的属性,会进入reactive proxy进行执行get方法, 从而触发里面的track方法进行收集
// 3.track方法里面如何获取当fn作用函数呢,需要将副作用赋值给全局变量 activeEffect
// 收集依赖数据结构为
// 比如响应对象是obj= { age: 10,name: 'wu'} 对应数据结构是 { [obj]: {age: [activeEffect], name: [activeEffect]}}
// 面试题为什么用 set来收集副作用 ,避免收集重复的副作用
const targetMap = new WeakMap();
export const track = (target, key) => {
// 判断是否副作用函数存在
if (!activeEffect) {
return
}
// 先判断是否已经收集过了
let desMap = targetMap.get(target)
if (!desMap) {
targetMap.set(target, desMap = (new Map()))
}
let des = desMap.get(key)
if (!des) {
desMap.set(key, des = (new Set()))
}
// 面试题为什么用 set来收集副作用 ,避免收集重复的副作用
des.add(activeEffect)
}
// 当代理对象的值发送改变,从targetMap找出副作用,然后执行它
export const trigger = (target,key) => {
const desMap = targetMap.get(target)
if (!desMap) return
const des = desMap.get(key)
if (!des) return
des.forEach(effect => {
effect()
});
}
utils/index.js
export function isObject(value) {
return typeof value === 'object' && value !== null;
}
export function isFunction(value) {
return typeof value === 'function';
}
export function isArray(value) {
return Array.isArray(value);
}
export function hasChanged(value, oldValue) {
return value !== oldValue && (value === value || oldValue === oldValue);
}