← 返回首页
数据和函数的内在联系
发表时间:2024-01-07 14:46:46
数据和函数的内在联系

数据和函数的内在联系

数据与函数的内在联系最核心的问题也是依赖收集和派发更新。

本小节代码结构图如下:

1.依赖收集

数据和函数的内在联系需要建立一个数据到函数的应对关系。这个对应关系肯定需要一个数据结构来保存。

这个数据结构如下下图所示:

那么,在propMap这个结构里面,为什么会有??不确定的属性呢?这是因为在使用for in或者for of这种遍历操作时,获取到的是Symbo.iterate类型。所以这里的问号主要是针对遍历操作。如下图所示:

import {reactive} from './reactive.js'

const obj = {name: 'baobao',age:18};
const state = reactive(obj);

function fn1(){
   for(const key in state){

   }
}

function fn2(){
   state.name;
   state.age;
}

fn1();
fn2();

我们在代码层面还需要建立一个ITERATOR_KEY,来它充当Symbo.iterate类型的属性。同时建立一个WeakMap来实现对象和属性的对应关系的保存。

handlers.js代码修改如下:

import {TrackTypes} from "./operations.js";

const targetMap = new WeakMap();
const ITERATE_KEY= Symbol('iterate');

let shouldTrack = true;
export function pauseTrack(){
    shouldTrack = false;
}

export function resumeTrack(){
    shouldTrack = true;
}

export function track(target,type,key){
    if(!shouldTrack){
        return;
    }

    if(type === TrackTypes.ITERATE){
        console.log(`%c依赖收集:[${type}]`,'color:#f00');
        return;
    }
    console.log(`%c依赖收集:[${type}]`,'color:#f00',key);
}
export function trigger(target,type,key){
    console.log(`%c派发更新:[${type}]`,'color:#00f',key);
}

这样保存这个对应关系的数据结构基本就确定了。下来的问题就是如何实现函数收集呢?因为有一些特殊情况,很难确定收集哪个函数?比如:index.js里面出现函数嵌套。

import {reactive} from './reactive.js'

const obj = {name: 'baobao',age:18};
const state = reactive(obj);

function fn1(){
   function fn2(){
      state.name  //这里究竟是收集fn1还是fn2呢?
   }
   fn2();
}
fn1();

对于这种系统无法确定收集哪个函数的情况,干脆让用户自己决定到底是哪个函数需要进行依赖收集。

effect.js修改如下:

import {TrackTypes} from "./operations.js";

const targetMap = new WeakMap();
const ITERATE_KEY = Symbol('iterate');
let activeEffect = undefined; //用来标记依赖函数
let shouldTrack = true;

export function pauseTrack() {
    shouldTrack = false;
}

export function resumeTrack() {
    shouldTrack = true;
}

export function effect(fn) {
   activeEffect = fn;
   fn();
   activeEffect = null;
}

export function track(target, type, key) {
    if (!shouldTrack || !activeEffect) {
        return;
    }
    console.log(activeEffect);
    if (type === TrackTypes.ITERATE) {
        console.log(`%c依赖收集:[${type}]`, 'color:#f00');
        return;
    }
    console.log(`%c依赖收集:[${type}]`, 'color:#f00', key);
}

export function trigger(target, type, key) {
    console.log(`%c派发更新:[${type}]`, 'color:#00f', key);
}

index.js测试。

import {reactive} from './reactive.js'
import {effect} from "./effect.js";

const obj = {name: 'baobao',age:18};
const state = reactive(obj);

function fn1(){
   function fn2(){
      state.name
   }
   fn2();
}

effect(fn1);

运行结果:

ƒ fn1(){
   function fn2(){
      state.name
   }
   fn2();
}
依赖收集:[get] name

但是现在有个问题:就是函数依赖收集后在派发更新的时候重新执行这个函数,就不会执行effect这个函数,丢失了effect函数里面的执行环境,从而无法再次收集依赖了。所以说收集依赖不是收集函数本身,而是收集运行函数时的环境。

effect.js调整如下:

import {TrackTypes} from "./operations.js";

const targetMap = new WeakMap();
const ITERATE_KEY = Symbol('iterate');
let activeEffect = undefined;
let shouldTrack = true;

export function pauseTrack() {
    shouldTrack = false;
}

export function resumeTrack() {
    shouldTrack = true;
}

export function effect(fn) {
    const effectFn = () => {
        try {
            activeEffect = fn;
            return fn();
        } finally {
            activeEffect = null;
        }
    }
    effectFn();
}

export function track(target, type, key) {
    if (!shouldTrack || !activeEffect) {
        return;
    }
    console.log(activeEffect);
    if (type === TrackTypes.ITERATE) {
        console.log(`%c依赖收集:[${type}]`, 'color:#f00');
        return;
    }
    console.log(`%c依赖收集:[${type}]`, 'color:#f00', key);
}

export function trigger(target, type, key) {
    console.log(`%c派发更新:[${type}]`, 'color:#00f', key);
}

在effect.js中,让targetMap 保存propMap,typeMap和depSet的信息。

import {TrackTypes} from "./operations.js";

const targetMap = new WeakMap();
const ITERATE_KEY = Symbol('iterate');
let activeEffect = undefined;
let shouldTrack = true;

export function pauseTrack() {
    shouldTrack = false;
}

export function resumeTrack() {
    shouldTrack = true;
}

export function effect(fn) {
    const effectFn = () => {
        try {
            activeEffect = fn;
            return fn();
        } finally {
            activeEffect = null;
        }
    }
    effectFn();
}

