组件化 mvvm 响应式 vdom 和 diff 模板编译 渲染过程 前端路由
目录
5 深入diff算法源码 - updateChildren函数
1 Vue 与MVVM
1 如如何理解MVVM模型
数据驱动视图
-
在”很早以前“就有了组件化(如:asp、jap、php)
-
node 也有类似组件化
-
但传统的组件化,只是静态渲染,更新还要依赖操作 DOM,这也是 jQuery 流行的原因
-
Vue、React 在这基础上做了一个微创新,即数据驱动视图
-
数据驱动视图的出现,使得我们更关注数据(业务逻辑),不用再去关心DOM的增删改查
-
Vue 是 MVVM,React 是 setState
-
M -> Model V -> View VM -> ViewModel
左到右:DOM事件被vm模型监听到之后,可以去修改Model中的数据
右到左:Model中的数据一旦修改就立刻更新view,重新渲染
如此一来视图就不用我们自己手动去改了
1 vue响应式如何实现
只要在 Vue 实例中声明过的数据,这个数据就是响应式的。
什么是响应式,即,数据发生改变的时候,视图会重新渲染,匹配更新为最新的值。
在具体实现上,vue用到了几个核心部件:
描述:
一开始渲染组件要运行render函数,把render函数交给watcher执行的
watcher的执行过程中会运行render函数 ,render函数执行中会用到一些数据,
这些数据又会用到getter(),这个getter是把原始对象通过observer把每一个属性变成getter和setter,
因此在render函数中会用到一些数据,这些数据会触发它的getter执行,而在触发getter的时候会有依赖收集,
就会记录watcher用到了这些数据,
有一天数据发生变化了,因为在之前数据已经被记录了,所以会通知watcher把render函数重新运行一遍,
watcher不会自己执行,它会把自己交给调度器,调度器会把watcher添加到队列,如果有重复就不添加。然后把执行
这个队列的操作加到nextTick里面,这里面是异步的,
1Observer
在组件生命周期中,这件事发生在beforeCreate
之后,created
之前。Observer把对象的每个属性通过Object.defineProperty
转换为带有getter
和setter
的属性。
Observer的目标,就是要让一个对象,它属性的读取、赋值,内部数组的变化都要能够被vue感知到。
vue
提供了$set
和$delete
两个实例方法,让开发者通过这两个实例方法对已有响应式对象添加或删除属性。
数组,vue
会更改它的隐式原型,之所以这样做,是因为vue需要监听那些可能改变数组内容的方法
2 Dep实例 Dependency
依赖
Vue
会为响应式对象中的每个属性、对象本身、数组本身创建一个Dep
实例,每个Dep
实例都有能力做以下两件事:
- 记录依赖:是谁在用我
- 派发更新:我变了,我要通知那些用到我的人
3 Watcher
我们不要直接执行函数,而是把函数交给一个叫做watcher的东西去执行,watcher是一个对象,每个这样的函数执行时都应该创建一个watcher,通过watcher去执行
watcher会设置一个全局变量,让全局变量记录当前负责执行的watcher等于自己,然后再去执行函数,在函数的执行过程中,如果发生了依赖记录dep.depend(),那么Dep就会把这个全局变量记录下来,表示:有一个watcher用到了我这个属性
当Dep进行派发更新时,它会通知之前记录的所有watcher:我变了
每一个vue组件实例,都至少对应一个watcher,该watcher中记录了该组件的render函数。
watcher首先会把render函数运行一次以收集依赖,于是那些在render中用到的响应式数据就会记录这个watcher。
当数据变化时,dep就会通知该watcher,而watcher将重新运行render函数,从而让界面重新渲染同时重新记录当前的依赖。
4 Scheduler 调度器
现在还剩下最后一个问题,就是Dep通知watcher之后,如果watcher执行重运行对应的函数,就有可能导致函数频繁运行,从而导致效率低下
这样显然是不合适的,因此,watcher收到派发更新的通知后,实际上不是立即执行对应函数,而是把自己交给一个叫调度器的东西
调度器维护一个执行队列,该队列同一个watcher仅会存在一次,队列中的watcher不是立即执行,它会通过一个叫做nextTick的工具方法,把这些需要执行的watcher放入到事件循环的微队列中,nextTick的具体做法是通过Promise完成的
nextTick 通过 this.$nextTick
暴露给开发者
也就是说,当响应式数据变化时,render
函数的执行是异步的,并且在微队列中
5 Object.defineProperty缺点
深度监听,需要递归到底,一次性计算量大
无法监听新增属性/删除属性(Vue.set Vue.delete)
data.x = '100'//新增属性,监听不到 - 所以有Vue.set delete data.name //删除属性,监听不到 - 所以有Vue.delete
2 监听data变化的核心API
1,Observer
将data中的数据用Object.defineProperty进行数据劫持,每个目标对象的键值(即data中的数据)转换成getter/setter形式,用于进行依赖收集和通过依赖通知更新
2,Dep(依赖管理)
1)什么是依赖?
数据响应式后,如何通知视图更新?Dep就是帮我们收集【究竟要通知到哪里的】
2)如何收集依赖
我们如何知道data中的某个属性被使用了,答案就是Object.defineProperty,因为读取某个属性就会触发get方法
3)Dep就是收集与视图相关的数据,触发了get的数据,主要起到依赖收集和通知更新的作用。用于收集当前响应式对象的依赖关系,每个响应式对象包括子对象都拥有一个 Dep 实例(里面 subs 是 Watcher 实例数组),当数据有变更时,会通过 dep.notify()通知各个 watcher。
4)initState 时,对 computed 属性初始化时,触发 computed watcher 依赖收集
5)initState 时,对侦听属性初始化时,触发 user watcher 依赖收集
6)render()的过程,触发 render watcher 依赖收集
7)re-render 时,vm.render()再次执行,会移除所有 subs 中的 watcer 的订阅,重新赋值。
3,Watcher
1)Watcher就是类似中介的角色,比如message就有三个中介,当message变化,就通知这三个中介,他们就去执行各自需要做的变化。
2)Watcher必须要有的2个方法。一个就是通知变化,另一个就是被收集起来到Dep中去。
3)遍历所有的 subs(Watcher 实例),调用每一个 watcher 的 update 方法。
4,Watcher 和 Dep 的关系
watcher 中实例化了 dep 并向 dep.subs 中添加了订阅者,dep 通过 notify 遍历了 dep.subs 通知每个 watcher 更新。
三,总结
1,在数据被改的时候,触发set方法,通过对应的所有依赖(Watcher),去执行更新。比如watch和computed就执行开发者自定义的回调方法。
2,Observer中进行响应式的绑定,在数据被读的时候,触发get方法,执行Dep来收集依赖,也就是收集Watcher。
3 如何深度监听·data变化
const data = { name: '张三',