vue 响应式原理

Vue 采用声明式编程替代过去的类 Jquery 的命令式编程,并且能够侦测数据的变化,更新视图。这使得我们可以只关注数据本身,而不用手动处理数据到视图的渲染,避免了繁琐的 DOM 操作,提高了开发效率。不过理解其工作原理同样重要,这样可以回避一些常见的问题,下面我们来介绍一下 Vue 是如何侦测数据并响应视图的。

Object.defineProperty

Vue 数据响应核心就是使用了 Object.defineProperty 方法( IE9 + ) 。

var obj = {};
Object.defineProperty(obj, ‘msg‘, {
  get () {
    console.log(‘get‘);
  },
  set (newVal) {
    console.log(‘set‘, newVal);
  }
});
obj.msg // get
obj.msg = ‘hello world‘ // set hello world

取 obj 对象中 msg 的值时会调用 get 方法,给 msg 赋值时会调用 set 方法,并接收新值作为其参数。

这里提一句,在 Vue 中我们调用数据是直接 this.xxx ,而数据其实是 this.data.xxx,原来 Vue 在初始化数据的时候会遍历 data 并代理这些数据。

Object.keys(this.data).forEach((key) => {
    this.proxyKeys(key);
});

proxyKeys (key) {
    Object.defineProperty(this, key, {
        enumerable: false,
        configurable: true,
        get() {
            return this.data[key];
        },
        set(newVal) {
            this.data[key] = newVal;
        }
    });
}

上面可以看到,取 this.key 的值其实是取 this.data.key 的值,赋值同理。

现在,我们已经知道如何去检测数据的变化,并且做出一些响应了。

观察者模式 ( 发布者-订阅者模式 )

vue 的响应式系统依赖于三个重要的类:Dep 类、Watcher 类、Observer 类。

Dep 类作为发布者的角色,Watcher 类作为订阅者的角色,Observer 类则是连接发布者和订阅者的纽带,决定订阅和发布的时机。

我们先看下面的代码,来对发布者和订阅者有个初步的了解。

class Dep {
    constructor() {
        this.subs = [];
    }

    addSub(watcher) {
        this.subs.push(watcher);
    }

    notify() {
        this.subs.forEach(watcher => {
            watcher.update();
        });
    }
}

class Watcher {
    constructor() {
    }

    update() {
        // 接收通知后的处理方法
    }
}

const dep = new Dep(); // 发布者 dep
const watcher1 = new Watcher(); // 订阅者1 watcher1
const watcher2 = new Watcher(); // 订阅者2 watcher2
dep.addSub(watcher1); // watcher1 订阅 dep
dep.addSub(watcher2); // watcher2 订阅 dep
dep.notify(); // dep 发送通知

上面我们定义了一个发布者 dep,两个订阅者 watcher1、watcher2。让 watcher1、watcher2 都订阅 dep,当 dep 发送通知时,watcher1、watcher2 都能做出各自的响应。

现在我们已经了解了发布者和订阅者的关系,那么剩下的就是订阅和发布的时机。什么时候订阅?什么时候发布?想到上面提到的 Object.defineProperty ,想必你已经有了答案。

我们来看 Observer 类的实现:

class Observer {
    constructor(data) {
        this.data = data;
        this.walk();
    }

    walk() {
        Object.keys(this.data).forEach(key => {
            this.defineReactive(this.data, key, this.data[key]);
        });
    }

    defineReactive(data, key, value) {
        const dep = new Dep();

        if ( value && typeof value === ‘object‘ ) {
            new Observer(value);
        }

        Object.defineProperty(data, key, {
            enumerable: true,
            configurable: true,
            get() {
                if (Dep.target) {
                    dep.addSub(Dep.target); // 订阅者订阅 Dep.target 即当前 Watcher 类的实例(订阅者)
                }
                return value;
            },
            set(newVal) {
                if (newVal === value) {
                    return false;
                }
                value = newVal;
                dep.notify(); // 发布者发送通知
            }
        });
    }
}

在 Observer 类中,为 data 的每个属性都实例化一个 Dep 类,即发布者。并且在取值时让订阅者(有多个,因为 data 中的每个属性都可以被应用在多个地方)订阅,在赋值时发布者发布通知,让订阅者做出各自的响应。

这里需要提的是 Dep.target,这其实是 Watcher 类的实例,我们可以看看 Watcher 的详细代码:

class Watcher {
    constructor(vm, exp, cb) {
        this.vm = vm;
        this.exp = exp; // data 属性名
        this.cb = cb; // 回调函数

        // 将自己添加到订阅器
        this.value = this.getValue();
    }

    update() {
        const value = this.vm.data[this.exp];
        const oldValue = this.value;
        if (value !== oldValue) {
            this.value = value;
            this.cb.call(this.vm, value, oldValue); // 执行回调函数
        }
    }

    getValue() {
        Dep.target = this; // 将自己赋值给 Dep.target
        const value = this.vm.data[this.exp]; // 取值操作触发订阅者订阅
        Dep.target = null;
        return value;
    }
}

Watcher 类在构造函数中执行了一个 getValue 方法,将自己赋值给 Dep.target ,并且执行了取值操作,这样就成功的完成了订阅操作。一旦数据发生变化,即有了赋值操作,发布者就会发送通知,订阅者就会执行自己的 update 方法来响应这次数据变化。

数据的双向绑定

