vue响应式原理(代码+文字过程详述)

58 阅读2分钟

下面描述一下响应式原理过程,首先创建一个obj对象,对象中添加一系列属性数据。使用observe函数封装。observe函数充当数据监听劫持的第一个关卡,判断data数据是否是一个‘object’,如果不是直接返回。如果是object的话,就调用new一个实例Observer对象。自动调用walk函数进行遍历data中的key,每次遍历调用defineReactive函数,该函数是响应式中核心代码,负责对每个属性的数据劫持行为。举一个例子,文中obj.a中的a作为key被劫持到,此时在defineReactive中会先new一个Dep的实例对象,Dep作为数据依赖者管理对象在new后,创建了一个subs数组,在对应的Dep实例对象中。当我们作为一个使用者去调用obj对象中的a前需要创建一个Watcher实例对象作为数据依赖对象,并为构造器中的属性赋值。同时赋值结束调用this.get()方法拿到当前这个调用属性的属性值,赋值给value。在get方法中同时也将Watcher实例对象放入一个数组中,同时将实例对象赋值给Dep.target属性保存。当使用者真正调用obj.a时,将会触发obj.a的getter函数,触发其中的dep.depend函数去添加当前新的Dep.target 中的值当subs数组中。同理若又有一个地方调用了obj.a,此时dep.depend也会被触发添加新依赖对象Watcher,此时obj.a的dep对象中的subs中就有了两个watcher对象。当监听的obj.a发生数据更新,obj.a的setter函数被触发,此时对新值做遍历劫持并且对调用了dep.notify函数触发Watcher实例对象,循环调用watcher的更新方法重新回去当前data中的obj.a的newValue,同时调用call执行函数回调。这样我们的所有obj.a的都可以实现响应式啦!

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>

    <script>
        // 调用该方法来检测数据
        function observe(data) {
            if (typeof data !== 'object') return
            new Observer(data)
        }

        class Observer {
            constructor(value) {
                this.value = value
                this.walk()
            }
            walk() {
                Object.keys(this.value).forEach((key) => defineReactive(this.value, key))
            }
        }

        // 数据拦截
        function defineReactive(data, key, value = data[key]) {
            const dep = new Dep()
            console.log(`为${key}添加dep管理`);
            observe(value)
            Object.defineProperty(data, key, {
                get: function reactiveGetter() {
                    dep.depend()
                    return value
                },
                set: function reactiveSetter(newValue) {
                    if (newValue === value) return
                    value = newValue
                    observe(newValue)
                    dep.notify()
                }
            })
        }

        // 依赖
        class Dep {
            constructor() {
                this.subs = []
            }

            depend() {
                if (Dep.target) {
                    console.log('添加wather',Dep.target);
                    this.addSub(Dep.target)
                }
            }

            notify() {
                const subs = [...this.subs]
                subs.forEach((s) => s.update())
            }

            addSub(sub) {
                this.subs.push(sub)
            }
        }

        Dep.target = null

        const TargetStack = []

        function pushTarget(_target) {
            TargetStack.push(Dep.target)
            Dep.target = _target
            console.log(11, TargetStack);
        }

        function popTarget() {
            Dep.target = TargetStack.pop()
        }

        // watcher
        class Watcher {
            constructor(data, expression, cb) {
                this.data = data
                this.expression = expression
                this.cb = cb
                this.value = this.get()
            }

            get() {
                pushTarget(this)
                const value = parsePath(this.data, this.expression)
                console.log(value);
                popTarget()
                return value
            }

            update() {
                const oldValue = this.value
                this.value = parsePath(this.data, this.expression)
                this.cb.call(this.data, this.value, oldValue)
            }
        }

        // 工具函数
        function parsePath(obj, expression) {
            const segments = expression.split('.')
            for (let key of segments) {
                if (!obj) return
                obj = obj[key]
            }
            return obj
        }

        // for test
        let obj = {
            a: 1,
            b: {
                m: {
                    n: 4
                }
            }
        }

        observe(obj)
        //创建一个Watcher作为订阅者
        let w1 = new Watcher(obj, 'b.m.n', (val, oldVal) => {
            console.log(`obj.a 从 ${oldVal}(oldVal) 变成了 ${val}(newVal)`)
        })
        
        // obj.b.m.n = 23


    </script>
</body>

</html>