export function track(target, type, key) {
    if (!shouldTrack || !activeEffect) {
        return;
    }
    let propMap = targetMap.get(target);
    if(!propMap){
        propMap = new Map();
        targetMap.set(target,propMap);
    }

    if(type===TrackTypes.ITERATE){
        key = ITERATE_KEY;
    }

    let typeMap = propMap.get(key)
    if(!typeMap){
        typeMap = new Map();
        propMap.set(key,typeMap);
    }
    let depSet = typeMap.get(type);
    if(!depSet){
        depSet = new Set();
        typeMap.set(type,depSet);
    }

    if(!depSet.has(activeEffect)){
        depSet.add(activeEffect);
    }

    console.log(targetMap);
    /*
    console.log(activeEffect);
    if (type === TrackTypes.ITERATE) {
        console.log(`%c依赖收集:[${type}]`, 'color:#f00');
        return;
    }
    console.log(`%c依赖收集:[${type}]`, 'color:#f00', key);*/
}

export function trigger(target, type, key) {
    console.log(`%c派发更新:[${type}]`, 'color:#00f', key);
}

至此依赖收集建立数据与函数的对应关系基本实现了。

2.派发更新

派发更新主要就是找到对应函数,依次执行。

effect.js

import { TrackTypes, TriggerTypes } from './operations.js';

const targetMap = new WeakMap();
const ITERATE_KEY = Symbol('iterate');
let activeEffect = undefined;
//自定义的函数调用栈
const effectStack = [];
let shouldTrack = true;

export function pauseTrack() {
    shouldTrack = false;
}

export function resumeTrack() {
    shouldTrack = true;
}

export function effect(fn, options = {}) {
    //从options选项中读取lazy是否需要懒加载
    const { lazy = false } = options;
    const effectFn = () => {
        try {
            activeEffect = effectFn;
            effectStack.push(effectFn);
            cleanup(effectFn);
            return fn();
        } finally {
            //执行完一个函数出栈....
            effectStack.pop();
            activeEffect = effectStack[effectStack.length - 1];
            //activeEffect = null;
        }
    };
    effectFn.deps = [];
    effectFn.options = options;
    if (!lazy) {
        effectFn();
    }
    //effectFn();
    return effectFn;
}


export function cleanup(effectFn) {
    const { deps } = effectFn;
    if (!deps.length) {
        return;
    }
    for (const dep of deps) {
        dep.delete(effectFn);
    }
    deps.length = 0;
}

// 依赖收集
export function track(target, type, key) {
    if (!shouldTrack || !activeEffect) {
        return;
    }
    let propMap = targetMap.get(target);
    if (!propMap) {
        propMap = new Map();
        targetMap.set(target, propMap);
    }
    if (type === TrackTypes.ITERATE) {
        key = ITERATE_KEY;
    }
    let typeMap = propMap.get(key);
    if (!typeMap) {
        typeMap = new Map();
        propMap.set(key, typeMap);
    }
    let depSet = typeMap.get(type);
    if (!depSet) {
        depSet = new Set();
        typeMap.set(type, depSet);
    }
    if (!depSet.has(activeEffect)) {
        depSet.add(activeEffect);
        activeEffect.deps.push(depSet);
    }
}

// 派发更新
export function trigger(target, type, key) {
    const effectFns = getEffectFns(target, type, key);
    if(!effectFns){
        return;
    }
    for (const effectFn of effectFns) {
        //防止无限递归,否则出现属性++、--操作会出现递归调用
        if (effectFn === activeEffect) {
            continue;
        }
        if (effectFn.options.scheduler) {
            effectFn.options.scheduler(effectFn);
        } else {
            effectFn();
        }
    }
}

function getEffectFns(target, type, key) {
    const propMap = targetMap.get(target);
    if (!propMap) {
        return;
    }
    const keys = [key];
    if (type === TriggerTypes.ADD || type === TriggerTypes.DELETE) {
        keys.push(ITERATE_KEY);
    }
    const effectFns = new Set();
    const triggerTypeMap = {
        [TriggerTypes.SET]: [TrackTypes.GET],
        [TriggerTypes.ADD]: [
            TrackTypes.GET,
            TrackTypes.ITERATE,
            TrackTypes.HAS,
        ],
        [TriggerTypes.DELETE]: [
            TrackTypes.GET,
            TrackTypes.ITERATE,
            TrackTypes.HAS,
        ],
    };
    for (const key of keys) {
        const typeMap = propMap.get(key);
        if (!typeMap) {
            continue;
        }
        const trackTypes = triggerTypeMap[type];
        for (const trackType of trackTypes) {
            const dep = typeMap.get(trackType);
            if (!dep) {
                continue;
            }
            for (const effectFn of dep) {
                effectFns.add(effectFn);
            }
        }
    }
    return effectFns;
}

index.js

import {reactive} from './reactive.js'
import {effect} from "./effect.js";

const obj = {name: 'baobao', gender: '男', age: 18, score: 98};
const state = reactive(obj);

function fn1() {
    console.log('fn1...')
    if (state.gender === '男') {
        console.log(state.age)

    } else {
        console.log(state.score);
        state.score++;
    }
}

effect(fn1);

//延迟执行
/*
const lazyEffect = effect(fn1,{
   lazy: true
})
lazyEffect();
*/

state.gender = '女';
//state.age = 20; //期望不要触发派发更新。
state.score = 100;

运行结果:

fn1...
18
fn1...
98
fn1...
100