好家伙,这是目前为止最绕的一章,也是十分抽象的一章

由于实在太过抽象,我只能用一个不那么抽象的实例去说服我自己

 

完整代码已开源https://github.com/Fattiger4399/analytic-vue.git

1.我们要做什么?

来看这个例子,

index.html

setTimeout(() => {
vm.msg = "张三"
vm._updata(vm._render())
},2000)

 

在这个例子中,我们能够看到hello在msg的值变化后,渲染的值也发生了改变

 

现在,我们要做的事情是

setTimeout(() => {
vm.msg = "张三"
// vm._updata(vm._render())
},2000)
vm._updata(vm._render())将这行代码去掉,也能实现相同的效果
原本我要手动更新,现在我引入一个监视者watcher帮我盯着这个数据,
当数据发生改变,自动更新视图
实现"自动更新"
 
 

2.代码实现

代码已开源https://github.com/Fattiger4399/analytic-vue.git

项目:

 (红框为本篇涉及到的文件)

 

2.1.dep.js

class Dep{
constructor(){
this.subs = []
}
//收集watcher
depend(){
this.subs.push(Dep.target)
}
//更新watcher
notify(){
this.subs.forEach(watcher =>{
watcher.updata()
})
}
} //添加watcher
Dep.target = null
export function pushTarget(watcher){
Dep.target = watcher
}
export function popTarget(){
Dep.target = null
}
export default Dep

Dep 类中有 subs 数组,用于存放依赖(即 Watcher 对象)。

depend() 方法用于收集依赖,将当前的 Watcher 对象添加到 subs 数组中。

notify() 方法用于更新依赖,遍历 subs 数组,调用各个 Watcher 对象的 update() 方法

 

2.2.index.js中数据劫持部分

//对对象中的属性进行劫持
function defineReactive(data, key, value) {
observer(value) //深度代理
let dep = new Dep() //给每一个对象添加dep
Object.defineProperty(data, key, {
get() {
// console.log('获取')
if(Dep.target){
dep.depend()
}
console.log(dep)
return value
},
set(newValue) {
// console.log('设置')
if (newValue == value) {
return;
}
observer(newValue)
value = newValue
dep.notify()
}
}) }

 

2.3.watcher.js

//(1) 通过这个类watcher 实现更新
import { pushTarget,popTarget } from "./dep"
let id = 0
class watcher {
//cb表示回调函数,options表示标识
constructor(vm, updataComponent, cb, options) {
//(1)将
this.vm = vm
this.exprOrfn = updataComponent
this.cb = cb
this.options = options
this.id = id++
//判断
if (typeof updataComponent === 'function') {
this.getter = updataComponent
}
//更新视图
this.get()
}
//初次渲染
get() {
console.log(this,'||this is this')
pushTarget(this) //给dep 添加 watcher
this.getter()
popTarget() //给dep 去除 watcher
}
//更新
updata() {
this.getter()
}
} export default watcher //收集依赖 vue dep watcher data:{name,msg}
//dep:dep 和 data 中的属性是一一对应
//watcher:监视的数据有多少个,就对应有多少个watcher
//dep与watcher: 一对多 dep.name = [w1,w2] //实现对象的收集依赖

初次渲染时,调用 get() 方法,会先调用 pushTarget() 方法将当前 Watcher 添加到 Dep 中,

然后调用 getter() 方法进行渲染,最后调用 popTarget() 方法去除该 Watcher。

而在更新时,直接调用 update() 方法,也会调用 getter() 方法进行更新。

 

2.4.lifecycle.js

 

