一起养成写作习惯!这是我参与「掘金日新计划 · 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
总结
有点乱,不明白的大家评论区一起交流
- 我们使用watchEffect函数,当他的依赖被读取,会执行get函数,此时activeEffect就是我们要收集的函数,会被dep收集,并且执行一次,也就是watchEffect的初始化和副作用
- 当数据发生更改,set函数会执行,我们的watchEffect所对应的函数需要执行一次