文章目录

  • 前言
  • 什么是响应式
  • 数据劫持
  • 发布者-订阅者模式
    • 模式简介
    • 发布者 Observer
    • 订阅器 dep
    • 订阅者 Watcher
  • 整体流程
    • 初始化data
    • data变为响应式数据
    • 解析模板
    • 收集依赖
    • 数据变化—视图更新
    • 视图更新—数据变化
  • 更新的粒度
    • 更细的粒度更新
    • 中等粒度更新
  • Vue3的Proxy数据劫持
    • 理解Proxy
    • 特点
    • 兼容性问题
  • 总结

前言

提起Vue的双向绑定数据响应式,很多人都知道是数据劫持发布者-订阅者模式,这里具体分析一下这两部分具体是怎么实现。

(最近看了相关资料,对原来不足的地方进行修改完善,还增加了更新的粒度和Vue3的Proxy内容)

什么是响应式

如果经常使用VueReact等框架开发,对响应式更新这个词并不陌生,简单来说就是视图会自动更新。

  • 原生JS实现就需要先找到DOM,再修改DOM

​ 比如

const clockDom = document.getElementById
clockDom.innerText = '修改文本内容'
  • 现在使用响应式框架,修改数据的时候,我们不需要关注DOM。在Vue中,this.xxx就可以实现页面数据更新。我们从数据劫持开始了解。

数据劫持

  • 数据劫持其实就是数据响应式基础,当获取数据或者修改数据的时候,能够被我们知道,然后触发响应操作,在Vue2中是通过Object.defineProperty()实现的。

比如,下面这个对象

let person = {name:'tom',age:15
}
  • 我们可以person.name获取到tom,但是我想在获取到tom的时候,还要进行其他操作,就要使用Object.defineProperty()
Object.defineProperty(person,'name',{get(){console.log('name属性被读取了...');},set(newVal){console.log('name属性被修改了...');}
})
  • 访问name属性的时候,会调用get方法,而修改name属性的时候,会调用set方法,可以去执行相应的操作。

  • 但是,这个时候访问被拦截了,我们获取不到name的属性值,所以需要在get方法里面return一个值,上面代码修改如下:

let person = {}
let val = 'tom'
Object.defineProperty(person,'name',{get(){console.log('name属性被读取了...');return val;},set(newVal){console.log('name属性被修改了...');val = newVal;}
})
  • 因为属性值可以由对象直接提供,不会单独声明,所以传入对象的时候,可以传入键和值。所以将val变量和defineProperty方法提取到一个函数中,就形成defineReactive函数
function defineReactive(obj, key, val) { // 这里相当于let val= val(传入的参数)Object.defineProperty(obj, key, {get() {console.log(`${key}属性被读取了...`);return val;},set(newVal) {console.log(`${key}属性被修改了...`);val = newVal;}})
}

至此,就完成了简单的数据劫持

发布者-订阅者模式

模式简介

  • 发布者和订阅者是互相不知道对方的存在的,发布者只需要把消息发送到订阅器里面,订阅者只管接受自己需要订阅的内容。

  • 主要有三个概念:发布者、订阅器、订阅者

发布者 Observer

  • Observer就是进行数据劫持,内部包含了defineReactive()函数。每次数据读或写时,我们能感知到数据被读取了或数据被改写了。要使数据变得“可观测”。

订阅器 dep

  • 收集依赖,内部维护了一个数组,用来记录该数据的所有Watcher,一旦数据发生变化就会发布通知所有Watcher

订阅者 Watcher

  • 作为依赖,会被dep收集。其实是个中介角色,数据发生变化时通知它,然后它去通知其他地方。

了解到这里,可能还会有些疑问:

  • 依赖是什么?怎么产生?这就需要知道解析器 Compile,它会对模板进行解产生Watcher。

  • 下面介绍一下双向绑定的整体流程,有一个更直观的了解。

整体流程

初始化data

  • 首先要知道每个组件都是一个Vue实例,也就是new Vue(),然后将data等数据传入进去。
