likes
comments
collection
share

Vue2数据响应式原理深度解析( 二 )

作者站长头像
站长
· 阅读数 16

Vue2数据响应式原理逻辑深度解析(二)

前言

家人们好,在上一篇文章中的结尾处,我们说要对一个对象内部的所有属性都要绑定上响应式方法。这里大致分为两个区块来讲,一个是对象内部的对象,另一个是对象内部的数组,今天我们来实现第一种,即完成对象内部的对象进行响应式方法绑定。

目标

利用模块式编程,通过多个文件之间相互调用,文件内部的函数等方法嵌套循环使用来实现对象的响应式绑定

在这个系列中,有很多地方需要创建方法和文件,初学者可能一开始不知为什么这样做,等慢慢深入了解就能够把逻辑衔接起来了

实现步骤

创建index.js文件

项目文件评论区滴滴

Vue2数据响应式原理深度解析( 二 )

创建这个文件完毕后,我们首先在这个文件其内部定义一个对象obj,代码如下

// 定义一个obj对象
let obj = {
  a: {
    b: {
      c: 4
    }
  },

  m: {
    n: 9
  }
}

可以看到这个对象内部第一层有两个属性,分别是a和m,它们的嵌套较深,我们要完成对它的数据响应式绑定

下面我们要调用一个observe函数,在这个函数内部来完成对obj对象的初步操作

Vue2数据响应式原理深度解析( 二 )

由于现在没有这个函数,又要遵循模块式开发,所以我们需再创建一个文件observe.js

Vue2数据响应式原理深度解析( 二 )

创建observe.js文件

在这个文件内部我们需要对外暴露一个函数observe,其接收一个外界传递进来的参数

export default observe = function (value) {
       console.log(value)
}

这时我们在index.js文件中将其引入,这里不多截图,此时由于执行了observe函数,所以控制台会打印obj的值

Vue2数据响应式原理深度解析( 二 )

这说明我们的导入导出没有问题,函数能够正常执行

接下来的问题是关于observe函数内部的逻辑代码,observe内部到底要对这个obj做什么?

首先我们要对这个传进来的数据的类型进行判断,如果不是对象类型,直接给它return出去 ; 接着我们要判断这个对象身上有没有__ob__这个属性,有的话我们直接把这个属性赋值给新变量ob ; 没有的话我们要调用一个方法给它添加上这个__ob__属性,并赋值给新变量ob,最终返回这个ob, 如下


export default function observe  (value) {
  // 判断传递进来的value是否是对象
  if (typeof value !== 'object') return 

  声明新变量ob
  let ob;

  // 判断传递进来的value身上有没有__ob__属性
  if (typeof value.__ob__ !== 'undefined') {
    // 有的话直接赋值给ob
    ob = value.__ob__
  } else {
    // 没有的话直接new一个对象
    ob = new Observer(value)
  }
  return ob
}

可以看到当对象身上没有ob这个属性的时候,我们new了一个Observer函数,接收上面传递下来的参数,new完之后并把结果赋值给ob。此时还没有Observer函数,因此我们需要创建一个Observer文件,里面声明Observer函数

创建Observer.js文件

Vue2数据响应式原理深度解析( 二 )

在内部我们也需要对外暴露一个Observer类,接着要在observe.js中引入。注意这里是一个类,其本质还是个函数

export default class Observer{
  constructor(value) {
    def(value, '__ob__', this, false)
  }
}

可以看到在这个constructor函数中,我们需要调用def方法。在这个def方法中,我们要对这个传递进来的obj添加一个__ob__属性,值就为当前Observer类的实例ob,并且要给这个__ob__属性设置不可被枚举,如果不设置,后期会出现一些问题

创建def.js文件

由于此时我们还没有def方法,因此我们需要再创建一个文件def.js,其内部对外暴露出一个函数def

Vue2数据响应式原理深度解析( 二 )

文件内部代码:

export const def = function (obj, key, value, enumerable) {
  // 给当前对象添加value属性,值为Observer的实例
  Object.defineProperty(obj, key, {

    // 下面这个value就是Observe的实例
    value,
    enumerable,
    writable: true,
    // 是否可以被删除
    configurable: true
  })
}

可以看到在这里def的形参value接收的就是我们的obj对象,key就是属性__ob__, 值是传递进来的this,也就是Observe的实例,并设置了其遍历,可读,以及被修改的特性。这样一来obj的身上就多了一层__ob__的属性,此时我们需要在Observer.js里将这个def函数引进一下,这里不截图,打印这个value,也就是obj,查看效果

打印效果

Vue2数据响应式原理深度解析( 二 )

在这里可以看到我们成功的给obj这个对象添加了__ob__属性

完善Observer.js文件

