好家伙,本章我们尝试实现computed属性

0.完整代码已开源

https://github.com/Fattiger4399/analytic-vue.git

1.分析

1.1computed的常见使用方法

1. 计算依赖数据:当某个数据发生变化时,computed属性可以自动更新,并返回计算结果。例如:
<template>
<div>
<p>用户姓名:{{ userName }}</p>
<p>用户年龄:{{ age }}</p>
</div>
</template> <script>
export default {
data() {
return {
userName: '张三',
age: 25,
};
},
computed: {
// 计算用户年龄显示格式
formattedAge() {
return this.age.toString().padStart(2, '0');
},
},
};
</script>

2. 数据过滤:利用computed属性对数据进行过滤处理,例如:
<template>
<div>
<p>列表数据:{{ filteredList }}</p>
</div>
</template> <script>
export default {
data() {
return {
list: [1, 2, 3, 4, 5],
filterValue: 3,
};
},
computed: {
// 计算过滤后的列表数据
filteredList() {
return this.list.filter((item) => item === this.filterValue);
},
},
};
</script>


而在computed定义中又分为两种写法:函数和属性
computed: {
//1.函数
fullName() {
console.log('执行')
return this.firstName + this.lastName
},
//2.属性
goodName: {
get() {
return this.firstName + this.lastName
},
set(value) {
// ....
}
}
}

1.2.Vue实现computed大概步骤

1. 初始化阶段:当创建 Vue 实例时,Vue 会遍历 data 中的每个属性,并使用 Object.defineProperty 方法将它们转化为响应式。
这个过程会在 data 对象上创建一层 Watcher 对象,用于监听数据的变化。 2. 创建 Computed:当创建一个 Computed 属性时,Vue 会调用 initComputed 函数,该函数负责注册这个 Computed 属性。
注册过程中,会创建一个 Watcher 对象,并将其挂载到 Computed 属性的表达式上。
这样,当表达式依赖的数据发生变化时,Watcher 对象可以监听到这些变化,并更新 Computed 属性的值。 3. 更新 Computed 值:当 data 中的数据发生变化时,对应的 Watcher 对象会收到通知。

Watcher 对象会执行 Computed 属性的 get 方法,计算出新的 Computed 值。然后,Watcher 对象会通知视图层更新,使用新的 Computed 值。 4. 缓存 Computed 值:为了提高性能,Vue 会缓存 Computed 属性的计算结果。
当 Computed 属性的依赖数据发生变化时,Vue 会先检查依赖数据的变化是否导致 Computed 值需要重新计算。
 如果需要重新计算,Vue 会清除缓存,并调用 Computed 属性的 get 方法计算新值。如果不需要重新计算,Vue 会直接使用缓存的旧值。

2、代码实现

//initState.js

 
 import Dep from "./observe/dep.js";
export function initState(vm) {
// console.log(vm)
//
const opts = vm.$options
if (opts.data) {
initData(vm);
}
if (opts.watch) {
initWatch(vm);
}
if (opts.props) {
initProps(vm);
} if (opts.computed) {
initComputed(vm);
}
if (opts.methods) {
initMethod(vm);
}
} function initComputed(vm) {
let computed = vm.$options.computed
console.log(computed)
let watcher = vm.computedWatchers = {} for (let key in computed) {
let userDef = computed[key] let getter = typeof userDef == 'function' ? userDef : userDef.get
watcher[key] = new Watcher(vm, getter, () => {}, {
//标记此为computed的watcher
lazy: true
})
defineComputed(vm, key, userDef)
}
}
let sharedPropDefinition = {} function defineComputed(target, key, userDef, ) {
sharedPropDefinition = {
enumerable: true,
configurable: true,
get: () => {},
set: () => {}
}
if (typeof userDef == 'function') {
sharedPropDefinition.get = createComputedGetter(key)
} else {
sharedPropDefinition.get = createComputedGetter(key)
sharedPropDefinition.set = userDef.set
}
Object.defineProperty(target, key, sharedPropDefinition)
}
//高阶函数
function createComputedGetter(key) {
return function () {
// if (dirty) {
// }
let watcher = this.computedWatchers[key]
if (watcher) {
if (watcher.dirty) {
//执行 求值
watcher.evaluate() //
}
if(Dep.targer){ //说明 watcher.depend()
}
return watcher.value
}
}
}

1.sharedPropDefinition: 这是一个空对象,用于定义计算属性的属性描述符(property descriptor)。属性配置
它包含了enumerable、configurable、get和set等属性配置,这些配置决定了计算属性在对象上的可枚举性、可配置性以及获取和设置属性时的行为。

