Vue响应式原理(简单理解)

15 篇文章 0 订阅

先把这张图供起来

在这里插入图片描述

0. 什么是响应式

官网的解释(可以挑着读一下)

当你把一个普通的 JavaScript 对象传入 Vue 实例作为 data 选项,Vue 将遍历此对象所有的 property,并使用 Object.defineProperty 把这些 property 全部转为 getter/setter。Object.defineProperty 是 ES5 中一个无法 shim 的特性,这也就是 Vue 不支持 IE8 以及更低版本浏览器的原因。

这些 getter/setter 对用户来说是不可见的,但是在内部它们让 Vue 能够追踪依赖,在 property 被访问和修改时通知变更。这里需要注意的是不同浏览器在控制台打印数据对象时对 getter/setter 的格式化并不同,所以建议安装 vue-devtools 来获取对检查数据更加友好的用户界面。

每个组件实例都对应一个 watcher 实例,它会在组件渲染的过程中把“接触”过的数据 property 记录为依赖。之后当依赖项的 setter 触发时,会通知 watcher,从而使它关联的组件重新渲染

注:

  1. shim可以将新的API引入到旧的环境中,而且仅靠就环境中已有的手段实现。

1. 实现响应式的关键所在

  1. Object.defineProperty
  2. 订阅者设计模式

2. Object.defineProperty() 方法

Object.defineProperty() 方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象。

语法

	Object.defineProperty(obj, prop, descriptor)

参数

  1. obj
    要定义属性的对象。
  2. prop
    要定义或修改的属性的名称或 Symbol 。
  3. descriptor
    要定义或修改的属性描述符。(对象)

返回值

被传递给函数的对象。

关键之处就在传递的 descriptor 参数里面的 set 和 get 方法

我们结合栗子来说

首先我们先定义一个对象,来模拟 vue 中的 data

    var obj = {              
      message: '哈哈',
      name: 'perry'     
    };

然后循环遍历里面的键,给 obj 里面的每一个属性调用 defineProperty 方法

    Object.keys(obj).forEach(key => {
      let value = obj[key]
      Object.defineProperty(obj, key, {
        set(newValue) { // 当改变obj中的的属性的时候调用set方法(关键)
          console.log("set");
          value = newValue
        },
        get() {// 当获取obj中的的属性的时候调用get方法(关键)
          console.log("get");
          return value
        }
      })
    })

是不是有想法了,没错,我们可以在 set 和 get 函数里面大展手脚了

3. 发布-订阅模式

发布—订阅模式,它定义了对象间的一种 一对多 的关系,让多个观察者对象同时监听某一个主题对象,当主题对象发生改变时,所有依赖于它的对象都将得到通知。(这句话好好理解!!!)

看到一个例子帮助理解

你在微博上关注了A,同时其他很多人也关注了A,那么当A发布动态的时候,微博就会为你们推送这条动态。A就是发布者,你是订阅者,微博就是调度中心,你和A是没有直接的消息往来的,全是通过微博来协调的
(你的关注(订阅),A的发布动态(发布))。

直接来看一下它简单的模型:

	// 发布者
    class Dep {
      constructor() {
        this.Subscription = [] // 用来记录所有的订阅者
      }
      addSub(watcher) { // 加入订阅者
        this.Subscription.push(watcher)
      }
      notify() { // 通知每一个订阅者更新
        this.Subscription.forEach(item => {
          item.update();
        })
      }
    }
    
    // 订阅者,也叫观察者
    class Watcher {
      constructor(name) {
        this.name = name;
      }
      update() {
        console.log(this.name + "发生update");
      }
    }

4. 两者相结合

  1. 我们在 set 中调用发布者的 notify ,即只要 obj 改变那么通知所有的订阅者更新
    // 1. 遍历obj中的所有的键
    Object.keys(obj).forEach(key => {
      let value = obj[key]
        // 2. 通过defineProperty方法监听改变
      Object.defineProperty(obj, key, {        
        set(newValue) {
          // 当改变obj中的的属性的时候在这里
          value = newValue        
          dep.notify(); // 然后在这个地方通知相应dep(需要做判断)的订阅者发生改变,不同的值改变需要不同的dep发送通知(关键)
          console.log('监听' + key + '改变' + ':' + value)
        },
        get() {    
          // 当获取obj中的的属性的时候在这里              
          console.log('获取' + key + '对应的值')
          return value
        }
      })
    })
  1. 然后我们创建发布者对象,并向里面添加订阅者

实例一个dep对象可以理解为当我们在data里面添加数据的时候需要做的事情
实例一个watcher对象并调用dep.addSub是在我们使用data数据的时候需要做的事情

    // 实例一个dep对象,模拟一个添加了一个data中的数据
    const name = new Dep();

    const w1 = new Watcher('张三'); // 张三用了它
    name.addSub(w1) // 张三就成为订阅者,被放到了subscription的数组里面

    const w2 = new Watcher('李四'); //李四用了它
    name.addSub(w2) // 李四就成为订阅者,被放到了subscription的数组里面
  1. 之后我们如果改变 obj 的属性则通知所有的订阅者
    在这里插入图片描述
    思路清晰了吗?
    不清晰我们再来看一下完整的代码,只有两个类和一个关键逻辑而已
    var obj = {              
      message: '哈哈',
      name: 'why'     
    };
    Object.keys(obj).forEach(key => {
      let value = obj[key]
      Object.defineProperty(obj, key, {        
        set(newValue) {
          value = newValue        
          name.notify(); // 然后在这个地方通知相应dep订阅者发生改变
        },
        get() {    
          return value
        }
      })
    })
    // 发布者
    class Dep {
      constructor() {
        this.Subscription = [] // 用来记录所有的订阅者
      }
      addSub(watcher) { // 加入订阅者
        this.Subscription.push(watcher)
      }
      notify() { // 通知每一个订阅者更新
        this.Subscription.forEach(item => {
          item.update();
        })
      }
    }
    // 订阅者,也叫观察者
    class Watcher {
      constructor(name) {
        this.name = name;
      }
      update() {
        console.log(this.name + "发生update");
      }
    }
    
    // 实例一个dep对象
    const name= new Dep();

    const w1 = new Watcher('张三');
    name.addSub(w1) // 张三就成为订阅者,被放到了subscription的数组里面

    const w2 = new Watcher('李四');
    name.addSub(w2) // 李四就成为订阅者,被放到了subscription的数组里面

参考

  1. MDN
    https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty
  2. vue官方文档
    https://cn.vuejs.org/v2/guide/reactivity.html
  3. codeWhy老师的笔记
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值