vue源码 - 响应式原理

129 阅读3分钟

简介:本文将深入探讨Vue.js的响应式原理,通过解析给定的代码,我们将学习到Vue.js是如何利用观察者模式和依赖收集实现数据的双向绑定的。

话不多说,上代码!

<!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>
        let depId = 0
        let watcherId = 0

        const vm = {
            _data: {
                msg: "哈哈哈哈哈哈哈",
            }
        };

        function render() {
            document.body.innerHTML = vm.msg
        }

        function defineReactive(target, key, val) {
            const dep = new Dep();
            Object.defineProperty(target, key, {
                get() {
                    if (Dep.target) {
                        dep.depend(Dep.target);
                    }
                    return vm._data[key];
                },
                set(newVal) {
                    vm._data[key] = newVal;
                    dep.notify();
                },
            });
        }

        class Dep {
            constructor() {
                this.subs = [];
                this.id = depId++
            }
            depend() {
                Dep.target.addDep(this)
            }
            notify() {
                this.subs.forEach((watcher) => watcher.update());
            }
            addWatcher(watcher) {
                this.subs.push(watcher)
            }
        }

        Dep.target = null;

        class Watcher {
            constructor(vm, effectFn) {
                this.getter = effectFn;
                this.vm = vm;
                this.id = watcherId++
                this.depsId = new Set()
                this.deps = [] 
                this.get();
            }
            get() {
                Dep.target = this;
                this.getter();
                Dep.target = null;
            }
            update() {
                this.get();
                console.log("值改变了-触发了更新");
            }
            addDep(dep) {
                if (!this.depsId.has(dep.id)) {
                    this.depsId.add(dep.id)
                    this.deps.push(dep)
                    dep.addWatcher(this)
                }
            }
        }

        defineReactive(vm, "msg");

        new Watcher(vm, render);

        setTimeout(() => {
            vm.msg = "嘿嘿嘿嘿嘿嘿嘿嘿";
        }, 3000)


    </script>
</body>

</html>

1. 响应式的概念和目标

在Vue.js中,响应式是指当数据发生变化时,相关的界面会自动更新,以保持数据和界面的同步。这种响应式的特性让开发者能够以声明式的方式编写代码,而无需手动处理DOM的更新。

2. 观察者模式

观察者模式是一种常见的设计模式,用于在对象之间建立一种一对多的依赖关系。在给定的代码中,我们可以看到以下几个关键角色:

  • Dep(依赖):它是一个观察者模式中的主题,负责管理所有的观察者(Watcher)对象,并在数据发生变化时通知它们。
  • Watcher(观察者):它是一个观察者模式中的观察者,负责订阅(依赖)数据的变化,并在变化发生时执行相应的更新操作。
  • defineReactive():这个函数用于定义数据的响应式,它利用了Object.defineProperty()方法来劫持数据的读取和修改操作。

3. defineReactive函数

defineReactive函数是实现数据响应式的核心。它接收三个参数:目标对象(target)、属性名(key)和初始值(val)。在函数内部,它创建了一个Dep实例,用于管理与该属性相关的所有观察者。

通过Object.defineProperty()方法,我们定义了属性的getter和setter函数。getter函数用于收集依赖(即将观察者添加到依赖中),而setter函数在属性值发生变化时,更新该属性的值,并通知所有依赖进行更新。

4. Watcher类

Watcher类表示一个观察者对象,它负责订阅依赖的变化,并在变化发生时执行相应的更新操作。在构造函数中,它接收两个参数:要观察的Vue实例(vm)和一个用于更新界面的回调函数(effectFn)。

Watcher对象在构造时会调用自身的get()方法,这个方法的作用是将当前Watcher实例赋值给Dep.target,并立即调用回调函数。这样做的目的是触发响应式数据的读取操作,从而将Watcher添加到对应的依赖中。

当数据发生变化时,Dep会调用每个依赖(Watcher)的update()方法,而update()方法内部又会调用Watcher的get()方法。这样,Watcher会重新执行一次回调函数,从而实现界面的更新。

5. 依赖收集

依赖收集是指在读取响应式数据时,收集当前正在读取数据的Watcher对象。这样做的目的是建立数据与Watcher之间的关系,当数据发生变化时,能够通知到相应的Watcher进行更新。

在给定的代码中,当定义一个响应式属性时,会为该属性创建一个Dep实例,并在getter函数中通过Dep.target判断是否存在当前的Watcher对象。如果存在,则将当前Watcher添加到Dep的依赖列表中。

而在Watcher的构造函数中,它在调用get()方法时会将自身赋值给Dep.target,这样在读取响应式数据时,Dep就能够收集到该Watcher对象。

6. 总结

Vue.js利用了观察者模式和依赖收集来实现数据的双向绑定。当数据发生变化时,依赖收集会通知相关的观察者(Watcher)进行更新,而观察者则负责执行相应的更新操作,以保持数据和界面的同步。