文章82
标签28
分类8

vue 响应式系统基本原理

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

在 Vue 中,数据模型下的所有属性,会被 Vue 使用Object.defineProperty(Vue3.0 使用 Proxy)进行数据劫持代理。

响应式的核心机制是观察者模式,数据是被观察的一方,一旦发生变化,通知所有观察者,这样观察者可以做出响应,比如当观察者为视图时,视图可以做出视图的更新。

Vue.js 的响应式系统以来三个重要的概念,ObserverDepWatcher.

Object.defineProperty()

我觉得先要从认识这个函数开始,何为Object.defineProperty() ? 这个方法它会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性, 并返回这个对象。

它有三个参数

  • obj: 要在其上定义的属性的对象,说白了就是你要操作对象

  • prop: 对象上的你需要修改的属性名称。

  • descriptor: 将被定义或修改的属性描述符。

例子:

let obj = {}
obj.singer = '周杰伦'

// obj: 目标对象
// prop: 需要操作的目标对象的属性名
// descriptor: 描述符
Object.defineProperty(obj, 'music', {
  // value: '刘德华',
  // writable : false,
  configurable: true,  // 可以配置对象,删除属性
  enumerable: true,     // 是否可以枚举
  // get,set设置时不能设置writable和value,它们代替了二者且是互斥的

  get () {
    return '青花瓷'
  },
  set (val) {
    console.log('视图更新');
  }
})

运行后,到浏览器的控制台中输入

obj
/* 打印
music: "青花瓷"
singer: "周杰伦"
get music: ƒ ()
set music: ƒ (val)
__proto__: Object
*/

当我们尝试更改数据,可以看到调用到了 set() 方法.

obj.music = '稻香'
/*
视图更新
"稻香"
*/

热身结束。我们开始下个阶段。

响应式系统

思路: -> 获取数据 -> 劫持数据 -> 绑定监听方法

// 调用它即代表更新视图
function cb(val) {
  console.log('视图更新了');
}

// 我们的 obj 的 key 属性在「读」的时候会触发 reactiveGetter 方法,
// 而在该属性被「写」的时候则会触发 reactiveSetter 方法。
function defineReactive(obj, key, val) {
  Object.defineProperty(obj, key, {
    enumerable: true,       /* 属性可枚举 */
    configurable: true,     /* 属性可被修改或删除 */
    get: function reactiveGetter() {
      return val;         /* 实际上会依赖收集 */
    },
    set: function reactiveSetter(newVal) {
      if (newVal === val) {
        return
      };
      cb(newVal);
    }
  })
}

Observe 扮演的角色是发布者,他的主要作用是在组件初始化的时,调用defineReactive函数,使用Object.defineProperty方法对对象的每一个子属性进行数据劫持/监听,即为每个属性添加getter和setter,将对应的属性值变成响应式。

/*
函数传入一个 value(需要「响应式」化的对象),
通过遍历所有属性的方式对该对象的每一个属性都通过 defineReactive 处理。
*/
function observer(value) {
  // 当 val 为空的时候或者 val 不等于一个对象的时候,结束这次操作
  if (!value || typeof value !== 'object') {
    return
  }
  Object.keys(value).forEach((key) => {
    defineReactive(value, key, value[key]);
  })
}
<!--模拟数据-->
constructor(options) {
    this._data = options.data;
    observer(this._data);
}

最后在页面中调用

let o = new Vue({
  data: {
    name: 'Hello Word',
    age: '18',
    hobby: 'PC Game'
  }
})