理解vue2中的响应式数据

通过首次渲染组件中将数据处理成响应式数据的逻辑来理解vue2中的响应式数据,主要是使用了Object.defineProperty()数据劫持,Dep类进行依赖收集与订阅通知,Watcher类进行视图更新与依赖更新等。根据自己的认知对关键步骤做简要描述:

1.数据劫持

  1. 首先是执行initData(vm)方法,入参是vue组件实例对象,获取到要处理的data对象,并将data对象传入observe函数中进行响应式观测处理
function initData(vm) {
// 获取data函数中的对象
  data = vm.$options.data.call(vm,vm) 
  var ob = observe(data);
  ob && ob.vmCount++;
}

function observe(value) {
  // 防止已被响应式观测的数据反复观测
  if (
    value &&
    value.hasOwnProperty("__ob__") &&
    value.__ob__ instanceof Observer
  ) {
    return value.__ob__;
  }
  // 是对象或数组
  if (Object(value) === value) {
    return new Observer(value);
  }
}
  1. 执行new Observer(data),先给data数据添加__ob__属性,再对该数据中的每个属性执行defineReactive方法,这个方法是让数据变为响应式数据的核心
function def(obj, key, val, enumerable) {
  Object.defineProperty(obj,key, {
    value: val,
    enumerable: !!enumerable,
    writable: true,
    configurable: true,
  })
}
class Observer {
  constructor(value) {
    this.vmCount = 0;
    this.value = value
    // 我理解的,这个地方是数组以及数组内子元素用来收集依赖的dep对象
    this.dep = new Dep()
    def(value, "__ob__", this); // 添加__ob__属性
    if (Array.isArray(value)) {
      value.__proto__ = arrayMethods // 重写数组原型方法
      this.observeArray(value)
    } else {
      Object.keys(value).forEach(key => defineReactive(value,key))
    }
  }
  observeArray(value) {
	  value.forEach(v => {
	    observe(v)
	  })
  } 
}

// 数组的处理
let arrayProto = Array.prototype;
let arrayMethods = Object.create(arrayProto);
let methodsToPatch = [
  'push',
  'pop',
  'shift',
  'unshift',
  'splice',
  'sort',
  'reverse'
];
methodsToPatch.forEach(function (method) {
  let original = arrayProto[method];
  def(arrayMethods, method, function mutator(...args) {
      let result = original.apply(this, args); // 原型方法的结果
      let ob = this.__ob__;
      let inserted; // 判断是否有新增操作
      switch (method) {
          case 'push':
          case 'unshift':
              inserted = args;
              break;
          case 'splice':
              inserted = args.slice(2);
              break;
      }
      if (inserted) ob.observeArray(inserted); // 添加观测
      ob.dep.notify(); // 订阅通知,数组用来订阅通知的触发地方
      return result;
  });
});
  1. 使用Object.defineProperty()方法给每个属性添加get与set方法。get方法中用于处理依赖收集,当有watcher观察者在执行自身的get方法时读取了某一响应式数据,就会触发该响应式数据的get方法。而set方法中用于处理订阅通知,当响应式数据的值被修改时就会触发set方法。
    每一个响应式数据都会有一个dep对象,用来存储所收集的依赖与发布订阅通知。
function defineReactive(obj, key) {
  let dep = new Dep(); // 创建一个dep对象
  let val = obj[key];
  // 递归处理
  let childOb = observe(val);
  Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    get() {
      // 此处进行依赖收集逻辑处理
      // DepTarget是当前正在读取该响应式数据的watcher观察者
      if (DepTarget) { 
        dep.depend();
        if (childOb) {
          childOb.dep.depend();
          if (Array.isArray(val)) {
            dependArray(val) // 触发数组中的子元素收集依赖
          }
        }
      }
      return val;
    },
    set(newVal) {
      val = newVal;
      // 此处进行订阅通知逻辑处理
      childOb = observe(newVal);
      dep.notify();
    },
  });
  return dep;
}

function dependArray(value) {
  for (let i = 0; i < value.length; i++) {
    let e = value[i];
    if (e && e.__ob__) {
      e.__ob__.dep.depend();
    }
    if (Array.isArray(e)) {
      dependArray(e);
    }
  }
}

2.Dep类

let uid$1 = 0;
class Dep {
  constructor() {
    this.id = uid$1++;
    this.subs = []; // 存储所收集的依赖
  }
  depend() { // 将自身存储在对应的watcher观察者中
    if (DepTarget) {
      // Watcher观察者中添加此dep对象
      DepTarget.addDep(this);
    }
  }
  addSub(sub) { // 收集watcher观察者
    this.subs.push(sub);
  }
  notify() { // 订阅通知
    let subs = this.subs.filter((v) => v);
    subs.forEach((watcherFn) => watcherFn.update());
  }
  removeSub(sub) { // 移除某一依赖
    this.subs = this.subs.filter(v => v.id !== sub.id)
  }
}

3.Watcher类

  1. 在挂载DOM元素前,会先创建一个Watcher观察者实例,观察当前vue组件实例对象
