Vue源码:浅谈vue2响应式原理

177 阅读3分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第12天,点击查看活动详情

前言

目前vue已经更新到了vue3,今天简单说一下vue2的响应式原理,明天再更新vue3
的响应式原理

什么是响应式

简单来说就是当我们的数据发生变化,vue会通知使用该数据的代码做出反应
在vue中常用的就是当数据发生变化,页面也会更新数据

实现的思路

先实现一个watchEffect函数,他接收一个函数,当依赖被修改会调用函数,初始化会运行一次

watchEffect函数的实现

原理很简单,把接收到的函数保存到一个Set里面,因为他不重复,所以用
当数据被修改用在把Set里面的函数执行一遍
const dep = new Set()                    
const activeEffect = null 
const watchEffect = function(effect){
    activeEffect = effect
    effect()
    dep.add(activeEffect)
}
  • 创建一个dep是一个Set实例
  • 创建activeEffect,负责把函数给dep
  • 因为watchEffect初次运行会执行,所以调用一次effect函数
  • 在把effect保存到dep中 当数据发生变化我们可以循环执行dep里面的函数
dep.forEach((item)=>{
    item()
})

到这里有的同学就会有疑问了,难道我们要把dep里面的所有的函数都执行一边?这和vue的watchEffect不同啊

在这里,我们还差一个函数依赖的变量,作为key

这里先暂停,我们先优化一下代码,我们需要一个添加effect到Set的函数,一个循环执行的函数,那么我们就可以创建一个类

class Dep {
    constructor(){
        this.subscribers = new Set()
    }
    //添加effect的函数
    depend() {
        if (activeEffect) {
            this.subscribers.add(activeEffect)
        }
    }
    //执行effect的函数
    notify() {
        this.subscribers.forEach((effect)=>{
            effect()
        })
    }
}
let activeEffect = null
const dep = new Dep()
function watchEffect(effect) {
    activeEffect = effect
    effect()
    //添加函数
    dep.depend()
    activeEffect = null
}

此时我们的代码就好一点了,

watchEffect只能监听响应式的数据,我们创建一个reactive函数

reactive函数

reactive函数接收一个对象,返回一个响应式对象,在vue2中使用Object.defineProperty进行响应式绑定
function reactive(raw) {
    Object.keys(raw).forEach((key)=>{
        let value = raw[key]
        Object.defineProperty(raw, key, {
            get(){
                //当数据加载时,拿到依赖
                dep.depend()
                return value
            },
            set(newValue){
                value = newValue
                //当数据修改时,运行监控的函数
                dep.notify()
            }   
        })
    })
    return raw
}

但是此时我们只有一个dep实例,总不能每次都用这个,我们希望一个属性,对应一个dep,每一个dep存储在map中

function getDep() {
    dep = new Dep()
    return dep
}

我们dep全部存储到map数据结构中,先判断dep所对应的map是否存在,如果不存在,新建一个,在判断dep是否存在,不存在就创一个,存在则返回dep

function getDep(target,key) {
    // 判断target在Map是否存在
    let depMap = targetMap.get(target)
    if (!depMap) {
        depMap = new Map()
        // map作为值添加到weakMap中,键为target
        targetMap.set(target,depMap)
    }
    let dep = targetMap.get(key) 
    if (!dep) {
        dep = new Dep()
        // 创建dep对象,传给map,一个dep对应一个reactive对象,key是counter
        depMap.set(key,dep)
    }
    return dep
}

创建一个函数,Object.keys(raw)循环时,调用getDep拿到一个dep,,在get时收集依赖,set时执行函数

function reactive(raw) {
    Object.keys(raw).forEach((key)=>{
        const dep = getDep(raw,key)
        let value = raw[key]
        Object.defineProperty(raw, key, {
            get(){
                //此时能够添加函数是因为我们的activeEffect放在了函数外面,是可以拿到的
                dep.depend()
                return value
            },
            set(newValue){
                value = newValue
                dep.notify()
            }   
        })
    })
    return raw
}

测试

const info = reactive({name:"joke"})
watchEffect(()=>{
    console.log(info.name);
})
info.name = "tom"

监听info.name

  • 应该会先输出joke(副作用)
  • 在输出tom

微信图片_20220423131716.png

总结

有点乱,不明白的大家评论区一起交流
  1. 我们使用watchEffect函数,当他的依赖被读取,会执行get函数,此时activeEffect就是我们要收集的函数,会被dep收集,并且执行一次,也就是watchEffect的初始化和副作用
  2. 当数据发生更改,set函数会执行,我们的watchEffect所对应的函数需要执行一次