目录
手动收集依赖+通知更新
effect():更改数据后执行,更新依赖该数据的数据(依赖)
track()收集依赖的effect()放进dep(set去重)
更新时触发trigger函数通知dep里所有effect()执行
当依赖的为object类型:WeakMap存obj,Map存obj.props
proxy:自动收集依赖+通知更新
new Proxy(target, handler)
Reflect
Reflect.get(target, propertyKey[, receiver])函数调用方式从对象中读值
reactive(被依赖的数据){return proxy}
vue3新增全局变量activeEffect存储当前执行的effect
track不必再判断obj和obj.key,直接dep存储当前activeEffect
应用
实现ref响应式
用ref实现computed
与Vue2区别
使用
初始化、监听:整个对象级别上进行拦截,无需遍历每个属性
性能:监听整个操作(不用遍历),不仅是读写
功能:可监听属性的增删
收集依赖
类型推导:TypeScript
手动收集依赖+通知更新
effect():更改数据后执行,更新依赖该数据的数据(依赖)
let name = '林三心', age = 22, money = 20
let myself = '', ohtherMyself = ''
const effect1 = () => myself = `${name}今年${age}岁,存款${money}元`
const effect2 = () => ohtherMyself = `${age}岁的${name}居然有${money}元`effect1() // 先执行一次
effect2() // 先执行一次
console.log(myself) // 林三心今年22岁,存款20元
console.log(ohtherMyself) // 22岁的林三心居然有20元
money = 300effect1() // 再执行一次
effect2() // 再执行一次console.log(myself) // 林三心今年22岁,存款300元
console.log(ohtherMyself) // 22岁的林三心居然有300元
track()收集依赖的effect()
放进dep(set去重)
更新时触发trigger函数
通知dep
里所有effect()
执行
let name = '林三心', age = 22, money = 20
let myself = '', ohtherMyself = ''
const effect1 = () => myself = `${name}今年${age}岁,存款${money}元`
const effect2 = () => ohtherMyself = `${age}岁的${name}居然有${money}元`const dep = new Set()
function track () {dep.add(effect1)dep.add(effect2)
}
function trigger() {dep.forEach(effect => effect())
}
track() //收集依赖
effect1() // 先执行一次
effect2() // 先执行一次
console.log(myself) // 林三心今年22岁,存款20元
console.log(ohtherMyself) // 22岁的林三心居然有20元
money = 300trigger() // 通知变量myself和otherMyself进行更新console.log(myself) // 林三心今年22岁,存款300元
console.log(ohtherMyself) // 22岁的林三心居然有300元
当依赖的为object类型:WeakMap存obj,Map存obj.props
const targetMap = new WeakMap()
function track(target, key) {let depsMap = targetMap.get(target)if (!depsMap) {targetMap.set(target, depsMap = new Map())}let dep = depsMap.get(key)if (!dep) {depsMap.set(key, dep = new Set())}...
}
function trigger(target, key) {let depsMap = targetMap.get(target)if (depsMap) {const dep = depsMap.get(key)if (dep) {dep.forEach(effect => effect())}}
}
proxy:自动收集依赖+通知更新
new Proxy(target, handler)
target
包装target (对象/数组/函数甚/proxy对象)
handler
被代理对象上的自定义行为(定义一组处理函数(例如get、set)的对象)
target:被代理者
prop:被代理者的属性
receiver:代理者 ,Proxy 或者继承 Proxy 的对象
const person = { name: '林三心', age: 22 }const proxyPerson = new Proxy(person, {get(target, key, receiver) {return target[key]},set(target, key, value, receiver) {target[key] = value}
})console.log(proxyPerson.name) // 林三心proxyPerson.name = 'sunshine_lin'console.log(proxyPerson.name) // sunshine_lin
Reflect
Proxy和Reflect
的方法都是一一对应的,在Proxy
里使用Reflect
会提高语义化
Proxy的get
对应Reflect.get
Proxy的set
对应Reflect.set
属性访问方法
A.属性访问器(访问):obj.key,obj[key]
B.函数调用:mp.get(key)
Reflect.get(target, propertyKey[, receiver])函数调用方式从对象中读值
receiver
如果target
对象中指定了getter
,receiver
则为getter
调用时的this
值
该方法会拦截目标对象的以下操作:
- 访问属性:
proxy[foo] 和
proxy.bar
- 访问原型链上的属性:
Object.create(proxy)[foo]
Reflect.get()
const person = { name: '林三心', age: 22 }const proxyPerson = new Proxy(person, {get(target, key, receiver) {return Reflect.get(receiver, key) // 相当于 receiver[key]},set(target, key, value, receiver) {Reflect.set(receiver, key, value) // 相当于 receiver[key] = value}
})console.log(proxyPerson.name)proxyPerson.name = 'sunshine_lin'
// 会直接报错,栈内存溢出 Maximum call stack size exceeded
reactive(被依赖的数据){return proxy}
function reactive(target) {const handler = {get(target, key, receiver) {track(receiver, key) // 访问时收集依赖return Reflect.get(target, key, receiver)},set(target, key, value, receiver) {Reflect.set(target, key, value, receiver)trigger(receiver, key) // 设值时自动通知更新}}return new Proxy(target, handler)
}const person = reactive({ name: '林三心', age: 22 }) // 传入reactive
const animal = reactive({ type: 'dog', height: 50 }) // 传入reactive...
vue3新增全局变量activeEffect存储当前执行的effect
track不必再判断obj和obj.key,直接dep存储当前activeEffect
let activeEffect = null
function effect(fn) {activeEffect = fnactiveEffect()activeEffect = null // 执行后立马变成null
}
function track(target, key) {// 如果此时activeEffect为null则不执行下面// 这里判断是为了避免例如console.log(person.name)而触发trackif (!activeEffect) returnlet depsMap = targetMap.get(target)if (!depsMap) {targetMap.set(target, depsMap = new Map())}let dep = depsMap.get(key)if (!dep) {depsMap.set(key, dep = new Set())}dep.add(activeEffect) // 把此时的activeEffect添加进去
}// 每个effect函数改成这么执行
effect(effectNameStr1)
effect(effectNameStr2)
effect(effectAgeStr1)
effect(effectAgeStr2)
effect(effectTypeStr1)
effect(effectTypeStr2)
effect(effectHeightStr1)
effect(effectHeightStr2)
应用
实现ref响应式
function ref (initValue) {return reactive({value: initValue})
}let num = ref(5)//num就会成为一个响应式的数据
console.log(num.value) // 5
用ref实现computed
function computed(fn) {const result = ref()effect(() => result.value = fn()) // 执行computed传入函数return result
}let num1 = ref(5)
let num2 = ref(8)
let sum1 = computed(() => num1.value * num2.value)
let sum2 = computed(() => sum1.value * 10)console.log(sum1.value) // 40
console.log(sum2.value) // 400num1.value = 10console.log(sum1.value) // 80
console.log(sum2.value) // 800num2.value = 16console.log(sum1.value) // 160
console.log(sum2.value) // 1600
与Vue2区别
使用
Vue2 中只要写在组件中 data 函数返回的对象里的属性 自动就有响应式
Vue3 则是通过 ref 定义普通类型响应式和 reactive 定义复杂类型响应式数据
<script setup>import { ref, reactive, toRefs } from "vue"const name = ref('沐华')const obj = reactive({ name: '沐华' })const data = { ...toRefs(obj) }
</script>
通过 toRefs 可以把响应式对象转为普通对象
因为使用 reactive 定义的响应式对象在进行解构(展开)或者销毁的时候,响应式就会失效了,因为 reactive 实例下有很多属性,解构就丢失了,所以在需要解构且保持响应式的时候就可以用 toRefs
初始化、监听:整个对象级别上进行拦截,无需遍历每个属性
性能:监听整个操作(不用遍历),不仅是读写
Proxy可以监听对象的整个操作,而不仅仅是属性的读写操作,这使得数据变化的检测更加高效。Vue2中,需要递归地遍历对象的所有属性,而Proxy可以一次性监听整个对象的操作。
功能:可监听属性的增删
Proxy可以监听新增属性和删除属性的操作,而Object.defineProperty只能监听已有属性的读写操作
收集依赖
Vue2 中是通过 Observer
,Dep
,Watcher
这三个类来实现依赖收集
Vue3 中是通过 track
收集依赖,通过 trigger
触发更新,本质上就是用 WeakMap,Map,Set 来实现
类型推导:TypeScript
由于Proxy是基于原生的Proxy对象实现的,所以可以更好地支持TypeScript等静态类型检查工具,提供更准确的类型推导和代码提示。
Vue2响应式原理
林三心画了8张图,最通俗易懂的Vue3响应式核心原理解析 - 掘金