Samgao0312 / Blog

MIT License
1 stars 1 forks source link

【再学前端】Vue响应式原理 #140

Open Samgao0312 opened 2 years ago

Samgao0312 commented 2 years ago

Vue 数据双向绑定是通过数据劫持 + 发布者-订阅者模式的方式来实现的。

怎么实现数据劫持❓

Object.defineProperty( )是用来做什么的❓

var Book = {}
var name = '';
Object.defineProperty(Book, 'name', {
  set: function (value) {
    name = value;
    console.log('你取了一个书名叫做' + value);
  },
  get: function () {
    return '《' + name + '》'
  }
})

Book.name = 'vue权威指南';  // 你取了一个书名叫做vue权威指南
console.log(Book.name);  // 《vue权威指南》

实现思路

vue采用的是MVVM这种架构模式,实现mvvm主要包含两个方面,(1)数据变化更新视图,(2)视图变化更新数据: image

(1)视图变化更新数据: 关键点在于data如何更新view,因为view更新data其实可以通过事件监听即可。比如input标签监听 'input' 事件就可以实现了。所以我们着重来分析下,当数据改变,如何更新视图的。

(2)数据变化更新视图 data更新View的重点是怎么知道数据变了,只要知道数据变了,那么接下去的事都好处理。Vue2中监听数据变更就是通过Object.defineProperty( ) 对属性设置一个 set 函数,当数据改变了就会来触发这个函数 在函数中执行相应的逻辑。所以我们只要将一些需要更新的方法放在这里面就可以实现data更新view了。

image


实现过程

实现一个监听器(Observer)

思路:核心方法就是前文所说的 Object.defineProperty( ), 如果要对所有属性都进行监听的话,那么可以通过递归方法遍历所有属性值,并对其进行 Object.defineProperty( ) 处理。

实现订阅者(watcher)

订阅者Watcher在初始化的时候需要将自己添加进 订阅器Dep 中,那该如何添加呢?我们已经知道 监听器Observer 是在get函数执行了 添加订阅者Wather 的操作的,所以我们只要在 订阅者Watcher 初始化的时候出发对应的 get函数 去执行添加订阅者操作即可,那要如何触发 get 的函数,再简单不过了,只要获取对应的属性值就可以触发了,核心原因就是因为我们使用了Object.defineProperty( )进行数据监听。

这里还有一个细节点需要处理,我们只要在 订阅者Watcher 初始化的时候才需要添加订阅者,所以需要做一个判断操作,因此可以在订阅器上做一下手脚:在 Dep.target 上缓存下订阅者,添加成功后再将其去掉就可以了。订阅者Watcher的实现如下:

实现Compile

虽然上面已经实现了一个双向数据绑定的例子,但是整个过程都没有去解析dom节点,而是直接固定某个节点进行替换数据的,所以接下来需要实现一个解析器Compile来做解析和绑定工作。解析器Compile实现步骤:

  1. 解析模板指令,并替换模板数据,初始化视图。
  2. 将模板指令对应的节点绑定对应的更新函数,初始化相应的订阅器。

为了解析模板,首先需要获取到 dom 元素,然后对含有 dom元素上含有指令的节点进行处理,因此这个环节需要对dom操作比较频繁,所有可以先建一个fragment片段,将需要解析的dom节点存入fragment片段里再进行处理:

Vue2.0 的响应式

Object.defineProperty

Vue 2.0的响应式主要用到了Object.defineProperty,我们先来说说这个方法。 Object.defineProperty(obj, prop, descriptor)用来定义属性描述符的, 它接收三个参数:


Vue3.0 的响应式

Vue 3.0的响应式原理跟2.0类似,也是在 get 的时候收集依赖,在 set 的时候更新视图。 但是3.0使用了 es6 的新API Proxy和Reflect,使用 Proxy 相对于 Object.defineProperty 有如下好处:

  1. Object.defineProperty 需要指定对象和属性,对于多层嵌套的对象需要递归监听,Proxy可以直接监听整个对象,不需要递归;
  2. Object.definePropertyget 方法没有传入参数,如果我们需要返回原值,需要在外部缓存一遍之前的值,Proxy的 get 方法会传入对象和属性,可以直接在函数内部操作,不需要外部变量;
  3. set 方法也有类似的问题,Object.defineProperty 的 set 方法传入参数只有 newValue,也需要手动将 newValue 赋给外部变量,Proxy 的 set 也会传入对象和属性,可以直接在函数内部操作;
  4. new Proxy() 会返回一个新对象,不会污染源原对象
  5. Proxy 可以监听数组,不用单独处理数组


参考阅读