Vue响应式原理解析(二)

Vue中数据响应式原理——假递归监测对象类型的所有属性

此篇是再 Vue响应式原理解析(一) 的基础之上展开

文章目录

情景准备

我们实验可以发现,单层 的obj对象里的所有属性是都可以被 defineReactive 方法通过 Object.defineProperty 监测到的,但是多层嵌套的情况下则内层无法被监测到,所以我们首先想到的就是通过递归让obj的每一层被监测到。

function defineReactive(data , key , value){
  if(arguments.length == 2){
    value = data[key]
  }
  Object.defineProperty(data,key,{
    enumerable: true, // 可以被枚举
    configurable: true, // 可以被操作 例如delete
    get(){
      console.log(`正在访问${key}`)
      return value
    },
    set(newValue){
      if(value == newValue) return
      console.log(`正在改变${key}`)
      value = newValue
    }
  })
}
let obj = {
  a:10,
  m:{
    n:20
  }
}
defineReactive(obj , 'n') // 这个写了也没用,不论你写 m.n还n 都是是都相当于重新创建了个属性,而无法对我们想要的属性进行监测
defineReactive(obj , 'a')
console.log(obj.m.n)
console.log(obj.a)

Vue响应式原理解析(二)
很明显,单层的a可以被监测到(因为输出了正在访问a),而obj.m.n则不可以(没有输出),所以要进行循环,解析出每一层实现监测。

开始思路(思路很重要,一定要先理解思路再去看代码)

既然提到了循环,并且我们也知道了可以通过 defineReactive 方法调用 Object.defineProperty 来实现监测,所以自然而然想到以下代码。

for(let key in value){
  defineReactive(value , key)
}

for-in方法并不可以把对象的所有内层遍历出来,所有我们想到的操作就是在 defineReactive方法中再次把上次传进来的一层再次进行for-in,依次实现循环,直到传入的值不再是对象类型为止,核心我们已经找到。

参考Vue的思路来写…

按照我理解的思路来写:

  1. 创建了一个 Observer类,一方面进行for-in中转,另一方面是进行优化,使用单例模式提高性能。
  2. 既然提到了单例模式,那么就得有一个单例模式的标识,参考Vue代码可以发现是通过__ob__这个属性来实现的,所以我们可以再写个方法叫def来给已经可以被监测的层级添加__ob__
  3. 所以写了一个observe方法来进行单例模式(实例化)
  4. 所以总体思路就清晰了,首先我们通过observe进行判断是否已经开启监测,如果没有开启,则进行实例化通过Observer for-in --> defineReactive对当前层级进行检测,然后defineReactive再把最新一层给observe方法,查看是否已经添加监测,直到最新一层不是对象的时候终止,依以此达到一个闭环,实现对象的所有层级实现被监测(响应式)。

思路大概就是这样,下面就不过多赘述了,直接上代码,大伙儿可以参考代码看看我的思路,方便理解记忆。

代码

Observer类(为单例模式做准备,并且进行for-in)

def方法,创建__ob__标识,并且要让他为不可被枚举的,拆分出去会让代码更加明了(不喜欢也可以直接写到Observer中)。

// 为__ob__服务,因为我们不想让__ob__成为可枚举的,所以要通过他去创建__ob__
export default function def(obj , key , value , enumerable) {
  Object.defineProperty(obj,key,{
    value,
    enumerable,
    configurable: true,
    writable: true
  })
}

Observer这里的value包括以后的value其实都是for-in遍历过后的新的一层对象

import def from './def.js'
import defineReactive from './defineReactive.js'

// observer 将一个正常的obj转换成一个每个层级的属性都是响应式的obj
export default class Observer {
  constructor(value) {
    // 这里的this代表的是Observer的实例也就是新的一层的对象(看observe),而他的实例本身则是一层一层的对象,其实就是为了确保不重复去创建Observe实例(单例模式)
    def(value , '__ob__' , this ,false)
    console.log('我是Observer类',value)
    this.walk(value)
  }
  walk(value){
    for(let key in value){
      defineReactive(value , key)
    }
  }
}

observe方法(使用单例模式,它也是所有方法的入口所在)

import Observer from './Observer.js'
// Observer 将一个正常的obj转换成一个每个层级的属性都是响应式的obj
// observe 函数是用来监测obj的每一层是否是对象
export default function observe (value){
  if(typeof value != 'object') return;
  // ob为Observer的实例
  let ob
  if(typeof value.__ob__ !== 'undefined'){
    ob = value.__ob__
  }else{
    // 也就是在Observer里添加了__ob__
    ob = new Observer(value)
  }
  return ob
}

defineReactive方法(也是主要工作的核心所在,使用了Object.defineProperty)

Observer类中的for--in里我们是只传了遍历出来的一层的上一层的对象和属性名,所以需要我们去组合成下一层的vaule。

注意:for-in里的value是代表上一层对象,而这里的value则代表下一层需要添加监测的对象,这里刚开始会比较容易弄混。

import observe from './observe.js'
/**
 *
 * @param {传进来的对象} data
 * @param {对象的属性名} key
 * @param {对象的属性值} value
 */
export default function defineReactive(data , key , value){
  if(arguments.length == 2){
    value = data[key]
  }
  // 将新的属性值再次进行observe,对他的属性进行监测,就是一层一层给检测到,直到传入的不是对象结束
  observe(value)
  Object.defineProperty(data,key,{
    enumerable: true, // 可以被枚举
    configurable: true, // 可以被操作 例如delete
    get(){
      console.log(`正在访问${key}`)
      return value
    },
    set(newValue){
      if(value == newValue) return
      console.log(`正在改变${key}`)
      value = newValue
    }
  })
}

效果

Vue响应式原理解析(二)

至此,我们想要的闭环实现了,直到遍历的最里层不再是对象为止,但是到目前为止,我们也仅仅是实现了对Object类型的数据进行监测(响应式),Array类型的数据还做不到监测(响应式),下一篇将是Array类型实现响应式。如有错误支持欢迎指出,互相交流学习。

上一篇:一、zookeeper概念


下一篇:zookeeper