vu2的数据劫持和响应式详解

295 阅读2分钟

前言

本文主要分析vue2源码中的数据劫持、响应式,围绕他们的设计思想和源码解析.
通过这篇文章可以学习到什么呢?
            1.数据劫持源码的整体实现思路
            2.什么是代理模式
            3.响应式源码的整体实现思路
            4.什么是发布订阅模式       

什么是代理模式

     为其他对象提供一种代理以控制对这个对象的访问
     在某些情况下,一个对象不适合或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用
     vue2中使用Object.defineProperty作为中间代理,并分别设定getset方法实现功能解耦

数据劫持

在vue2和vue3中数据劫持分别使用了object.defineProperty和proxy,并使用代理模式思想.
第一步:初始化
     function initData(){
         let data = vm.$options.data;//获取初始data
         data = isFunction(data)?data.call(vm,vm):data;
         //vm为Vue的实例对象
         //通过data.call绑定vm的实例对象,且调用data函数生成独立的数据
         const ob = observe(data);//对所有data数据进行数据劫持
     }
     
第二步:开始数据劫持
    function observe(value){
        return new Observer(value);
    }
    
    class Observer{
        dep;
        costructor(value,shallow = false,mock = false){
            this.dep = new Dep();
            if(isArray(value)){
               //这是对数据进行数据劫持
               value.proto = arrayMethods;//对数据上方法的重写,来实现数组方法触发响应式
               //这里并没对数组下标进行监听,数组下标过多,影响性能
               this.observeArray(value);//对数组值进行监听
            }else{
               //这里对非数组进行数据劫持
               const keys = Object.keys(value);//获取对象的键,形成数组
               for(let i=0;i<keys.length;i++){
                   const key = keys[i];
                   defineReactive(value,key,shallow,mock);
               }
            }
        }
        observeArray(value){
            for(let i=0,l=value.length;i<l;i++){
                observe(value[i]);
            }
        }
    }
    
    function defineReactive(obj,key,shallow,mock){
        const dep = new Dep();
        //创建订阅中心,每个属性的劫持都有自己的dep.属于响应式中的一部分.
        let childOb = !shallow && observe(val,false,mock);
        //如果value还是一个对象或数组,会进行递归
        
        //下面使用Object.defineProperty实现数据劫持
        Object.defineProperty(obj,key,{
            enumerable:true,
            configurable:true,
            get:function reactiveGetter(){
                 //使用这个属性时,调用的方法
                 //通知对应的dep去依赖对应的值
                dep.depend({
                    target:obj,
                    type:'get',
                    key,
                })
            },
            set:function reactiveSetter(newVal){
                //修改这个属性时,调用的方法
                //使对应的dep订阅中心去通知watcher观察者
                dep.notify({
                    type:'set',
                    target:obj,
                    key,
                    newValue,
                    oldValue:value
                })
            }
        })
    }
    

订阅发布模式

    发布-订阅模式定义了一种一对多的关系,通过一个中间者让多个订阅者对象同时监听某一个发布者
stateDiagram-v2
observe --> dep
dep --> watcher1
dep --> watcher2
watcher1 --> dep
watcher2 --> dep

响应式原理

 将observe作为发布者,dep作为中间者,watcher作为订阅者
    
    1.发布者通过Object.defineProperty的setget去通知中间者进行depend依赖自己和notify通知订阅中间者的订阅者进行更新update
    
    2.中间者
        通过this.subs数组来存储订阅者,
        通过addSub方法去添加,
        通过removeSub方法去移除
        
    3.订阅者
        通过this.deps数组来存储需要订阅的中间者
        通过depend方法直接
        通过addDep方法直接订阅到特定的中间者dep上,
        通过cleanupDeps方法清除订阅不同的dep,
        通过update方法产生更新,
        通过depend方法遍历this.deps数组,通知dep去订阅
        
 实现代码
      //中间者
        class Dep{
            static target;
            id;
            subs;
            _pending = false;

            constructor(){
                this.id = uis++;
                this.subs = [];//保存多个订阅者
            }

            //添加到subs数组中
            addSub(sub){
                this.subs.push(sub);
            }

            //将subs数组中移除对应数据
            removeSub(sub:DepTarget){
                this.subs[this.subs.indexOf(subs)] = null;
            }
            
            //依赖
            depend(info){
                Dep.target.addDep(this);
                Dep.target.onTrack({
                effect: Dep.target,
                ...info
                })
            }

            //通知更新
            notify(info){
                const subs = this.subs.filter(s=>s);
	
                for(let i = 0,l = subs.length;i<l;i++){
		const sub = subs[i];
		sub.update();
                }
            }
        }
        
   //订阅者
        class Watcher{
            //订阅传入的中间者
            addDep(dep){
                dep.addSub(this);
            }

            //通知订阅的多个中间者去去除这个订阅者
            cleanupDeps(){
                let i = this.deps.length;
                while(i--){
		const dep = this.deps[i];
		dep.removeSub(this);
                }
            }

            //订阅者进行更新
            update(){

            }

            //通知所有中间着去依赖这个订阅者
            depend(){
                let i = this.deps.length;
                while(i--){
		this.deps[i].depend();
                }
            }
        }