数据的双向绑定即数据和视图之间的同步,视图随着数据变化而变化,反之亦然。我们知道 Vue 是支持数据的双向绑定的,主要应用于表单,是通过 v-model 指令来实现的。而通过上面介绍的知识我们是可以知道如何实现视图随着数据变化的,那么如何让数据也随着视图变化而变化呢?其实也很简单,只要给有 v-model 指令的节点监听相应的事件即可,在事件回调中来改变相应的数据。这一切都 Compile 类中完成,假设有一个 input 标签应用了 v-model 指令,在开始编译模板时,遇到 v-model 指令时会执行:更新 dom 节点的值,订阅者订阅,事件监听。

compileModel (node, vm, exp) {
    let val = vm[exp];

    // 更新内容
    this.modelUpdater(node, val);

    // 添加订阅
    new Watcher(vm, exp, (value) => {
        // 数据改变时的回调函数
        this.modelUpdater(node, value);
    });

    // 事件监听
    node.addEventListener(‘input‘, (e) => {
        const newValue = e.target.value;
        if (val === newValue) {
            return false;
        }
        vm[exp] = newValue;
        val = newValue;
    });
}

当我们在文本框中输入数据时,会给原有 data 中的某个属性 a 赋值,这时候会触发发布者发起通知,那么所有属性 a 的订阅者都能够同步到最新的数据。

最后,附上一个小 demo

时间: 2024-11-08 23:38:05

vue 响应式原理的相关文章

深度解析 Vue 响应式原理

深度解析 Vue 响应式原理 该文章内容节选自团队的开源项目 InterviewMap.项目目前内容包含了 JS.网络.浏览器相关.性能优化.安全.框架.Git.数据结构.算法等内容,无论是基础还是进阶,亦或是源码解读,你都能在本图谱中得到满意的答案,希望这个面试图谱能够帮助到大家更好的准备面试. Vue 初始化 在 Vue 的初始化中,会先对 props 和 data 进行初始化 Vue.prototype._init = function(options?: Object) { // ...

深入Vue响应式原理

深入Vue.js响应式原理 一.创建一个Vue应用 new Vue({ data() { return { name: 'yjh', }; }, router, store, render: h => h(App), }).$mount('#app'); 二.实例化一个Vue应用到底发生了什么? this._init() callHook(vm, 'beforeCreate') observe(vm._data) vm._data = vm.$options.data() proxy(vm, _

vue响应式原理解析

# Vue响应式原理解析 首先定义了四个核心的js文件 - 1. observer.js 观察者函数,用来设置data的get和set函数,并且把watcher存放在dep中 - 2. watcher.js 监听者函数,用来设置dep.target开启依赖收集的条件,和触发视图的更新函数 - 3. compile.js 编译者函数,用来编译模版和实例化 watcher 函数 - 4. index.js 入口文件 注意dep函数就是一个壳子,用来存放watcher和触发watcher更新的 首先从

Vue响应式原理

前面的话 Vue最显著的特性之一便是不太引人注意的响应式系统(reactivity system).模型层(model)只是普通JS对象,修改它则更新视图(view).这会让状态管理变得非常简单且直观,不过理解它的工作原理以避免一些常见的问题也是很重要的本文将详细介绍Vue响应式系统的底层细节 追踪变化 把一个普通JS对象传给Vue实例的data选项,Vue将遍历此对象所有的属性,并使用Object.defineProperty把这些属性全部转为getter/setter.Object.defi

深入探讨vue响应式原理

现在是时候深入一下了!Vue 最独特的特性之一,是其非侵入性的响应式系统.数据模型仅仅是普通的 JavaScript 对象.而当你修改它们时,视图会进行更新.这使得状态管理非常简单直接,不过理解其工作原理同样重要,这样你可以避开一些常见的问题.在这个章节,我们将研究一下 Vue 响应式系统的底层的细节. 当你把一个普通的 JavaScript 对象传入 Vue 实例作为 data 选项,Vue 将遍历此对象所有的属性,并使用 Object.defineProperty 把这些属性全部转为 get

关于Vue响应式原理

Vue最独特的特性之一,是其非侵入性的响应式系统.数据模型仅仅是普通的javascript对象.而当你修改他们时,视图会进行更新.这使得状态管理非常简单直接,不过理解其工作原理同样重要. 当你把一个普通的javascript对象传入Vue实例作为data选项,Vue将遍历此对象所有属性,并使用Object.defineProperty 把这些属性全部转为getter/setter.Object.defineProperty 是es5中一个无法shim的特性,这也就是Vue不支持IE8以及更低版本

Vue响应式原理深入解析

Vue最明显的特性之一便是响应式系统,其数据模型即是普通的 JavaScript 对象.而当你读取或写入它们时,视图便会进行响应操作. 响应式data: <div id = "exp">{{ message }}</div> const vm = new Vue({ el: '#exp', data: { message: 'This is A' } }) vm.message = 'This is B' // 响应式 vm._message = 'This i

vue响应式原理整理

vue是数据响应性,这是很酷的一个地方.本文只为理清逻辑.详细请看官方文档 https://cn.vuejs.org/v2/guide/reactivity.html vue的data在处理数据时候,会遍历data内对象的所有属性,并使用Object.defineProperty将属性转为getter/setter. 这里的getter/setter对用户是不可见的,但是方便vue对数据进行内部跟踪,来维护数据. 用Object.defineProperty这是一个ES5无法支持特性,所有vue

浅析vue响应式原理

图很清晰 当我们把一个普通的 JavaScript 对象传给 Vue 实例的 data 选项,Vue 将遍历此对象所有的属性,并使用 Object.defineProperty 把这些属性全部转为 getter/setter.这些 getter/setter 对用户来说是不可见的,但是在内部它们让 Vue 追踪依赖,在属性被访问和修改时通知变化.每个组件实例都有相应的 watcher 实例对象,它会在组件渲染的过程中把属性记录为依赖,之后当依赖项的 setter 被调用时,会通知 watcher