数据和函数的内在联系
数据与函数的内在联系最核心的问题也是依赖收集和派发更新。
本小节代码结构图如下:

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

那么,在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);
}
至此依赖收集建立数据与函数的对应关系基本实现了。
派发更新主要就是找到对应函数,依次执行。
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