new Vue({el: '#app', // 挂载点data: {  // 状态},methods: { // 方法},
});
  • 在Cass Vue中,可以在constructor中获取到data
 constructor(options) {this.$el = options.el //获取挂载点this.$data = options.data}
  • 然后对data.xx的一级属性进行劫持,方法是直接遍历data的key,使用Object.defineProperty方法对每个属性都进行劫持,返回对应的值data[key]

  • 这也是为什么可以在Vue实例中直接通过this.xxx访问到data中的数据的原因。

  • 使用过React就知道,React使用setdata()方法才能修改数据。

data变为响应式数据

  • data对象实例化一个Observer实例,绑定在data的ob属性上面,防止重复绑定

  • Observer实例中创建一个dep实例,用于收集依赖

  • Observer内部有Object.definedpropty,对属性进行劫持,修改成 gettersetter方法,用于依赖收集和派发更新

  • 如果data中包含数组,Vue重写了数组的7种原生方法,实现响应式

  • 如果data为多级对象,需要深度监听,递归data对象进行监听,data值更新的时候也需要进行判断深度监听

解析模板

  • 对节点和Vue指令进行编译

  • 编译过程中如果遇到{{}}v-bindv-model等指令使用到的时候,实例化Wacther

    (模板解析也另一大块内容,后面有机会再详细分析)

收集依赖

  • 编译过程中当data中的某个属性被读时(模板中使用了data数据),get 方法会被调用, 该属性的dep实例会收集该属性的Watcher,放置到dep维护的数组中。

至此,修改data中的数据,就能够影响模板中的数据

数据变化—视图更新

  • 修改data数据,Observer实例就会触发set方法,然后调用Dep 的 notify 方法,notify方法中又去调用所有依赖该属性的 Watcher 的 updater 方法,进行视图更新

视图更新—数据变化

  • 上面的流程主要是数据变化更新视图,要实现双向绑定,还需要进行事件监听,也就是注册监听用户对视图的修改事件,触发修改data数据的方法

  • 这也是v-model的实现双向绑定的原理

更新的粒度

更细的粒度更新

  • 假如有一个状态绑定着好多依赖,每个依赖表示一个具体的DOM节点,那么当这个状态发生变化时,向这个状态的所有依赖发送通知,让它们进行DOM更新操作。

  • 但是这样的代价是:粒度越细,每个状态所绑定的依赖就越多,依赖追踪的开销就越大。

中等粒度更新

  • Vue2.0开始,它引入了虚拟DOM,将粒度调整为中等粒度,即一个状态所绑定的依赖不再是具体的DOM节点,而是一个组件。
  • 这样状态变化后,会通知到组件,组件内部再使用虚拟DOM进行对比后进行重新渲染。
  • 这可以大大降低依赖数量,从而降低依赖追踪所消耗的内存。

Vue3的Proxy数据劫持

  • 从前面的内容可以看出Vue2defineProperty方法和重写数组方法的形式存在很多不足,针对这些问题,Vue3使用Proxy来代替defineProperty进行数据劫持。

理解Proxy

  • Proxy是ES6新增的类,这个词的原意是代理,用在这里表示由它来“代理”某些操作,可以译为“代理器”。
  • Proxy 可以理解为:在目标对象之前架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写。

特点

  • 能监听对象和数组

  • 新增的属性也能够被拦截,多层对象需要递归处理,对每一层对象进行代理

  • 除了能拦截访问修改操作之外,还能拦截 in操作符delete 操作符

// 不需要关心是哪个属性
new Proxy(data, {get(key) { },set(key, value) { },
})// Vue2
Object.defineProperty(data, 'count', {get() {},set() {},
})

defineProperty的相同点就是对操作进行拦截,不同点是需要关心是哪个属性。

兼容性问题

  • 但是Proxy 是不能通过babel 转译的,因为在ES5中完全没有一种语法可以模拟出Proxy 的特性。因此Vue3.x 版本没有办法兼任一些低版本浏览器。

总结

  • 双向绑定:就是数据变化更新视图,视图变化更新数据

  • 数据响应式:通过对数据的访问和修改进行劫持,然后进行相应的操作,是实现双向绑定的基础。

  • Vue2数据劫持的缺点

    监听对象Object.defineProperty

    1. 只能监听对象,这个对象不是指引用类型,所以不包括数组
    2. 不能够对新增属性进行监听
    3. 不能监听数组内部的元素

    监听数组:Vue2重写了部分数组方法去实现,这部分和Object.defineProperty 就没有关系了。

    1. 直接通过索引修改数组无法触发更新

    2. 最好用**splice**方法对数组进行增删操作,因为splice在vue中重写了

    新增属性$set方法为对象的新增属性并进行拦截,如果是数组,$set内部调用splice方法

    删除属性$delete删除数据中的某个属性,并且能够侦测到数据的变化。

  • Vue3数据劫持使用Proxy

    对比Vue2的拦截方式更加全面,避免了需要考虑使用$set之类等情况

  • 发布者-订阅者模式

    Observer:发布者,内部对数据访问和修改进行拦截,发送通知给Dep

    Dep:订阅器,收集Watcher和通知Watcher

    Watcher:订阅者,通知视图更新

这一篇文章主要从原理方面进行说明,在这个基础上可以看我下一篇实现双向绑定的文章。
Vue双向绑定:实现篇

参考

Proxy - ECMAScript 6入门 (ruanyifeng.com)。

Vue双向绑定:原理篇(详细)相关推荐

  1. vue双向绑定原理及实现

    vue双向绑定原理及实现 一.MVC模式 二.MVVM模式 三.双向绑定原理 1.实现一个Observer 2.实现一个Watcher 3.实现一个Compile 4.实现一个MVVM 四.最后写一个 ...

  2. 【vue双向绑定原理浅析】

    vue双向绑定原理浅析 1.什么是双向绑定? ​ 所谓双向绑定,指的是vue实例中的data与其渲染的DOM元素的内容保持一致,无论谁被改变,另一方会相应的更新为相同的数据.(数据变化更新视图,视图变 ...

  3. 前端技巧|vue双向绑定原理,助你面试成功

    在面试一些大厂的时候,面试官可能会问到你vue双向数据绑定的原理是什么?有些小伙伴不知道是什么东西,这样你在面试官的眼里就大打折扣了.今天小千就来给大家介绍一下vue的双向绑定原理,千万不要错过啦. ...

  4. 浅谈vue双向绑定原理

    简析mvvm框架 目前angular,reat和vue都是mvvm类型的框架 以vue为例 这里的vm 就是vue框架,它相当于中间枢纽的作用,连接着model 和view. 当前台显示的view发生 ...

  5. Vue双向绑定原理代码实现

    1.代码实现Vue双向绑定与事件绑定,v-bind v-model v-on DOM结构准备 <body><div id="app"><form> ...

  6. 浅聊vue双向绑定原理Object.defineProperty-/-Proxy

    什么是双向绑定呢?vue又是怎么做的我们接下来就聊一聊 什么是双向绑定? 当数据模型data变化时,页面视图会得到响应更新 vue又是怎么做的? vue其实现原理是对data的getter/sette ...

  7. 通俗易懂了解Vue双向绑定原理及实现

    https://www.cnblogs.com/wangjiachen666/p/9883916.html 亲测可用

  8. Vue基础知识总结 6:vue双向绑定原理

  9. Vue数据双向绑定原理(vue2向vue3的过渡)

    众所周知,Vue的两大重要概念: 数据驱动 组件系统 接下来我们浅析数据双向绑定的原理 一.vue2 1.认识defineProperty vue2中的双向绑定是基于defineProperty的ge ...

  10. vue的数据双向绑定原理

    前言: 什么是数据双向绑定? vue是一个mvvm框架,即数据双向绑定,即当数据发生变化的时候,视图也就发生变化,当视图发生变化,数据也会跟着同步变化.这也算是vue的精髓之处了.单项数据绑定是使用状 ...

最新文章

  1. linux发送email错误 501 Syntax: HELO hostname
  2. python操作excel-Python对Excel的读写等操作(转)
  3. 文件系统与数据库的优缺点
  4. 方法重载(overload)和方法重写(override)的比较
  5. 使用Servlet上传多张图片——实体层(ProductInfo.java)
  6. TCP流中各种队列:
  7. 【渝粤教育】电大中专Office办公软件 (2)作业 题库
  8. element table批量删除_element 表格批量删除
  9. VMware虚拟机文件
  10. python中datetime默认的1990年改为_如何更改numpy datetime64中的年份值?
  11. centos6.5 下搭建lamp环境
  12. 线性代数 : 方程组的几何解释
  13. 【Python秒杀脚本】淘宝或京东等秒杀抢购
  14. Qt实现提示音以及QSound的使用说明
  15. R语言 判别分析:线性判别、K最邻近、有权重的K最邻近、朴素贝叶斯
  16. 梦三国解析服务器spl文件头失败解决,梦三国手游|深度解析“伪法师”张角:“不容忽视的战场杀器”...
  17. 高级软件工程第七次作业:LLS战队Alpha敏捷冲刺5
  18. css设置十六进制背景色和透明度
  19. 中国移动 云MAS平台HTTP2.1(HTTP版)发送普通短信
  20. 《JOEL说软件》中文版翻译质量令人失望

热门文章

  1. ThinkPad T440s 改 T450s
  2. C++排序算法之归并排序
  3. 【机器人学】机器人开源项目KDL源码学习:(4)机械臂逆动力学的牛顿欧拉算法
  4. linux下rsync启动命令,linux下rsync命令详细整理
  5. 使用GPG验证文件签名
  6. 使用VBA操作文件(1):使用Excel对话框
  7. 项目记录—workbench静力分析
  8. HTTP状态码:400\500 错误代码(个人总结)
  9. 手把手教你ECSHOP去版权与标志
  10. 安卓街机模拟器对战源码修改详解(1)