Vue源码解析之数据响应式原理

178 阅读2分钟

Object.defineProperty

1659337204663.png

一.侵入式和非侵入式

非侵入式 this.++ //vue数据变化
侵入式 this.setdData({a:1})//小程序

二.Object.defineProperty()

Object.defineProperty(obj, prop, descriptor)

developer.mozilla.org/zh-CN/docs/…

var obj = {}
Object.defineProperty(obj, 'a', {
    value: 5,
    enumerable: true , //可被枚举
    writable: true,// 可被改变
    
    get () {
    },
    set () {
    }
})

image.png

三.defineReactive

function defineReactive (data,key,val) {
    const dep = new Dep()
    if(arguments.length>0) {
        val = obj[key];
    }
    
    // 子元素递归
    let childOb = observe(val)
    
    Object.defineProperty(data, key, {
        enumerable: true,
        writable: true,
        get () {
            console.log('正在访问obj的'+ key + '属性')
            if(Dep.target) {
                dep.depend();
                if(childOb) {
                    childOb.dep.depend()
                }
            }
            return val
        },
        set (newValue) {
            console.log('正在改变obj的'+ key + '属性' + newValue)
            if(newValue == val) return;
            val = newValue
            // 设置新值的observe
            childOb = observe(newValue)
            // 发布订阅模式
            dep.notify()
        },
    })
}

四.循环递归(Observer)

将一个正常的object转化为每个层级的属性都是响应式,可以被侦测

export const def = function (obj,key,value,enumerable) {
    Object.defineProperty(obj, key , {
        value,
        enumerable,
        writable: true,
        configurable: true
    })
    
}
export default Class Observer {
    constructor (value) {
        // 每个Observer的实例上都有一个dep
        this.dep = new Dep()
        //this不是表示类本身,而是表示实例
        def(value, '_obj_', this, false)
        console.log('Observer构造器', value)
        // 检查是否为数组还是对象
        if(Array.isArray(value)) {
            Object.setPrototypeOf(value, arrayMethods)
            this.observeArray(value)
        } else {
        
        }
        this.walk(value)
    }
    // 遍历
    walk(value) {
        for(let k in value) {
            defineReactive(value, k) 
        }
    }
    // 数组的特殊遍历
    observeArray(arr) {
        for(let i=0;i=arr.length;i++) {
            observe(arr[i])
        }
    }
}

function observe(value) {
    if(typeof value != 'object') return;
    var obj;
    if(typeof value._ob_ != 'undefined') {
        obj = value._obj_
    } else {
        obj = new observe(value)
    }
    return obj

}

五.数组的响应式

改写的7个方法:

['push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse']

const arrayPrototype = Array.prototype
// 以Array.prototype为原型创建arrayMethods对象
const arrayMethods = Object.create(arrayPrototype)
// 要被改写的七个方法
const methodsMeedChange = [
'push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse'
]
methodsMeedChange.forEach(methodName=>{
    // 备份原来的方法
    const original = arrayMethods[methodName]
    
    def(arrayMethods, methodName, function(){
        const result = original.apply(this, arguments)
        // 把类数组变成数组
        const args = [...arguments]
        // 恢复原来的功能
        const ob = this._ob_
        
        let insterted = []
        
        switch(methodName) {
            case 'push':
            case 'unshift':
                insterted = args
                break
            case 'splice':
                insterted = args.slice(2)
                break
        }
        // 新数据变为相应
        if(insterted) {
            ob.observeArray(insterted)
        }
        ob.dep.notify()
        
        return result
    }, false)
})

六.收集依赖

vue2.x 中等粒度依赖, 用到数据的组件是依赖

在getter种收集依赖, 在setter种触发依赖

把依赖收集的代码封装成一个Dep类,它专门用来管理依赖,每个Observer的实例,成员中都有一个Dep的实例;

Watcher是一个中介, 数据发生变化时通过Watcher中专,通知组件

image.png

image.png

依赖就是Watcher. 只有Watcher触发的getter才会收集依赖,那个Watcher触发了getter, 就把那个Watcher收集到Dep中

Dep使用发布订阅模式,当数据发生变化时,会循环依赖列表,把所有的Watcher都通知一遍.

Watcher把自己设置到全局的一个指定位置, 然后读取数据, 所以会触发这个数据的getter.在getter中就能得到当前正在读取数据的Watcher,并把这个Watcher收集到Dep中.

var uid = 0
export default const Dep {
    constructor () {
        this.uid = uid++
        // 用数组存储自己的订阅者,放的是Watcher的实例
        this.subs = []
    }
    // 添加订阅
    addSub(sub) {
        this.subs.push(sub)
    }
    //添加依赖
    depend() {
        // Dep.target就是设置一个自己制定的全局的位置.用window.target也行,只要是全局唯一
        if(Dep.target){
            this.addSub(Dep.target)
        }
    }
    
    notify() {
        const subs = this.subs.slice()
        for(let i=0, i - subs.length;i<1;i++) {
            subs[i].update()
        }
    }
}
var uid = 0
export default const Watcher {
    constructor (target,expression,callback) {
        this.id = uid++
        this.target = target
        this.getter = parsePath(expression)
        this.callback = callback()
        this.value = this.get()
    }
    update () {
        this.run()
    }
    get() {
        Dep.target = this;
        const obj = this.target;
        var value;
        try{
            value = this.getter(obj)
        } finally {
            Dep.target = null;
        }
        return value
    }
    run () {
        this.getAndInvoke(this.callback)
    }
    getAndInvoke (cb) {
        if(value!==this.value || typeof value == 'object') {
            const oldValue = this.value
            this.value = value
            cb.callback(this.target,value,oldValue)
        }
    }
}

function parsrPath (str) {
    var segments = str.split('.')
    return (obj) => {
        for(let i =0;i<segments.length;i++){
            if(!obj) return
            obj = obj[segments[i]]
        }
    }
}