Vue.js响应式数据的简单实现方法(一看就会)
目录
- 引言
- 基本概念
- 副作用函数
- 响应式数据
- 响应式数据的基本实现
- 实现思路
- 初步实现尝试
- 完善响应系统
- 泛化副作用函数名
- 修复漏洞
- 总结
引言
在Vue.js之中,Vue会自动跟踪JavaScript状态变化并在状态发生改变时响应式地更新DOM,这就是Vue.js的两大核心功能之一——响应性,是每一个Vue.js框架使用者必须熟练掌握的的功能。而得益于Vue.js自身支持的声明式渲染,Vue.js的学习成本大大降低,就算是一个前端领域的小白,只要能看懂并简单使用基本的HTML、JavaScript以及CSS,就能够很快上手Vue.js。学,确实好学;用,真的好用!但,你对Vue.js框架的内部实现原理掌握多少呢?今天,就让我们一起来简单复现一下Vue.js数据响应式。
基本概念
副作用函数
什么是副作用函数?意如其名,副作用函数指的就是会产生副作用的函数。什么是副作用呢?就是会对函数作用域外的其他部分产生影响。俗话说:是药三分毒,能治病,亦能致病。药,就有副作用,副作用函数也是。
副作用函数代码示例如下:
当effect函数执行时,它会设置body的文本内容,从而直接或间接影响到其他任何对body文本内容有所依赖的函数的执行。这就是一个简单的副作用函数。
响应式数据
以我的理解,相较“响应式数据”而言更直白的叫法应该是“副作用数据”,就好像副作用函数的执行可能会影响到函数作用域外的其他内容一样,“副作用数据”的更改可能会直接或间接影响到所有依赖该数据的函数。
假响应式数据代码示例如下:
如上图,假设每一次修改对象obj的text属性值,都会触发函数effect的重新执行,那么就可以说对象obj是一个响应式数据。当然,在这个示例里,实际上并没有实现对obj对象的数据响应。
响应式数据的基本实现
实现思路
仔细观察思考上述的例子,你可能会发现响应式数据的实现存在两个关键点:
- 副作用函数effect的执行会触发字段obj.text的读取操作
- 响应式数据obj.text值的修改会触发字段obj.text的设置操作
事情的脉络渐渐清晰起来:如果我们能够拦截对象obj的读取和设置操作,在副作用函数effect首次读取字段obj.text的值时将它与对象obj关联起来,此后每次重新设置字段obj.text的值,都会重新调用一次effect函数,这样不就简单的实现了对obj对象的响应吗?
初步实现尝试
实现的思路有了,那现在最关键的问题就是:如何实现拦截一个对象属性的读取和设置操作。如果你对JavaScript足够熟悉,你可能就会想到Object.defineProperty函数以及Proxy对象代理。是的,这两种方案都可以实现拦截一个对象属性的读取以及设置操作。事实上,用Object.defineProperty函数实现数据响应正是Vue.js 2中所采用的方法,而Proxy对象代理则正是Vue.js 3中所采用的方法。
接下来让我们顺着上面的思路采用proxy实现一下:
这就是采用Proxy代理对象简单实现的数据响应式,你完全可以自行创建一个副作用函数effect进行测试。当然,考虑到复杂多变的环境,此时的数据相应式还有很多继续完善的地方,让我们再加加班,尽可能地给出一个相对完美的响应式数据实现方案。
完善响应系统
泛化副作用函数名
假如有一天,副作用的函数名不叫effect了。而是叫effect1或者effect2,甚至副作用函数没有直白的名字了,变成了一个匿名函数,那么上述的响应系统方案显然是行不通的。此时,为了满足需求,我们需要提供一个用来注册副作用函数的机制,达到泛化副作用函数名的效果。
注册副作用函数名的代码示例如下:
这样,即使是一个匿名函数,也能够被成功地注册为副作用函数,注册方法如下:
当然,此时拦截数据的读取操作也需要做细微的调整:
修复漏洞
在很多时候,debug都是最头疼的,特别是明明知道有bug,但就是找不到修复的方案,那种感觉真的像在坐牢……
现在我们来考虑一个极端条件:假如,在响应式数据对象obj上添加了一个原本不存在的属性,那么会发生什么?如果你对前面的内容还不熟悉,可以再返回去翻翻代码。你会发现一个惊人的事实:在响应式数据对象obj上添加一个原本不存在的属性,会在这个新添加的属性与相关的副作用函数之间建立响应联系。冷静下来思考一下,其实,导致该问题出现的根本原因是,没有在副作用函数与被操作的目标字段之间建立明确的联系。那,该如何解决呢?
你想想,在数据结构中存不存在一种结构,它具有一一对应的关系?有,当然有,映射就是。顺着这个思路,那我们可不可以用映射的来建立目标字段与副作用函数key-value对应的关系?当然可以!那么我们就可以先把负责储存函数的变量bucket声明为一个映射Map<target, Map<key, Set()>>用来储存响应式数据(key)-该响应式数据所有属性相关的副作用函数(value),其中,Map<key, Set>储存的就是响应式对象属性与相应的副作用函数集,这样,一个明确的联系就建立起来了。而在着手优化响应代码之前,我们再想一想:bucket用WeakMap会不会比用Map要更好一点?我实话直说吧,当然应该用WeakMap,因为WeakMap的key是弱引用,不会影响到垃圾回收器的工作,会在合适的时候被回收,用在这里更合适。
具体实现代码如下:
总结
总的来说,要想实现一个响应式数据其实就是利用Proxy对象代理或者Object.defineProperty对象来拦截对数据的读取和设置操作并与相应的副作用函数作精确绑定。那么,如果现在要求你用Object.defineProperty对象来实现数据响应,你能够独立实现了吗?试一下呗!
到此这篇关于Vue.js响应式数据的简单实现方法的文章就介绍到这了,更多相关Vue.js响应式数据的实现内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!