Vue3实现原理

Vue3实现原理

核心方法

3.x中,使用reactive创建数据池。其和2.x中的data起一定相同作用。
其本质是将传入的普通对象转为该普通对象的响应式代理(响应式数据)。
等同于2.xVue.obserable()

    let proxy = Vue.reactive({name: 'Jonham'}); 
    // 含义:副作用 数据变化了 更新视图、请求数据
    Vue.effect(() => {
        proxy.name = 'Alarikshaw'; // 此处查询数据 会调用get方法
    })

在这个代码里面,必然是先渲染。随后数据改变之后再渲染一次。
核心方法通过effect()方法实现
默认effect()会先执行一次

3.x 响应式实现原理 核心代码

// 判断是否是对象
function isObject (val) {
    return typeof val === 'object' && val!== null;
}
// 初始化方法
function reactive (target) {
    // 创建响应式对象
    return createReactiveObject(target)
}

// 创建响应式对象
function createReactiveObject (target) {
    // 不是对象则无需其他操作 直接返回原数据
    if (!isObject(target)) {
        return target;
    }
    // 查看是否被代理
    let proxy = toProxy.get(target); // 如果已经代理过了,就直接返回proxy
    if (proxy) {
        return proxy;
    }
    if (toRaw.has(proxy)) { // 防止代理过的对象再次被代理(嵌套)
        return proxy;

    }
    // 创建一个观察者
    // 代理target对象
    let baseHandler = { // 源码里面是五个,此处只写三个
        get (target, key, receiver) { // 查询
            // proxy + reflect 反射
            let result = Reflect.get(key, target, receiver);
            // 收集依赖
            return isObject(result) ? reactive(result) : result;
        }, 
        set (target, key, value, receiver) { // 设置
            let result = Reflect.set(target, key, value, receiver); 
            return result;
        }, 
        deleteProperty () { // 删除
            let result = Reflect.deleteProperty(target, key);
            return result;
        }, 
    };
    let observed = new Proxy(target, baseHandler); // es6
    toProxy.set(target, observed);
    toRaw.set(observed, target);
    return observed;
}
// proxy 代理对象 该对象不需要重写 name、key、value属性
let proxy = reactive({name: 'Jonham'})

当对proxy代理对象进行增、删、改、查操作的时候,分别触发baseHandler内部相应的方法

Proxy 缺点 兼容性差 止于IE11

代理对象,原对象的方法,增删改查。则需要对其原来的方法进行拦截(baseHandler)

内部方法接收多个参数,target原对象。key是具体查的某个字段。receiver为代理后的对象(proxy)

receiver就是新的 proxy

get方法:如果设置没成功如果对象不可以被更改 writeable

set方法:返回Boolean类型

Porxy 另做讲解

Reflect是ES6为了操作对象而新增的API

将Object对象的一些明显属于语言内部的方法(比如Object.defineProperty),放到Reflect对象上,那么以后我们就可以从Reflect对象上可以拿到语言内部的方法。

在使用对象的 Object.defineProperty(obj, name, {})时,如果出现异常的话,会抛出一个错误,需要使用try catch去捕获,但是使用 Reflect.defineProperty(obj, name, desc) 则会返回false

3.x 也用了递归思路。但其巧妙之处在于并不是如2.x一上来就直接递归。而是在对数据进行操作的时候(get方法内部判断触发)进行多层代理

get (target, key, receiver) { // 查询
    let result = Reflect.get(key, target, receiver);
    return isObject(result) ? reactive(result) : result;
}, 

::: danger
当然,Porxy代理对象仅需要一次即可。

但不可否认,在书写代码同时,会有一些小错误。造成同一个对象,多次调用代理方法。又或者将代理对象再次进行reactive(ProxyObject)代理

此时需要记录一下,避免此种情况发生。

:::

hash表

hash表 映射表
注册映射表

let tpProxy = new WeakMap(); // 原对象:被代理过的对象
let toRaw = new WeakMap(); // 被代理过的对象:原对象

let observed = new Proxy(target, baseHandler); // es6
toProxy.set(target, observed);
toRaw.set(observed, target);
return observed;

new WeakMap()弱引用映射表 es6 放置的是源对象

WeakMap 不使用map的原因是防止被回收掉

这样,进行代理时可判断是否被代理