import {
patch
} from "./vnode/patch" import watcher from "./observe/watcher" export function mounetComponent(vm, el) {
//源码
callHook(vm, "beforeMounted")
//(1)vm._render() 将 render函数变成vnode
//(2)vm.updata()将vnode变成真实dom
let updataComponent = () => {
vm._updata(vm._render())
}
new watcher(vm, updataComponent,()=>{},true)
callHook(vm, "mounted")
} export function lifecycleMixin(Vue) {
Vue.prototype._updata = function (vnode) {
// console.log(vnode)
let vm = this
//两个参数 ()
vm.$el = patch(vm.$el, vnode)
// console.log(vm.$el, "||this is vm.$el")
}
} //(1) render()函数 =>vnode =>真实dom //生命周期调用
export function callHook(vm, hook) {
// console.log(vm.options,"||this is vm.options")
// console.log(hook,"||this is hook")
// console.log(vm.$options,"||this is vm.$options")
const handlers = vm.$options[hook]
if (handlers) {
for (let i = 0; i < handlers.length; i++) {
handlers[i].call(this) //改变生命周期中的指向
}
}
}

在挂载方法中新建watcher实例

 

 

于是,到这里,我们已经实现了与上述等去掉vm._updata(vm._render())前的效果

 

接下来是个人的一些思考

3.一些思考

3.1.vue的响应式原理和dep依赖收集的关系?

Vue 的响应式原理是通过利用 Object.defineProperty() 方法拦截对象的访问和修改,从而实现对数据的观测。

Vue 在初始化时会遍历 data 对象的属性,为每个属性创建一个 Dep(依赖)对象。

Dep 对象主要负责管理依赖收集和派发更新。

当读取数据时,会触发该属性的 getter 函数,收集依赖。

Dep 对象会存储当前正在执行的 Watcher(观察者)对象,将其添加到自身的 subs(订阅者)数组中。

在修改数据时,会触发该属性的 setter 函数,通知 Dep 对象进行依赖的派发。

Dep 对象会遍历 subs 数组,调用每个 Watcher 对象的 update() 方法,进而触发 Watcher 的更新操作。

Watcher 是 Vue 响应式系统的核心,它负责建立响应式数据与视图之间的关系。

在渲染过程中,Vue 会将模板中涉及到的数据对应的 Watcher 实例化并添加到 Dep 的 subs 数组中。

当数据发生变化,会触发相应的 Watcher 进行更新操作,从而实现视图的重新渲染。(所谓的订阅发布)

3.2.Dep作用:

  1. 收集依赖:Dep 实例内部有一个 subs 数组,用于存储依赖(Watcher)的引用。当一个观察者(Watcher)初始化时,会通过调用对应数据的 getter 方法,触发依赖收集的过程。在这个过程中,Dep.target(当前正在访问的观察者)会被添加到 Dep 实例的 subs 数组中。这样就建立了数据与观察者之间的依赖关系。

  2. 触发更新:当被观察的数据发生变化时,它的 setter 方法会通知 Dep 实例。Dep 实例会遍历 subs 数组,依次调用每个依赖(Watcher)的 update 方法,通知观察者进行更新操作。

  3. 依赖管理:Dep 实例通过 subs 数组维护了一组观察者(Watcher)的引用。当数据发生变化时,可以快速找到需要更新的观察者,避免了不必要的触发和更新。

  4. 多重依赖管理:在 Vue 中,一个观察者(Watcher)可以依赖多个数据,一个数据也可以被多个观察者依赖。通过 Dep 实例和 subs 数组的组合,可以实现对多个数据和观察者的管理和通知。

Dep 依赖收集的作用就是在 getter 函数中将 Watcher 对象添加到自己的 subs 数组中,而 Watcher 通过 update() 方法来触发视图的更新,从而保持数据与视图的同步。

 

3.3.为什么要收集依赖?

收集依赖的目的是为了建立起数据与视图之间的关联关系,

当数据发生变化时,能够准确地知道需要更新哪些视图。

在 Vue 中,当数据对象的某个属性被访问时,会触发该属性的 getter 函数,并在这个时候收集依赖。

这个依赖就是 Watcher 对象,它会被添加到 Dep 对象的 subs 数组中。这样在数据发生变化时,就可以遍历 subs 数组,依次调用每个 Watcher 对象的 update() 方法来更新对应的视图。

