39、Vue的响应式原理(双向数据绑定的原理)

  •  Vue2.x 使用了Object.defineProperty,对 数据的所有属性添加getter和setter方法
  • Vue3.x 使用了Proxy 

vue源码解析深入vue源码,了解vue的双向数据绑定原理 

1、 Vue2.x响应式原理

所谓的双向绑定,就是view的变化能反映到ViewModel上,ViewModel的变化能同步到view上

原理:Object.defineproperty() 重新定义属性的setter方法和getter方法来实现的

          vue的数据双向绑定是通过数据劫持发布-订阅者功能来实现的

(1)原理(主要掌握这一条):

  1. 当你把一个普通的 JavaScript 对象传入 Vue 实例作为 data 选项,Vue 将遍历此对象所有的 属性,并使用 Object.defineProperty 重新定义属性的setter方法和getter方法。(Object.defineProperty 是 ES5 中的特性,这也就是 Vue 不支持 IE8 以及更低版本浏览器的原因
  2. 这些 getter/setter 对用户来说是不可见的,但是在内部它们让 Vue 能够追踪依赖在属性被访问和修改时通知变更
  3. 每个组件实例都对应一个 watcher 实例,它会在组件渲染的过程中把“接触”过的数据属性记录为依赖(getter触发,进行依赖收集)
  4. 之后当依赖项的 setter 触发时,会通知 watcher,从而使它关联的组件重新渲染re-render(即重新渲染 修改属性所关联的组件),生成虚拟DOM树

注意:

  • watcher中存放的是组件实例,每个组件实例对应一个watcher;
  • 依赖:组件渲染过程中,所接触过的数据属性。
  • 当依赖中的属性变化时,会通知watcher,重新渲染该变化属性所关联的组件
  • Watcher是订阅类

vue的数据双向绑定是通过数据劫持和发布-订阅者功能来实现的,实现步骤:

1.实现一个监听者Oberver来劫持并监听所有的属性,一旦有属性发生变化就通知订阅者

2.实现一个订阅者watcher来接受属性变化的通知并执行相应的方法,从而更新视图

3.实现一个解析器compile,可以扫描和解析每个节点的相关指令,并根据初始化模板数据以及初始化相对应的订阅者

data

(2)举例:

  1. 如下图所示,new Vue一个实例对象a,其中有一个属性a.b;
  2. 在实例化的过程中,通过Object.defineProperty()会对a.b添加getter和setter;
  3. 同时Vue.js会对模板做编译(即组件),解析生成一个指令对象(这里是v-text指令),每个指令对象(即组件实例)都会关联一个Watcher;
  4. 当对a.b求值的时候,就会触发它的getter;
  5. 当修改a.b的值的时候,就会触发它的setter,同时会通知被关联的Watcher;
  6. 然后Watcher就会通知到指令,调用指令的update()方法;
  7. 由于指令是对DOM的封装,所以就会调用DOM的原生方法去更新视图,这样就完成了数据改变到视图更新的一个自动过程;

(3)源码解析:

1)Vue在初始化时调用方法initData,该方法会拿到用户传入的data数据;

2)通过new Observer对数据进行观测,如果是数组,则改写数组的原型方法,解决不能直接利用数组索引 设置或修改 一个数组项的问题;如果数据是对象类型,在Observer中调用this.walk(value)进行对象的处理;

3)循环遍历data的所有属性,在walk中调用defineReactive对属性重新定义;

4)采用Object.defineProperty为属性添加getter和setter方法

5)对依赖中的属性执行setter方法时,通过notify方法,通知watcher,重新渲染watcher关联的组件。

总结:拦截属性的获取,进行依赖收集(组件渲染的过程中把“接触”过的数据)。拦截属性的更新操作,对相关依赖进行通知。

(4)参照代码解析

1)开发者通过选项定义普通js对象

2) 将每个成员转化为getter/setter形式

getter:访问数据时,自动执行该方法。例如:访问 obj.message 

setter:修改数据时,自动执行该方法。例如:修改 obj.message = "hahaha"

3)模板最终被编译为一个render函数

4)getter触发,进行依赖收集

5)后续message一旦被修改,就通知之前收集的依赖进行更新

2、Object.defineProperty()方法

Object.defineProperty(obj, prop, descriptor) 有三个参数,分别为

  • obj :要定义属性的对象,如obj
  • prop :要定义或修改的属性,如 obj中的mesg属性
  • descriptor :具体的改变方法,如getter和setter方法

data中有多个属性,使用forEach进行遍历

 3、 vue3.x为什么要重写响应式(即vue2.x存在缺陷)?

(1)vue2.x 中obj.defineproperty的问题:

   1)针对 对象obj中的每一个属性都调用了defineReactive,为每个属性重写getter和setter方法,影响性能。

   2)单独对数组进行了处理,不能直接利用数组索引 设置或修改 一个数组项,即不能使arr[已有元素的数组下标] = val 变成响应式

例如:通过数组下标进行修改数据项时,vue2.x 设置失败而且不报错, 在vue3.x 中设置成功。如下所示,代码一模一样:

 

4、 vue3.x 使用proxy:避免了循环,解决了数组问题

  • 当我们从一个组件的 data 函数中返回一个普通的 JavaScript 对象时,Vue 会将该对象包裹在一个带有 get 和 set 处理程序的 Proxy 中
  • Proxy对 对象中的所有元素进行监听和劫持
  • Proxy 是 ES6 仅有的特性,因此在Vue3.x版本中也使用了Object.defineProperty来支持IE浏览器。

(1)模拟vue3.x的proxy

访问和修改数组,都能成功操作

5、Vue 3.0 Composition API

(1)vue3中的ref(点击按钮时,实现nu++

(2)vue3中的reactive

补充:

dep 实例在执行getter时,收集该 key 所有 watcher 实例;watcher 实例用来监听某个 key ,如果该 key 产生变化,便会执行 watcher 实例自身的回调 

export class Dep {
  constructor() {
    this.subs = [];
  }
  // 将 watcher 实例置入队列
  addSub(sub) {
    this.subs.push(sub);
  }
  // 通知队列里的所有 watcher 实例,告知该 key 的 对应的 val 被改变
  notify() {
    this.subs.forEach((sub, index, arr) => sub.update());
  }
}

// Dep 类的的某个静态属性,用于指向某个特定的 watcher 实例.
Dep.target = null
observer.js

import {Dep} from './dep'
function makeItReactive(obj, key, val) {
 var dep = new Dep()
Object.defineProperty(obj, key, {
  enumerable: true,
  configurable: true,
  get: () => {
    // 收集依赖! 如果该 key 被某个 watcher 实例依赖,则将该 watcher 实例置入该 key 对应的 dep 实例里
    if(Dep.target){
      dep.addSub(Dep.target)
    }
    return val
  },
  set: (newVal) => {
    if (newVal === val) {
      return;
    }
    else if (isObject(newVal)) {
      new Observer(newVal);
      val = newVal;
    // 通知 dep 实例, 该 key 被 set,让 dep 实例向所有收集到的该 key 的 watcher 实例发送通知
    dep.notify()
    }
    else if (!isObject(newVal)) {
      val = newVal;
    // 通知 dep 实例, 该 key 被 set,让 dep 实例向所有收集到的该 key 的 watcher 发送通知
    dep.notify()
    }
  }
})
     }    

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值