vue相关原理进阶
1 整体目标
- 了解
Object.defineProperty
实现响应式 - 了解
指令编译
的基础原理 - 清楚
observe/watcher/dep
具体指的是什么 - 了解
发布订阅模式
以及其解决的具体问题
2 数据响应式
2.1 响应式是什么
一旦数据发生变化,我们可以立刻知道,并且做一些你想完成的事情,这些事情包括但不限于以下:
- 发送一个网络请求
- 打印一段文字
- 操作一个dom
- …
2.2 如何实现数据响应式
在Javascript里实现数据响应式一般有俩种方案,分别对应着vue2.x 和 vue3.x使用的方式,他们分别是:
-
对象属性拦截 (vue2.x)
Object.defineProperty
-
对象整体代理 (vue3.x)
Proxy
其中对象属性拦截,是我们本次课程关注的重点,不管使用其中的哪种方式,道理都是相通的
3.0 响应式总结
- 所谓的响应式其实就是拦截对象属性的访问和设置,插入一些我们自己想要做的事情
- 在Javascript中能实现响应式拦截的方法有俩种,
Object.defineProperty
方法和Proxy对象代理
- 回归到vue2.x中的data配置项,只要放到了data里的数据,不管层级多深不管你最终会不会用到这个数据都会进行递归响应式处理,所以要求我们如非必要,尽量不要添加太多的冗余数据在data中
- 需要了解vue3.x中,解决了2中对于数据响应式处理的无端性能消耗,使用的手段是Proxy劫持对象整体 + 惰性处理(用到了才进行响应式转换)
- 不管是指令也好,插值表达式也好,这些都是将数据反应到视图的标记而已,通过标记我们可以把数据的变化响应式的反应到对应的dom位置上去
- 找标记,把数据绑定到dom的过程,我们称之为
binding
6.0 发布订阅模式优化
6.1 优化思路思考
1.数据更新之后实际上需要执行的代码是什么?
node.innerText = data[dataProp]
为了保存当前的node和dataProp,我们再次设计一个函数执行利用闭包函数将每一次编译函数执行时候的node和dataProp都缓存下来,所以每一次数据变化之后执行的是这样的一个更新函数
() => {
node.innerText = data[dataProp]
}
2.一个响应式数据可能会有多个视图部分都需要依赖,也就是响应式数据变化之后,需要执行的更新函数可能不止一个,如下面的代码所示,name属性有俩个div元素都使用了它,所以当name变化之后,俩个div节点都需要得到更新,那属性和更新函数之间应该是一个一对多的关系
<div id="app">
<div v-text="name"></div>
<div v-text="name"></div>
<p v-text="age"></p>
<p v-text="age"></p>
</div>
<script>
let data = {
name: 'cp',
age: 18
}
</script>
经过分析我们可以得到下面的存储架构图,每一个响应式属性都绑定了相对应的更新函数,是一个一对多的关系,数据发生变化之后,只会再次执行和自己绑定的更新函数
6.2 理解发布订阅模式(自定义事件)
理解发布订阅,关键是理解
一对多
1. 从浏览器事件说起
dom绑定事件的方式,我们学过俩种
- dom.onclick = function(){}
- dom.addEventListener(‘click’,()=>{})
这俩种绑定方式的区别是,第二种方案可以实现同一个事件绑定多个回调函数,很明显这是一个一对多的场景,既然浏览器也叫作事件,我们试着分析下浏览器事件绑定实现的思路
-
首先addEventListenr是一个函数方法,接受俩个参数,分别是
事件类型
和回调函数
-
因为是一个事件绑定多个回调函数,那在内存里大概会有这样的一个数据结构
{ click: ['cb1','cb2',....], input: ['cb1','cb2',...] }
-
触发事件执行,浏览器因为有鼠标键盘输入可以触发事件,大概的思路是通过事件名称找到与之关联的回调函数列表,然后遍历执行一遍即可
ok,我们分析了浏览器事件的底层实现思路,那我们完全可以自己模仿一个出来,事件的触发,我们也通过设计一个方法来执行
2. 实现简单的发布订阅
// 增加dep对象 用来收集依赖和触发依赖
const dep = {
map: Object.create(null),
// 收集
collect(dataProp, updateFn) {
if (!this.map[dataProp]) {
this.map[dataProp] = []
}
this.map[dataProp].push(updateFn)
},
// 触发
trigger(dataProp) {
this.map[dataProp] && this.map[dataProp].forEach(updateFn => {
updateFn()
})