2.computedWatchers: 该对象用于存储计算属性的观察者(watcher)。
每一个计算属性都会被创建一个对应的观察者对象,并将其存储在computedWatchers对象中。
观察者对象负责侦听计算属性的依赖变化,并在需要时更新计算结果。

3.createComputedGetter(): 该函数用于创建计算属性的 getter 。
getter 函数会在访问计算属性时被调用,它首先会检查观察者对象是否存在,如果存在则判断观察者对象是否需要重新计算计算属性的值,
如果需要则执行求值操作,并最终返回计算属性的值。
此外,通过Dep.target的判断,可以将计算属性的依赖添加到依赖收集器中,以便在依赖变化时及时更新计算属性的值。

watcher.js

class Watcher {
//vm 实例
//exprOrfn vm._updata(vm._render())
constructor(vm, exprOrfn, cb, options) {
// 1.创建类第一步将选项放在实例上
this.vm = vm;
this.exprOrfn = exprOrfn;
this.cb = cb;
this.options = options;
//for conputed
this.lazy = options.lazy
this.dirty = this.lazy
// 2. 每一组件只有一个watcher 他是为标识
this.id = id++
this.user = !!options.user
// 3.判断表达式是不是一个函数
this.deps = [] //watcher 记录有多少dep 依赖
this.depsId = new Set()
if (typeof exprOrfn === 'function') {
this.getter = exprOrfn
} else { //{a,b,c} 字符串 变成函数
this.getter = function () { //属性 c.c.c
let path = exprOrfn.split('.')
let obj = vm
for (let i = 0; i < path.length; i++) {
obj = obj[path[i]]
}
return obj //
}
}
// 4.执行渲染页面
// this.value = this.get() //保存watch 初始值
this.value = this.lazy ? void 0 : this.get() }
addDep(dep) {
//去重 判断一下 如果dep 相同我们是不用去处理的
let id = dep.id
// console.log(dep.id)
if (!this.depsId.has(id)) {
this.deps.push(dep)
this.depsId.add(id)
//同时将watcher 放到 dep中
// console.log(666)
dep.addSub(this) }
// 现在只需要记住 一个watcher 有多个dep,一个dep 有多个watcher
//为后面的 component
}
run() { //old new
let value = this.get() //new
let oldValue = this.value //old
this.value = value
//执行 hendler (cb) 这个用户wathcer
if (this.user) {
this.cb.call(this.vm, value, oldValue)
}
}
get() {
// Dep.target = watcher pushTarget(this) //当前的实例添加
const value = this.getter.call(this.vm) // 渲染页面 render() with(wm){_v(msg,_s(name))} ,取值(执行get这个方法) 走劫持方法
popTarget(); //删除当前的实例 这两个方法放在 dep 中
return value
}
//问题:要把属性和watcher 绑定在一起 去html页面
// (1)是不是页面中调用的属性要和watcher 关联起来
//方法
//(1)创建一个dep 模块
updata() { //三次
//注意:不要数据更新后每次都调用 get 方法 ,get 方法回重新渲染
//缓存
// this.get() //重新 // 渲染
if(this.lazy){
this.dirty = true
}else{
queueWatcher(this)
}
}
evaluate() {
this.value = this.get()
this.dirty = false
}
depend(){
let i = this.deps.length
while(i--){
this.deps[i].depend()
}
}
}

1.evaluate(): 该方法用于求值计算属性的结果。
它会调用计算属性的 getter 方法(也就是sharedPropDefinition.get或createComputedGetter()函数中返回的函数),
获取计算属性的最新值,并将该值保存在观察者对象的value属性中。
同时,将观察者对象的dirty属性设置为false,表示计算属性的值已经是最新的了。

2.depend():遍历所有的依赖对象,并调用它们的depend()方法,

dep.js

class Dep {
constructor() {
this.subs = []
this.id = id++
}
//收集watcher
depend() { //我们希望water 可以存放 dep
//实现双休记忆的,让watcher 记住
//dep同时,让dep也记住了我们的watcher
Dep.targer.addDep(this)
// this.subs.push(Dep.targer) // id:1 记住他的dep
}
addSub(watcher){
this.subs.push(watcher)
}
//更新
notify() {
// console.log(Dep.targer)
this.subs.forEach(watcher => {
watcher.updata()
})
}
}

2.depend(): 该方法用于将计算属性的观察者对象添加到依赖收集器(Dependency)中。
在计算属性的 getter 方法执行过程中,如果访问了其他响应式属性(依赖),

