学习vue2源码笔记5

本文详细解析了Vue中计算属性的工作原理,包括其定义、特性(如懒加载和脏值检测)、实现步骤(涉及Dep对象、Watcher结构等),以及watch机制,展示了如何监听值变化并执行回调。
摘要由CSDN通过智能技术生成

目录

1. 计算属性的原理

关于计算属性的特性:

实现步骤 

代码:

2. watch 的原理

特性:

实现步骤


1. 计算属性的原理
关于计算属性的特性:

(1)相当于是一个defineProperty,详情看如下代码:

 const vm = new Vue({
        data() {
            return {
                name: 'aaa',
                age: 18,
                address: {
                    ad :1
                },
                arr:[1,2,3,4,[3]]
            }
        },
        computed: {
          // 相当于只执行了get方法
          fullName() {
              return this.name + this.age
          },
          fullName1: {
              get() {
                  return this.name + this.age
              },
              set(newVal) {
                  console.log(newVal);
              }
          }
        },
        el: "#app",
    })

(2)多次取值,在不改变原数值的情况下只取一次,也就是有脏值检测

(3)当name属性改变时,计算属性也会改变,所以我们就可以知道计算属性也是一个watcher

下图为计算属性watcher与渲染watcher的关系

实现步骤 

   1. 将watcher维护成一个队列在Dep中有stack栈,两个方法 PushTarget (watcher) 和 popTarget( ).  
   2.在state.js看用户选项有无computed,有调.initComputed 初始化
  3. 在initComputed内循环遍历.拿到每个计算属性userDef
  4.声明 defineComputed 方法 在 vm上加上 key.  
  写到这里会发现页面也会随着数据而更新,但是没有实现计算实的②特征. 原因是依赖的数据收集到了外层的 watcher.我会给所有计算属性加上 watcher

代码:

1. Dep.js

let stack = []
export function pushTarget(watcher) {
    stack.push(watcher)
    Dep.target = watcher
}
export function popTarget() {
    stack.pop()
    Dep.target = stack[stack.length - 1];
}

2,3,4步

export function initState(vm) {
    if(vm.$options.data) {
        initDate(vm)
    }
    if(vm.$options.computed) {
        initComputed(vm)
    }
}
function initComputed(vm) {
    // 循环获取用户的computed
    let computed = vm.$options.computed
    for (const key in computed) {
        let userDef = computed[key]
        defineComputed(vm,key,userDef)
    }
}
// 将计算属性重写在vm上,方便取值
function defineComputed(target,key,userDef) {
    let getter = typeof userDef === 'function' ? userDef : userDef.get
    let setter = typeof userDef === 'function' ? (()=>{}) : userDef.set
    Object.defineProperty(target,key,{
        get:getter,
        set:setter
    })
}

 
4. new watcher的方法去获取自己的 watcher,参数为.  vm. fu(get). {lazy:true}. 将watcher 存放起来  
5.将 watcher 类加上 this.lazy和 this、drity,并执行如果 lazy 为 flase才执行 this. get的方法  
6.因为要实现多次取同样值只执行一次,就需要对 defineProperty的get 进行包装.有函数 createComputer (key).key是 具体的计算属性因为之前把计算属性列表watcher  放到了vm,_C……中,看 dirty 属性如果是脏的就去watcher的evaluate方法取值,

7. get方法的 this.不正确.需要 call 一下
8.这时候我们会发现属性的Dep只收集到了.  计算属性 watcher 无法去更新 渲染页面.那么就需要让计算属性内的 Dep 收集到上层渲染watcher,  也就是求完值后 调用watcher 的 depend 方法后让 this.Deps. depend.  最后.当更新的时候还需要让 dirty 为true

5,7,8步 

class Watcher {
    constructor(vm,fn,options) {
        // 每一个组件都有一个watcher,要添加唯一标识
        this.id = id++;
        this.getter = fn;
        // 标识是否为初渲染
        this.renderWatcher = options
        // 用来记住dep方便后续的一些方法
        this.deps = []
        this.depsId = new Set()
        this.lazy = options.lazy
        this.drity = this.lazy
        this.vm = vm

        //调用渲染函数进行渲染
        this.lazy ? undefined : this.get()
    }
    addDep(dep) {
        let id = dep.id
        if(!this.depsId.has(id)) {
            this.deps.push(dep)
            this.depsId.add(id)
            dep.addSub(this)
        }
    }
    // 针对于计算属性的求值
    evaluate() {
        this.value = this.get()
        this.drity = false
    }
    // 针对于计算属性的dep绑定上层的渲染watcher
    depend() {
        this.deps.forEach(dep => {
            dep.depend()
        })
    }
    get() {
        pushTarget(this)
        let value = this.getter.call(this.vm)
        // 只对在模板当中使用的变量进行依赖收集
        popTarget()
        return value
    }
    update() {
        if(this.lazy) {
            this.drity = true
        }else {
            queueWatcher(this)
        }
    }
    run() {
        this.get()
    }
}

4,6

