Vue源码学习(十七):实现computed计算属性
好家伙,本章我们尝试实现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
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计算属性的更多相关文章
- Vue源码学习1——Vue构造函数
Vue源码学习1--Vue构造函数 这是我第一次正式阅读大型框架源码,刚开始的时候完全不知道该如何入手.Vue源码clone下来之后这么多文件夹,Vue的这么多方法和概念都在哪,完全没有头绪.现在也只 ...
- 【Vue源码学习】依赖收集
前面我们学习了vue的响应式原理,我们知道了vue2底层是通过Object.defineProperty来实现数据响应式的,但是单有这个还不够,我们在data中定义的数据可能没有用于模版渲染,修改这些 ...
- Vue源码学习三 ———— Vue构造函数包装
Vue源码学习二 是对Vue的原型对象的包装,最后从Vue的出生文件导出了 Vue这个构造函数 来到 src/core/index.js 代码是: import Vue from './instanc ...
- Vue源码学习二 ———— Vue原型对象包装
Vue原型对象的包装 在Vue官网直接通过 script 标签导入的 Vue包是 umd模块的形式.在使用前都通过 new Vue({}).记录一下 Vue构造函数的包装. 在 src/core/in ...
- 最新 Vue 源码学习笔记
最新 Vue 源码学习笔记 v2.x.x & v3.x.x 框架架构 核心算法 设计模式 编码风格 项目结构 为什么出现 解决了什么问题 有哪些应用场景 v2.x.x & v3.x.x ...
- Vue 源码学习(1)
概述 我在闲暇时间学习了一下 Vue 的源码,有一些心得,现在把它们分享给大家. 这个分享只是 Vue源码系列 的第一篇,主要讲述了如下内容: 寻找入口文件 在打包的过程中 Vue 发生了什么变化 在 ...
- 【Vue源码学习】响应式原理探秘
最近准备开启Vue的源码学习,并且每一个Vue的重要知识点都会记录下来.我们知道Vue的核心理念是数据驱动视图,所有操作都只需要在数据层做处理,不必关心视图层的操作.这里先来学习Vue的响应式原理,V ...
- VUE 源码学习01 源码入口
VUE[version:2.4.1] Vue项目做了不少,最近在学习设计模式与Vue源码,记录一下自己的脚印!共勉!注:此处源码学习方式为先了解其大模块,从宏观再去到微观学习,以免一开始就研究细节然后 ...
- Vue源码学习(一):调试环境搭建
最近开始学习Vue源码,第一步就是要把调试环境搭好,这个过程遇到小坑着实费了点功夫,在这里记下来 一.调试环境搭建过程 1.安装node.js,具体不展开 2.下载vue项目源码,git或svn等均可 ...
- Vue.js 源码分析(六) 基础篇 计算属性 computed 属性详解
模板内的表达式非常便利,但是设计它们的初衷是用于简单运算的.在模板中放入太多的逻辑会让模板过重且难以维护,比如: <div id="example">{{ messag ...
随机推荐
- [python]enumerate迭代
Python中有个内置的函数叫做 enumerate,可以在迭代时返回元素的索引. # 示例代码01 warframe = ["saryn", "wisp", ...
- Java 日志系列:JUL 使用和原理分析
目录 一. 简介 二.使用 三.日志级别 四.Logger 继承关系 五.配置文件 六.原理解析 一. 简介 JUL 全称 Java util Logging 是 java 原生的日志框架,使用时不需 ...
- 02.中台框架前台项目 admin.ui.plus 学习-介绍与简单使用
中台框架前台项目 admin.ui.plus 的初识 基于 vue3.x + CompositionAPI setup 语法糖 + typescript + vite + element plus + ...
- Tarjan基础用法
\(\operatorname{Tarjan}\) 基础用法 目录 \(\operatorname{Tarjan}\) 基础用法 \(\operatorname{Tarjan}\) 求最近公共祖先 前 ...
- Kafka Stream 高级应用
9.1将Kafka 与其他数据源集成 对于第一个高级应用程序示例,假设你在金融服务公司工作.公司希望将其现有数据迁移到新技术实现的系统中,该计划包括使用 Kafka.数据迁移了一半,你被要求去更新公司 ...
- ISO/OSI七层模型的分层与作用
ISO/OSI的七层模型 第七层:应用层 为用户提供服务,给用户一个操作界面,如window的图形界面,Linux的命令行: 第六层:表示层 数据提供表示:把01二进制转换为图像数字等用户可以看懂的内 ...
- 聊聊基于Alink库的推荐系统
概述 Alink提供了一系列与推荐相关的组件,从组件使用得角度来看,需要重点关注如下三个方面: 算法选择 推荐领域有很多算法,常用的有基于物品/用户的协同过滤.ALS.FM算法等.对于不同的数据场景, ...
- [NSSRound#1 Basic]basic_check
打开网站,发现啥也没有: 就用dirsearch扫了一遍.发现还是没有有用信息: 只有再另找方法: 再用nikto扫一次: 发现一个put方法,就用put上传一个一句话木马:可以用插件restlien ...
- getc()、getchar()、getch() 和 getche() 的区别
所有这些函数都从输入中读取一个字符并返回一个整数值.返回整数以容纳用于指示失败的特殊值.EOF值通常用于此目的. getc() 它从给定的输入流中读取单个字符,并在成功时返回相应的整数值(通常 ...
- 【Flask模板注入】
[Flask模板注入]--概览 背景 Flask是python语言下的轻量级web应用框架,可以用来开发一些简单的网站.它使用Jinjia2渲染引擎(将html文件存放在templates文件夹中,当 ...