# Vue.js 响应式原理

# 关于 Vue.js

Vue.js 是一款 MVVM 框架,上手快速简单易用,通过响应式在修改数据的时候更新视图。Vue.js 的响应式原理依赖于Object.defineProperty,这也是 Vue.js 不支持 IE8 以及更低版本浏览器的原因。Vue 通过设定对象属性的 setter/getter 方法来监听数据的变化,通过 getter 进行依赖收集,而每个 setter 方法就是一个观察者,在数据变更的时候通知订阅者更新视图。

# 将数据 data 变成可观察(observable)的

那么 Vue 是如何将所有 data 下面的所有属性变成可观察的(observable)呢?

function observe(value, cb) {
  Object.keys(value).forEach((key) => defineReactive(value, key, value[key], cb));
}

function defineReactive(obj, key, val, cb) {
  Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    get: () => {
      /*....依赖收集等....*/

      return val;
    },
    set: (newVal) => {
      val = newVal;
      cb(); /*订阅者收到消息的回调*/
    }
  });
}

class Vue {
  constructor(options) {
    this._data = options.data;
    observe(this._data, options.render);
  }
}

let app = new Vue({
  el: '#app',
  data: {
    text: 'text',
    text2: 'text2'
  },
  render() {
    console.log('render');
  }
});
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
31
32
33
34
35
36
37

为了便于理解,首先考虑一种最简单的情况,不考虑数组等情况,代码如上所示。在initData中会调用observe这个函数将 Vue 的数据设置成 observable 的。当_data 数据发生改变的时候就会触发 set,对订阅者进行回调(在这里是 render)。

那么问题来了,需要对 app._data.text 操作才会触发 set。为了偷懒,我们需要一种方便的方法通过 app.text 直接设置就能触发 set 对视图进行重绘。那么就需要用到代理。

# 代理

我们可以在 Vue 的构造函数 constructor 中为 data 执行一个代理proxy。这样我们就把 data 上面的属性代理到了 vm 实例上。

_proxy.call(this, options.data); /*构造函数中*/

/*代理*/
function _proxy(data) {
  const that = this;
  Object.keys(data).forEach((key) => {
    Object.defineProperty(that, key, {
      configurable: true,
      enumerable: true,
      get: function proxyGetter() {
        return that._data[key];
      },
      set: function proxySetter(val) {
        that._data[key] = val;
      }
    });
  });
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

我们就可以用 app.text 代替 app._data.text 了。

Last Updated: 4/1/2024, 4:22:43 PM