【手写 Vue2.x 源码】第七篇 - 阶段性梳理

news2024/12/26 10:45:58

一,前言

上篇,介绍了 Vue 数据初始化流程中,Vue 实例上数据代理的实现,核心思路如下:

  • 将 data 暴露在 vm._data 实例属性上
  • 利用 Object.defineProperty 将 vm.xxx 操作代理到 vm._data 上

本篇,对当前版本的数据劫持和实例取值代理进行断点调试与流程梳理


二,数据劫持

1,调试 Demo

let vm = new Vue({
  el: '#app',
  data() {
    return { 
      message: 'Hello Vue',     // 值
      obj: { key: "val" },      // 嵌套对象
      arr:[1,2,3]}              // 数组
  }
});

vm.message     // 访问属性
vm.arr.push(4) // 操作数组

准备工作完成,进入断点调试:

image.png

2,Vue 的初始化入口

Vue 构造函数,传入外部 options 选项,调用原型方法 _init 开始 Vue 的初始化流程

image.png

3,initMixin 方法

在 Vue 原型上挂载 _init 方法,以 Vue 初始化时传入的 options 选项作为参数

image.png

initMixin 方法做了以下几件事:

  • 数据的初始化(多种数据:data、props、watch、computed…)
  • 数据初始化完成后,将数据渲染到页面

vm.$options 说明:

  • 将 Vue 初始化时传入的 options 选项,通过 vm.$options 对外进行暴露,
  • 使 Vue 中的方法能够通过实例的 $options 变量轻松获取到 options 选项
  • 将 options 放到$options变量上,options 中属性不会污染 vm 实例

4,initState 方法

initState 方法:进行状态的初始化操作(这里的状态就是数据,数据有多种来源)

目前仅对 data 进行处理,如果options.data存在,进入 initData 进行 data 数据的初始化

image.png

5,initData 方法

initData 方法:进行 data 初始化操作

image.png

通过 vm.$options.data 可以直接获取到 Vue 初始化时传入的 data 属性;

这里的 data,可以是函数,也有可能是对象:

  • data 是函数时:调用 data 拿到函数的返回值-对象,作为当前实例数据源 data
  • data 不是函数时:data 一定是对象,直接作为实例数据源 data

通过这一步处理之后,data 被统一处理为对象类型,供后续流程使用

data = vm._data 的说明:

  • 外部实例 vm,目前是无法直接访问到 initData 方法内部 data 属性的;
  • 为了使外部实例 vm 能够直接访问到 data 属性,在 vm 实例上添加了 _data 实例属性,即 data = vm._data;
  • data 是一个对象,即引用类型,所以 data 与 vm._data 共享引用

6,observe 方法(观测入口)

observe 方法:数据观测的入口

observe 被调用后,就实现了对 data 数据的观测,此时 data 即为响应式数据

image.png

数据的观测是会进行深层递归的,这里的 observe 就是最开始进行数据观测的入口;

所以,这里是第一次调用 observe 方法,value 为整个 data 根对象;

  • 如果 value 不是对象,当前处理结束(在后续递归中,表示本层处理结束,返回到上一层);
  • 如果 value 是对象,将数据创建为 Observer 实例;(首次调用 observe,会将 data 根对象,创建为Observer 实例)

image.png

7,Observer 类

image.png

在 Observer 类的构造方法中,对 value 为数组和对象的两种情况做分别处理

数据劫持-对象类型:

  • walk 方法:遍历对象属性,为每个属性调用 defineReactive方法;
  • defineReactive方法:通过 Object.defineProperty 为属性添加 get、set 方法,进行数据劫持

数据劫持-数组类型:

  • 对能够改变数组原数据的 7 个原型方法进行重写(如:splice、push、unshift)
  • 第一次进入 Observer 时:value 为根数据(必是对象类型),调用 walk 方法

image.png

8,walk 方法

walk 方法:遍历对象上的全部(可枚举)属性,依次执行 defineReactive 方法进行数据劫持操作;

image.png

在循环中,依次为 3 个属性调用 defineReactive 添加数据劫持

message:

image.png

obj:

image.png

arr:

image.png

9,defineReactive 方法

defineReactive 是在外部 walk 方法进行对象属性遍历时,为每个属性递归调用;
defineReactive 方法:为每个属性递归创建 Object.defineProperty,是一个深度优先的处理

深层观测 data.message

image.png

第一次进入 defineReactive 方法:

obj 为 data 根对象,key 为 “messge”,value 为字符串 “Hello Vue”

先走 observe 方法,做深层递归处理(深度优先)

image.png

