首页 > 编程语言 >Vue源码学习(十七):实现computed计算属性

Vue源码学习(十七):实现computed计算属性

时间:2023-12-01 22:33:32浏览次数:44  
标签:Vue computed get dep vm watcher 源码 属性

好家伙,本章我们尝试实现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,get,dep,vm,watcher,源码,属性
From: https://www.cnblogs.com/FatTiger4399/p/17870847.html

相关文章

  • [Vue] vue如何监控数据的
    vue会监测data属性下所有层次的数据变化监控对象中的数据通过setter监控,在新建vue实例时就要传入要监控的数据对象中含有的,之后追加的属性,vue默认不做响应式处理,即没有对应的getter/setter如果需要响应式处理,要利用对应的APIVue.set(target,propertyName/index,value)......
  • linux源码趣读总结
    总结linux源码趣读花了半个月左右,看完了闪客的linux源码趣读。感觉之前上的操作系统原理课程只能给你一个模糊的印象,啊,有这个概念来着,有这个算法来着。比起从理论到实践的文字游戏,我还是更喜欢从实践讲理论的脚踏实地。从阅读linux-0.11源码,了解操作系统的构成。所谓的总结......
  • 小市值选股策略代码分享(附python源码)
    小市值选股策略的核心在于通过综合分析公司的基本面、行业定位、财务健康状况以及市场趋势,来寻找那些被市场低估但具备显著成长潜力的股票,同时也要重视风险管理和投资组合的多样化。 今天来给大家分享下小市值策略代码如下:#显式导入BigQuant相关SDK模块frombigdatas......
  • 关于Vue3中调试APP触发异常:exception:white screen cause create instanceContext fai
    bug:reportJSException>>>>exceptionfunction:createInstanceContext,exception:whitescreencausecreateinstanceContextfailed,checkjsstack->atuseStore(app-service.js:2309:15)问题在于:使用了pinia,并且在所有js文件或ts文件中调用超前,导致的加载错误 解决方......
  • kprobes源码走读
    粗略看了下kernel/kprobes.c下的register_kprobe方法。逻辑:调用kprobe_addr方法来根据symbol或者addr+offset来获取需要劫持的地址,symbol和addr不能同时设置,symbol是利用kprobe_lookup_name->kallsyms_lookup_name来查找内核中的符号地址。检查这个kprobe是否重注册了?......
  • vue3+vite项目优化静态资源使用云存储
    项目中的问题1.当我们在维护自己的博客或者自己的网站的时候没有特别好的服务器就会响应特别的慢2.当我们项目特别大的时候也会首屏加载特别慢而且vue项目打包后的js文件特别的庞大还要加载各种资源就会特别的卡顿3.当我们项目中用到了一些3D效果各种3D资源部特别的大的时......
  • vue2+element 表单内使用el-rate组件时,校验失败后重新校验通过了,但校验提示信息未消
    问题:el-rate组件自定义的校验规则,必填项。打开表单,不做任何动作,点击提交,这时表单校验一次,然后再选择el-rate组件的评分,校验提示却并未消除。问题复现:1.打开表单,直接点确定 2.填写完所有的选项后,仍未消除提示 解决:首先排除了v-model是否绑定正确,单词有无错误,校验有没有写......
  • js vue中pdf与img互转
    需要npminstallvue-pdf和npminstallpdfjs-dist,新建js文件pdtToImg.js:importpdffrom"vue-pdf";importJsPDFfrom'pdfjs-dist';constPDFJS=require('pdfjs-dist/build/pdf.js');//import会报错window.pdfjsWorker=require('......
  • 关于解决vue报错"Problems loading reference 'https://schemastore.azurewebsites.ne
    打开setting时会看到有一条三角形的警告信息 看问题描述:无法从该网站加载解决方法:打开设置,找到扩展下的json项 设置之后可以在settings.json文件中看到新增加一项 "json.schemaDownload.enable":false可以直接在界面上设置: "json.schemaDownload.enable":false......
  • Vue3快速上手(B站尚硅谷张天禹老师主讲vue全家桶)
    Vue3快速上手Vue3快速上手1.Vue3简介2.Vue3带来了什么1.性能的提升2.源码的升级3.拥抱TypeScript4.新的特性一、创建Vue3.0工程1.使用vue-cli创建2.使用vite创建二、常用CompositionAPI1.拉开序幕的setup2.ref函数3.reactive函数4.Vue3.0中的响应式原理vue2.x的响应式Vue3.0......