林三心画了8张图,最通俗易懂的Vue3响应式核心原理解析
source link: https://segmentfault.com/a/1190000041207010
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
林三心画了8张图,最通俗易懂的Vue3响应式核心原理解析
大家好,我是林三心,大家也知道,本菜鸟平时写基础文章比较多,我始终坚信两句话
- 用最通俗易懂的话,讲最难的知识点
- 基础是进阶的前提
其实Vue3已经出来很久了,可能大部分公司都用上了,但是,Vue3究竟比Vue2好在哪里?其实很多人都不知道。今天我就先给大家讲一讲Vue3的响应式原理
吧,顺便说一说Vue3的响应式到底比Vue2的响应式好在哪
。
好的,咱们先来讲讲为什么Vue3的响应式 优于 Vue2响应式
。可能平时问大家:请问你知道Vue的响应式是怎么实现的吗?大家都能粗略回答出来
- Vue2的响应式是基于
Object.defineProperty
实现的 - Vue3的响应式是基于ES6的
Proxy
来实现的
是的,虽然上面的回答抽象了点,但是确实是回答出了Vue的两个版本的响应式的核心原理,并且Vue的两个版本响应式的好坏,也确实就是体现在Object.defineProperty
和Proxy
的差异上。
大家都知道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
来设值的原因。
从上面,咱们知道了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) } })
我是林三心,一个热心的前端菜鸟程序员。如果你上进,喜欢前端,想学习前端,那咱们可以交朋友,一起摸鱼哈哈,摸鱼群,加我请备注【思否】
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK