Vue原理
Vue02 基础原理
1、vue的初始化流程
- new Vue时,传入options参数,并进行初始化操作_init
- 该初始化函数
_init
在initMixin中定义,主要是为了_init
方法传递vue构造函数,并在原型上挂载_init
方法 - 在
_init
方法中进行初始化- initState:主要处理props、methods、data、compute、watch等对象
- 最后,在
_init
方法中判断有无传入el参数,如果传入则将数据挂载到页面上
2、vue中如何处理传入的data数据
由于在初始函数_init方法中,调用initState处理props、methods、data、computed、watch等数据,对于data通过initData的方法对数据进行处理
initData函数的处理:
- 如果传入的是一个函数,则让函数的this指向Vue实例,并将函数返回值作为对象;如果传入的是一个对象,则直接使用对象
- 数据代理:通过proxy的方法进行代理data数据,即
vm.name = vm._data.name
,实际还是通过Object.defineProperty
进行数据劫持 - 通过observe方法递归对象的所有属性及其子属性,通过object.defineProperty方法重写data对象的所有属性,将data变成响应式。
3、如何将传入的data变成响应式
- new Vue会调用_init方法进行初始化
- 会将用户的选项放到vm.$options上
- 会对当前属性上搜索有没有data数据 (initState)
- 有data判断data是不是一个函数,如果是函数取返回值 (initData)
- observe 去观测data中的对象
- 如果更新对象不存在的属性,会导致视图不更新,如果是数组更新索引和长度不会触发更新。
- 如果是替换成一个新对象,新对象会被进行劫持,如果是数组存放新内容 push unshift()新增的内容也会被劫持
- 通过
__ob__
进行标识这个对象被监控过。
Vue中主要通过observe方法对data中的数据进行检测:
1、先判断传入的是不是对象,如果不是对象,则直接return,不做观测(如果是函数则取函数的返回值作为对象)
2、通过Observe类来对data进行处理,如果观测过就直接跳过观测(__ob__
)
4、vue中如何识别被观测过的数据?(__ob__
)
Observe类具体实现:
- 在data上添加
__ob__
属性,为Observe类的实例,所有被劫持过的属性都有__ob__
- 如果传入的值是对象,通过调用
walk
方法,循环遍历对象,通过defineReactive方法重新定义属性,进行数据劫持 - 如果传入的是数组
- 先对数组的方法进行重写,因为push、pop、shift、unshift、reverse、sort、splice,因为这些方法会改变数组本身
- 然后通过observeArray方法,使数组里的引用类型定义成响应式,实际上就是通过遍历数组对每一项进行observe函数处理
5、数组为什么不用defineProperty进行响应式处理
数组也可以使用defineProperty,但是我们很少采用 arr[i] = xxx
,如果数组也使用了defineProperty还是可以实现修改索引触发更新的,但是这种操作概率低,所以源码没有采用这种方式。
所以数组修改索引不会导致视图更新,修改length也不会更新。
但是是vue3中为了兼容proxy,内部对数组用的就是defineProperty
6、Watcher类是如何实现的?
Watcher更新时,传入5个参数
- vm:vue实例
- expOrFn:页面渲染逻辑
- cb:回调函数
- options
- isRenderWatcher:是否为渲染Watcher
传入参数后,进行参数的处理
- this.getter = fn :fn就是页面渲染逻辑
然后调用get方法,进行初始化Watcher
- 调用pushTarget,将当前Watcher放入到Dep.target中用于依赖收集
- 调用this.getter方法,进行渲染逻辑
- 这样就能保证之后当前渲染到的属性才能获取到当前的Watcher
当收集依赖时,会调用addDep方法
- 其中维护着一个数组newDepIds,用于Dep去重
- 然后把传入的Dep加入到newDeps中将Dep存储起来
- 然后调用dep.addSub的方法,将当前的Watcher存储到Dep的subs数组中
7、Dep类是如何实现的?
1、Dep的属性:
- subs:用于存放Watcher的数组
- id:用于做唯一标识
- target:表示当前依赖的目标是谁(哪个Watcher)
2、当用户取属性时,会触发depend的方法,调用Watcher即Dep.target的addDep方法,用于Dep去重,然后Watcher存储当前dep,然后调用addSub方法
3、当调用addSub方法,就是将当前的Watcher存入subs数组中
8、收集依赖的过程(Dep.append)
进行数据初始化的时候,如果传入的值是对象的话,通过调用walk方法,循环调用defineReactive方法对每个属性进行数据劫持。所以要为每个属性增加Dep的话,可以在defineReactive中定义
1、页面更新的时候,会调用mountComponent的方法,在该方法中会再实例化一个渲染Watcher,然后调用Watcher中的get方法,主要做一下工作:
- 调用pushtarget,将当前Watcher放到Dep.target的属性中
- 然后调用updateComponent的方法,进行初渲染
- 然后通过popTarget,重置Dep.target的值
2、当用户取值时,会调用defineProperty的get方法,这时触发Dep.append
3、在Dep.append方法中,会调用Dep.target(即Watcher)的addDep方法进行收集Watcher
4、而Watcher.addDep方法,会通过id的方法,用Set数据结构先对Dep进行去重,然后将dep收集到newDeps数组中,然后调用Dep.addSub方法,将当前Watcher放到Dep.subs数组中
5、这样dep就收集到了多个Watcher,而Watcher也收集到了Dep
9、依赖收集后,数据该如何进行更新呢?(Dep.notify、Watcher.update)
用户取数据后,会调用defineProperty的get方法,进行依赖收集,即Dep获得多个Watcher,Watcher也获得了多个Dep
当用户修改数据的时候,会调用Dep.notify方法,进行数据更新
notify() {
// stabilize the subscriber list first
const subs = this.subs.slice()
for (let i = 0, l = subs.length; i < l; i++) {
subs[i].update()
}
}
实际上是操作就是,通过循环subs数组中收集的Watcher,调用Watcher中的update方法,实际上就是调用Watcher的get方法(updateComponent)进行渲染
在update方法中可以做异步更新逻辑
10、如何实现vue异步更新逻辑?(queue Watcher)
当用户更新数据时,会调用defineProperty中的set方法,设置值后,会调用Dep.notify方法,进行循环调用Watcher中的update方法。
由于每次更新都会调用这个update方法,我们可以将更新的逻辑存储起来,等到同步更新数据的逻辑执行完毕后,依次调用(含有去重逻辑)
每次更新调用update方法,都会执行queue Watcher方法
- 去重:维护着has数组,用于存储已有的Watcher的id,用has[id] == null来判断是否重复
- 缓存:通过维护queue队列,每次把watcher都放入queue队列当中
- 防抖:通过维护waitting变量,默认值为false,只有清空queue中的watcher才会设置为true
- 异步:当waitting变量为false,才会设置nextTick,清空queue队列,然后执行flushSchedulerQueue的方法,实际就是遍历每个watcher执行其run()方法
11、数组如何依赖收集?数组更新时,又如何触发更新?
对数据进行响应式观测的时候,会调用observe方法进行观测,如何在observer方法中,进行new Observer实例化
数组依赖收集
- 在实例化Observer类的时候,添加dep属性,值为new Dep
- 当取属性值时,会调用defineProperty的get方法,在get方法中,调用数组或对象的Observer实例上的dep.depend()进行依赖收集
- 如果是数组中嵌套着数组,通过调用dependArray方法,遍历数组第一项,调用
__ob__
的dep.depend方法,进行依赖收集,再递归调用dependArray
function dependArray(value: Array<any>) {
for (let e, i = 0, l = value.length; i < l; i++) {
e = value[i]
e && e.__ob__ && e.__ob__.dep.depend()
if (Array.isArray(e)) {
dependArray(e)
}
}
}
数组触发更新:
对于数组的响应式处理,是通过重重写数组的七个方法进行处理的,只需要再重写的方法中,再调用数组本身的dep.notify方法
12、vue的响应式原理
1、默认vue在初始化的时候,会对对象每个属性进行劫持,添加dep属性,当取值的时候会做依赖收集
2、默认还会对属性值是对象和数组本身进行添加dep属性,进行依赖收集
3、如果属性变化,触发属性对应的dep进行更新
4、如果是数组更新,触发数组的本身的dep进行更新
5、如果取值的时候是数组还要让数组中的对象类型也进行依赖收集(递归依赖收集)
6、如果数组里面放对象,默认对象里的属性是会进行依赖收集的,因为在取值时,会进行JSON.stringify操作