接回到Observer.js文件中,此时我们需要调用一下walk函数,接收参数obj,这个函数的作用就是对obj对象身上的的属性进行遍历,每次遍历后把obj整体和它对应的属性名传递给即将要调用的defineReactive函数,来完成数据响应式绑定

Vue2数据响应式原理深度解析( 二 )

接着在constructor外部我们写一下这个walk函数

export default class Observer {
  constructor(value) {
    // 直接调用def方法给当前的对象添加一个__ob__
    def(value, '__ob__', this, false)
    // console.log(value)
      this.walk(value)
  }

  // 其次循环遍历,让每个属性都变成响应式数据
  walk(value) {
    for (let k in value) {
      // 打印k和value
      console.log(k)
      console.log(value)
      
      //赋予对象内部属性变成响应式的方法
      // defineReactive(value, k)
    }
  }
}

打印结果:

Vue2数据响应式原理深度解析( 二 )

可以看到打印结果正确的输出了属性k和对象obj

打印完毕后将上面注释掉的defineReactive函数打开

创建defineReactive.js文件

上面说到我们需要借助defineReactive( )方法来使得对象的每一个属性及深层的属性都变成响应式,因此我们现在需要创建一个defineReactive.js文件,其内部导出一个defineReactive函数,再在Observer文件中将其导入

Vue2数据响应式原理深度解析( 二 )

至此关于对象绑定响应式的文件大致清晰。再往下,这个defineReactive( )方法接收两个参数,分别是传递进来的对象obj和它的第一个属性k

注意此时必须得等到obj的第一个属性a执行完以后才能轮得到属性m

那在这个defineReactive函数中,我们要判断传进来参数的个数是否为2,如果为2,我们让上面的val的值就为data[ key ] ,代码如下

export default function defineReactive(data, key, val) {
  if (arguments.length === 2) {
    val = data[key]
    console.log(val)
  }
}

伪数组只能在函数内部使用,这时候val的值就是obj里属性a所对应的值,打印val的值

打印结果

Vue2数据响应式原理深度解析( 二 )

可以看到val的值显示正确

重点,难点,核心点

而由于此时这个val还是个对象,我们还得对它进行observe( ),让它的每一层属性值都添加上__ob__这个属性。这里我们做的相当于是对一个对象剥丝抽茧的过程,于是新的对象将进入observe函数,进行一次循环,再进入Observe函数,再到def... 再到这一步,再循环,如此往复,直到这个对象内的最深层只有数字或者字符时, 不满足observe函数里必须传递的是对象这个判断条件,跳出循环,再执行defineReactive函数下面的操作。

Vue2数据响应式原理深度解析( 二 )

记得在最上面引入observe.js文件,否则Observe函数在这里无法被调用

而当这一步结束之后,接着下面我们就要对当前的属性进行响应式绑定

export default function defineReactive(data, key, val) {
  if (arguments.length === 2) {
    val = data[key]
    // console.log(val)
  }

  // 再次循环,走到这一步都会循环,除非此时val不是对象或者数组,跳出循环,执行并绑定下面的响应式代码
  observe(val)

  Object.defineProperty(data, key, {
    get() {
      console.log(`您在在试图查看${key}的值`)
      // console.log('haha', val)
      return val
    },

    set(newVal) {
      console.log(`您正在视图修改${key}的值`)
      if (val === newVal) {
        return
      }
      val = newVal
      // 当设置新值时,这个新值也需要被observe
       observe(newVal)
    }

  })
}

关于这个函数的结构在第一篇中已经讲过,在此不做赘述

这里observe(newVal)理解一下,如果我们此时修改原obj对象内部原始的某一个属性,并改成一个新的对象,那这个新对象也需要被响应式绑定。

这里必须要理解的地方在于,当上面的 observe(val)结束后,并立即执行下面的Object.defineProperty方法,给当前的属性绑定响应式操作,这一步完成后,相当于前一个循环步骤完成,会返回上一次让当前key执行observe( )的步骤,并让上一次的key绑定下面的响应式方法直到最外层也绑定上,至此所有的对象和内部的属性都完成了数据响应式绑定

这时我们回到index.js页面

Vue2数据响应式原理深度解析( 二 )

执行图中的代码,来看打印结果

Vue2数据响应式原理深度解析( 二 )

打印结果输出完全正确,说明我们的代码到目前为止都能跑的通

总结

这篇文章中我们完成了关于对象的响应式数据绑定,其中有非常多的细节和难点需要消化并牢牢掌握,并需要在脑子里多过上几遍。下篇文章中我们将讲解如何为对象内部的数组及数组内部的元素绑定响应式方法,今天先到这里,谢谢大家

本节代码

由于文件数量及大小限制,暂无法存入网盘,需要的评论区滴滴