observe 方法中,由于 message 值为字符串’Hello Vue’,并不是对象,

所以,无需向下递归出路,return 出来处理 data 中 message 属性

image.png

在 defineReactive 方法中:

通过 Object.defineProperty 在 obj 对象上(第一次进入,obj 为根对象 data)

重新定义 message 属性,值分别是 get 和 set

image.png

这样,message 的处理就完成了

深层观测 data.obj

image.png

第二次,进入 defineReactive 方法:

obj 为 data 根对象,key 为 “obj”,value 为对象 { key : “val” }

先走 observe 方法,做深层递归处理(深度优先)

image.png

由于 value 是对象类型 { key : “val” },需要递归处理下层(是对象,就递归)

所以,将 value 创建成为一个 Observer 实例

image.png

Observer 构造函数内,执行对象处理逻辑,通过 walk 方法遍历对象所有属性

image.png

继续对每一个属性调用 defineReactive 方法,继续做深层递归观测

image.png

walk 方法会遍历参数 data(值为对象 { key : “val” } )的所有属性,调用 defineReactive 处理下一层:

第一次调用defineReactive方法:

obj 为对象 { key : “val” }, key 为 “key” value 为"val"

image.png

observe 方法中,由于 key 值为字符串’val’,并不是对象,

所以,无需向下递归出路,return 出来处理 { key : “val” } 对象的"key"属性

image.png

为对象{ key : “val” } 中的"key"属性,通过Object.defineProperty,实现 key 属性的观测

image.png

{ key : “val” } 中的 "key"属性处理完成后,对象内部一层就除了完了,继续处理{ key : “val” } 对象观测

执行 observe 方法后,{ key : “val” } 对象内部属性的深层观测已完成

继续除了{ key : “val” } 对象的观测,此时 defineReactive 方法:

obj 为 data 根对象,key 为"obj",value为对象 { key : “val” }

image.png

这样,obj 的处理就完成了

深层观测 data.arr

image.png

第三次,进入 defineReactive 方法:

obj 为 data 根对象,key 为 " arr",value 为数组 [1,2,3]

先走 observe 方法,做深层递归处理(深度优先)

image.png

由于value 是数组 { key : “val” }(对象类型),需要递归处理下层,

所以,将 value 创建成为一个 Observer 实例

image.png

value 为数组类型,需重写部分数组原型上的方法:

image.png

重写以下 7 个方法,相当于拦截了数组的更新

image.png

observe 内部对数组进行数组原型方法的重写后

再对 data 的 arr 属性进行 Object.defineProperty 处理

这样,arr 的处理就完成了,即整个 data 的处理就完成了,完成了对数据的观测

完成 data 的观测

image.png

内层:

  • 数组类型:没有监听数组的每一项,只重写了数组部分原型方法;
  • 对象类型:对对象的属性进行了深层观测

image.png

备注:
由于在递归处理中,将会为对象进行深层处理,
所以,如果数组中有对象,也会实现深层观测;

三,数据代理

1,实现数据代理

为了实现在实例上直接操作数据,将对象的所有属性都进行了一次代理

vm实例的取值代理:将所有 vm.xxx 取值操作,代理到 vm._data.xxx 上

image.png

对 data 中 3 个属性分别做代理:

1)代理 message:

image.png

2)代理 obj:

image.png

3)代理 arr

image.png

实现原理:

使用 Object.defineProperty,为取值、更新添加一层代理,代理到 vm._data 上

image.png

2,实例取值 vm.message

image.png

由于 proxy 方法中,通过 Object.defineProperty 将 vm.xxx 代理到了 vm._data.xxx上

所以,当通过 vm.xxx 进行取值操作时,会进入 get 方法,将被代理到 vm._data.xxx 上

image.png

vm._data.xxx会,再触发vm._data 的 get 方法

image.png

原理:

vm.message 被代理到了 vm[_data][message] 上进行取值,
通过这样一层代理,就取到了原有 message 属性,
(因为,此时 _data 中数据已成功被defineProperty劫持)

3,数组操作 vm.arr.push

image.png

此操作会先执行 vm.arr 取值操作,和上边一样:

由于 proxy 方法中,通过 Object.defineProperty 将 vm.xxx 代理到了 vm._data.xxx上

所以,当通过 vm.xxx 进行取值操作时,会进入 get 方法,被将代理到 vm._data.xxx 上

即,将 vm.arr 的取值,代理到 vm.[‘_data’].[‘arr’] 上

image.png

vm.[‘_data’].[‘arr’] 会再触发vm._data.arr 的 get 方法

image.png