function initComputed(vm) {
    // 循环获取用户的computed
    let computed = vm.$options.computed
    // 计算属性watcher
    let watcher = vm._computedWatcher = {}
    for (const key in computed) {
        let userDef = computed[key]
        let getter = typeof userDef === 'function' ? userDef : userDef.get
        watcher[key] = new Watcher(vm,getter,{lazy:true})

        defineComputed(vm,key,userDef)
    }
}
// 将计算属性重写在vm上,方便取值
function defineComputed(target,key,userDef) {
    let setter = typeof userDef === 'function' ? (()=>{}) : userDef.set
    Object.defineProperty(target,key,{
        get:crateComputedGetter(key),
        set:setter
    })
}
function crateComputedGetter(key) {
    return function () {
       let watcher = this._computedWatcher[key]
       if(watcher.drity) {
           watcher.evaluate()
       }
       // 将属性的dep放到渲染watcher上
       if(Dep.target) {
           Dep.target.depend()
       }
       return watcher.value
    }
}
2. watch 的原理
特性:

(1)可以监听一个值的变化,当这个值发生变化时,可以执行一个回调,会传两个参数一个是变化的值另一个是变化之前的值

用法:底层都是调的$watch这个方法

 const vm = new Vue({
        data() {
            return {
                name: 'aaa',
                age: 18,
            }
        },
        watch: {
            // 第一种函数写法
          name: function (newV,oldV) {
              console.log(oldV,newV);
          },
            // 第二种数组写法
          age: [
              (newV,oldV)=>{
                  console.log(oldV,newV);
              }
          ]
        },
        el: "#app",
    })
    // 第三种写法
    vm.$watch(()=>vm.name,(newV,oldV)=>{
        console.log(oldV,newV);
    })
    setTimeout(()=>{
        vm.name = 'bbb'
        vm.age = 30
    },1000)
实现步骤

1. 在index文件内,Vue的原形上面加上$watch方法,其参数有experOrFn,cb
2. 在state文件内,判断用户选线有无watch属性,有就使用initWatch对其进行初始化

initWatch函数所做的事情:首先拿到用户的watch选项,循环遍历拿到watch里的属性,属性值一共会有三种情况,1.字符串,2.数组,3.函数,判断如果是数组再进行遍历拿出每一个回调。传给createWatcher不是数组直接调createWatcher

createWatcher函数所做的事情:判断拿到的handler是函数还是字符串,函数直接调vm.$watch函数字符串就再到vm拿到相应函数后再调

3. 在$watch里创建new Watcher 因为watch本来就是一个自定义的观察者。然后对于watcher类的代码进行一些修改。

1. 

Vue.prototype.$watch = function (exprOrFn,cb) {
    new Watcher(this,exprOrFn,{user:true},cb)
}

2.

// 对于计算属性的操作
function initWatch(vm) {
    let watch = vm.$options.watch
    // 三种情况,字符串,函数,数组
    for (const key in watch) {
        let handler = watch[key]
        if(Array.isArray(watch[key])) {
            for (let i = 0;i < handler.length;i++) {
                createWatcher(vm,key,handler[i])
            }
        }else {
            createWatcher(vm,key,handler)
        }
    }
}
function createWatcher(vm,key,handler) {
    // 对于handle是一个字符串,那么函数体就是挂载在method上
    if(typeof handler === 'string') {
        handler = vm[handler]
    }
    return vm.$watch(key,handler)
}

3.

class Watcher {
    constructor(vm,exprOrFn,options,cb) {
        // 每一个组件都有一个watcher,要添加唯一标识
        this.id = id++;
        if(typeof exprOrFn === 'string') {
            this.getter = function () {
                return vm[exprOrFn]
            }
        }else {
            this.getter = exprOrFn;
        }
        // 标识是否为初渲染
        this.renderWatcher = options
        // 用来记住dep方便后续的一些方法
        this.deps = []
        this.depsId = new Set()
        this.lazy = options.lazy
        this.drity = this.lazy
        this.cb = cb
        this.user = options.user
        this.vm = vm

        //调用渲染函数进行渲染
        this.value = this.lazy ? undefined : this.get()
    }
    addDep(dep) {
        let id = dep.id
        if(!this.depsId.has(id)) {
            this.deps.push(dep)
            this.depsId.add(id)
            dep.addSub(this)
        }
    }
    // 针对于计算属性的求值
    evaluate() {
        this.value = this.get()
        this.drity = false
    }
    // 针对于计算属性的dep绑定上层的渲染watcher
    depend() {
        this.deps.forEach(dep => {
            dep.depend()
        })
    }
    get() {
        pushTarget(this)
        let value = this.getter.call(this.vm)
        // 只对在模板当中使用的变量进行依赖收集
        popTarget()
        return value
    }
    update() {
        if(this.lazy) {
            this.drity = true
        }else {
            queueWatcher(this)
        }
    }
    run() {
        let oldValue = this.value
        let newValue = this.get()
        if(this.user) {
            this.cb.call(this.vm,newValue,oldValue)
        }
    }
}

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值