Vue源码学习记录
vue实现响应式?
对象是通过Object.defineProperty()实现的,数组是通过写数组的方法(push、pop 、shift、unshift reverse sort),设置拦截器实现响应式的。一般包括四个部分:dep(收集、删除、通知依赖)、watcher(指依赖,类似中介,被dep收集起来,数据发生变化时dep通知watcher,它再通知其它地方)、Observer(将每个对象数据的属性都变为响应式:即添加上getter和setter)、Data(数据)
四者关系图如下:
对象的实现如下:
基本响应式框架:
function defineReactive(data,key,val) {
if(typeof val === 'object') { // 递归子属性
new Observer(val)
}
let dep = new Dep() // 将依赖收集加入
Object.defineProperty(data,key,{
enumerable: true,
configurable: true,
get:function() { // 在getter中收集依赖
dep.depend()
return val
},
set(newVal) { // 在set中触发依赖
if(val === newVal) {
return
}
val = newVal
dep.notify()
}
})
}
Dep依赖管理器实现
class Dep{
constructor() {
this.subs = []
}
addSub(sub) {
this.subs.push(sub)
}
remove(sub) {
remove(this.subs,sub)
}
depend() { // 添加订阅对象
if(window.target) {
this.addSub(window.target)
}
}
notify() {
let subs = this.subs.slice()
for(let i=0;i<subs.length;i++) {
subs[i].update() // watcher中自定义函数
}
}
}
function remove(arr,item) {
if(arr.length) {
let index = arr.indexOf(item)
if(index > -1) {
return arr.splice(index,1)
}
}
}
watcher的实现
class Watcher {
constructor(vm,exporFn,cb) { // cb:回调函数
this.vm = vm
this.getter = parsePath(exporFn) // 读取路径:exporFn类似’a.b.c.c‘
this.cb = cb
this.value = this.get()
}
get(){
window.target = this // 设置成当前watcher实例
let value = this.getter.call(this.vm,this.vm)
window.target = undefined
return value
}
update() {
const oldVal = this.value
this.value = this.get()
this.cb.call(this.vm,this.value,this.oldVal)
}
}
function parsePath(path) {
let reg = /^\w$/
if(!reg.test(path)) {
return
}
let splitArr = path.split('.')
return function(obj) {
for(let i=0;i<splitArr.length;i++) {
if(!obj) return
obj = obj[splitArr[i]]
}
return obj
}
}
Observer类的实现
class Observer{
constructor(value) {
this.value = value
if(!Array.isArray(value)) {
this.walk(value)
}
}
walk(obj) { // 只有对象才被调用
const keys = Object.keys(obj)
for(let i=0;i<keys.length;i++) {
defineReactive(obj,keys[i],obj[keys[i]])
}
}
}
只能检测到对象的修改、不能检测到增加和删除属性,可以使用vue.delete来检测两者。