取到 arr 数组后,再调用 .push 方法操作数组:

image.png

这时,会进入数组重写的 push 方法:

image.png


四,当前版本问题分析

1,深层观测逻辑

当前版本的源码,实现深层观测的逻辑如下:

  • 对 data 根对象进行深层观测
  • data 内部的属性如果是对象,会进行递归观测
  • data 内部的属性如果是数组,会重写数组的原型链上的方法(7 个)

2,已支持的数据观测

当前版本,已支持的数据观测有以下情况:

  • data 根对象(对象及嵌套对象实现了深层观测)
  • data 中的值(为对象中的属性添加 get、set 方法)
  • data 中的数组(重写数组原型方法,目前没有做递归处理,仅实现了数组的单层劫持)
  • data 中的对象(对象及嵌套对象实现了深层观测)
  • data 中的对象中的对象…(对象及嵌套对象实现了深层观测)
  • data 中的对象中的值(对象及嵌套对象深层观测,同时为对象中的属性添加 get、set 方法)

3,尚不支持的数据观测

当前版本,尚不支持的数据观测有以下情况:

  • data 中的数组中的对象(重写数组原型方法,目前没有做递归处理,仅实现了数组的单层劫持)
  • data 中的数组中的数组(重写数组原型方法,目前没有做递归处理,仅实现了数组的单层劫持)
  • 新加入的对象不会被观测
  • 新加入的数组不会被观测

4,Vue2.x 的机制

  • 修改数组下标和长度不会触发更新(仅重写了数组部分原型方法);【可使用 vm.$set 实现】
  • 对于新增属性无法,无法进行数据观测,不会触发更新;【可使用 vm.$set 实现】

四,结尾

本篇通过 Vue Demo 断点调试,对当前版本的数据劫持和数据代理进行流程梳理

同时,对照 Vue2.x 提供的功能,分析了当前版本数据观测的问题和不足

下一篇,数组的深层观测

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

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

相关文章

[VP]河南第十三届ICPC大学生程序竞赛 J.甜甜圈

前言 传送门 : https://ac.nowcoder.com/acm/contest/17148/J 题意 : 给定两个甜甜圈堆 , 每次只能吃所有中最甜的那一个 , 否则的话就移动 询问最小的移动步骤 思路 : 很明确的一个目标 , 寻找当前最大值并且求出距离 那么我们考虑将两个甜甜圈堆进行合并 , 使得变成一个…

LeetCode:17. 电话号码的字母组合

17. 电话号码的字母组合1)题目2)思路3)代码4)结果1)题目 给定一个仅包含数字 2-9 的字符串,返回所有它能表示的字母组合。答案可以按 任意顺序 返回。 给出数字到字母的映射如下(与电话按键相同…

【C++入门】C向C++过渡(上)

前言在正式进入C之前,我们首先要对C有一个基本的认知。这里我就不过多的进行描述了,有兴趣的可以去网络搜索一番。总而言之,从名称上面我们也可以看得出来,C是在C的基础上进行不断地优化发展。事实上确实是这样,C语言中…

k8s部署gitlab

Gitlab以容器方式运行,需要持久化如下几个目录中的数据: 持久化本地位置 容器位置 使用 ${sc}/data /var/opt/gitlab 用于存储应用程序数据。 ${sc}/logs /var/log/gitlab 用于存储日志。 ${sc}/config /etc/gitlab 用于存储 GitLab 配置 文件。 1、下载…

C语言柔性数组的‘美‘

目录 柔性数组的使用和概念 柔性数组的特点 柔性数组与指针的区别 柔性数组的好处 柔性数组的使用和概念 arr[]编译 C99 中,结构中的最后一个元素允许是未知大小的数组,这就叫做『柔性数组』成员。 柔性数组的特点 1、柔性数组成员前必须至少有一个其他…

黑龙江创维E900V22C_S905L3(B)_安卓9.0_开启设置隐藏无线_完美线刷固件包

黑龙江创维E900V22C_S905L3(B)_安卓9.0_开启隐藏设置无线WIFI_完美线刷固件包 提示:大部分芯片均为S905L3A/AB,这两个是可以通刷的。 1、此分享固件包芯片特殊,芯片为S905L3,还有S905L3B芯片,理论上通刷…

C++设计模式:建造者模式(详解+案例代码)

文章目录建造者模式实现步骤实现代码案例一: 组装电脑案例二:汉堡店点餐优缺点建造者模式 建造者模式是一种对象创建型模式之一,用来隐藏复合对象的创建过程,它把复合对象的创建过程加以抽象,通过子类继承和重载的方式…

【JavaEE】设计模式之单例模式

✨哈喽,进来的小伙伴们,你们好耶!✨ 🛰️🛰️系列专栏:【JavaEE】 ✈️✈️本篇内容:设计模式之单例模式。 🚀🚀代码存放仓库gitee:JavaEE初阶代码存放! ⛵⛵作者简介&am…

Redis 连接池报错:jwtCalibrateHandler 48 max number of clients reached

问题[ERROR 2023-01-06 15:37:58,877] jwtCalibrateHandler 48 max number of clients reached早上突然看到反馈说redis连接异常,就是这个:max number of clients reached。问题很显然就是redis连接数超了.原因分析redis maxclients 是redis server的重要…

VMware vCenter Server Appliance空间不足问题处理

原创作者:运维工程师 谢晋 前提提要 客户环境为VSAN环境,VCSA版本为6.7,偶然间发现VSAN监控的性能不能正常打开,如下图:     后登陆https://VCSAIP:5480发现出现告警,报错log日志满了 故障处理 开…

rabbitmq+netcore6 【3】Publish/Subscribe:发布/订阅

文章目录1)前言2)临时队列3)绑定4)综合以上代码准备工作1、生产者2、消费者13、消费者25)验证官网教程原文链接: https://www.rabbitmq.com/tutorials/tutorial-three-dotnet.html翻译版参考链接&#xff1…

