Vue2、Vue3响应式原理和相关源码(持续更新~)

目录

一、vue2响应式原理

对应常规源码?:

数组有哪些不同?:

总结下,vue2有哪些缺陷?


在以往的经验中,我们主技术栈为vue的前端开发,在面试的时候被问到响应式原理的概率是非常大的。而现在市场上,基本上老项目还是vue2,新项目大部分都会选择vue3来开发。

所以,我们有必要来总结对应的源码和面试题,come on ~

一、vue2响应式原理

      简述:在组件初始化时,通过Object.defineProperty来进行对数据进行劫持,构造get set方法。get来进行依赖的收集dep,set用于通知dep.notify。

        对应常规源码?:
  1.  observe()函数返回一个ob实例。
export function observe (value: any, asRootData: ?boolean): Observer | void {// 观察者let ob: Observer | voidif (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {ob = value.__ob__ } else if (shouldObserve &&!isServerRendering() &&(Array.isArray(value) || isPlainObject(value)) &&Object.isExtensible(value) &&!value._isVue) {// 创建观察者ob = new Observer(value)}if (asRootData && ob) {ob.vmCount++}return ob
}

2. ob对象根据不同的数据类型进行不同的响应化操作

export class Observer {value: any;dep: Dep; // 保存数组类型数据的依赖constructor (value: any) {this.value = valuethis.dep = new Dep()def(value, '__ob__', this) // 在getter中可以通过__ob__可获取ob实例if (Array.isArray(value)) { // 数组响应化protoAugment(value, arrayMethods)  this.observeArray(value)} else { // 对象响应化this.walk(value)}}/*** 遍历对象所有属性定义其响应化*/walk (obj: Object) {const keys = Object.keys(obj)for (let i = 0; i < keys.length; i++) {defineReactive(obj, keys[i])}}/*** 对数组每一项执行observe*/observeArray (items: Array<any>) {for (let i = 0, l = items.length; i < l; i++) {observe(items[i])}}
}

3. defineReactive()函数定义get、set方法,并收集依赖。

export function defineReactive (obj: Object,key: string,val: any,customSetter?: ?Function,shallow?: boolean
) {const dep = new Dep() // 一个key一个Dep实例// 递归执行子对象响应化let childOb = !shallow && observe(val)// 定义当前对象getter/setterObject.defineProperty(obj, key, {enumerable: true,configurable: true,get: function reactiveGetter () {// getter负责依赖收集if (Dep.target) {dep.depend()// 若存在子observer,则依赖也追加到子obif (childOb) {childOb.dep.depend()if (Array.isArray(value)) {dependArray(value) // 数组需特殊处理}}}return value},set: function reactiveSetter (newVal) {if (newVal === value || (newVal !== newVal && value !== value)) {return}    val = newVal // 更新值childOb = !shallow && observe(newVal) // childOb更新dep.notify() // 通知更新}})
}

4. Dep管理一组watcher, dep管理的值更新时,通知对应的watcher进行更新

export default class Dep {static target: ?Watcher; // 依赖收集时的wacher引用subs: Array<Watcher>; // watcher数组constructor () {this.subs = [] }//添加watcher实例addSub (sub: Watcher) {this.subs.push(sub)}//删除watcher实例removeSub (sub: Watcher) {remove(this.subs, sub)}//watcher和dep相互保存引用depend () {if (Dep.target) {Dep.target.addDep(this)}}notify () {// stabilize the subscriber list firstconst subs = this.subs.slice()for (let i = 0, l = subs.length; i < l; i++) {subs[i].update()}}
}

5. watcher监控数据或者关联组件的一个更新函数,数据更新则执行回调或者更新函数背调用。

