好家伙,本章我们尝试实现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. python处理类似json的文件

    前言 有些文件长得像json的键值对格式,但又不完全是.有时需要提取出其中某些值,可以先手动处理成json文件,然后用python的json模块. 示例1:每行键值对 提取其中的caseId的值 {& ...

  2. 如何在达梦数据库中追踪慢SQL

    在达梦数据库中,我们可以通过开启日志记录和设置最小执行时间来追踪慢SQL.下面是具体的步骤: 1. 修改dm.ini文件 使用以下命令编辑dm.ini文件: cd /home/dmdba/dmdbms ...

  3. 《小白WEB安全入门》02. 开发篇

    @ 目录 初识HTML潜在漏洞 初识CSS潜在漏洞 初识JS潜在漏洞 初识后端潜在漏洞 后端能做什么 后端种类 后端框架 潜在漏洞 本系列文章只叙述一些超级基础理论知识,极少有实践部分 本文涉及到的语 ...

  4. QA|20211013|SecureCRT:如图,有很多^,中文显示有问题,乱码,如何解决

    Q1:如图,有很多^,中文显示有问题,乱码,如何解决 Q2:securecrt的vi展示有问题:少很多字.有很多^M和^,光标无法移动到最右侧 A: 首先检查当前编码格式: 1 echo $LANG ...

  5. python一键过杀软

    python过杀软新 利用python加载shellcode过360.火绒等杀软 先上代码 将以下代码保存到 mt.py import base64 import os import shutil b ...

  6. 使用极速全景图下载大师下载720yun全景图片(一键下载建E、720云全景原图)

    VR全景图片下载 软件简介 极速全景图下载大师下载地址: 点击进入下载页面 极速全景图下载大师(VR全景图下载器)软件官网: 点击进入官网 极速全景图下载大师如何下载720yun全景图片 1.首先,在 ...

  7. WebStrom提交代码到GitLab报错Error: Cannot find any-observable implementation nor global.Observable.

    项目场景: 前端代码完成后,提交代码 问题描述 提交代码到GitLab时,因自动检测机制导致项目提交失败 C:\D\insper\inspur_works\custom-manage-front\no ...

  8. dedebiz实时时间调用

    {dede:tagname runphp='yes'}@me = date("Y-m-d H:i:s", time());{/dede:tagname}

  9. Record - Nov. 27st, 2020 - Exam. REC & SOL

    Problem. 1 Junior - Thinking Desc. & Link. 注意到值域乘范围刚好能过. 然后就存两个桶即可...(数组开小飞了半天才调出来...) Problem. ...

  10. RPM软件包:Red HatPackage Manager,RPM

    RPM软件包是按照GPL条款发行在各个linux版本上使用. 用途 可以安装.删除.升级.刷新和管理RPM软件包 通过RPM软件包管理能知道软件包包含哪些文件,也能知道系统中的某个文件属于哪个RPM软 ...