Vue响应式原理以及简单实现

6,295 阅读3分钟

在vue原理中,最重要的部分就是如何实现数据的观测,依赖的收集,视图的更新。本文讲的就是Observer, Dep, Watcher这三个的简单实现。 pub(publish)表示发布者,sub(subscribe)表示订阅者, cb(callback)表示回调函数 如果你觉得这篇讲的对你有所帮助,请帮我点个star

observer的实现

Observer的作用简单来说就是让object对象的属性都用Object.defineProperty()来进行定义,这样当获取object的属性,或者修改属性的时候,就能够触发get,set达到数据的观测的效果。

class Observer {
    constructor(value) {
        this.value = value 
        this.walk(this.value)
    }
    walk (value) {
        // 递归遍历value的属性
        Object.keys(value).forEach((key) = > {
            defineReactive(value, key, value[key])
        })
    }
}
function defineReactive(obj, key ,val) {
    let childOb = observe(val)
    Obeject.defineProperty(obj, key, {
        enumerable: true,
        configurable: true,
        get() {
            console.log('')
            return val
        },
        set(newVal) {
            console.log('')
            val = newVal
            childOb = observe(val)
        }
    })
}
function observe (value) {
    if (typeof value === 'object' && !Array.isArray(value)) {
        value = new Observer(value)
    }
}

defineReactive的作用就是给对象的属性进行简单的数据观测,一旦值获取或者设置就会触发一些行为.因为一个对象的属性可能还是对象,所以在这里我们添加observe函数来遍历值,让一个对象的属性的属性还是可以进行观测的,简单呢来说的意思就是让所有属性都可以进行忽略。当然在实际情况中,我们还需要考虑数组的情况,但都大同小异。 这样做代码似乎有点丑,我们在设置属性触发set会发生console.log()函数,有没有一种更加智能的方式来实现通知变化呢。这里我们就需要用消息订阅器来进行实现,这样做我们就不需要通过观察console.log()输出的值来看进行的情况,我们只需要在set方法里边加一个通知,一旦值发生变化,就通知外边值发生了改变

Dep的实现:

Dep的作用就是用来收集属性值的变化,一旦set方法触发的时候,就更新视图。那就准备一个数组来进行收集吧! 下面是Dep的实现:

class Dep {
    constructor() {
        this.subs = []
    }
    addSub (sub) {
        this.subs.push(sub)
    }
    notify () {
        const subs = this.subs.slice()
        subs.forEach((sub) => {
            sub.update() // 视图更新
        })
    }
}

上面就是Dep的简单实现,addSub的作用是增加订阅者,因为有很多订阅者,我们需要用一个数组将它进行存储,notify()函数的作用就是当set发生的时候,进行通知,update()这个函数待会在watcher中会讲到。实现了Dep我们是不是该更改了set()函数了呢,下面是defineReactive()修改后的代码

function defineReactive(obj, key ,val) {
    let dep = new Dep() // 毕竟要使用Dep的方法
    let childOb = observe(val)
    Obeject.defineProperty(obj, key, {
        enumerable: true,
        configurable: true,
        get() {
            return val
        },
        set(newVal) {
            val = newVal
            childOb = observe(val)
            dep.notify() // 因为数据改变了,我们就通知Dep
        }
    })
}

一旦触发set,就调用dep.notify(),notify的作用就是针对订阅者遍历进行更新。

Watcher的简单实现:

watcher的作用,就是当状态发生改变的时候,更新视图,我们可以假设

class Watcher {
    constructor (vm, cb, expOrFn) {
        this.vm = vm // 这表示一个Vue的实例
        this.cb = cb
        // 这里需要考虑expOrFn是字符串或者函数的情况
        // 这里做一个简化,只考虑函数的情况
        this.getter = expOrFn
        this.value = this.get()
    }
    get () {
        Dep.target = this 
        const vm = this.vm
        value = this.getter.call(this.vm, vm)
        Dep.target = null
        return value 
    }
    update () {
        this.run()
    }
    run () {
        const value = this.get()
        if (value !== this.value) {
            const oldValue = this.value
            this.value = value
            this.cb.call(this.vm, value, oldValue) 
        }
    }
}

Watcher的简单实现就完成了,在Dep()构造函数中,我们使用了sub.update()这行代码,而update函数是Watcher里边的方法,说明每一个sub都是Wathcer的实例,问题是我们应该如何通过addSub()这个方法,将Watcher加入到subs这个数组中尽心存储呢,答案还是在defineReactive()里边进行修改

function defineReactive(obj, key ,val) {
    let dep = new Dep() // 毕竟要使用Dep的方法
    let childOb = observe(val)
    Obeject.defineProperty(obj, key, {
        enumerable: true,
        configurable: true,
        get() {
            if(Dep.target) {
                dep.addSub(Dep.target)
            }
            return val
        },
        set(newVal) {
            val = newVal
            childOb = observe(val)
            dep.notify() // 因为数据改变了,我们就通知Dep
        }
    })
}

这样是不是就实现了往Dep里边加Watcher了,vue源码中比这个复杂的多,各种参数,看着头大。本文的宗旨就是通过简化让你了解内部原理,如果需要更深入了解就需要阅读源码了。