彻底理解前端依赖收集

作者: 前端大课堂 | 来源:发表于2019-01-19 22:58 被阅读1次

    依赖收集是 Vue.js 和 Mobx.js 核心的之一,那么依赖追踪算法如何工作呢?本文将带读者自己动手实现一个依赖收集的库。

    早起很多双向绑定的框架,Model和Dom同步更新,或者现在较多场景Model更新时,Dom自动更新。 

    例子1: 

    data = {

        label: '深圳欢迎你'

    }; 

    bind(dom, data);  //实现一个bind函数,让dom和data绑定起来

    data.label = "北京欢迎你";  // dom 上显示的label就是"北京欢迎你" 

    例子2: 

    计算属性 sum 

    data = {

        num: 3,

        price: 20,

        sum: 60    // sum = num * price    

    }

    那么每次data.num和data.price改变,自动更新data.sum

    这两个例子用Object.defineProperty很好实现。但是耦合度很高。 

    function defineReactive (obj, key, val) {

        Object.defineProperty(obj, key, {

                get () { return val },

                set (newValue) {

                    // 例子1 这里要写dom更新的代码 

                    // 例子2 这里要写sum更新数据的代码 

                    val = newValue

                }

        })

    }

    也就是被依赖者要知道依赖者的存在。 那如果一个源数据被很多方依赖,那么在set部分的代码将越来越多。 

    那现在 mobx 和 vuejs 的依赖收集是怎么做的呢?

    我们先看个题目,计算某个同学的岁数? 

    那么如何实现呢?首先,依赖于当前的年份,其次,依赖于这个同学的出生年份。 

    let current = {

        year: 2018

    }; 

    let student = {

        born: 1995

    }; 

    我们需要两个函数,defineReactive(obj, key) 和 defineComputed(obj, key, computeFn, updateCallback) 来实现整个过程。 

    let agedObj = {    

        age: 0   // 初始的岁数

    }; 

    defineReactive(current, 'year'); 

    defineReactive(student, 'born'); 

    defineComputed(agedObj, age, () => current.year - student.born, value => {

        console.log(`this student's aged is ${value}`); 

    }); 

    // 标记当前正在求值的 computed 函数

    let Dep = null

    // 定义 computed,需传入求值函数与 computed 更新时触发的回调

    function defineComputed (obj, key, computeFn, updateCallback) {

        // 封装供 reactive 收集的更新回调,以触发 computed 的更新事件

        const onDependencyUpdated = function () {

            // 在此调用 computeFn 计算出的值用于触发 computed 的更新事件

            // 供后续可能的 watch 等模块使用

            const value = computeFn()

            updateCallback(value)

        }

        Object.defineProperty(obj, key, {

            get () {

                // 标记当前依赖,供 reactive 收集

                Dep = onDependencyUpdated

                // 调用求值函数,中途收集依赖

                const value = computeFn()

                // 完成求值后,清空标记

                Dep = null

                // 最终返回的 getter 结果

                return value

            },

             // 计算属性无法 set

             set () {}

        })

    }

    // 通过 getter 与 setter 定义出一个 reactive

    function defineReactive (obj, key) {

         let val = obj[key];

         // 在此标记哪些 computed 依赖了该 reactive

         const deps = []

         Object.defineProperty(obj, key, {

             // 为 reactive 求值时,收集其依赖

            get () {

                if (Dep) deps.push(Dep)

                 // 返回 val 值作为 getter 求值结果

                 return val;

             },

             // 为 reactive 赋值时,更新所有依赖它的计算属性

             set (newValue) {

                 // 在 setter 中更新值

                 val = newValue

                 // 更新值后触发所有 computed 依赖更新

                 deps.forEach(changeFn => changeFn())

                 }

            })

    }

    其实原理很简单,就是defineComputed(obj, key, computeFn, updateCallback) 在调用的时候,先把computeFn和updateCallback组成的一个新的函数赋给全局的Dep函数,因为computeFn在执行的时候回去调用依赖对象的字段的get方法。 get 方法在调用的时候,会把Dep函数作为依赖收集起来,在set的时候会把所有依赖函数调用更新。 

    相关文章

      网友评论

        本文标题:彻底理解前端依赖收集

        本文链接:https://www.haomeiwen.com/subject/zbsrdqtx.html