数据劫持到数据绑定原理

241 阅读4分钟

「这是我参与2022首次更文挑战的第26天,活动详情查看:2022首次更文挑战

继上篇文章简单写了一下实现双向数据绑定需要的基本知识之后,现在咱们可以慢慢打造一个属于自己的双向数据绑定了,也就是实现一个简易的minniVue

1.数据绑定(model==>View):

1). 一旦更新了data中的某个属性数据, 所有界面上直接使用或间接使用了此属性的节点都会更新(更新)

2.数据劫持

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

3.四个重要对象

1). Observer
	* 用来对data所有属性数据进行劫持的构造函数
  	* 给data中所有属性重新定义属性描述(get/set)
  	* 为data中的每个属性创建对应的dep对象
2). Dep(Depend)
  	* data中的每个属性(所有层次)都对应一个dep对象
  	* 创建的时机:
    	* 在初始化define data中各个属性时创建对应的dep对象
    	* 在data中的某个属性值被设置为新的对象时
  	* 对象的结构
        {
          id, // 每个dep都有一个唯一的id
          subs //包含n个对应watcher的数组(subscribes的简写)
        }
	* subs属性说明
		* 当一个watcher被创建时, 内部会将当前watcher对象添加到对应的dep对象的subs中
		* 当此data属性的值发生改变时, 所有subs中的watcher都会收到更新的通知, 从而最终更新对应的界面
3). Compile
	* 用来解析模板页面的对象的构造函数(一个实例)
	* 利用compile对象解析模板页面
	* 每解析一个表达式(非事件指令)都会创建一个对应的watcher对象, 并建立watcher与dep的关系
	* complie与watcher关系: 一对多的关系
4). Watcher
  	* 模板中每个非事件指令或表达式都对应一个watcher对象
  	* 监视当前表达式数据的变化
  	* 创建的时机: 在初始化编译模板时
  	* 对象的组成
		{
          vm,  //vm对象
          exp, //对应指令的表达式
          cb, //当表达式所对应的数据发生改变的回调函数
          value, //表达式当前的值
          depIds //表达式中各级属性所对应的dep对象的集合对象
                  //属性名为dep的id, 属性值为dep
		}
	
5). 总结: dep与watcher的关系: 多对多
	* 一个data中的属性对应对应一个dep, 一个dep中可能包含多个watcher(模板中有几个表达式使用到了属性)
	* 模板中一个非事件表达式对应一个watcher, 一个watcher中可能包含多个dep(表达式中包含了几个data属性)
	* 数据绑定使用到2个核心技术
		* defineProperty()
		* 消息订阅与发布

4.双向数据绑定

1). 双向数据绑定是建立在单向数据绑定(model==>View)的基础之上的
2). 双向数据绑定的实现流程:
  	* 在解析v-model指令时, 给当前元素添加input监听
  	* 当input的value发生改变时, 将最新的值赋值给当前表达式所对应的data属性
    
    
function Observer(data) {
    // 保存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) {
        // 创建与当前属性对应的dep对象
        var dep = new Dep();
        // 间接递归调用实现对data中所有层次属性的劫持
        var childObj = observe(val);
        // 给data重新定义属性(添加set/get)
        Object.defineProperty(data, key, {
            enumerable: true, // 可枚举
            configurable: false, // 不能再define
            get: function() {
                // 建立dep与watcher的关系
                if (Dep.target) {
                    dep.depend();
                }
                // 返回属性值
                return val;
            },
            set: function(newVal) {
                if (newVal === val) {
                    return;
                }
                val = newVal;
                // 新的值是object的话,进行监听
                childObj = observe(newVal);
                // 通过dep
                dep.notify();
            }
        });
    }
};

function observe(value, vm) {
    // value必须是对象, 因为监视的是对象内部的属性
    if (!value || typeof value !== 'object') {
        return;
    }
    // 创建一个对应的观察都对象
    return new Observer(value);
};


var uid = 0;

function Dep() {
    // 标识属性
    this.id = uid++;
    // 相关的所有watcher的数组
    this.subs = [];
}

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;