vue原理图

vue原理图

理解Observer

上一节我们实现了数据代理,这一节我们来研究一下Observer。首先我们需要明白以下几点:

  1. Observer观察的对象: Observer实际是对data进行观察,从而达到第2点中的目的
  2. Observer的目的:Observer的目的有两个。其一,依赖收集,其二,更新显示
  3. Observer实现原理:: Observer跟数据代理一样,是通过Object.defineProperty(…)来实现的

具体而言,我们定义Observer这么一个对象,在内部通过Object.defineProperty(…)对data中的所有数据进行劫持,并给每个数据添加getter和setter,从而达到依赖收集和更新显示的目的
这里还有两个概念需要明确,即什么是依赖收集和更新显示。

  • 依赖收集: 什么是依赖,依赖就是一个个的大括号表达式和指令表达式
<p>{{name}}</p> //{{name}}是一个依赖,每一个大括号表达式都是一个依赖
<p v-text='name'></p> //v-text='name'是一个依赖,每一个v-普通(非事件)指令都是一个依赖

知道了什么是依赖,那我们为什么要把依赖收集起来呢?很显然,这些依赖其实都对应了data中的一个个数据,我们在视图中用到了data中的数据,就形成了依赖。 但视图不可能一成不变,往往是需要交互的,有了交互data中的数据就会发生变化,数据变化了就需要更新视图的显示。所以,依赖收集的目的就是为了应对将来数据的变化,从而当依赖的数据发生变化时能够准确的批量的更新依赖所在地的显示。

  • 更新显示:更新显示就是数据发生变化时,更新视图的显示。这里面还有一个初始化显示的概念,这个是在编译阶段完成的工作,会在后续的章节中进行研究。

实现Observer

理解了Observer后,我们就来实现一下Observer:

//ES6实现
class Vue {
    constructor(options) {
   		//缓存配置项
        this.$el = options.el || document.body;
        this.$options = options;
        const data = this.$data = options.data;
		//劫持数据
        this.hijackData(data);
    }

    hijackData(data) {
        if (!data || typeof data !== 'object') { //不存在或者不为对象,返回(递归退出条件)
            return;
        }
        //拿到data中所有可枚举的属性
        Object.keys(data).forEach(key => {
        	//递归遍历所有层次
            this.hijackData(data[key]);
            //创建观察者
            new Observer(data, key);
        });
    }
}

//观察者实现
class Observer {
    constructor(data, key) {
    	//对数据进行观察
        this.observe(data, key, data[key]);
    }
    observe(data, key, val) {
    	//data中每一个数据对应一个Dep容器,存放所有依赖于该数据的依赖项
        const dep = new Dep();
        Object.defineProperty(data, key, {
            enumerable: true, //可枚举
            configurable: false,//不能再配置
            get() {
                if (Dep.target) {//Dep.target存放具体的依赖,在编译阶段检测到依赖后被赋值
                    dep.addDep(Dep.target); //依赖收集
                }
                return val;
            },
            set(newVal) {
                if (newVal === val) {
                    return;
                }
                val = newVal;
                dep.notify(); //当数据发生变化时,通知所有的依赖进行更新显示
            }
        });
    }
}

//Dep容器,data中的每个数据会对应一个,用来收集并存储依赖
class Dep {
    constructor() {
        this.deps = []; //所有的依赖将存放在该数组中
    }
    //收集依赖
    addDep(dep) {
        this.deps.push(dep);
    }
    //通知更新
    notify() {
        this.deps.forEach(dep => {
            dep.updata();
        });
    }
}

这一节先到这,下一节我们来实现模版/指令编译
下一节: 模版/指令编译
上一节: 数据代理

Logo

前往低代码交流专区

更多推荐