export default class Watcher {constructor (vm: Component,expOrFn: string | Function,cb: Function,options?: ?Object,isRenderWatcher?: boolean) {this.vm = vm// 组件保存render watcherif (isRenderWatcher) {vm._watcher = this}// 组件保存非render watchervm._watchers.push(this)// options...// 将表达式解析为getter函数// 如果是函数则直接指定为getter,那什么时候是函数?// 答案是那些和组件实例对应的Watcher创建时会传递组件更新函数updateComponentif (typeof expOrFn === 'function') {this.getter = expOrFn} else {// 这种是$watch传递进来的表达式,它们需要解析为函数this.getter = parsePath(expOrFn)if (!this.getter) {this.getter = noop}}// 若非延迟watcher,立即调用getterthis.value = this.lazy ? undefined : this.get()}/*** 模拟getter, 重新收集依赖re-collect dependencies.*/get () {// Dep.target = thispushTarget(this)let valueconst vm = this.vmtry {// 从组件中获取到value同时触发依赖收集value = this.getter.call(vm, vm)} catch (e) {} finally {// deep watching,递归触发深层属性if (this.deep) {traverse(value)}popTarget()this.cleanupDeps()}return value}addDep (dep: Dep) {const id = dep.idif (!this.newDepIds.has(id)) {// watcher保存dep引用this.newDepIds.add(id)this.newDeps.push(dep)// dep添加watcherif (!this.depIds.has(id)) {dep.addSub(this)}}}update () {// 更新逻辑if (this.lazy) {this.dirty = true} else if (this.sync) {this.run()} else {//默认lazy和sync都是false,所以会走该逻辑queueWatcher(this)}}
}
数组有哪些不同?:
  1. 7个数组方法的打补丁和原型覆盖
    // 数组原型
    const arrayProto = Array.prototype
    // 修改后的原型
    export const arrayMethods = Object.create(arrayProto)
    // 七个待修改方法
    const methodsToPatch = ['push','pop','shift','unshift','splice','sort','reverse']/*** 拦截这些方法,额外发送变更通知*/
    methodsToPatch.forEach(function (method) {// 原始数组方法const original = arrayProto[method]// 修改这些方法的descriptordef(arrayMethods, method, function mutator (...args) {// 原始操作const result = original.apply(this, args)// 获取ob实例用于发送通知const ob = this.__ob__// 三个能新增元素的方法特殊处理let insertedswitch (method) {case 'push':case 'unshift':inserted = argsbreakcase 'splice':inserted = args.slice(2)break}// 若有新增则做响应处理if (inserted) ob.observeArray(inserted)// 通知更新ob.dep.notify()return result})
    })
    if (Array.isArray(value)) {// 替换数组原型protoAugment(value, arrayMethods) // value.__proto__ = arrayMethodsthis.observeArray(value)
    }

    2. 数组响应式的循环o'berve

    observeArray (items: Array<any>) {for (let i = 0, l = items.length; i < l; i++) {observe(items[i])}}

    3. 依赖收集时做特殊处理,如果数组项中还是数组需要deepArray

