Vue源码分析-数据劫持

数据劫持(数据绑定)

observer

  1. 给配置对象data对象中的数据进行劫持
  2. 给data中的每个属性重新定义get和set
  3. 为data中每个属性创建dep对象

compile

在解析表达式的时候会创建对应的watcher对象,并建立watcher与dep的关系,

dep

  1. 在进行数据劫持时,给data每个属性都对应一个dep对象
  2. dep对象结构:id – 每个dep对象的唯一标识,subs—包含多个watcher对象的数组

特殊说明:当模板编译时,每个表达式编译会创建watcher对象,会将当前watcher对象添加到dep对象的subs中,当data属性改变时,会通知dep对象sub中的所有watcher,最终去更新页面

watcher

当编译模板时,模板中非事件指令或表达式都对应一个watcher对象

对象组成{

                     vm--vm对象

                     exp --对应指令的表达式

                     cb--当表达式对应的数据发生改变时,执行的回调,会更显页面显示

                     value – 表达式对应的值

                     depIds – 表达式对应的dep对象集合

}

dep与watcher的关系:多对多

在模板中一个表达式使用多次,一个dep对象对应多个watcher对象(表达式使用的次数)

表达式是对象点的形式,1个watcher对象对应多个dep对象(表达式包含data属性的数量)

相关代码

//mvvm.js

observe(data,this)  //observe-观察者

//observe.js

 

function observe(value, vm) {

  // 判断值是否是对象,只有对象才需要变成响应式

  if (!value || typeof value !== "object") {

    return;

  }

  return new Observer(value);

}

 

 

function Observer(data) {

  // 存储data

  this.data = data;

  // 开始干活

  this.walk(data);

}

 

  walk: function (data) {

    var me = this;

    // 遍历第一层属性

    Object.keys(data).forEach(function (key) {

      // me.convert(key, data[key]);--无用

      // 将数据重新定义成响应式数据

      me.defineReactive(data, key, data[key]);

    });

  },

 

  convert: function (key, val) {

    this.defineReactive(this.data, key, val);

  },

 

//定义响应式数据

  defineReactive: function (data, key, val) {

    // 创建dep对象

    // 所有响应式数据(data)都会有一个唯一的dep对象

    // dep对象会通过闭包的方式保存响应式数据的gettersetter

    var dep = new Dep();

    // 因为遍历只能遍历第一层属性,如果属性值是对象,还需要再进行响应式处理

    // 进行隐式递归调用, data上所有属性都会变成响应式属性

    var childObj = observe(val);

 

    // data上数据定义成响应式

    Object.defineProperty(data, key, {

      enumerable: true, // 可枚举

      configurable: false, // 不能再define

      get: function () {

        // 判断 Dep.target --> 当前有没有要处理的watcher,在创建watcher对象的时候,来执行get方法

        if (Dep.target) {

          // 建立 dep watcher 的联系

          dep.depend();

        }

        return val;

      },

      set: function (newVal) {

        if (newVal === val) {

          return;

        }

        // 更新值

        val = newVal;

        // 新的值是object的话,又要改成响应式数据

        childObj = observe(newVal);

        // 通知订阅者

        // 通过watcher更新用户界面

        dep.notify();

      },

    });

  },

 

//Dep对象

 

var uid = 0;

 

function Dep() {

  this.id = uid++;

  this.subs = [];

}

 

Dep.prototype = {

  addSub: function (sub) {

    this.subs.push(sub);

  },

 

  depend: function () {

    // watcher添加dep

    // watcher.addDep(dep)

    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) {

      // 调用watcher的更新方法

      sub.update();

    });

  },

};

 

Dep.target = null;

 

watcher.js

