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

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

 

完整代码已开源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. Linux从文件中逐行读取文件名并将匹配的文件复制到指定目录

    问题应该算挺常见的但是一句话还挺难说清楚,所以百度特别难搜. 场景就是,有一堆以员工名称命名的文件(名称可能还有字母数字等前后缀),现在给定一个员工清单,需要从这些文件中筛选出员工清单上列出的员工的文 ...

  2. border属性之border-radius

    border-radius - 指定每个圆角 如果你在 border-radius 属性中只指定一个值,那么将生成 4 个 圆角. 但是,如果你要在四个角上一一指定,可以使用以下规则: 四个值: 第一 ...

  3. 电脑安装JDk

    JDK软件下载链接:https://pan.baidu.com/s/1OG6wD-Fvgxu6FwuOUMDmQQ提取码:yu0l Eclipse软件下载链接:https://pan.baidu.co ...

  4. java根据配置文件读取值

    <!-- https://mvnrepository.com/artifact/org.apache.commons/commons-lang3 --> <dependency> ...

  5. CF1794B Not Dividing题解

    如果 \(a_i\) 可以整除 \(a_{i - 1}\),只要在 \(a_i\) 上 \(+1\) 即可,这样 \(a_i \bmod a_{i - 1} = 1\) 就满足题目要求了,如果这样算来 ...

  6. 基于ChatGPT上线《你说我猜》小游戏

    摘要 AIGC.GPT.休闲小游戏三者可以怎么结合? AIGC.GPT与小游戏的结合为游戏体验带来了新的可能性.AIGC(Artificial Intelligence Game Content)作为 ...

  7. tensorflow.js 对视频 / 直播人脸检测和特征点收集

    前言: 这里要介绍的是 Tensorflow.js 官方提供的两个人脸检测模型,分别是 face-detection 和 face-landmarks-detection.他们不但可以对视频中的人间进 ...

  8. Proxmox VE软件防火墙的配置

    1 软件防火墙的基本概念 防火墙是计算机网络中用于保护网络安全的关键技术.防火墙可以是硬件设备部署在网络出口,也可以是软件部署在终端设备出口.本文主要介绍软件防火墙. 软件防火墙可以根据网络流量的方向 ...

  9. 2.0 Python 数据结构与类型

    数据类型是编程语言中的一个重要概念,它定义了数据的类型和提供了特定的操作和方法.在 python 中,数据类型的作用是将不同类型的数据进行分类和定义,例如数字.字符串.列表.元组.集合.字典等.这些数 ...

  10. idea的maven home默认路径无法修改成功的问题

    主要原因是项目里有许多初始默认文件 删除项目初始化后的生成的.mvn mvnw mvnw.cmd help.md .gitignore等文件后再修改mavenhome,然后reload maven就修 ...