富森美身为LP,一周投三笔
06-18
vue的一大优势就是双向数据绑定,但是在react或者小程序中,我们需要手动setState和setData来修改视图数据。 vue2中使用Object.defineProperty来劫持对象属性的getter和setter,因此data函数需要返回一个对象。
如果data中没有定义该属性,则不会进行双向绑定,因为它没有被劫持。双向数据绑定也使用了设计模式中的发布/订阅模式。
当触发getter时,执行依赖收集,当触发setter时,通知相应的执行收集的依赖回调。 Object.defineProperty使用语法:Object.defineProperty(obj,prop,descriptor)。
具体使用请参考下面的demo。请注意,使用单独的值变量来存储年龄值。
如果没有,直接在get函数中写入person.age来获取值,会再次触发get死循环。集合中可以直接通过修改value的值来改变人物的年龄属性值。
这是因为我们使用了外部值变量。直接在set中修改value的值。
当获取值时,get返回的值实际上就是返回的值。 。
代码语言:javascript copy let person = { name: '周小黑',age: 18} let value = person.ageObject.defineProperty(person, 'age', { get() { console.log('获取年龄:' + value) return value }, set(e) { console.log('修改年龄:' + e) ??value = e }})console.log(person.age) // 18person.age = 20console.log(person .age) // 20 依赖于收集并执行数据变化时需要完成的所有操作。我们需要提前收集它们。
当真正发生变化的时候,就会把一些东西拿出来执行。简单理解双向数据绑定就是指当属性值发生变化时,我们需要程序自动做一些依赖于当前值的操作。
代码语言:javascript copy let person = { name: '周小黑' ,age: 18}let value = person.ageObject.defineProperty(person, 'age', { get() { console .log('获取年龄:' + value) 返回值 }, set(e) { console.log( '修改年龄:' + e) value = e action() }})function action() { console. log('我是数据变化时要执行的操作') const val = person.age * person.money = val console .log(person)}person.age = 20//修改age:20//我是数据变化要执行的操作//获取age:20// { name: '周小黑', Age: [Getter/Setter ], Money: 0 } 为了简单模拟,当人的年龄发生变化时,我们添加一个钱归因于该人。这里的代码执行逻辑:我们提前定义了一个action函数来执行操作。
当我们修改age属性时,该集合就会被触发。当设置被触发时,就意味着数据发生了变化。
直接执行集合中的动作函数即可。不过上面的代码有一个明显的问题,就是没有自动收集action函数。
我们不能为每个属性定义额外的action1,action2...操作函数。自动依赖收集为了实现自动依赖收集,我们修改上述代码,通过封装一个onChange公共函数来专门收集依赖。
它的参数是一个执行操作的函数: 代码语言:javascript copy let person = { name : '周小黑',age: 18}let value = person.ageObject.defineProperty(person, 'age', { get() { console.log('获取年龄:' + value) return value }, set(e ) { console.log('修改年龄:' + e) value = e action() }})let action = nullconst onChange = (callback) => { action = callbackcallback() // 这里先执行,触发获取依赖集合}onChange(() => { console.log('我是数据变化时要执行的操作') const val = person.age * person.money = val console.log(person)})person.age = 20//我是数据变化时要执行的操作 // 获取age:18 // { name: '周小黑',age: [Getter/Setter], Money: 0 }//修改age:20 //我是数据变更要执行的操作 //获取age: 20 // { name: '周小黑',age: [ Getter/Setter], Money: 0 } 在调用依赖收集函数onChange时,我们首先将依赖收集到外部action中,当修改年龄触发set时,我们可以直接执行action,这样就可以收集多个依赖可以实现回调。不过,上面的代码仍然存在一个问题:需要手动调用onChange函数。
只会执行最后一次调用onChange收集的回调,并且无论当前依赖属性是否发生变化都会执行。下面继续改造: 代码语言:javascript copy let person = { name: '周小黑',age: 18} let value = person.ageObject.defineProperty(person, 'age', { get() { onCollect('age' ) console.log('获取年龄:' + value) return value }, set(e) { console.log('修改年龄:' + e) ??value = e onExecute('age') }})let action = null const onChange = (callback) => { action = callback callback() // 这里先执行,触发获取依赖收集}//收集所有依赖框 const eventBox = {}//收集依赖 function onCollect(key) { let arr = 事件框[键] || [] arr.push(action) eventBox[key] = arr}//执行函数 onExecute(key) { let arr = eventBox[key] || [] arr.map(fn => fn( ))}onChange(() => { console.log('我是数据变化时要执行的操作') const val = person.age * person.money = val console.log(person)})onChange(() = > { console.log('我是数据更改2要执行的操作') const val = person.age * person.money= val console.log(person)})onChange(() => { console.log('我是数据变化时要执行的操作,但我没有依赖')})person.age = 20//我是数据变化要执行的操作 // 获取age: 18 // { name: '周小黑',age: [Getter/Setter], Money: 0 }//我是要执行的操作the data change 2 // 获取age: 18 // { name: '周小黑',age: [Getter/Setter], Money: 0 }//我是数据变化要执行的操作,但是我没有任何依赖 // 修改age: 20 // 我是数据变化要执行的操作 // 获取age: 20 // { name: '周小黑',age: [Getter/Setter], Money: 0 }// I am 数据变化要执行的操作 2 // 获取age :20 // { name: '周小黑',age: [Getter/Setter], Money: 0 } 定义一个eventBox对象,用于存储所有属性的依赖回调。
当 get 被触发时,会调用 onCollect 将依赖项收集到框中。 ,当修改数据触发set时,会从eventBox框中取出对应属性的依赖回调来执行核心代码。
理解上面的代码并不难。也许最难理解的是get中如何完成自动依赖收集。
,当我们调用onChange时,外部action会存储当前要收集的依赖回调(记住这一点很重要),然后直接执行回调函数触发获取依赖收集。如果回调内部触发了get(比如上面的代码中,通过person.age获取年龄),那么就会走内部get函数。
我们只需要在get中调用onCollect即可将action收集到eventBox盒子对应的key值中。如果还是看不懂,可以在断点处运行一下代码,你就会明白了。
其实到这里你就基本可以了解Vue双向数据绑定的实现原理和步骤了:getter自动将依赖收集到一个盒子里,setter取出收集到的对应依赖并遍历执行。核心是发布/订阅模式。
。其实上面的代码还有一个问题:执行set中的回调会触发get,然后往box里添加重复的回调。
可以通过将之前的数组数组改为Set数据结构来存储key对应的回调来解决这个问题。解决方案;另外,上面的代码有一个没有依赖的回调,也添加到了age对应的回调中。
这里,每次执行action时都需要将action重置为null,然后在get中还需要对action进行判断。仅当它不为空时才会收集依赖项。
之前的版本为了理解简单的数据存储,直接使用了最简单的Object和Array。实际中需要结合使用WeakMap、Map、WeakSet、Set来存储数据。
完整的修改代码请参考下面的代理版本。 vue3中的proxyvue2中,使用Object.defineProperty来劫持对象的getter和setter。
在vue3中,被proxy替代。其实核心原理还是和上面一样,只不过收集和执行依赖改为proxy来劫持getter和setter。
。将上面的 demo 替换为 proxy 来实现: 代码语言:javascript copy let person = { name: '周小黑',age: 18} let action = nullconst onChange = (callback) => { action = callback callback() //这里先执行一个触发器 get 依赖收集 action = null }//收集所有依赖框 const eventBox = {}//收集依赖 function onCollect(key) { let arr = eventBox[key] || new Set() arr.add (action) eventBox[key] = arr}//执行函数 onExecute(key) { let arr = eventBox[key] || [] arr.forEach(fn => fn())}let value = person.ageconst proxyPerson = new Proxy(person, { get(target, key) { action && onCollect(key) console.log('get' + key ) return target[key] }, set(target, key, newValue) { target[key] = newValue console.log('修改' + key + ':' + newValue) onExecute(key) }})onChange(() => { console.log('我是数据变化时要执行的操作') const val = proxyPerson .age * proxyPerson.money = val console.log(proxyPerson)})onChange(() => { console.log('我是数据变更2要执行的操作') const val = proxyPerson.age * proxyPerson.money = val console.log(proxyPerson)})onChange (( ) => { console.log('我是要进行数据变更的操作,但我没有任何依赖')})proxyPerson.age = 20我正在参加第三期腾讯科创特训营会包括获奖论文和团队组建。
版权声明:本文内容由互联网用户自发贡献,本站不拥有所有权,不承担相关法律责任。如果发现本站有涉嫌抄袭的内容,欢迎发送邮件 举报,并提供相关证据,一经查实,本站将立刻删除涉嫌侵权内容。
标签:
相关文章
06-18
06-17
06-06
06-17
06-21
06-18
06-06
06-18
最新文章
【玩转GPU】ControlNet初学者生存指南
【实战】获取小程序中用户的城市信息(附源码)
包雪雪简单介绍Vue.js:开学
Go进阶:使用Gin框架简单实现服务端渲染
线程池介绍及实际案例分享
JMeter 注释 18 - JMeter 常用配置组件介绍
基于Sentry的大数据权限解决方案
【云+社区年度征文集】GPE监控介绍及使用