前言

置信大家对于 Vue 的数据响应式原理并不生疏,vue2 中利用 Object.defineProperty() 实现变更检测,而 Vue3 则利用了 ES6 提供的 ProxyAPI 来取代了之前的 defineProperty 来实现这一性能。既然晓得其响应式原理,那么咱们该怎么实现一个数据拦挡办法呢?接下来,让咱们一步步来实现一个本人的 数据拦挡库吧!

基本概念

MVVM 框架

MVVM是 Model-View-ViewModel 的简写。它实质上就是MVC 的改进版。MVVM 就是将其中的 View 的状态和行为抽象化,让咱们将视图 UI 和业务逻辑离开。当然这些事 ViewModel 曾经帮咱们做了,它能够取出 Model 的数据同时帮忙解决 View 中因为须要展现内容而波及的业务逻辑。

MVVM 框架的三个因素:数据响应式、模版引擎及其渲染

  • 数据响应式:监听数据变动并在试图中更新(数据变更可能响应在视图中,就是数据响应式)

    - Vue 2.x 版本: Object.defineProperty()- Vue 3.x 版本:Proxy
  • 模版引擎:提供形容视图的模版语法

    - 插值:{{}}- 指令:v-bind, v-on, v-model, v-for, v-if...
  • 渲染:如何将模版转换为 html

    - 模版 => vnode => dom

实现数据侦测

1. 根底办法定义

// reactive.jsfunction defineReactive (obj, key, val) {  Object.defineProperty(obj, key, {    get() {      // 每次取值时输入日志,不便调试      console.log(`some data was get --- key: ${key}, val: ${val}`)      return val    },    set(newVal) {      if(newVal !== val) {        // 每次赋值时输入日志,不便调试          console.log(`new data was set --- key: ${key}, val: ${newVal}`)        val = newVal      }    }  })}

当初咱们根本实现了一个最原始的数据拦挡函数,无妨来测试一下

// reactive.jslet test = {}defineReactive(test, 'foo', 'firstblood')// 取 foo 的值test.foo// 设置 foo 的值test.foo = 'foo'

下面咱们定义了一个对象 test,并用之前已写好的 defineReactive 函数对其进行加工后尝试取 foo 的值。

此时当咱们运行 node reactive.js 后控制台输入后果

表明咱们对 test 这个对象的取值和赋值曾经胜利拦挡!

尽管目前这个繁难版本的 defineReactive 曾经根本实现了对象拦挡的操作,但仍有许多有余,譬如:

  • 须要咱们手动解决对象的每一个属性(key)

    defineReactive(test, 'foo', 'foo')defineReactive(test, 'bar', 'bar')defineReactive(test, 'baz', 'baz')
  • 当对象属性也是一个对象的时候,无奈持续检测对象属性的属性

    let test = {  foo: {    id: 1,    name: 'foo',  }}defineReactive(test, 'foo', {name: 'newFoo'})test.foo.id// node 执行以后文件后输入 'some data was get --- key: foo, val: [object Object]'// 阐明只有 foo 属性胜利被检测,而 foo 的 id 属性无奈被检测
  • 赋值为对象时,也无奈持续检测

    let test = {}defineReactive(test, 'foo', {name: 'newFoo'})test.foo.name// node 执行以后文件后输入 'some data was get --- key: foo, val: [object Object]'// 阐明只有 foo 属性胜利被检测,而 foo 的 name 属性无奈被检测
  • 如果对象增加/删除了新属性无奈检测

    let test = {}defineReactive(test, 'foo', 'firstblood')// foo 取值test.foo// node 执行后输入 'some data was get --- key: foo, val: firstblood'test.bar// node 执行后仅仅输入 'some data was get --- key: foo, val: firstblood', 并未监测到 bar 取值

基于以上不足之处,咱们须要持续欠缺咱们的对象拦挡操作

2. 革新欠缺 defineReactive

- 遍历须要响应化的对象

// reactive.jsfunction observe (obj) {  // 对传入的参数做类型判断  if (typeof obj !== 'object' || obj === null) return     // 对象响应化:遍历每个key,定义getter、setter  Object.keys(obj).forEach(key => {      // 调用后面曾经写好的拦挡办法    defineReactive(obj, key, obj[key])  })}

通过 observe 办法, 咱们对象每个属性进行遍历并对其设置了拦挡操作,这样咱们只有将须要做拦挡的对象交由 observe 解决一下,就能够实现对象的所有属性主动拦挡

const myData = {  foo: 'foo',  bar: 'bar',  baz: {    name: 'baz'  }}observe(myData)// testmyData.foomyData.bar = 'newBar'myData.baz.name

node执行以上代码后控制台输入,证实目前对象属性主动拦挡性能曾经根本实现, 但嵌套对象仍旧是有问题的

- 解决嵌套对象问题

当对象的属性值也为对象时,咱们只须要对象的属性值也交给 observe 解决一下就能够了

// reactive.jsfunction defineReactive(obj, key, val) {  observe(val)  Object.defineProperty(obj, key, {    //...  })    //...}

测试看看:

const myData = {  foo: 'foo',  bar: 'bar',  baz: {    name: 'baz'  }}observe(myData)myData.baz.name

node 执行后控制台输入如下,阐明咱们实现了对嵌套对象数据存取侦测

- 解决赋值是对象的问题

如果在给对象的某个属性赋值时,值为对象,那么咱们须要对该属性值也 observe 一下,使其也成为侦测对象

// reactive.jsfunction defineReactive(obj, key, val) {  observe(val)  Object.defineProperty(obj, key, {    get () {      // ...    },    set (newVal) {      // ...      observe(newVal) // 新值是对象的状况      // ...    }  })    //...}

- 解决 增加/删除了新属性问题

// reactive.js// 新增一个set函数来解决function set(obj, key, val) {  defineReactive(obj, key, val)}

至此,咱们就实现了一个简易版的数据拦挡库!

完整版代码如下:

/** * 将对象转化为响应式数据 * @param {*} obj 须要响应化的对象 * @param {*} key 属性 * @param {*} val 值 */function defineReactive (obj, key, val) {  // 解决诸如 test.baz.a 对象嵌套问题  observe(val)  Object.defineProperty(obj, key, {    get() {      console.log(`get ${key}: ${val}`)      return val    },    set(newVal) {      if (newVal!==val) {        console.log(`set ${key}: ${newVal}`)        val = newVal        // 解决赋的值是对象的状况(譬如test.foo={f1: 666})        observe(val)      }    }  })}/** * 对象响应化:遍历每个key,定义getter、setter * @param {*} data 须要响应化的对象 */function observe (data) {  if(typeof data !== 'object' || data === null) {    return  }  Object.keys(data).forEach(key=> {    defineReactive(data, key, data[key])  })}/** * 增加新属性 * @param {*} obj  * @param {*} key  * @param {*} val  */function $set (obj, key, val){  defineReactive(obj, key, val)}

结语

明天咱们曾经根本实现了一个简易版的数据拦挡库,那么咱们如何利用这个库来实现数据响应化,使数据变动驱动视图响应呢?Vue2.x 里又是怎么做的呢?篇幅无限,且听下回分解吧~!