Vue响应式系统的基本原理

响应式系统

Vue.js 是一款 MVVM 框架,数据模型仅仅是普通的 JavaScript 对象,但是对这些对象进行操作时,却能影响对应视图,它的核心实现就是「响应式系统」。

Object.defineProperty

使用方法:

1
2
3
4
5
6
7
8
/*
obj: 目标对象
prop: 需要操作的目标对象的属性名
descriptor: 描述符

return value 传入对象
*/
Object.defineProperty(obj, prop, descriptor)...

descriptor的一些属性,简单介绍几个属性,具体可以参考 MDN 文档

  • enumerable,属性是否可枚举,默认false
  • configurable,属性是否可以被修改或者删除,默认false
  • get,获取属性的方法
  • set,设置属性的方法

实现observer(可观察的)

在 new Vue() 之后。 Vue 会调用 _init 函数进行初始化,也就是这里的 init 过程(流程图的init),它会初始化生命周期、事件、 props、 methods、 data、 computed 与 watch 等。其中最重要的是通过 Object.defineProperty 设置 setter 与 getter 函数,用来实现「响应式」以及「依赖收集」…

img

为了便于理解,我们不考虑数组等复杂的情况,只对对象进行处理。

  1. 首先我们定义一个 cb 函数,这个函数用来模拟视图更新,调用它即代表更新视图,内部可以是一些更新视图的方法。
1
2
3
4
5
// 第一步
function cb (val) {
/* 渲染视图 */
console.log("视图更新啦~");
}

vue的双向数据绑定

  1. 定义一个 defineReactive ,这个方法通过 Object.defineProperty 来实现对对象的「响应式」化,入参是一个 obj(需要绑定的对象)、key(obj的某一个属性),val(具体的值)。经过 defineReactive 处理以后,我们的 obj 的 key 属性在「读」的时候会触发 reactiveGetter 方法,而在该属性被「写」的时候则会触发 reactiveSetter 方法。
  2. 当然这是不够的,我们需要在上面再封装一层 observer 。这个函数传入一个 value(需要「响应式」化的对象),通过遍历所有属性的方式对该对象的每一个属性都通过 defineReactive 处理。(注:实际上 observer 会进行递归调用,为了便于理解去掉了递归的过程)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
var data = {name: 'kindeng'};
observe(data);
data.name = 'dmq'; // 哈哈哈,监听到值变化了 kindeng --> dmq

// 第二步
function defineReactive(data, key, val) {
observe(val); // 监听子属性
Object.defineProperty(data, key, {
enumerable: true, // 可枚举
configurable: false, // 不能再define
get: function() {
return val;
},
set: function(newVal) {
console.log('哈哈哈,监听到值变化了 ', val, ' --> ', newVal);
val = newVal;
}
});
}

// 第三步
function observe(data) {
if (!data || typeof data !== 'object') {
return;
}
// 取出所有属性遍历
Object.keys(data).forEach(function(key) {
defineReactive(data, key, data[key]);
});
};

应用

  1. observer 来封装一个 Vue

    1
    2
    3
    4
    5
    6
    7
    8
    // 第四步
    class Vue {
    /* Vue构造类 */
    constructor(options) {
    this._data = options.data;
    observer(this._data);
    }
    }

    这样我们只要 new 一个 Vue 对象,就会将 data 中的数据进行「响应式」化。如果我们对 data 的属性进行下面的操作,就会触发 cb 方法更新视图。

    1
    2
    3
    4
    5
    6
    let o = new Vue({
    data: {
    test: "I am test."
    }
    });
    o._data.test = "hello,world."; /* 哈哈哈,监听到值变化了,I am test. --> hello,world. */

    Over