MVVM框架介绍--简易mvvm框架(5) | 青训营笔记 青训营笔记

91 阅读3分钟

这是我参与「第五届青训营 」伴学笔记创作活动的第 8 天

reactive板块-简易框架里的代码分析

树形结构

上文提到没有建立联系

原因:当读取属性时,无论读取 的是哪一个属性,其实都一样,都会把副作用函数收集到“桶”里;当 设置属性时,无论设置的是哪一个属性,也都会把“桶”里的副作用函 数取出并执行。

解决办法依靠一个保存有依赖与更新对应关系的WeakMap数据结构--书中给了这样的树形结构

image.png

所以使用 WeakMap 代替 Set 作为桶的数据结构:

image.png

WeakMap

  1. targetMap: WeakMap 类型,用来记录目标对象和depsMap的关系
  2. depsMap: Map 类型,用来记录目标对象属性和dep的关系
  3. dep: Set 类型,用来记录属性对应的更新函数

image.png

let targetMap = new WeakMap()
function track(target, key) {
    // 判断activeEffect是否存在
    if (!activeEffect) {
        return;
    }
    // depsMap存储对象和effect的对应关系
    let depsMap = targetMap.get(target)
    // 如果不存在则创建一个map存储到targetMap中
    if (!depsMap) {
        targetMap.set(target, (depsMap = new Map()))
    }
    // 根据属性查找对应的dep对象
    let dep = depsMap.get(key)
    // dep是一个集合,用于存储属性所对应的effect函数
    if (!dep) {
        // 如果不存在,则创建一个新的集合添加到depsMap中
        depsMap.set(key, (dep = new Set()))
    }
    dep.add(activeEffect)
}

优点:

  1. WeakMap 对 key 是弱引用,不影响垃圾回收器的工 作。一旦 key 被垃圾回收器回收,那么对应的键和 值就访问不到了。所以 WeakMap 经常用于存储那些只有当 key 所引 用的对象存在时(没有被回收)才有价值的信息。

  2. 如果使用 Map 来代替 WeakMap, 那么即使用户侧的代码对 target 没有任何引用,这个 target 也不 会被回收,最终可能导致内存溢出。

effect (有点缺漏)

let activeEffect = null;
function effect(callback) {
    activeEffect = callback;
    // 访问响应式对象属性,收集依赖
    callback(); 
    // 依赖收集结束要置null
    activeEffect = null;
}

trigger

function trigger(target, key) {
    const depsMap = targetMap.get(target)
    // 如果没有找到直接返回
    if (!depsMap) {
        return;
    }
    const dep = depsMap.get(key)
    if (dep) {
        dep.forEach(effect => {
            effect()
        })
    }
}

嵌套对象

// 判断是否是一个对象
const isObject = val => val !== null && typeof val === 'object'
// 如果是对象则调用reactive
const convert = target => isObject(target) ? reactive(target) : target
// 判断对象是否存在key属性
const haOwnProperty = Object.prototype.hasOwnProperty
const hasOwn = (target, key) => haOwnProperty.call(target, key)

function reactive(target) {
    if (!isObject(target)) {
        // 如果不是对象直接返回
        return target
    }

    const handler = {
        get(target, key, receiver) {
            // 收集依赖
            track(target, key)
            const result = Reflect.get(target, key, receiver)
            console.log(result, "get")
            // 如果属性是对象则需要递归处理
            return convert(result)
        },
        set(target, key, value, receiver) {
            const oldValue = Reflect.get(target, key, receiver)
            let result = true;
            // 需要判断当前传入的新值和oldValue是否相等,如果不相等再去覆盖旧值,并且触发更新
            if (oldValue !== value) {
                result = Reflect.set(target, key, value, receiver)
                // 触发更新...
                trigger(target, key)
            }
            // set方法需要返回布尔值
            console.log(result, "set")
            return result;
        },
        deleteProperty(target, key) {
            // 首先要判断当前target中是否有自己的key属性
            // 如果存在key属性,并且删除要触发更新
            const hasKey = hasOwn(target, key)
            const result = Reflect.deleteProperty(target, key)
            if (hasKey && result) {
                // 触发更新...
                trigger(target, key)
            }
            return result;
        }
    }
    return new Proxy(target, handler)
}


补充:

在compile模块里的v-model命令,当涉及到input框里修改值的时候会多次改变,所以执行的时候加入了防抖功能

  model(node, vm, expr) {
        node.value = this.getVMValue(vm, expr)
        // 实现双向的数据绑定,给NODE注册INPUT事件,当前元素的value发生改变,便修改node数据
        node.addEventListener("input", MyDebounce(function () {
            vm.$data[expr] = this.value
        }))
        effect(() => {
            node.value = this.getVMValue(vm, expr)
        })

    },