收集依赖的好处有以下几点:

  1. 减少不必要的更新:只有当数据被访问时,才会触发依赖收集的过程。如果数据没有被使用,那么就不会收集对应的依赖。这样可以避免不必要的视图更新,提升性能。

  2. 精确追踪依赖关系:通过依赖收集,可以准确地确定哪些数据被使用,以及被使用的地方。当某个数据发生变化时,只需要更新和这个数据相关联的视图,而不是所有的视图。

  3. 动态的更新依赖关系:如果在数据使用过程中有新的 Watcher 注册进来,依赖收集可以动态地将这个新的 Watcher 添加到对应的依赖中。这样可以保证当数据变化时,新的 Watcher 也能够得到通知。

总之,通过收集依赖,可以确保数据与视图之间的同步更新,提高程序的效率和可靠性。

 

3.4.什么是收集依赖

收集依赖是一个在观察者模式中常见的概念,用于建立起观察者和被观察者之间的关联关系

在 Vue.js 中的响应式系统中,当我们访问一个响应式对象的属性时,会触发属性的 getter 方法。而在 getter 方法中,会有一个收集依赖的过程。

具体来说,收集依赖的过程如下:

  1. 创建一个 Watcher 对象,用于表示当前正在进行数据观察的实例。

  2. 在创建 Watcher 对象之前,会将这个 Watcher 对象先设置为“活动”的状态,即将其赋值给一个特定的变量 Dep.target。

  3. 在 getter 方法中,会通过 Dep.target 去判断当前正在进行依赖收集的 Watcher。如果存在 Dep.target,说明当前正在进行依赖收集的操作,那么就将这个 Watcher 添加到相关的依赖列表中。

  4. 当依赖列表中收集完所有的 Watcher 后,清空 Dep.target,将其置为 null,表示依赖收集完成。

     

通过收集依赖,我们可以在数据发生变化时,快速找到需要更新的观察者,从而实现数据与视图的同步更新。收集依赖的过程是自动进行的,无需手动调用,由 Vue 内部的响应式系统自动管理。

收集依赖是实现 Vue 的响应式系统的重要环节之一,它保证了当响应式对象的属性被访问时能够建立起数据与观察者之间的关联关系,从而实现了数据的动态更新。

 

Vue源码学习(九):响应式前置:实现对象的依赖收集(dep和watcher)的更多相关文章

  1. 【Vue源码学习】响应式原理探秘

    最近准备开启Vue的源码学习,并且每一个Vue的重要知识点都会记录下来.我们知道Vue的核心理念是数据驱动视图,所有操作都只需要在数据层做处理,不必关心视图层的操作.这里先来学习Vue的响应式原理,V ...

  2. 读Vue源码二 (响应式对象)

    vue在init的时候会执行observer方法,如果value是对象就直接返回,如果对象上没有定义过_ob_这个属性,就 new Observer实例 export function observe ...

  3. vue源码解析之响应式原理

    关于defineReactive等使用细节需要自行了解 一些关键知识点 $mount时 会 new Watcher 把组件的 updateComponent 方法传给watcher 作为getter ...

  4. 【Vue源码学习】依赖收集

    前面我们学习了vue的响应式原理,我们知道了vue2底层是通过Object.defineProperty来实现数据响应式的,但是单有这个还不够,我们在data中定义的数据可能没有用于模版渲染,修改这些 ...

  5. Vue源码学习1——Vue构造函数

    Vue源码学习1--Vue构造函数 这是我第一次正式阅读大型框架源码,刚开始的时候完全不知道该如何入手.Vue源码clone下来之后这么多文件夹,Vue的这么多方法和概念都在哪,完全没有头绪.现在也只 ...

  6. Vue源码学习三 ———— Vue构造函数包装

    Vue源码学习二 是对Vue的原型对象的包装,最后从Vue的出生文件导出了 Vue这个构造函数 来到 src/core/index.js 代码是: import Vue from './instanc ...

  7. Vue源码学习二 ———— Vue原型对象包装

    Vue原型对象的包装 在Vue官网直接通过 script 标签导入的 Vue包是 umd模块的形式.在使用前都通过 new Vue({}).记录一下 Vue构造函数的包装. 在 src/core/in ...

  8. 最新 Vue 源码学习笔记

    最新 Vue 源码学习笔记 v2.x.x & v3.x.x 框架架构 核心算法 设计模式 编码风格 项目结构 为什么出现 解决了什么问题 有哪些应用场景 v2.x.x & v3.x.x ...

  9. Vue 源码学习(1)

    概述 我在闲暇时间学习了一下 Vue 的源码,有一些心得,现在把它们分享给大家. 这个分享只是 Vue源码系列 的第一篇,主要讲述了如下内容: 寻找入口文件 在打包的过程中 Vue 发生了什么变化 在 ...

  10. Vue源码学习之双向绑定

    首发地址:CJWbiu's Blog 原理: ‘当你把一个普通的 JavaScript 对象传给 Vue 实例的 data 选项,Vue 将遍历此对象所有的属性,并使用 Object.definePr ...

