vNode
首先template转为字符串,然后用正则匹配,转为ast,后续使用xxx优化,标记静态节点和静态根,用render函数渲染。 第一次生成真实dom,后面每次生成虚拟dom,后续用虚拟dom和真实dom比较。进行 diff。
defineproperty
在es6中,class转换后就是defineProperty。
由于defineproperty在处理引用数据的时候,需要遍历数据,导致性能低下,并且通常情况下只对数据进行浅处理,所以vue3放弃了使用defineproperty处理对象、数组。转用reactive处理,也就是proxy。但是用于处理基础数据类型的ref依旧是defineproperty。
defineProperty只能对对象对已知属性进行操作,所以才会导致没有在data中声明的对象属性直接赋值时无法触发试图更新,需要通过$set处理。
Proxy
首先看下Proxy基本使用。
const obj = { name: 'MiyueFE', age: 28 };
const proxyObj = new Proxy(obj, {
get(target, property) {
console.log(`Getting ${property} value: ${target[property]}`);
return target[property];
},
set(target, property, value) {
console.log(`Setting ${property} value: ${value}`);
target[property] = value;
},
deleteProperty(target, property) {
console.log(`Deleting ${property} property`);
delete target[property];
},
});
console.log(proxyObj.name); // Getting name value: MiyueFE, 输出 "MiyueFE"
proxyObj.name = 'MY'; // Setting name value: MY
console.log(proxyObj.name); // Getting name value: MY, 输出 "MY"
delete proxyObj.age; // Deleting age property
console.log(proxyObj.age); // undefined
在使用Proxy时,只有使用proxyObj才能拦截到对象,直接使用原对象是无法触发拦截器的,这也是为什么reactive直接修改原对象无法触发视图更新。
另外,Proxy只能对引用数据类型进行代理。
ref
跟vue2中defineproperty是一样的。
在ref中会在开头判断是否是Object类型,如果是的话,则转为使用Reactive处理。
ref返回的是一个RefImpl实例。
function ref(value){
return createRef(value, false)
}
createRef
接收两个参数,第一个为初始化值,第二个表示是否为浅
function createRef(rawValue, shallow){
if(isRef(rawValue)){
return rawValue //这里是判断传入的数据是否已经是响应式数据,如果是的话则直接返回出去,不做处理
}
return new RefImpl(rawValue, shallow)
}
在这里可以看出ref返回的是RefImpl实例。
RefImpl
class RefImpl {
constructor(value, __v_isShallow) {
this.__v_isShallow = __v_isShallow;
this.dep = undefined;
this.__v_isRef = true;
this._rawValue = __v_isShallow ? value : toRaw(value);
this._value = __v_isShallow ? value : toReactive(value);
}
get value() {
trackRefValue(this);
return this._value;
}
set value(newVal) {
const useDirectValue = this.__v_isShallow || isShallow(newVal) || isReadonly(newVal);
newVal = useDirectValue ? newVal : toRaw(newVal);
if (hasChanged(newVal, this._rawValue)) {
this._rawValue = newVal;
this._value = useDirectValue ? newVal : toReactive(newVal);
triggerRefValue(this, newVal);
}
}
}
这里的get和set都设置为value,所以在调用和修改ref变量时,都需要.value。
trackRefValue
在RefImpl函数中,当触发get时,会触发trackRefValue函数,这个函数就是用于依赖收集的函数。依赖收集是响应式和监听的重要组成部分。
function trackRefValue(ref) {
if (shouldTrack && activeEffect) {
ref = toRaw(ref);
if ((process.env.NODE_ENV !== 'production')) {
trackEffects(ref.dep || (ref.dep = createDep()), {
target: ref,
type: "get" /* TrackOpTypes.GET */,
key: 'value'
});
}
else {
trackEffects(ref.dep || (ref.dep = createDep()));
}
}
}
可以看到该函数核心是trackEffects,而传入该函数的第一个参数是一个由createDep创建的依赖。
createDep
const createDep = (effects) => {
const dep = new Set(effects);
dep.w = 0;
dep.n = 0;
return dep;
};
这里使用到了Set去创建实例,作为依赖使用。
dep对象中存储的是依赖w属性用于表示当前依赖的状态n属性用于表示该依赖的计数
trackEffects
用于处理依赖收集。
接收两个参数,第一个参数是存储依赖的地方,由上可知,传入时会判断是否有依赖收集,没有则调用依赖的创建函数createDep。
trackEffects函数主要干了这几件事:
dep.n和trackOpBit按位或运算:0|2 = 2- 计算
shouldTrackd的值,得到的值为true
最后的activeEffect对象是new ReactiveEffect得到的对象。
function trackEffects(dep, debuggerEventExtraInfo) {
let shouldTrack = false;
if (effectTrackDepth <= maxMarkerBits) {
if (!newTracked(dep)) {
dep.n |= trackOpBit; // set newly tracked
shouldTrack = !wasTracked(dep);
}
}
else {
// Full cleanup mode.
shouldTrack = !dep.has(activeEffect);
}
if (shouldTrack) {
dep.add(activeEffect);
activeEffect.deps.push(dep);
if ((process.env.NODE_ENV !== 'production') && activeEffect.onTrack) {
activeEffect.onTrack(Object.assign({ effect: activeEffect }, debuggerEventExtraInfo));
}
}
}
reactive
使用两个weakmap实现原始数据和响应数据的双向映射
Reactive返回的是一个Proxy实例。
使用weakmap的原因:
const reactiveMap = new WeakMap()
function reactive(target) {
// if trying to observe a readonly proxy, return the readonly version.
if (isReadonly(target)) {
return target;
}
return createReactiveObject(target, false, mutableHandlers, mutableCollectionHandlers, reactiveMap);
}
createReactiveObject
function createReactiveObject(target, isReadonly, baseHandlers, collectionHandlers, proxyMap) {
if (!isObject(target)) {
if ((process.env.NODE_ENV !== 'production')) {
console.warn(`value cannot be made reactive: ${String(target)}`);
}
return target;
}
// target is already a Proxy, return it.
// exception: calling readonly() on a reactive object
if (target["__v_raw" /* ReactiveFlags.RAW */] &&
!(isReadonly && target["__v_isReactive" /* ReactiveFlags.IS_REACTIVE */])) {
return target;
}
// target already has corresponding Proxy
const existingProxy = proxyMap.get(target);
if (existingProxy) {
return existingProxy;
}
// only specific value types can be observed.
const targetType = getTargetType(target);
if (targetType === 0 /* TargetType.INVALID */) {
return target;
}
const proxy = new Proxy(target, targetType === 2 /* TargetType.COLLECTION */ ? collectionHandlers : baseHandlers);
proxyMap.set(target, proxy);
return proxy;
}
effect
ReactiveEffect
watch
computed
依赖收集
Vue2通过Object.defineProperty来实现数据读取和更新时的操作和劫持,通过更改默认的getter/setter函数,在get过程中收集依赖,在set过程中派发变更。
Vue3在ref中的get value函数中会收集依赖。