const obj = observable({
    a: "a",
    b: "b"
})

autorun(() => {
    console.log(obj.a)
})

obj.b = "1" // 不触发回调
obj.a = "2" // 触发回调,打印出 "2"

obj.b没有出现在autorun的回调中,无论怎么修改都不会触发回调,obj.a的修改就会触发回调, mobx是怎样在不通过参数传递(指定)的情况下,能够智能实现收集函数中出现的参数的监听的

  • 首先要明确的两点
  1. 只有回调函数中出现的对象的改变才能触发回调函数
  2. 要想改变对象触发回调,该对象必须被observable修饰
  • mobx怎么知道,或者说“监听“哪些对象的改变可以触发回调

奥秘就在autorun

let autorun = function (handler) {
collectHandlerStart(handler);
handler();
collectHandlerEnd();
};

autorun函数在调用的时候会执行一次回调函数,这也是唯一一次搜集回调的机会

为了简单起见,我们在全局定义一个栈stack,用来保存当前最新的handler,而collectHandlerStart要做的就是将回调压栈

const collectHandlerStart = function (fn) {
stack.push(fn);
};

按照autorun的执行顺序,将回调压栈后,回调收集只完成了一半

最重要的就是还要将收集到的回调绑定在指定的对象上,这里指定的对象是指出现在回调里的对象属性

可是怎样才能知道哪些对象属性出现在一个函数的代码块内呢?执行该函数即可

因为在执行过程中肯定会触发该对象的get访问器属性

  • 将回调绑定在出现过在回调中的对象属性上
const dependenceManager = new WeakMap();

get: function (target, key, receiver) {
    const fn = stack.slice(-1)[0];
    if (fn) {
        dependenceManager.set(target, {
            ...dependenceManager.get(target),
            [key]: fn
        });
    }
    if (typeof target[key] === 'object' && target[key] !== null) {
        return new Proxy(target[key], handler);
    }
    return Reflect.get(target, key, receiver);
},

使用proxy可以很轻易的拦截get

将刚才压栈的函数“借用”,现在还不能真正弹出栈他,定义一个WeakMap()用来存储 {“对象” : “值”} 这样的数据结构再合适不过了

判断一下如果fn不为空,说明现在还在handler收集阶段(正在运行autorun),而不是平时的get调用时期

我不清楚真正的mobx内部的唯一标识符是怎么生成的,所以我使用WeakMap,将对象(内存地址总是唯一的)作为唯一标识符,与其对应的回调函数绑定起来

  • 如何在修改时触发回调

很容易想到搜集是通过get,那么修改肯定是set了

set: function (target, key, value, receiver) {
    target[key] = value;
    trigger(target, key, value);
}

const trigger = function (target, key, value,) {
    const mapValue = dependenceManager.get(target);
    mapValue[key] && mapValue[key]();
};

直接用当前的对象最为WeakMap的Key值取出对应的回调函数,然后执行

运行一下文章开头的代码看看效果

在初始化打印a之后,修改b,a,则只有修改a能触发回调

修改一下autorun为

autorun(() => {
    console.log(obj.b);
    console.log(obj.a);
});

打印结果:

初始化打印了b,a,且修改b触发回调,a还是a,再修改a触发回调,b,a都已改变

  • 结束收集

回到autorun函数,在第一次执行了回调函数之后,完成了搜集工作,此时出栈全局stack中的handler

const collectHandlerEnd = function () {
stack.pop();
};

这样,在以后触发get属性时,由于栈为空,就不会再进行handler绑定

到此一个乞丐版mobx便完成了

1 个评论

发表评论

您的电子邮箱地址不会被公开。 必填项已用*标注