共计 15831 个字符,预计需要花费 40 分钟才能阅读完成。
前言
大家好,我是林三心,大家也晓得,本菜鸟平时写根底文章比拟多,我始终深信两句话
- 用最通俗易懂的话,讲最难的知识点
- 根底是进阶的前提
其实 Vue3 曾经进去很久了,可能大部分公司都用上了,然而,Vue3 到底比 Vue2 好在哪里?其实很多人都不晓得。明天我就先给大家讲一讲Vue3 的响应式原理
吧,顺便说一说Vue3 的响应式到底比 Vue2 的响应式好在哪
。
好在哪?
好的,咱们先来讲讲为什么Vue3 的响应式 优于 Vue2 响应式
。可能平时问大家:请问你晓得 Vue 的响应式是怎么实现的吗?大家都能粗略答复进去
- Vue2 的响应式是基于
Object.defineProperty
实现的 - Vue3 的响应式是基于 ES6 的
Proxy
来实现的
是的,尽管下面的答复形象了点,然而的确是答复出了 Vue 的两个版本的响应式的外围原理,并且 Vue 的两个版本响应式的好坏,也的确就是体现在 Object.defineProperty
和Proxy
的差别上。
Vue2
大家都晓得 Vue2 的响应式是基于 Object.defineProperty
的,那我就拿 Object.defineProperty
来举个例子
// 响应式函数
function reactive(obj, key, value) {
Object.defineProperty(data, key, {get() {console.log(` 拜访了 ${key}属性 `)
return value
},
set(val) {console.log(` 将 ${key}由 ->${value}-> 设置成 ->${val}`)
if (value !== val) {value = val}
}
})
}
const data = {
name: '林三心',
age: 22
}
Object.keys(data).forEach(key => reactive(data, key, data[key]))
console.log(data.name)
// 拜访了 name 属性
// 林三心
data.name = 'sunshine_lin' // 将 name 由 -> 林三心 -> 设置成 ->sunshine_lin
console.log(data.name)
// 拜访了 name 属性
// sunshine_lin
通过下面的例子,我想大家都对 Object.defineProperty
有了一个理解,那问题来了?它到底有什么弊病呢?使得尤大大在 Vue3 中摈弃了它,咱们接着看:
// 接着下面代码
data.hobby = '打篮球'
console.log(data.hobby) // 打篮球
data.hobby = '打游戏'
console.log(data.hobby) // 打游戏
这下大家能够看出 Object.defineProperty
有什么弊病了吧?咱们能够看到,data 新增了 hobby
属性,进行拜访和设值,然而都不会触发 get 和 set
,所以弊病就是:Object.defineProperty
只对初始对象里的属性有监听作用,而对新增的属性有效。这也是为什么 Vue2 中对象新增属性的批改须要应用 Vue.$set
来设值的起因。
Vue3
从下面,咱们晓得了 Object.defineProperty
的弊病,咱们接着讲 Vue3 中响应式原理的外围 Proxy
是怎么补救这一缺点的,老样子,咱们还是举例子(先粗略讲,具体参数上面会细讲):
const data = {
name: '林三心',
age: 22
}
function reactive(target) {
const handler = {get(target, key, receiver) {console.log(` 拜访了 ${key}属性 `)
return Reflect.get(target, key, receiver)
},
set(target, key, value, receiver) {console.log(` 将 ${key}由 ->${target[key]}-> 设置成 ->${value}`)
Reflect.set(target, key, value, receiver)
}
}
return new Proxy(target, handler)
}
const proxyData = reactive(data)
console.log(proxyData.name)
// 拜访了 name 属性
// 林三心
proxyData.name = 'sunshine_lin'
// 将 name 由 -> 林三心 -> 设置成 ->sunshine_lin
console.log(proxyData.name)
// 拜访了 name 属性
// sunshine_lin
能够看到,其实成果与下面的 Object.defineProperty
没什么差异,那为什么尤大大要摈弃它,抉择 Proxy
呢?留神了,最最最要害的来了,那就是对象新增属性,来看看成果吧:
proxyData.hobby = '打篮球'
console.log(proxyData.hobby)
// 拜访了 hobby 属性
// 打篮球
proxyData.hobby = '打游戏'
// 将 hobby 由 -> 打篮球 -> 设置成 -> 打游戏
console.log(proxyData.hobby)
// 拜访了 hobby 属性
// 打游戏
所以当初大家晓得 Vue3 的响应式比 Vue2 好在哪了吧?
Vue3 响应式原理
说完 Proxy
的益处,咱们正式来讲讲 Vue3 的响应式原理的外围局部吧。
前言
先看看上面这段代码
let name = '林三心', age = 22, money = 20
let myself = `${name}往年 ${age}岁,贷款 ${money}元 `
console.log(myself) // 林三心往年 22 岁,贷款 20 元
money = 300
// 预期:林三心往年 22 岁,贷款 300 元
console.log(myself) // 理论:林三心往年 22 岁,贷款 20 元
大家想一下,我想要让 myself
跟着 money
变,怎么办才行?嘿嘿,其实,只有让 myself = '${name} 往年 ${age}岁,贷款 ${money}元'
再执行一次就行,如下
let name = '林三心', age = 22, money = 20
let myself = `${name}往年 ${age}岁,贷款 ${money}元 `
console.log(myself) // 林三心往年 22 岁,贷款 20 元
money = 300
myself = `${name}往年 ${age}岁,贷款 ${money}元 ` // 再执行一次
// 预期:林三心往年 22 岁,贷款 300 元
console.log(myself) // 理论:林三心往年 22 岁,贷款 300 元
effect
下面说了,每一次 money
扭转就得再执行一次 myself = '${name} 往年 ${age}岁,贷款 ${money}元'
,能力使 myself
更新,其实这么写不优雅,咱们能够封装一个effect 函数
let name = '林三心', age = 22, money = 20
let myself = ''
const effect = () => myself = `${name}往年 ${age}岁,贷款 ${money}元 `
effect() // 先执行一次
console.log(myself) // 林三心往年 22 岁,贷款 20 元
money = 300
effect() // 再执行一次
console.log(myself) // 林三心往年 22 岁,贷款 300 元
其实这样也是有害处的,不信你能够看看上面这种状况
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 = 300
effect1() // 再执行一次
effect2() // 再执行一次
console.log(myself) // 林三心往年 22 岁,贷款 300 元
console.log(ohtherMyself) // 22 岁的林三心竟然有 300 元
减少了一个 ohtherMyself
,就得再写一个effect
,而后每次更新都执行一次,那如果减少数量变多了,那岂不是每次都要写好多好多的effect 函数
执行代码?
track 和 trigger
针对下面的问题,咱们能够这样解决:用 track 函数
把所有依赖于 money 变量
的effect 函数
都收集起来,放在 dep
里,dep
为什么用 Set
呢?因为 Set
能够主动去重。收集起来之后,当前只有 money 变量
一扭转,就执行 trigger 函数
告诉 dep
里所有依赖 money 变量
的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 = 300
trigger() // 告诉变量 myself 和 otherMyself 进行更新
console.log(myself) // 林三心往年 22 岁,贷款 300 元
console.log(ohtherMyself) // 22 岁的林三心竟然有 300 元
对象呢?
下面都是讲根底数据类型的,那咱们来讲讲 对象
吧,我先举个例子,用最原始的形式去实现他的响应
const person = {name: '林三心', age: 22}
let nameStr1 = ''let nameStr2 =''
let ageStr1 = ''let ageStr2 =''
const effectNameStr1 = () => { nameStr1 = `${person.name}是个大菜鸟 ` }
const effectNameStr2 = () => { nameStr2 = `${person.name}是个小蠢才 ` }
const effectAgeStr1 = () => { ageStr1 = `${person.age}岁曾经算很老了 ` }
const effectAgeStr2 = () => { ageStr2 = `${person.age}岁还算很年老啊 ` }
effectNameStr1()
effectNameStr2()
effectAgeStr1()
effectAgeStr2()
console.log(nameStr1, nameStr2, ageStr1, ageStr2)
// 林三心是个大菜鸟 林三心是个小蠢才 22 岁曾经算很老了 22 岁还算很年老啊
person.name = 'sunshine_lin'
person.age = 18
effectNameStr1()
effectNameStr2()
effectAgeStr1()
effectAgeStr2()
console.log(nameStr1, nameStr2, ageStr1, ageStr2)
// sunshine_lin 是个大菜鸟 sunshine_lin 是个小蠢才 18 岁曾经算很老了 18 岁还算很年老啊
下面的代码,咱们也看进去了,感觉写的很无脑。。还记得后面讲的 dep
收集 effect
吗?咱们暂且把 person 对象里的 name 和 age 看成两个变量,他们都有各自的 依赖变量
- name:nameStr1 和 nameStr2
- age:ageStr1 和 ageStr2
所以name 和 age
应该领有本人的dep
,并收集各自依赖变量所对应的effect
后面说了 dep
是应用 Set
,因为 person 领有age 和 name
两个属性,所以领有 两个 dep
,那用什么来贮存这两个 dep 呢?咱们能够用 ES6 的另一个数据结构 Map
来贮存
const person = {name: '林三心', age: 22}
let nameStr1 = ''let nameStr2 =''
let ageStr1 = ''let ageStr2 =''
const effectNameStr1 = () => { nameStr1 = `${person.name}是个大菜鸟 ` }
const effectNameStr2 = () => { nameStr2 = `${person.name}是个小蠢才 ` }
const effectAgeStr1 = () => { ageStr1 = `${person.age}岁曾经算很老了 ` }
const effectAgeStr2 = () => { ageStr2 = `${person.age}岁还算很年老啊 ` }
const depsMap = new Map()
function track(key) {let dep = depsMap.get(key)
if (!dep) {depsMap.set(key, dep = new Set())
}
// 这里先暂且写死
if (key === 'name') {dep.add(effectNameStr1)
dep.add(effectNameStr2)
} else {dep.add(effectAgeStr1)
dep.add(effectAgeStr2)
}
}
function trigger (key) {const dep = depsMap.get(key)
if (dep) {dep.forEach(effect => effect())
}
}
track('name') // 收集 person.name 的依赖
track('age') // 收集 person.age 的依赖
effectNameStr1()
effectNameStr2()
effectAgeStr1()
effectAgeStr2()
console.log(nameStr1, nameStr2, ageStr1, ageStr2)
// 林三心是个大菜鸟 林三心是个小蠢才 22 岁曾经算很老了 22 岁还算很年老啊
person.name = 'sunshine_lin'
person.age = 18
trigger('name') // 告诉 person.name 的依赖变量更新
trigger('age') // 告诉 person.age 的依赖变量更新
console.log(nameStr1, nameStr2, ageStr1, ageStr2)
// sunshine_lin 是个大菜鸟 sunshine_lin 是个小蠢才 18 岁曾经算很老了 18 岁还算很年老啊
下面咱们是只有一个 person 对象,那如果有多个对象呢?怎么办?咱们都晓得,每个对象会建设一个 Map
来存储此对象里属性的 dep(应用 Set 来存储)
,那如果有多个对象,该用什么来存储每个对象对应的Map
呢?请看下图
其实 ES6 还有一个新的数据结构,叫做 WeakMap
的,咱们就用它来存储这些对象的 Map
吧。所以咱们得对 track 函数
和trigger 函数
进行革新,先看看之前他们长啥样
const depsMap = new Map()
function track(key) {let dep = depsMap.get(key)
if (!dep) {depsMap.set(key, dep = new Set())
}
// 这里先暂且写死
if (key === 'name') {dep.add(effectNameStr1)
dep.add(effectNameStr2)
} else {dep.add(effectAgeStr1)
dep.add(effectAgeStr2)
}
}
function trigger (key) {const dep = depsMap.get(key)
if (dep) {dep.forEach(effect => effect())
}
}
之前的代码只做了单个对象的解决计划,然而当初如果要多个对象,那就得应用 WeakMap
进行革新了(接下来代码可能有点啰嗦,但都会为了关照基础薄弱的同学)
const person = {name: '林三心', age: 22}
const animal = {type: 'dog', height: 50}
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())
}
// 这里先暂且写死
if (target === person) {if (key === 'name') {dep.add(effectNameStr1)
dep.add(effectNameStr2)
} else {dep.add(effectAgeStr1)
dep.add(effectAgeStr2)
}
} else if (target === animal) {if (key === 'type') {dep.add(effectTypeStr1)
dep.add(effectTypeStr2)
} else {dep.add(effectHeightStr1)
dep.add(effectHeightStr2)
}
}
}
function trigger(target, key) {let depsMap = targetMap.get(target)
if (depsMap) {const dep = depsMap.get(key)
if (dep) {dep.forEach(effect => effect())
}
}
}
通过了下面的革新,咱们终于实现了多对象的依赖收集,咱们来试一试吧
const person = {name: '林三心', age: 22}
const animal = {type: 'dog', height: 50}
let nameStr1 = ''let nameStr2 =''
let ageStr1 = ''let ageStr2 =''
let typeStr1 = ''let typeStr2 =''
let heightStr1 = ''let heightStr2 =''
const effectNameStr1 = () => { nameStr1 = `${person.name}是个大菜鸟 ` }
const effectNameStr2 = () => { nameStr2 = `${person.name}是个小蠢才 ` }
const effectAgeStr1 = () => { ageStr1 = `${person.age}岁曾经算很老了 ` }
const effectAgeStr2 = () => { ageStr2 = `${person.age}岁还算很年老啊 ` }
const effectTypeStr1 = () => { typeStr1 = `${animal.type}是个大菜鸟 ` }
const effectTypeStr2 = () => { typeStr2 = `${animal.type}是个小蠢才 ` }
const effectHeightStr1 = () => { heightStr1 = `${animal.height}曾经算很高了 ` }
const effectHeightStr2 = () => { heightStr2 = `${animal.height}还算很矮啊 ` }
track(person, 'name') // 收集 person.name 的依赖
track(person, 'age') // 收集 person.age 的依赖
track(animal, 'type') // animal.type 的依赖
track(animal, 'height') // 收集 animal.height 的依赖
effectNameStr1()
effectNameStr2()
effectAgeStr1()
effectAgeStr2()
effectTypeStr1()
effectTypeStr2()
effectHeightStr1()
effectHeightStr2()
console.log(nameStr1, nameStr2, ageStr1, ageStr2)
// 林三心是个大菜鸟 林三心是个小蠢才 22 岁曾经算很老了 22 岁还算很年老啊
console.log(typeStr1, typeStr2, heightStr1, heightStr2)
// dog 是个大菜鸟 dog 是个小蠢才 50 曾经算很高了 50 还算很矮啊
person.name = 'sunshine_lin'
person.age = 18
animal.type = '猫'
animal.height = 20
trigger(person, 'name')
trigger(person, 'age')
trigger(animal, 'type')
trigger(animal, 'height')
console.log(nameStr1, nameStr2, ageStr1, ageStr2)
// sunshine_lin 是个大菜鸟 sunshine_lin 是个小蠢才 18 岁曾经算很老了 18 岁还算很年老啊
console.log(typeStr1, typeStr2, heightStr1, heightStr2)
// 猫是个大菜鸟 猫是个小蠢才 20 曾经算很高了 20 还算很矮啊
Proxy
通过下面的学习,咱们曾经能够实现当数据更新时,他的依赖变量也跟着扭转,然而还是有毛病的,大家能够发现,每次咱们总是得本人手动去执行 track 函数
进行依赖收集,并且当数据扭转时,我么又得手动执行 trigger 函数
去进行告诉更新
那么,到底有没有方法能够实现,主动收集依赖,以及主动告诉更新呢?答案是有的,Proxy
能够为咱们解决这个难题。咱们先写一个 reactive 函数
,大家先照敲,了解好Proxy-track-trigger
这三者的关系,前面我会讲为什么这里 Proxy
须要搭配Reflect
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)
}
而后改一改之前的代码,把 手动 track
和 手动 trigger
去掉,发现也能实现之前的成果
const person = reactive({name: '林三心', age: 22}) // 传入 reactive
const animal = reactive({type: 'dog', height: 50}) // 传入 reactive
effectNameStr1()
effectNameStr2()
effectAgeStr1()
effectAgeStr2()
effectTypeStr1()
effectTypeStr2()
effectHeightStr1()
effectHeightStr2()
console.log(nameStr1, nameStr2, ageStr1, ageStr2)
// 林三心是个大菜鸟 林三心是个小蠢才 22 岁曾经算很老了 22 岁还算很年老啊
console.log(typeStr1, typeStr2, heightStr1, heightStr2)
// dog 是个大菜鸟 dog 是个小蠢才 50 曾经算很高了 50 还算很矮啊
person.name = 'sunshine_lin'
person.age = 18
animal.type = '猫'
animal.height = 20
console.log(nameStr1, nameStr2, ageStr1, ageStr2)
// sunshine_lin 是个大菜鸟 sunshine_lin 是个小蠢才 18 岁曾经算很老了 18 岁还算很年老啊
console.log(typeStr1, typeStr2, heightStr1, heightStr2)
// 猫是个大菜鸟 猫是个小蠢才 20 曾经算很高了 20 还算很矮啊
可能有的同学会有点懵逼,对下面的代码有点纳闷,也可能有点绕,我还认为通过一张图给大家解说一下流程,图可能会被压缩,倡议点开看看
解决写死问题
在下面有一处中央,咱们是写死的,大家都还记得吗,就是在 track 函数
中
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())
}
// 这里先暂且写死
if (target === person) {if (key === 'name') {dep.add(effectNameStr1)
dep.add(effectNameStr2)
} else {dep.add(effectAgeStr1)
dep.add(effectAgeStr2)
}
} else if (target === animal) {if (key === 'type') {dep.add(effectTypeStr1)
dep.add(effectTypeStr2)
} else {dep.add(effectHeightStr1)
dep.add(effectHeightStr2)
}
}
}
理论开发中,必定是不止两个对象的,如果每多加一个对象,就得多加一个 else if
判断,那是万万不行的。那咱们要怎么解决这个问题呢?其实说难也不难,Vue3 的作者们想出了一个十分奇妙的方法,应用一个全局变量 activeEffect
来奇妙解决这个问题,具体是怎么解决呢?其实很简略,就是每一个 effect 函数
一执行,就把本身放到对应的 dep
里,这就能够不须要写死了。
咱们怎么能力实现这个性能呢?咱们须要改装一下 effect 函数
才行,并且要批改track 函数
let activeEffect = null
function effect(fn) {
activeEffect = fn
activeEffect()
activeEffect = null // 执行后立马变成 null
}
function track(target, key) {
// 如果此时 activeEffect 为 null 则不执行上面
// 这里判断是为了防止例如 console.log(person.name)而触发 track
if (!activeEffect) return
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())
}
dep.add(activeEffect) // 把此时的 activeEffect 增加进去
}
// 每个 effect 函数改成这么执行
effect(effectNameStr1)
effect(effectNameStr2)
effect(effectAgeStr1)
effect(effectAgeStr2)
effect(effectTypeStr1)
effect(effectTypeStr2)
effect(effectHeightStr1)
effect(effectHeightStr2)
实现 ref
咱们在 Vue3 中是这么应用 ref
的
let num = ref(5)
console.log(num.value) // 5
而后 num
就会成为一个响应式的数据,而且应用 num
时须要这么写 num.value
能力应用
实现 ref 其实很简略,咱们下面曾经实现了reactive
,只须要这么做就能够实现ref
function ref (initValue) {
return reactive({value: initValue})
}
咱们能够来试试成果如何
let num = ref(5)
effect(() => sum = num.value * 100)
console.log(sum) // 500
num.value = 10
console.log(sum) // 1000
实现 computed
咱们顺便简略实现一下 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) // 400
num1.value = 10
console.log(sum1.value) // 80
console.log(sum2.value) // 800
num2.value = 16
console.log(sum1.value) // 160
console.log(sum2.value) // 1600
自此咱们就实现了本文章所有性能
最终代码
const targetMap = new WeakMap()
function track(target, key) {
// 如果此时 activeEffect 为 null 则不执行上面
// 这里判断是为了防止例如 console.log(person.name)而触发 track
if (!activeEffect) return
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())
}
dep.add(activeEffect) // 把此时的 activeEffect 增加进去
}
function trigger(target, key) {let depsMap = targetMap.get(target)
if (depsMap) {const dep = depsMap.get(key)
if (dep) {dep.forEach(effect => effect())
}
}
}
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)
}
let activeEffect = null
function effect(fn) {
activeEffect = fn
activeEffect()
activeEffect = null
}
function ref(initValue) {
return reactive({value: initValue})
}
function computed(fn) {const result = ref()
effect(() => result.value = fn())
return result
}
Proxy 和 Reflect
Proxy
const person = {name: '林三心', age: 22}
const proxyPerson = new Proxy(person, {get(target, key, receiver) {console.log(target) // 原来的 person
console.log(key) // 属性名
console.log(receiver) // 代理后的 proxyPerson
},
set(target, key, value, receiver) {console.log(target) // 原来的 person
console.log(key) // 属性名
console.log(value) // 设置的值
console.log(receiver) // 代理后的 proxyPerson
}
})
proxyPerson.name // 拜访属性触发 get 办法
proxyPerson.name = 'sunshine_lin' // 设置属性值触发 set 办法
Reflect
在这列举 Reflect
的两个办法
get(target, key, receiver)
:集体了解就是,拜访target
的key
属性,然而this
是指向receiver
,所以理论是拜访的值是receiver 的 key
的值,然而这可不是间接拜访receiver[key]
属性,大家要辨别一下set(target, key, value, receiver)
:集体了解就是,设置target
的key
属性为value
,然而this
是指向receiver
,所以理论是是设置receiver 的 key
的值为value
,但这可不是间接receiver[key] = value
,大家要辨别一下
下面咱们强调了,不能间接 receiver[key]
或者receiver[key] = value
,而是要通过Reflect.get 和 Reflect.set
,绕个弯去拜访属性或者设置属性,这是为啥呢?上面咱们举个反例
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
为什么会这样呢?看看下图解答
当初晓得为什么不能间接 receiver[key]
或者 receiver[key] = value
了吧,因为间接这么操作会导致有限循环,最终报错。所以正确做法是
const person = {name: '林三心', age: 22}
const proxyPerson = new Proxy(person, {get(target, key, receiver) {return Reflect.get(target, key, receiver)
},
set(target, key, value, receiver) {Reflect.set(target, key, value, receiver)
}
})
console.log(proxyPerson.name) // 林三心
proxyPerson.name = 'sunshine_lin'
console.log(proxyPerson.name) // sunshine_lin
必定有的同学就要问了,上面这么写也能够,为什么也不倡议呢?我放到上面一起说
const proxyPerson = new Proxy(person, {get(target, key, receiver) {return Reflect.get(target, key)
},
set(target, key, value, receiver) {Reflect.get(target, key, value)
}
})
为什么要一起用
其实 Proxy 不搭配 Reflect 也是能够的。咱们能够这么写,也照样能实现想要的成果
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
那为什么倡议 Proxy 和 Reflect
一起应用呢?因为 Proxy 和 Reflect
的办法都是一一对应的,在 Proxy
里应用 Reflect
会进步语义化
Proxy 的 get
对应Reflect.get
Proxy 的 set
对应Reflect.set
- 还有很多其余办法我就不一一列举,都是一一对应的
还有一个起因就是,尽量把 this 放在 receiver
上,而不放在 target
上
为什么要尽量把 this 放在代理对象 receiver
上,而不倡议放原对象 target
上呢?因为原对象 target
有可能原本也是是另一个代理的代理对象,所以如果 this 始终放 target
上的话,出 bug 的概率会大大提高,所以之前的代码为什么不倡议,大家应该晓得了吧?
const proxyPerson = new Proxy(person, {get(target, key, receiver) {return Reflect.get(target, key)
},
set(target, key, value, receiver) {Reflect.set(target, key, value)
}
})
结语
我是林三心,一个热心的前端菜鸟程序员。如果你上进,喜爱前端,想学习前端,那咱们能够交朋友,一起摸鱼哈哈,摸鱼群,加我请备注【思否】