function Watcher(vm, expOrFn, cb) {

//cb—updaterFn ,更新页面的函数

  this.cb = cb;

  this.vm = vm;

// expOrFn—获取vm表达式对应值的函数

  this.expOrFn = expOrFn;

  this.depIds = {};

 

  if (typeof expOrFn === "function") {

    this.getter = expOrFn;

  } else {

    this.getter = this.parseGetter(expOrFn.trim());

  }

//get方法内部调用this.getter,获取表达式对应的值

  this.value = this.get();

}

 

 

  get: function () {

//当前watcher对象作为Dep.target的值,先创建实现响应式数据,创建Dep对象,然后模板辨析,创建watcher对象

    Dep.target = this;

    var value = this.getter.call(this.vm, this.vm);

    Dep.target = null;

    return value;

  },

  parseGetter: function (exp) {

    if (/[^\w.$]/.test(exp)) return;

    var exps = exp.split(".");

    return function (obj) {

      for (var i = 0, len = exps.length; i < len; i++) {

        if (!obj) return;

//取值,执行get方法,创建dep对象

        obj = obj[exps[i]];

      }

      return obj;

    };

  },

 

 

  addDep: function (dep) {

    // 1. 每次调用run()的时候会触发相应属性的getter

    // getter里面会触发dep.depend(),继而触发这里的addDep

    // 2. 假如相应属性的dep.id已经在当前watcherdepIds里,说明不是一个新的属性,仅仅是改变了其值而已

    // 则不需要将当前watcher添加到该属性的dep

    // 3. 假如相应属性是新的属性,则将当前watcher添加到新属性的dep

    // 如通过 vm.child = {name: 'a'} 改变了 child.name 的值,child.name 就是个新属性

    // 则需要将当前watcher(child.name)加入到新的 child.name dep

    // 因为此时 child.name 是个新值,之前的 setterdep 都已经失效,如果不把 watcher 加入到新的 child.name dep

    // 通过 child.name = xxx 赋值的时候,对应的 watcher 就收不到通知,等于失效了

    // 4. 每个子属性的watcher在添加到子属性的dep的同时,也会添加到父属性的dep

    // 监听子属性的同时监听父属性的变更,这样,父属性改变时,子属性的watcher也能收到通知进行update

    // 这一步是在 this.get() --> this.getVMVal() 里面完成,forEach时会从父级开始取值,间接调用了它的getter

    // 触发了addDep(), 在整个forEach过程,当前wacher都会加入到每个父级过程属性的dep

    // 例如:当前watcher的是'child.child.name', 那么child, child.child, child.child.name这三个属性的dep都会加入当前watcher

 

    // 判断有没有保存过dep(通过id判断)

    if (!this.depIds.hasOwnProperty(dep.id)) {

      // dep保存watcher

      // 为什么给dep保存watcher

      // 因为watcher有更新用户界面的方法,当dep对应的响应式数据发生变化,

      // 就能通过dep找到所有的watcher,从而更新用户界面

      dep.addSub(this);

      // watcher保存dep

      // 为什么给watcher保存dep

      // 防止dep保存多次同一个watcher

      this.depIds[dep.id] = dep;

      // 为什么dep保存watchersubs数组?而watcher保存depdepIds对象?

      // this.depIds = { 0: dep0, 1: dep1 } 对象查找属性比数组遍历查找值快的多

      // this.depIds = [0, 1] 就需要遍历,相当于要遍历所有元素

    }

  },

 

更新页面的操作

 update: function () {

    this.run();

  },

  run: function () {

    // 读取属性最新的值

    var value = this.get();

    // 读取属性上一次的值

    var oldVal = this.value;

    // 如果值相等,就不更新

    if (value !== oldVal) {

      // 将最新的值保存起来

      this.value = value;

      // 调用更新用户界面的方法cb去更新

      this.cb.call(this.vm, value, oldVal);

    }

  },

 

 

双向数据绑定

  1. 在解析v-model指令的时候,会给当前元素绑定input事件监听
  2. 当input事件执行的时候,会将最新的值赋值给vm实例身上对应的属性,触发属性的set方法,通知dep对象subs中所有watcher,从而去更新页面

相关代码

    var me = this,

      // 读取当前属性的值 

      val = this._getVMVal(vm, exp);

    // 绑定input事件,监听元素的value的变化

    node.addEventListener("input", function (e) {

      // 获取当前元素最新的值

      var newValue = e.target.value;

      // 如果相等就不更新

      if (val === newValue) {

        return;

      }

      // 更新data数据 --> 触发setter方法,从而更新用户界面

      me._setVMVal(vm, exp, newValue);

      // 将当前值存起来,方便进行下一次比较~

      val = newValue;

    });

 

 

  _getVMVal: function (vm, exp) {

    var val = vm;

    exp = exp.split("."); // ['wife', 'name']

    // 第一次遍历 val = vm['wife'] = {xxx}

    // 第二次遍历 val = {xxx}['name'] = 'rose'

    exp.forEach(function (k) {

      val = val[k];

    });

    return val; // 'rose'

  },

 

  // 设置vm上对应表达式的值

  _setVMVal: function (vm, exp, value) {

    var val = vm;

    exp = exp.split(".");

    exp.forEach(function (k, i) {

      // 非最后一个key,更新val的值

      if (i < exp.length - 1) {

        val = val[k];

      } else {

        val[k] = value;

      }

    });

  },

 

 

猜你喜欢

转载自blog.csdn.net/shkstart/article/details/108466880