vue源码解析3——数据劫持

3 数据劫持

数据劫持是vue中用来实现数据绑定的一种技术,通过defineProperty()来监视data中所有属性(任意层次)数据的变化, 一旦变化就去更新界面。

function Watcher(vm, exp, cb) {
    this.cb = cb; // <1>保存回调函数,主要用与更新节点 
    this.vm = vm;
    this.exp = exp;
    this.depIds = {}; // 声明dep容器 用来存放dep
    this.value = this.get();
}

Watcher.prototype = {
    update: function() {
        this.run();
    },
    run: function() {
        var value = this.get(); // 
        var oldVal = this.value;
        if (value !== oldVal) {
            this.value = value;
            this.cb.call(this.vm, value, oldVal); // <2>调用回调函数更新对应的界面
        }
    },
    addDep: function(dep) {
        if (!this.depIds.hasOwnProperty(dep.id)) {
            dep.addSub(this); // 将watcher加入对应的dep  在observe里调用
            this.depIds[dep.id] = dep; // 将dep加入watcher的depIds里
        }
    },
    get: function() {
        Dep.target = this;
        var value = this.getVMVal(); //获取当前表达式的值, 内部会导致属性的get()调用
        Dep.target = null;
        return value;
    },

    getVMVal: function() {
        var exp = this.exp.split('.');
        var val = this.vm._data;
        exp.forEach(function(k) {
            val = val[k];
        });
        return val;
    }
};

在第一篇文章中,new myVue的时候我们调用了observe方法,我们看一下observe都干了什么。

function Observer(data) {
    this.data = data;
    this.walk(data);
}
Observer.prototype = {
    walk: function(data) {
        var me = this;
        // 遍历data中所有属性,针对指定属性进行处理
        Object.keys(data).forEach(function(key) {
            me.convert(key, data[key]);
        });
    },
    convert: function(key, val) {
        // 对指定属性实现响应式数据绑定
        this.defineReactive(this.data, key, val);
    },

    defineReactive: function(data, key, val) {
        // <3>创建与当前属性对应的dep对象 用于存放wather
        var dep = new Dep();
        // 间接递归调用实现对data中所有层次属性的劫持
        var childObj = observe(val);
        // <4>给data中的每一个属性重新定义:(添加set/get)
        Object.defineProperty(data, key, {
            enumerable: true, // 可枚举
            configurable: false, // 不能再define
            get: function() {
                if (Dep.target) {
                    dep.depend();  //<5>建立dep与watcher的关系,调用Watcher里的depend
                }
                return val;
            },
            set: function(newVal) {
                if (newVal === val) {
                    return;
                }
                val = newVal;
                // 新的值是object的话,进行监听
                childObj = observe(newVal);
                dep.notify(); //notify函数遍历dep里的watcher 并触发对应的update函数
            }
        });
    }
};

function observe(value, vm) {
    // <1>判断传进来的data是不是对象, 因为监视的是对象内部的属性
    if (!value || typeof value !== 'object') {
        return;
    }
    // <2>创建一个对应的观察都对象
    return new Observer(value);
};


var uid = 0;

function Dep() {
    this.id = uid++; //生成唯一depId
   this.subs = []; // 相关的所有watcher的数组
}

Dep.prototype = {
    addSub: function(sub) {
        this.subs.push(sub);
    },
    depend: function() {
        Dep.target.addDep(this);
    },
    removeSub: function(sub) {
        var index = this.subs.indexOf(sub);
        if (index != -1) {
            this.subs.splice(index, 1);
        }
    },
    notify: function() {
        // 通知所有相关的watcher(一个订阅者)
        this.subs.forEach(function(sub) {
            sub.update();
        });
    }
};

Dep.target = null;

总结:

  1. Observer 用来对data所有属性数据进行劫持的构造函数,给data中所有属性重新定义属性描述(get/set),为data中的每个属性创建对应的dep对象

  2. Dep(Depend)  data中的每个属性(所有层次)都对应一个dep对象,在初始化define data中各个属性时创建对应的dep对象

    对象的结构

    {
      id, // 每个dep都有一个唯一的id
    
      subs //包含n个对应watcher的数组(subscribes的简写)
    }

     

  3. Compile 用来解析模板页面的对象的构造函数,每解析一个表达式(非事件指令)都会创建一个对应的watcher对象, 并建立watcher与dep的关系

  4. Watcher 模板中每个非事件指令或表达式都对应一个watcher对象,监视当前表达式数据的变化

         对象的组成    

{
   vm, //vm对象

   exp, //对应指令的表达式

   cb, //当表达式所对应的数据发生改变的回调函数

   value, //表达式当前的值

   depIds //表达式中各级属性所对应的dep对象的集合对象,属性名为dep的id, 属性值为dep
          //例如:a.b.c 就是 {0: a的depid, 1: b的depid, 2: c的depid}
}

 

你可能感兴趣的:(vue源码解析3——数据劫持)