大话测试数据(二):概念测试数据的获取

在大话测试数据(一)文章中,我提到,获取数据的第一步是获取概念上数据。这一步看起来简单,其实不是那么容易。获取概念数据和获取需求的过程是交织在一起的,事实上,它们其实是一个事儿&#xff0…

Ribbon、Feign、Hystrix超时重试熔断问题

文章目录问题描述重试次数不生效开启熔断后重试次数生效fallbackFactory回退降级异常为空问题1分析问题2、3分析总结feign请求次数计算Hystrix超时时间设置公式问题描述 在使用Ribbon、Feign、Hystrix组合时,因为配置的问题出现以下现象,让我的大脑CPU烧…

[SWPU2019]Web1

目录 [SWPU2019]Web1 无列名查看表数据 不使用列名查询表中数据 [SWPU2019]Web1 首先我们先注册,登录进来后看到如下界面: 我们点击申请发布广告,并发送: 查看广告详情,发现疑似存在注入点: 于是我们在发…

Docker 应用篇 | Docker 学习笔记总结

Docker 视频内容可以参考黑马程序员的Docker篇 详细完整内容可以查询菜鸟教程:Docker 教程 本篇博文主要让读者对Docker有一个基本理解并可以借助Docker发布自己的项目 一、初识Docker 1.1 Docker概述 Docker是一个集装箱式的思想 Docker可以让开发者打包他们的…

招聘求职系统|基于Springboot+Vue+Nodejs实现求职招聘系统

作者主页:编程指南针 作者简介:Java领域优质创作者、CSDN博客专家 、掘金特邀作者、多年架构师设计经验、腾讯课堂常驻讲师 主要内容:Java项目、毕业设计、简历模板、学习资料、面试题库、技术互助 收藏点赞不迷路 关注作者有好处 文末获取源…

电脑系统更新后桌面的文件全部不见了怎么恢复?

电脑系统更新是很常见的一种情况,自动更新电脑系统后我们可以进行更优质的使用体验,但是最近有位小伙伴,出现了win10电脑系统更新后桌面文件丢失情况,那么电脑系统更新桌面文件没了怎么办?电脑系统更新桌面文件不见了怎…

实验二十三 基于时间的ACL配置及策略

实验二十三 基于时间的ACL配置及策略实验要求: 某公司通过router实现各部门之间的互连。公司要求禁止销售部门在上班时间(8:00 至18:00)访问工资查询服务器(IP地址为192.168.10.10),财务部门不受限制,可以 随时访问。网络拓扑图:实…

如何定义算法?10分钟带你弄懂算法的基本概念

算法是指完成一个任务所需要的具体步骤和方法。也就是说给定初始状态或输入数据,经过计算机程序的有限次运算,能够得出所要求或期望的终止状态或输出数据。 编程界的“Pascal之父”Nicklaus Wirth有一句人尽皆知的名言:“算法数据结构程序”…

【目标检测】G-GhostNet

1、论文 论文题目:《GhostNets on Heterogeneous Devices via Cheap Operations》 论文地址: https://arxiv.org/pdf/2201.03297.pdf 代码地址: https://github.com/huawei-noah/CV-Backbones 2、引言 本文针对网络部署时面临的内存和资源…