概念

  • 数据绑定(model==>View):一旦更新了 data 中的某个属性数据,所有界面上直接使用或间接使用了此属性的节点都会更新
  • 数据劫持:数据劫持是 vue 中用来实现数据绑定的一种技术
    • 基本思想: 通过 defineProperty()来监视 data 中所有属性(任意层次)数据的变化, 一旦变化就去更新界面

数据劫持:Observer()

  • 在 new MVVM 时执行 observe(data, this)
  • observe()负责判断 vm._data 是不是一个对象,然后创建一个对应的观察者对象 new Observer(value)
  • Observer(value)保存 vm._data 对象到当前观察者对象的实例的 data 属性中,然后调用 walk()
  • walk()中遍历 data 中所有属性,对每个属性调用 convert()
  • convert()对指定属性实现响应式数据绑定 this.defineReactive(this.data, key, val)
function MVVM(options) {
  // 对data进行监视,重点是给data的每个属性定义get,set方法
  observe(data, this);
}
1
2
3
4
function observe(value, vm) {
  // value必须是对象, 因为监视的是对象内部的属性
  if (!value || typeof value !== 'object') {
    return;
  }
  // 创建一个对应的观察者对象
  return new Observer(value);
}
1
2
3
4
5
6
7
8
function Observer(data) {
  // 保存data对象
  this.data = data;
  // 走起
  this.walk(data);
}
1
2
3
4
5
6
walk: function(data) {
    var me = this;
    // 遍历data中所有属性
    Object.keys(data).forEach(function(key) {
        // 针对指定属性进行处理
        me.convert(key, data[key]);
    });
},
1
2
3
4
5
6
7
8
convert: function(key, val) {
    // 对指定属性实现响应式数据绑定
    this.defineReactive(this.data, key, val);
},
1
2
3
4

负责记录订阅:dep()

  • this.defineReactive()中首先创建一个 dep 对象 new Dep()
  • dep()的实例中首先保存一个 id 标识当前属性,然后创建一个 subs 属性,是一个保存相关的所有 watcher 的数组
  • 然后 this.defineReactive()间接递归调用实现对 data 中所有层次属性的劫持 observe(val),当 val 不是一个对象,在 observe()判断 data 中就会退出递归
  • 然后给 vm._data 中的属性定义属性(添加 set/get)
  • 其中 getter 中首先判断,,Dep.target 的初始值 null,所以不执行 dep.depend()
  • getter 最后返回 vm._data 中对应属性
  • setter 中首先把新值保存到 vm._data 对应属性中,然后如果设置的新值是 object 的话,进行监听 observe(newVal)
  • 最后执行 dep.notify(),它负责遍历当前属性的 dep 实例中的 subs,调用每个元素的 update(),通知所有相关的 watcher(一个订阅者)去更新页面
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);
            // 调用每个元素的update()
            dep.notify();
        }
    });
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
function Dep() {
  // 标识属性
  this.id = uid++;
  // 相关的所有watcher的数组
  this.subs = [];
}
1
2
3
4
5
6
Dep.prototype = {
  notify: function() {
    // 通知所有相关的watcher(一个订阅者)
    this.subs.forEach(function(sub) {
      sub.update();
    });
  }
};

Dep.target = null;
1
2
3
4
5
6
7
8
9
10

负责更新节点:Watcher()

  • 由于刚开始 dep.subs 是空,所以 Watcher()第一次发生在模板编译中
  • 模板中每一个用到模板语法并且用了 vm._data 的值的地方在刚开始解析时通过调用 Watcher(),向它传进对应的更新节点的函数
    // 真正用于解析指令的方法
    bind: function(node, vm, exp, dir) {
        // 创建表达式对应的watcher对象
        new Watcher(vm, exp, function(value, oldValue) {
            /*更新界面*/
            // 当对应的属性值发生了变化时, 自动调用, 更新对应的节点
            updaterFn && updaterFn(node, value, oldValue);
        });
    },
1
2
3
4
5
6
7
8
9
  • Watcher()首先在实例中创建五个属性,get()负责获取值
    • vm:vm 对象,
    • exp:对应指令的表达式,
    • cb:当表达式所对应的数据发生改变的回调函数,
    • value:表达式当前的值,
    • depIds:表达式中各级属性所对应的 dep 对象的 id
function Watcher(vm, exp, cb) {
  this.cb = cb; // callback
  this.vm = vm;
  this.exp = exp;
  this.depIds = {}; // {0: d0, 1: d1, 2: d2}
  this.value = this.get();
}
1
2
3
4
5
6
7
  • get()中 Dep.target=this,this 是 Watcher()的当前实例
  get: function () {
    Dep.target = this;
    // 获取当前表达式的值, 内部会导致属性的get()调用
    var value = this.getVMVal();

    Dep.target = null;
    return value;
  },