随机推荐

  1. Serverless试飞员的夙愿 | 带您扶摇直上,酣畅淋漓的云上作战

    ​上期博文带您体验了外挂云函数Demo包,感受通过云函数使用云数据库快速突破"音障",进入"长机"云函数+"僚机"云数据库的Serverle ...

  2. 使用Stable Diffusion生成艺术二维码

    在数字艺术的世界中,二维码已经从单纯的信息承载工具转变为可以展示艺术表达的媒介.这是通过使用Stable Diffusion的技术实现的,它可以将任何二维码转化为独特的艺术作品.接下来,我们将一步步教 ...

  3. 【Redis】模糊查询

    Redis模糊查询 1.支持的通配符*.?.[] 2.通配符* a.单个 * 模式 # 查询所有的key keys * b.双 * 模式,匹配任意多个字符 # key中含有rich的key keys ...

  4. 踏入数字天地之中 | Metaworld SDK 2.0进化纵览

    ​ ZEGO从未停止对技术边界的探索,我们力图让用户能够更高效.便捷地使用技术去创造价值. 去年8月,ZEGO打造的元宇宙智能互动引擎首次与大家见面,Metaworld SDK作为其中的核心能力组件, ...

  5. subprocess Python执行系统命令最优选模块

    简介 subprocess 是 Python 中执行操作系统级别的命令的模块,所谓系级级别的命令就是如ls /etc/user ifconfig 等和操作系统有关的命令. subprocess 创建子 ...

  6. 【NestJS系列】核心概念:Controller控制器

    前言 控制器主要是用来处理客户端传入的请求并向客户端返回响应. 它一般是用来做路由导航的,内部路由机制控制哪个控制器接收哪些请求. 路由 为了创建基本控制器,我们需要使用@Controller装饰器, ...

  7. git:gitignore常用配置

    配置 在项目文件中添加.gitignore文件 .DS_Store node_modules /dist

  8. 手动安装vur-router并引用

    安装并引用 安装 npm install vue-router 引用 步骤一:在src路径下,创建router文件夹, 其下创建index.js // router/index.js import V ...

  9. asset module type 替代 loader 处理图片字体等文件资源

    前面文章中 体验了webpack的打包 .解析css资源 ,接下来看看项目中常用到的图片.字体.文件该怎么处理吧~ 项目路径如下,在上一篇 解析css资源 项目基础上增加了一些文件 demo ├─ s ...

  10. 《CUDA编程:基础与实践》读书笔记(2):CUDA内存

    1. 全局内存 核函数中的所有线程都能够访问全局内存(global memory).全局内存的容量是所有设备内存中最大的,但由于它没有放在GPU芯片内部,因此具有相对较高的延迟和较低的访问速度,cud ...