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

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

 

完整代码已开源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. 磐舟磐基平台:基于KubeEdge的落地实践

    摘要:实现统一管理.简化多集群的运维系统.减少运营成本:同时也成功将前面提到的500台鲲鹏服务器以及它上面的BC Linux for Euler集群纳入磐基PaaS平台的大家庭之中,运维效率大幅增加. ...

  2. 细节决定成败,聊聊JS的类型(上)

    今天我们来讲讲 JavaScript 的内容,在这个部分,我首先想跟你聊一聊类型. JavaScript 类型对每个前端程序员来说,几乎都是最为熟悉的概念了.但是你真的很了解它们吗?我们不妨来看看下面 ...

  3. LocalTime转String类型,如下图

  4. Linux网络设备命名规则简介

    Linux网络设备命名规则简介 几年前, Linux内核为网络接口分配名称采用的是一种简单和直观的方式:一个固定的前缀和一个递增的序号.比如,内核使用eth0名称以标识启动后第一个加载的网络设备,第二 ...

  5. 利用Aspose.Word对Word文件添加印章处理以及实现业务数据的替换处理

    有时候,我们在处理大量文档的时候,需要批量给Word文档添加印章处理,方便打印操作,本篇随笔介绍利用Aspose.Word对Word文件添加印章处理以及实现业务数据的替换处理. 1.利用Aspose. ...

  6. iptables防火墙调试,想打印个日志就这么难

    背景 怎么会讲这个话题,这个说来真的长了.但是,长话短说,也是可以的. 我前面的文章提到,线上的服务用了c3p0数据库连接池,会偶发连接泄露问题,而分析到最后,又怀疑是db侧主动关闭连接,或者是服务所 ...

  7. Cilium 系列-3-Cilium 的基本组件和重要概念

    系列文章 Cilium 系列文章 前言 安装完了,我们看看 Cilium 有哪些组件和重要概念. Cilium 组件 如上所述,安装 Cilium 时,会安装几个运行组件(有些是可选组件), 它们各是 ...

  8. VScode 中golang 调试 F5,json文件内容更改 Go

    调试:在Vscode软件中调试.go文件代码 1.1.1 确保调试文件正常运行 准备好.go文件,并且该文件能正常运行,终端输入命令运行程序,如:go run time.go 测试代码: packag ...

  9. 关于微信小程序原生组件与uniApp混合开发过程遇到的问题与解决方式

    前言: 在实际开发过程中,尤其是小程序的开发,我们常常会遇到一些在文档中解决不了的问题,在这里,我就浅谈一下我遇到的一些问题 1.小程序的构建框架是uni-app,却突然被要求用原生的微信小程序代码来 ...

  10. 超详细的mysql总结(DQL)

    上一篇文章总结了 DDL.DML的使用,这一篇文章把剩下的 DQL 加上~   DQL(Data Query Language)即数据库查询语言,用来查询所需要的信息,在查询的过程中,需要判断所查询的 ...