1
2
3
4
5
6
7
8
  • 然后调用 gteVMVal(),遍历表达式中所有用到 vm._data 的值的地方,去 vm._data 中取值
  getVMVal: function () {
    var exp = this.exp.split('.');
    var val = this.vm._data;
    exp.forEach(function (k) {
      val = val[k];
    });
    return val;
  }
1
2
3
4
5
6
7
8
  • 取值导致下面代码运行
if (Dep.target) {
  dep.depend();
}
dep.notify();
1
2
3
4
  • Dep.target 此时不是是 Watcher()的当前实例是 Watcher()的当前实例,dep.depend()负责往 dep 对象的 subs 属性添加 Dep.target
depend: function() {
    Dep.target.addDep(this);
},
1
2
3
  • 如果 depIds 中没有 dep. id,则把当前 watcher 添加到 dep 的 subs 中,还把 dep.id 添加到当前 watcher 的 depIds 中,建立 watcher 到 dep 的关系
  addDep: function (dep) {
    if (!this.depIds.hasOwnProperty(dep.id)) {
      // 建立dep到watcher
      dep.addSub(this);
      // 建立watcher到dep的关系
      this.depIds[dep.id] = dep;
    }
  },
1
2
3
4
5
6
7
8
addSub: function(sub) {
    this.subs.push(sub);
},
1
2
3
  • 然后调用 dep.notify()
    notify: function() {
        // 通知所有相关的watcher(一个订阅者)
        this.subs.forEach(function(sub) {
            sub.update();
        });
    }
1
2
3
4
5
6
  • 里面调用 sub.update()去更新节点
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);
    }
  }
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

双向数据绑定

双向数据绑定是建立在单向数据绑定(model==>View)的基础之上的
双向数据绑定的实现流程:

  • 在解析 v-model 指令时, 给当前元素添加 input 监听
  • 当 input 的 value 发生改变时, 将最新的值赋值给当前表达式所对应的 data 属性
  • 当把最新的值赋值给当前表达式所对应的 data 属性,触发 setter
    // 解析: v-model
    model: function(node, vm, exp) {
        this.bind(node, vm, exp, 'model');

        var me = this,
            val = this._getVMVal(vm, exp);
        node.addEventListener('input', function(e) {
            var newValue = e.target.value;
            if (val === newValue) {
                return;
            }

            me._setVMVal(vm, exp, newValue);
            val = newValue;
        });
    },
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
    _setVMVal: function(vm, exp, value) {
        var val = vm._data;
        exp = exp.split('.');
        exp.forEach(function(k, i) {
            // 非最后一个key,更新val的值
            if (i < exp.length - 1) {
                val = val[k];
            } else {
                val[k] = value;
            }
        });
    }
1
2
3
4
5
6
7
8
9
10
11
12

总结:四个重要对象

Observer

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

Dep(Depend)

  • data 中的每个属性(所有层次)都对应一个 dep 对象
  • 创建的时机:
    • 在初始化 define data 中各个属性时创建对应的 dep 对象
    • 在 data 中的某个属性值被设置为新的对象时
  • 对象的结构
{
    id, // 每个dep都有一个唯一的id
    subs //包含n个对应watcher的数组(subscribes的简写)
  }
1
2
3
4
  • subs 属性说明
    • 当一个 watcher 被创建时, 内部会将当前 watcher 对象添加到对应的 dep 对象的 subs 中
    • 当此 data 属性的值发生改变时, 所有 subs 中的 watcher 都会收到更新的通知, 从而最终更新对应的界面

Compile

  • 用来解析模板页面的对象的构造函数(一个实例)
  • 利用 compile 对象解析模板页面
  • 每解析一个表达式(非事件指令)都会创建一个对应的 watcher 对象, 并建立 watcher 与 dep 的关系
  • complie 与 watcher 关系: 一对多的关系

Watcher

  • 模板中每个非事件指令或表达式都对应一个 watcher 对象
  • 监视当前表达式数据的变化
  • 创建的时机: 在初始化编译模板时
  • 对象的组成
{
    vm,  //vm对象
    exp, //对应指令的表达式
    cb,//当表达式所对应的数据发生改变的回调函数
    value, //表达式当前的值
    depIds//表达式中各级属性所对应的dep对象的集合对象
         //属性名为dep的id, 属性值为dep
}
1
2
3
4
5
6
7
8

dep 与 watcher 的关系

  • 一个 data 中的属性对应对应一个 dep, 一个 dep 中可能包含多个 watcher(模板中有几个表达式使用到了属性)
  • 模板中一个非事件表达式对应一个 watcher, 一个 watcher 中可能包含多个 dep(表达式中包含了几个 data 属性)

TOC