vue官方源码剖析

  1. 变化侦测
    UI = render(state),数据驱动视图,Vue 在表达式中充当 render 这个角色,当 state 改变时,Vue 给出相应的变化到 UI 上,Vue 如何得知 state 变化了呢,即 state 变化怎么通知给 Vue

  2. Object.defineProperty 使对象数据变得可观测

        let car = {
            'brand': 'BMW',
            'price': 300
        }
        let val = 300
        Object.defineProperty(car, 'price', {
            enumerable: true,
            configurable: true,
            get(){
                console.log('price 属性读取')
                return val
            },
            set(newVal){
                console.log('price 属性修改')
                val = newVal
            }
        })
    

    Object.defineProperty 对对象的属性进行拦截,并执行自己想要的操作,这样就可以检测对象的变化了

       // 源码位置:src/core/observer/index.js
       // Observer类会通过递归的方式把一个对象的所有属性都转化成可观测对象
        export class Observer {
            constructor (value) {
            this.value = value
            // 给value新增一个__ob__属性,值为该value的Observer实例
            // 相当于为value打上标记,表示它已经被转化成响应式了,避免重复操作
            def(value,'__ob__',this)
            if (Array.isArray(value)) {
                // 当value为数组时的逻辑
                // ...
            } else {
                this.walk(value)
            }
            }
    
            walk (obj: Object) {
            const keys = Object.keys(obj)
            for (let i = 0; i < keys.length; i++) {
                defineReactive(obj, keys[i])
            }
            }
        }
        function defineReactive (obj,key,val) {
            // 如果只传了obj和key,那么val = obj[key]
            if (arguments.length === 2) {
            val = obj[key]
            }
            if(typeof val === 'object'){
                new Observer(val)
            }
            Object.defineProperty(obj, key, {
            enumerable: true,
            configurable: true,
            get(){
                console.log(`${key}属性被读取了`);
                return val;
            },
            set(newVal){
                if(val === newVal){
                    return
                }
                console.log(`${key}属性被修改了`);
                val = newVal;
            }
            })
        }
    
  3. 依赖收集
    在第一二步中我们将数据变成响应式,可观测的状态,在此过程中我们可以在 getter 中收集依赖,在 setter 中通知依赖更新;
    并在此过程将依赖置于依赖收集器中

    export default class Dep {
        constructor() {
            this.subs = []
        }
        addSubs(sub) {
            this.subs.push(sub)
        }
        removeSubs(sub) {
            remove(this.subs, sub)
        }
        depend() {
            if(window.target) {
                this.addSubs(window.target)
            }
        }
        notify() {
            const sub = this.subs.slice()
            for(let i = 0;i < sub.length;i++) {
                subs[i].update()
            }
        }
    }
    
    const remove = (arr, item) => {
        if(arr.length) {
            const index = arr.indexOf(item)
            if(index > -1) {
                return arr.splice(index, 1)
            }
        }
    }
    

    在收集器中定义一个 subs 来进行依赖收集,并在这个对象中包含了对依赖的增、删、以及更新。有了收集器我们就可以将其置于 getter 中,代码如下:

    function defineReactive(obj, key, val) {
        if(arguments.length === 2) {
            val = obj[key]
        }
        if(typeof val === 'object') {
            new Observe(val)
        }
        const dep = new Dep()
        Object.defineProperty(obj, key, {
            enumerable: true,
            configurable: true,
            get() {
                dep.depend()
                return val
            },
            set(newVal) {
                if(newVal === val) return
                val = newVal
                dep.notify()
            }
        })
    }
    

    在 get 中我们进行了依赖收集, 即 dep.depend; 在 set 中我们进行了通知依赖更新,即 dep.notify;但是我们不知道通知谁,或者说我们不知道谁是依赖。
    在 Vue 中定义过一个 Watch 类,在数据变化后,我们不直接通知依赖更新,而是告诉 Watch 类的实例,让实例去通知真正的试图更新

    export default class Watcher {
        constructor(vm, expOrFn, cb) {
            this.vm = vm
            this.cb = ch
            this.getter = parsePath(expOrFn)
            this.value = this.get()
        }
        get() {
            window.taget = this
            const vm = this.vm
            let value = this.getter.call(vm, vm)   // 触发dep的依赖收集  dep.depend
            window.target = undefined   // 依赖存放后,释放window.target
            return value
        }
        update() {
            const oldVal = this.value
            this.value = this.get()
            this.cb.call(this.vm, this.value, oldVal)  // set 触发dep的notify,在notify中遍历watcher里面的依赖,并执行对应的update
        }
    }
    
    
    /*
        Parse simple path
        把形如 a.b.c.d的字符串所表示的值,从真是的data对象里取出来
        data = {a:{b:{c:{d:2}}}}
        parsePath('a.b.c.d')(data) // 2
    
    */
    const bailRE = /[^w.$]/
    function parsePath(path) {
        if(bailRE.test(path)) return
        const segments = path.split('.')
        return function(obj) {
            for(let i=0; i<segments.length; i++) {
                if(!obj) return
                obj = obj[segments[i]]
            }
            return obj
        }
    }
    

    不足:对于后续对象添加的属性或者删除已有的属性,object.definePropery 不能够监听,即无法通知依赖,无法驱动视图进行响应式更新
    流程:

    1. Data 通过 obsever 转换成了 getter/setter 的形式追踪变化
    2. 当外界通过 Watcher 进行数据读取时,会触发 getter 将 Watcher 添加到依赖中
    3. 当数据发生变化时,会触发 setter,从而向 Dep 中的依赖(Watcher)发送通知
    4. Watcher 收到通知后,会向外界发送通知,可能会驱动数据更新,也可能触发某些回调
原文地址:https://www.cnblogs.com/tutao1995/p/14252043.html