vue响应式原理学习笔记总结

114 阅读2分钟

实现入口文件

//index.js
import observe from "./observe.js";
var data = {
    a: {
        m: {
            n: 5
        }
    },
    c: {
        d: {
            e: {
                f: 6666
            }
        }
    },
    hh:10,
    g:[1,2,3,4,5]
};
observe(data)
data.hh = 100

首先是需要把对象变为响应式的函数。

实现响应式的函数

observe()

import Observer from "./Observer.js";

export default function(value){
    //该方法使用__ob__属性来判断数据是否被响应式
    if(typeof value != 'object') return
    var ob;
    if(typeof value.__ob__ != 'undefined'){
        ob = value.__ob__
    }else{
        ob = new Observer(value)
    }
    return ob 
}

一个对象是否被响应式了,是通过该对象上是否有__ob__属性来判断。这时候我们已经成功为对象添加响应式了。

Observer类做了什么?

export default class Observer {
    constructor(value){
        // 每一个Observer的实例身上,都有一个dep
        this.dep = new Dep()//用于收集依赖
        def(value,'__ob__',this,false)
        if(Array.isArray(value)){
            //改变原型
            Object.setPrototypeOf(value, arrayMethods);
            this.observeArray(value)
        }else{
            this.walk(value)
        }
    }
    walk(value){
        for(let key in value){
            defineReactive(value,key)
        }
    }
    observeArray(arr){
        for(let i=0,l=arr.length;i<l;i++){
           observe(arr[i])
        }
    }
}
function def(data,key,value,enumerable){
    Object.defineProperty(data,key,{
        value,
        enumerable,
        configurable:true,
        writable:true
    })
}

其实就是为对象添加__ob__属性。判断传入的数据是数组还是对象。

1.对象:继续遍历对象,利用Object.defineProperty劫持数据。

2.是数组,遍历每一项,利用递归继续判断。

对象还是数组?

  • 对象:劫持数据。
export default function (data,key,val){
    if (arguments.length == 2) {
        val = data[key];
    }
    observe(val);
    Object.defineProperty(data,key,{
        configurable:true,
        enumerable:true,
        get(){
            console.log("你视图访问"+key+"属性",val)
            return val
        },
        set(newval){
            console.log("你视图改变"+key+"属性",val,newval)
            if(val == newval){
                return
            }
            val = newval
        }
    })
}
  • 数组:改写数组的方法
const arrayPrototype = Array.prototype

//创建一个新对象指向数组原型
export const arrayMethods = Object.create(arrayPrototype)

//需要改写的方法
const arrayNeedChange = [
    'push',
    'pop',
    'shift',
    'unshift',
    'splice',
    'sort',
    'reverse'
]
arrayNeedChange.forEach(methodName=>{ 
    // 备份原来的方法,因为push、pop等7个函数的功能不能被剥夺
    const original = arrayPrototype[methodName];
    //给arrayMethods添加方法
    def(arrayMethods,methodName,function(){
        //调用函数
        const result = original.apply(this,arguments)
        const args = [...arguments]
        const ob = this.__ob__
        let inserted = []
        switch(methodName){
            case 'push':
            case 'unshift':
                inserted = args
                break;
            case 'splice':
                inserted = args.slice(2)
                break
        }
        if(inserted){
            //递归调用判断数组里面的项
            ob.observeArray(inserted)//添加的项再次变为响应式
        }
        console.log('啦啦啦')
        return result
    },false)
})

单单劫持数据是没有什么意义的,最重要的还是依赖的收集。vue中有三种依赖。视图依赖,computed依赖,watch依赖。

这里实现的是watch依赖

依赖收集器(订阅者)

var uid = 0
export default class Dep{
    constructor(){
        console.log('我是dep构造器')
        this.id = uid++
        this.sub = []
    }
    //添加订阅
    addsub(sub){
        this.sub.push(sub)
    }
    //在此收集依赖
    depend(){
        if(Dep.target){
            this.addsub(Dep.target)
        }
    }
    //通知更新
    notify(){
        let subs = this.sub.slice()
        console.log('通知',subs)
        for(let i=0,l=subs.length;i<l;i++){
            subs[i].update()
        }
    }
}

依赖(观察者)

import Dep from "./Dep"
var uid = 0
export default class Watcher{
    constructor(target, expression, callback){
        this.id = uid++
        this.target = target;
        this.getter = parsePath(expression); //1.expression=>a.b.c | 2.此时getter为一个函数,参数为一个对象
        this.callback = callback //回调函数
        this.value = this.get() //当调用this.get()时,其实就会读取obj上的属性,就会触发get方法,触发添加依赖方法,等同于自动添加依赖。
    }
    get(){
        //进入依赖收集
        Dep.target = this //当读取obj的属性时,此时Dep.target就为真
        const obj = this.target
        var value
        try{
            value = this.getter(obj)
        }finally{
            Dep.target = null
        }
        return value
    }
    update(){
        this.run()
    }
    run(){
        this.getAndInvoke(this.callback);
    }
    getAndInvoke(cb){
        const value = this.get()
        if(value!==this.value || typeof value == 'object'){
            const oldvalue = this.value
            cb.call(this.target,value,oldvalue) //触发回调函数
        }
    }
}

function parsePath(str) { //此方法用于取出obj.a.b.c这种的值
    var segments = str.split('.');

    return (obj) => {
        for (let i = 0; i < segments.length; i++) {
            if (!obj) return;
            obj = obj[segments[i]]
        }
        return obj;
    };
}

调整defineReactive.js

export default function (data,key,val){
    const dep = new Dep()
    if (arguments.length == 2) {
        val = data[key];
    }
    let childOb = observe(val);
    Object.defineProperty(data,key,{
        configurable:true,
        enumerable:true,
        get(){
            if(Dep.target){
                dep.depend()//读取属性就会自动添加依赖
                if(childOb){
                    childOb.dep.depend() //子也要和当前watcher建立联系
                }
            }
            return val
        },
        set(newval){
            console.log("你视图改变"+key+"属性",val,newval)
            if(val == newval){
                return
            }
            val = newval
            childOb = observe(newval);//新数据继续响应式
            dep.notify()//触发依赖更新
        }
    })
}

调整数组方法

import {def} from "./utils"
const arrayPrototype = Array.prototype

//创建一个新对象指向数组原型
export const arrayMethods = Object.create(arrayPrototype)

//需要改写的方法
const arrayNeedChange = [
    'push',
    'pop',
    'shift',
    'unshift',
    'splice',
    'sort',
    'reverse'
]
arrayNeedChange.forEach(methodName=>{ 
    // 备份原来的方法,因为push、pop等7个函数的功能不能被剥夺
    const original = arrayPrototype[methodName];
    //给arrayMethods添加方法
    def(arrayMethods,methodName,function(){
        const result = original.apply(this,arguments)
        const args = [...arguments]
        const ob = this.__ob__
        ob.dep.notify() //**只修改了该行触发依赖**
        let inserted = []
        switch(methodName){
            case 'push':
            case 'unshift':
                inserted = args
                break;
            case 'splice':
                inserted = args.slice(2)
                break
        }
        if(inserted){
            //递归调用判断数组里面的项
            ob.observeArray(inserted)
        }
        console.log('啦啦啦')
        return result
    },false)
})

最后

new Watcher(obj,'hh.value',function(val){
    console.log('★我是watcher,我在监控a.m.n', val);
})
通过watcher依赖来监听数据,当数据改变时,就会触发依赖修改,输出回调函数。

疑问:不是很明白给数组添加依赖那步的操作?

学习地址