Vue原理记录

126 阅读7分钟

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.ntrackOpBit按位或运算: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过程中派发变更。

Vue3ref中的get value函数中会收集依赖。

响应式