那么这些响应式属性对应的观察者对象会将当前的计算属性的观察者对象添加到它们的依赖列表中。
这样,在依赖变化时,观察者对象会被通知到,并重新执行计算属性的 getter 方法来更新计算属性的值。
depend()方法通过遍历观察者对象的deps数组,将每个依赖的观察者对象的depend()方法依次调用,

从而将计算属性的观察者对象添加到每个依赖的依赖列表中。(在compoted的watcher执行完毕后,还有其他元素的wathcer等待执行)

3.预览效果

 

Vue源码学习(十七):实现computed计算属性的更多相关文章

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

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

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

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

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

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

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

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

  5. 最新 Vue 源码学习笔记

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

  6. Vue 源码学习(1)

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

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

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

  8. VUE 源码学习01 源码入口

    VUE[version:2.4.1] Vue项目做了不少,最近在学习设计模式与Vue源码,记录一下自己的脚印!共勉!注:此处源码学习方式为先了解其大模块,从宏观再去到微观学习,以免一开始就研究细节然后 ...

  9. Vue源码学习(一):调试环境搭建

    最近开始学习Vue源码,第一步就是要把调试环境搭好,这个过程遇到小坑着实费了点功夫,在这里记下来 一.调试环境搭建过程 1.安装node.js,具体不展开 2.下载vue项目源码,git或svn等均可 ...

  10. Vue.js 源码分析(六) 基础篇 计算属性 computed 属性详解

    模板内的表达式非常便利,但是设计它们的初衷是用于简单运算的.在模板中放入太多的逻辑会让模板过重且难以维护,比如: <div id="example">{{ messag ...

随机推荐

  1. Java不能操作内存?Unsafe了解一下

    前言 C++可以动态的分类内存(但是得主动释放内存,避免内存泄漏),而java并不能这样,java的内存分配和垃圾回收统一由JVM管理,是不是java就不能操作内存呢?当然有其他办法可以操作内存,接下 ...

  2. 2023-08-24:请用go语言编写。给定一个长度为n的数组arr, 现在你有一次机会, 将其中连续的K个数全修改成任意一个值, 请你计算如何修改可以使修改后的数 列的最长不下降子序列最长。 请输出

    2023-08-24:请用go语言编写.给定一个长度为n的数组arr, 现在你有一次机会, 将其中连续的K个数全修改成任意一个值, 请你计算如何修改可以使修改后的数 列的最长不下降子序列最长. 请输出 ...

  3. 在阿里云和腾讯云的轻量应用服务器上搭建Hadoop集群

    引入 本文在两台2核2g的云服务器上搭建了Hadoop集群,两台云服务器分别是阿里云(hjm)和腾讯云(gyt),集群部署规划如下: hjm gyt HDFS NameNode\SecondaryNa ...

  4. GeoServer发布影像WMTS服务

    WMTS提供了一种采用预定义图块方法发布数字地图服务的标准化解决方案. WMTS: 切片地图web服务(OpenGIS Web Map Tile Service) 使用GeoServer发布WMTS服 ...

  5. Jmeter连接数据库sql语句操作,查询后取值做变量

    第一步 :导入jar包 第二步 :创建JDBC Reques 第三步 :创建JDBC Connection Configuration  第四步:在request中输入数据进行操作 Query Typ ...

  6. Graph RAG: 知识图谱结合 LLM 的检索增强

    本文为大家揭示 NebulaGraph 率先提出的 Graph RAG 方法,这种结合知识图谱.图数据库作为大模型结合私有知识系统的最新技术栈,是 LLM+ 系列的第三篇,加上之前的图上下文学习.Te ...

  7. .NET高性能开发-位图索引(一)

    首先来假设这样一个业务场景,大家对于飞机票应该不陌生,大家在购买机票时,首先是选择您期望的起抵城市和时间,然后选择舱等(公务舱.经济舱),点击查询以后就会出现航班列表,随意的点击一个航班,可以发现有非 ...

  8. 网络层IP数据包

    网络层 功能 选择数据通过网络(IP地址)的最佳路径 协议字段 版本号(4bit):指IP协议版本.并且通信双方使用的版本必须一致,目前我们使用的是IPv4,表示为0100 十进制 是4 首部长度(4 ...

  9. Spring扩展接口(4):InstantiationAwareBeanPostProcessor

    在此系列文章中,我总结了Spring几乎所有的扩展接口,以及各个扩展点的使用场景.并整理出一个bean在spring中从被加载到最终初始化的所有可扩展点的顺序调用图.这样,我们也可以看到bean是如何 ...

  10. Django admin 注册自己的路由

    通过 admin.py 在 Django 后台注册自己的路由(对应自己的视图) 要在一个 app 里面的 admin.py 添加如下代码 # django2\apps\business\admin.p ...