这年头你不研究点vue源码都不好意思说自己可以熟练使用vue,对于源码的的学习,我只说三件事:耐心,耐心,还是***耐心
响应式原理
源码分析
- 监听器
Observer
拦截所有的属性并监听属性的变化
// core/instance/state.js
function initData(){
.....
// 对data的是否是对象
// 对属性的判重等操作
observe(data, true /* asRootData */) //去监听数据
}
// core/observer/index.js
export function observe(value, asRootData){
let ob
ob = new Observer(value) // 创建一个监听器实例
return ob
}
// core/observer/index.js
export class Observer{
constructor(value) {
if (Array.isArray(value)) { // 是数组类型
// 重写数组的一些方法 'push', 'pop', 'shift','unshift','splice','sort', 'reverse'
// ...
} else {
this.walk(value)
}
}
walk(obj) {
const keys = Object.keys(obj)
for (let i = 0; i < keys.length; i++) {
defineReactive(obj, keys[i]) // 遍历属性添加拦截
}
}
}
// core/observer/index.js
export function defineReactive(){
const dep = new Dep() //每个属性 对应一个Dep,
// ...
!shallow && observe(val) // data里面可能存在嵌套对象,继续递归
Object.defineProperty(obj, key,{
get(){
dep.depend() // 在get中收集watcher 访问时触发
},
set(){
childOb = !shallow && observe(newVal) //set赋值了对象 继续递归这个对象进行拦截
dep.notify() //发布订阅消息
}
})
}
- 页面中一个数据可能在多个地方被使用,所以一个属性可能对应多个
Watcher
,设置一个Dep
来管理Watcher
// core/observer/dep.js
class Dep(){
depend(){
if (Dep.target) { //Dep.target是一个watcher
Dep.target.addDep(this)
}
}
addSub (sub: Watcher) {
this.subs.push(sub) //一个Dep对应多个Watcher
}
notify () {
for (let i = 0, l = subs.length; i < l; i++) { //遍历收集的Watcher,
subs[i].update()//依次更新Watcher
}
}
}
- 订阅者
Watcher
接收到属性的变化去更新相应的视图
// core/observer/watcher.js
class Watcher(){
addDep(dep){
dep.addSub(this) //将watcher添加到dep.subs中, 里面存放的是watcher列表
}
update() {
//同步或者异步更新?
this.run()
}
run(){
const value = this.get() // 获取当前的值
if (
value !== this.value ||
isObject(value) ||
this.deep
){ // 旧值新值是否不等,值是一个对象,deep模式
this.cb.call(this.vm, value, oldValue) //执行Watcher的回调函数
}
}
get(){
value = this.getter.call(vm, vm) //重新触发属性的get方法,收集watcher
this.cleanupDeps()
return value
}
cleanupDeps() {
//遍历所有的Dep,移除每个dep里面对Watcher的订阅
let i = this.deps.length
while (i--) {
const dep = this.deps[i]
if (!this.newDepIds.has(dep.id)) {
// 根据订阅的id移除掉废弃的Watcher,避免更新时的额外消耗
dep.removeSub(this)
}
}
// 将newDeps,newDepIds赋值给depIds,deps,然后清空
let tmp = this.depIds
this.depIds = this.newDepIds
this.newDepIds = tmp
this.newDepIds.clear()
tmp = this.deps
this.deps = this.newDeps
this.newDeps = tmp
this.newDeps.length = 0
}
}
具体过程
依赖收集
get()===>dep.depend()===>(Dep.target就是Watcher)Dep.target.addDep()===>dep.addSub()
- 去读取属性的时候,触发属性的
get
拦截操作 - 进入到
Dep
对象中,每个属性对应创建一个Dep
,使用dep.depend()
方法去收集相关的依赖 - 进入到
watcher.addDep
方法,Watcher
对象中存在一个newDeps
和newDepIds
属性,它存储了与这个Watcher
相关的Dep
对象,一个Watcher
对应多个Dep
,newDeps
里面存储的是Dep
列表 - 重新回到
Dep
对象中,将这个Watcher
添加到subs
属性中,一个Dep
对应多个Watcher
派发更新
set()--->dep.notify()--->watcher.update()--->watcher.run()--->watchrt.get()--->this.getter.call(vm, vm)--->watcher.cleanupDeps()---> this.cb.call(this.vm, value, oldValue)
- 给属性赋值的时候触发
set()
拦截操作 - 进入这个属性对应的
Dep
对象中,遍历subs
属性里面存放的相关的watcher
列表,准备更新 - 进行更新时会判断更新的方式是同步还是异步更新,然后更具更新方式执行不同的操作
- 同步更新进入到
run()
方法,触发get()
方法获取更新的值 - 进入
get()
方法会重新调用his.getter.call(vm, vm)
触发getter
拦截器,获取属性的值,并且重新去收集依赖 - 重新收集依赖之后会触发
clearupDeps()
清理掉与当前更新无关的的`Watcher - 执行
watcher
中得回调函数