// 查看是否被代理
let proxy = toProxy.get(target); // 如果已经代理过了,就直接返回proxy
if (proxy) {
    return proxy;
}
if (toRaw.has(proxy)) { // 防止代理过的对象再次被代理(嵌套)
    return proxy;
}

代理对象更改为数组

let arr = [1, 2, 3];
let proxy = reactive(arr);
proxy.push(4); 

会执行两个步骤

1、 会在arr中索引为3的位置上设置为4;(新增)

2、 会把arrlength更改为4;(修改)

弊端: push数组时会触发两次视图更新

所以,在set方法内部需要识别是改属性还是新增属性

set内,判断target是否有key,有的话就是更改,没有的话就是新增

function hasOwn(target, key) {

}
// set 内执行
let oldValue = target[key];
let hasKey = hasOwn(target, key); // 判断这个属性 以前有没有
if (!hasKey) { // 新增属性
    trigger(target, 'add', key); 
} else if (oldValue !== value) { // 修改属性
    trigger(target, 'set', key); 
} // 屏蔽无意义的修改

依赖收集

发布订阅
栈 先进后出
响应式 副作用

let activeEffectStack  = []; // 栈型结果
function effect(fn) {
    // 需要把fn这个函数编程响应式的函数
    let effect = createReactiveEffect(fn);
    effect(); // 默认先执行一次
}

function createReactiveEffect (fn) {
    let effect = function () { // 这个就是创建的响应式effect
        return run(); // 运行 
    }
    return effect;
}
let obj = reactive({name: 'Jonham'});
effect(( => { // 默认先执行一次,依赖的数据变化了会在执行一次
    console.log(obj.name);
})
obj.name = 'Alarikshaw'

effect作用和watch相当

此处的obj.name数据变化,则effect方法会被执行

核心

function createReactiveEffect (fn) {
    let effect = function () { // 这个就是创建的响应式effect
        return run(effect, fn);
    }
    return effect;
}

run():

1、让fn()执行。

2、同时把effect存放到栈(activeEffectStack)中

function run(effect, fn) { // 运行run 并且将effect存起来
  try {
      activeEffectStack.push(effect);
      fn(); // 同2.x,利用了js是单线程
  } finally {// 即便fn()报错,也能执行 清理方法
      activeEffectStack.pop(effect);
  }
}

::: danger

收集依赖,本质上是修改某一属性,则对其绑定一个effect方法,组成键值对,并且存放在activeEffectStack中,组成数组。

// name 对应key; effect对应相应的函数
activeEffectStack = [
    {
        target: {
            key: [fn, fn]
        }, 
    }
]

也就是俗称 依赖收集
:::

get (target, key, receiver) { // 查询
    // proxy + reflect 反射
    let result = Reflect.get(key, target, receiver);
    // 收集依赖 订阅 把当前的key 和 这个 effect 对应起来
    trak(target, key, effect); // 如果目标上的 key 变化了 重新让数组中的effct 执行即可
    return isObject(result) ? reactive(result) : result; // 是个递归
}, 

依赖收集
一般收集依赖时,需看是否已经创建关系,如果没有,才可往WeakMap()内部添加关系
查询值会触发track,里面是个WeakMap,其key可以是个对象并且是原对象,值value是个映射表,表的值也是keyvaluekey也是原对象,value是不重复的数

let targetMap = new WeakMap(); // 集合和hash表
// 如果当前的 target 中 key 变化了 我就执行数组里的方法
function trak (target, key) {
    let effect = activeEffectStack[activeEffectStack.length - 1];
    if (effect) { // 有对应关系 才创建关联
        let depsMap = targetMap.get(target);
        if (!deppsMap) { // 取不到则设置一个默认值depsMap
            targetMap.set(target, depsMap = new Map());
        }
        // 查看key是否存下
        let deps = depsMap.get(key);
        if (!deps) {
            depsMap.set(key, deps = new Set());
        }
        if (!deps.has(effect)) {
            deps.add(effect);
        }
    }
}

设置值触发trigger

function trigger (target, type, key) {
    let depsMap = targetMap.get(target); // 去映射表当中查询
    if (depsMap) {
        let deps = depsMap.get(key);
        if (deps) { // 将当前key 对应的effect 依次执行
            deps.forEach(effect => {
                effect();
            })
        }
    }
}   

核心方法trak()tigger().

# Vue  Vue3原理 

评论

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×