    //getter中
    if (Array.isArray(value)) {dependArray(value)
    }// 数组中每一项也需要收集依赖
    function dependArray (value: Array<any>) {for (let e, i = 0, l = value.length; i < l; i++) {e = value[i]e && e.__ob__ && e.__ob__.dep.depend()if (Array.isArray(e)) {dependArray(e)}}
    }
    总结下,vue2有哪些缺陷?
    1. 不能监听数组长度的变化
    2. 不能监听数组根据下标修改
    3. 不能监听对象的新增和删除
    4. 所以vue2提供了修补方法,使用$set()

持续更新中~ 如有不足,敬请指出,共同进步~

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/2760.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

单细胞分析|整合 scRNA-seq 和 scATAC-seq 数据

引言 单细胞转录组学极大地提升了对细胞状态进行分类的能力&#xff0c;但要深入理解生物学现象&#xff0c;不能仅仅停留在对细胞群的简单列举上。随着新方法的不断涌现&#xff0c;用于测量细胞的不同状态&#xff0c;一个关键的挑战是如何将这些数据集整合起来&#xff0c;以…

24pht春3

pht春3 A 显然 f [ i ] [ j ] f[i][j] f[i][j] 表示前 i j ij ij 个&#xff0c;有 i i i 个用 a a a &#xff0c; j j j 个选 b b b 的方案数。然后显然。 不是&#xff0c;好像假了&#xff0c; k s ≠ n ks\neq n ksn 直接按 a a a 排序&#xff0c;那样子只要…

达梦数据库的DMRMAN工具-数据备份

达梦数据库的DMRMAN工具-数据备份 基础信息 OS版本&#xff1a; Red Hat Enterprise Linux Server release 7.9 (Maipo) DB版本&#xff1a; DM Database Server 64 V8 DB Version: 0x7000c 03134284132-20240115-215128-200811 备份数据库 1.1 设置备份选项 备份命令如果仅…

Spring三级缓存源码解析

Spring三级缓存 前置知识三级缓存定义SpringBean生命周期 Bean的初始化getSingleton 分析加入一级缓存 CreateBean过程(A)A填充属性BB填充属性A,执行getSingleton&#xff08;A&#xff09;B完成初始化 前置知识 三级缓存定义 public class DefaultSingletonBeanRegistry ext…

云打印为什么这么便宜?

随着云打印的火热发展&#xff0c;越来越多的用户开始选择云打印服务了。我们在之前的内容里也介绍过&#xff0c;现在的易绘创云打印服务A4低至5分钱/页。那么云打印为什么这么便宜呢&#xff1f;今天小易就带大家来了解一下。 云打印为什么这么便宜&#xff1f; 相信很多用户…

C#面:简述常用的集合类

List&#xff1a;List 是一个动态数组&#xff0c;可以根据需要自动调整大小。它提供了添加、删除、查找和排序等常见操作。例如&#xff0c;可以使用 List 来存储一组整数。Dictionary<TKey&#xff0c; TValue>:是一个键值对集合&#xff0c;其中每个键都是唯一的。它提…

Element-plus DatePicker 日期选择器【正则校验时间范围】

效果图&#xff1a; 利用element-plus中的form表单验证完成效果。 <el-form-item label"检查计划截止日期&#xff1a;" prop"deadline"><el-date-pickerv-model"form.deadline"value-format"YYYY-MM-DD"style"width: …

陇剑杯 省赛 攻击者3 CTF wireshark 流量分析

陇剑杯 省赛 攻击者3 CTF wireshark 流量分析 题目 链接&#xff1a;https://pan.baidu.com/s/1KSSXOVNPC5hu_Mf60uKM2A?pwdhaek 提取码&#xff1a;haek ├───LogAnalize │ ├───linux简单日志分析 │ │ linux-log_2.zip │ │ │ ├───misc日志…

压测步骤-uload

1.在群里申请进行性能测试 2.搭建环境&#xff0c;开始测试 3.关闭服务&#xff0c;停掉所有相关进程 4.在群里通知大家环境可用 在压测机器 &#xff08;2台&#xff09;A B 杀掉RECOMMEND进程 ps -ef|grep recommend|awk {print $2}|xargs kill -9 其中一台搭建RS测试环境 提…

NAT的知识点和实现

1.NAT的作用&#xff1a; &#xff08;1&#xff09;、把内网私网IP转换公网IP&#xff1b; &#xff08;2&#xff09;、隐藏内网&#xff0c;起到保护内网作用&#xff1b; &#xff08;3&#xff09;、适当的缓解的IPv4地址空间枯竭&#xff1b; &#xff08;4&#xff…

【数据分析面试】28.查找职工信息 (SQL文字处理/通配符查找)

题目 现在公司里有职工信息表Worker, HR需要你提供不同的职工信息&#xff0c;包括&#xff1a; 从 Worker 表中获取 “FIRST_NAME” 并以大写形式显示。从 Worker 表中获取 DEPARTMENT 的唯一值。查找 Worker 表中 FIRST_NAME 的前三个字符。查找 Worker 表中名为 ‘Amitabh…

值传递和地址传递

文章目录 目录值传递地址传递 目录 值传递 package com.zhang.parameter; //值传递 public class MethodDemo1 {public static void main(String[] args) {int a 10;System.out.println(a);System.out.println("~~~~~~~~~~~~~~~");change(a);//无论你传入的是什么 …

【Java, Git, React】Technical documentation 和 问题汇总解决

1. Technical documentation 1.1 Git 配置 git config --global user.name “名称” git config --global user.email “邮箱” git config --global http.sslVerify false git clone XXX 2. 问题汇总&#xff1a; 2.1 Permission Issue fatal: could not create work tree…

C脚本实现Wincc单按钮启动/停止

文章目录 前言实现步骤&#xff1a; 前言 在Wincc中实现单按钮启动/停止。即按一下&#xff0c;按钮关联的变量置位为1&#xff0c;再按一下&#xff0c;变量复位为0。 实现步骤&#xff1a; 在变量管理器新建变量"BF01_CP_HMI_SevName_Play"&#xff1b;添加一个按…

备战Java面试

一&#xff0e;JAVA基础 1.八个基本数据类型&#xff0c;长&#xff0c;占几个字节&#xff0c;取值范围是多少。 基本类型&#xff1a; Byte 一般的数据 1个字节 取值范围 -128—127 short 极大的数据 2个字节 取值范围 -2的15次方到2的15次方减一 int 4个字节…

JS绘制电流闪烁流动效果

<!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>电流闪动动效</title><style>.sd1 {dis…

618买什么最划算?618买什么东西便宜?必备数码好物清单分享

​只不&#xff0c;马上又到了618购物节咯&#xff0c;数码产品的优惠力度尤为显著&#xff0c;是购买数码产品的绝佳时机。接下来&#xff0c;我将为大家分享几款性价比超高的数码产品&#xff0c;相信总有一款能吸引你的目光。 一、南卡OE MIX开放式蓝牙耳机 在618购物狂欢节…

linux中git的使用

为什么要有git git相当于一个仓库可以让我们更好的去管理我们的代码&#xff0c;实现版本的控制&#xff0c;上传到云端仓库。有了git,就可以实现多人同时开发一个项目&#xff08;每个负责一部分代码&#xff0c;最后都上传到同一个仓库&#xff09;。 git github/gitee 的区…

js 手写防抖、节流函数

防抖 在第一次触发事件时&#xff0c;不立即执行函数&#xff0c;而是给出一个期限值&#xff0c;比如200ms 如果在200ms内没有再次触发滚动事件&#xff0c;那么就执行函如果在200ms内再次触发滚动事件&#xff0c;那么当前的计时取消&#xff0c;重新开始计时 function de…

【Linux】LInux下的进程状态

&#x1f466;个人主页&#xff1a;Weraphael ✍&#x1f3fb;作者简介&#xff1a;目前正在学习c和算法 ✈️专栏&#xff1a;Linux &#x1f40b; 希望大家多多支持&#xff0c;咱一起进步&#xff01;&#x1f601; 如果文章有啥瑕疵&#xff0c;希望大佬指点一二 如果文章对…