function mountComponent(vm,el) {
  // 挂载DOM,读取响应式数据
  let updateComponent = function () {
    vm._update(vm._render(), hydrating);
  };
  new Watcher(vm,updateComponent) // 观察者Watcher实例
}
  1. 会主动触发updateComponent方法的执行,在执行中会读取template模板中使用到的响应式数据
let uid$2 = 0;
let DepTarget = null;
let targetStack = [];
function pushTarget(target) {
  targetStack.push(target);
  DepTarget = target;
}
function popTarget() {
  targetStack.pop();
  DepTarget = targetStack[targetStack.length - 1];
}

class Watcher {
  constructor(vm, expOrFn) {
    this.vm = vm;
    vm._watcher = this;
    this.id = ++uid$2; // 赋值id
    this.deps = []; // dep对象队列
    this.newDeps = []; // 新的队列
    this.depIds = new Set();
    this.newDepIds = new Set();
    this.getter = expOrFn;
    this.value = this.get();
  }
  get() {
    // 赋值DepTarget,这里赋值是为了在读取响应式数据时收集此依赖
    pushTarget(this);
    let vm = this.vm;
    value = this.getter.call(vm, vm); // 响应式数据读取
    popTarget(); // 重置DepTarget
    this.cleanupDeps(); // 更新依赖
    return value;
  }
  addDep(dep) {
    let id = dep.id;
    if (!this.newDepIds.has(id)) {
      this.newDepIds.add(id);
      this.newDeps.push(dep);
      if (!this.depIds.has(id)) {
        dep.addSub(this);
      }
    }
  }
  update() { // 接收订阅通知,进入异步队列进行统一处理
    queueWatcher(this);
  }
  run() { // 视图更新
    this.value = this.get();
  }
  cleanupDeps() {
    this.deps.forEach((v) => {
      if (!this.newDepIds.has(dep.id)) {
        v.removeSub(this); // 移除不再需要的依赖
      }
    });
    let tmp = this.depIds;
    this.depIds = this.newDepIds;
    this.newDepIds = tmp;
    this.newDepIds.clear();
    tmp = this.deps;
    this.deps = this.newDeps;
    this.newDeps = tmp;
    this.newDeps.length = 0;
  }
}

4.响应式数据的值修改

  1. 当响应式数据的值被修改时,会通过set方法触发订阅通知,执行watcher.update()方法,再触发queueWatcher(watcher)方法
let has = {}; // 防止多次塞入同一个watcher观察者
let queue = [];
let waiting = false;
function queueWatcher(watcher) {
  let id = watcher.id;
  if (!has[id]) return;
  has[id] = true;
  queueWatcher.push(watcher);
  if (!waiting) { // 频繁的push,触发一次异步处理即可
    waiting = true;
    nextTick(flushSchedulerQueue); // nextTick以前介绍过,不再讲述
  }
}
  1. 订阅通知完成后,触发flushSchedulerQueue方法
function flushSchedulerQueue() {
  // 排序,确保组件从父组件更新到子组件
  queue.sort(function (a, b) {
    return a.id - b.id;
  });
  let watcher;
  for (let i = 0; i < queue.length; i++) {
    watcher = queue[i];
    watcher.run(); // 视图更新
  }
  resetSchedulerState(); // 重置数据
}
function resetSchedulerState() {
  queue.length = 0;
  has = {};
  waiting = false;
}

相关推荐

  1. 理解vue2响应数据

    2024-04-01 11:00:04       15 阅读
  2. 理解 Vue 响应系统

    2024-04-01 11:00:04       7 阅读
  3. 深入理解 Vue 响应系统

    2024-04-01 11:00:04       6 阅读
  4. Vue3 响应数据

    2024-04-01 11:00:04       34 阅读
  5. Vue响应渲染 watcher

    2024-04-01 11:00:04       31 阅读
  6. Vue响应渲染 watcher

    2024-04-01 11:00:04       29 阅读

最近更新

  1. TCP协议是安全的吗?

    2024-04-01 11:00:04       16 阅读
  2. 阿里云服务器执行yum,一直下载docker-ce-stable失败

    2024-04-01 11:00:04       16 阅读
  3. 【Python教程】压缩PDF文件大小

    2024-04-01 11:00:04       15 阅读
  4. 通过文章id递归查询所有评论(xml)

    2024-04-01 11:00:04       18 阅读

热门阅读

  1. 普中51单片机学习笔记——点亮第一个LED

    2024-04-01 11:00:04       12 阅读
  2. postgresql wal 源码核心模块概述

    2024-04-01 11:00:04       14 阅读
  3. hibernate session管理

    2024-04-01 11:00:04       19 阅读
  4. Node.js常用命令

    2024-04-01 11:00:04       17 阅读
  5. 普中51单片机学习笔记——蜂鸣器

    2024-04-01 11:00:04       11 阅读
  6. 多线程面试题

    2024-04-01 11:00:04       12 阅读
  7. Photoshop笔记大全

    2024-04-01 11:00:04       14 阅读