前言

本篇是分析new Vue()过程中对于data的处理细节以及响应式原理,主要了解如下两点:

  • Vue实例的data选项具体的逻辑
  • Vue响应式原理学习

data处理逻辑

下面是Vue实例创建中initState的处理逻辑:
这里写图片描述
initState的处理逻辑是处理组件中选项即props、data、computed、watch、methods,而关于data部分的处理逻辑,具体如下:

opts.data ? initData(vm) : observe(vm._data = {}, true);

首先看看initData的具体处理:
这里写图片描述

从上图中的逻辑处理中可以看出,实际上initData的主要处理:

  • data为函数或对象的处理
  • 判断data中定义的key是否在methods、props中同名,同名就报错
  • 定义_data并使用内部定义的proxy方法代理所有属性(实际上此处是将属性代理到vm实例上,支持this.attr这种形式的支持调用)
  • 调用observe来观察data中所有属性

上面需要主要关注的点:

  • getData中具体处理
  • proxy(vm, ‘_data’, key)的具体处理
  • observe(data, true):实现data响应式的具体处理
getData

实际上该方法主要执行就是调用data,即data.call(vm, vm)

proxy

实际上该方法就是使用Object.defineProperty实现数据监听,但是是在_data对象上(_data是Vue源码内data的拷贝),具体处理如下:

var proxy = function(target, source, key) {
	sharedPropertyDefinition.get = function proxyGetter () {
	  return this[sourceKey][key]
	};
	sharedPropertyDefinition.set = function proxySetter (val) {
	  this[sourceKey][key] = val;
	};
	Object.defineProperty(target, key, sharedPropertyDefinition);
};

从上面可以看出这边是对将data中属性拷贝到_data上,并实现监听。实际上在Vue中使用this.attr形式,正是proxy实现了一层的代理,即:

this.attr 触发了proxy中getter继而获取this.$data.attr值

observe

这是实现data响应式的关键处理,实际上这边主要的处理如下:

// value如果非对象或虚拟节点对象直接退出
if (!isObject(value) || value instanceof VNode) {
	return
}
var ob = new Observer(value);
return ob;

接下来就具体分析Observer对象创建过程:
在这里插入图片描述
上面是Observer整个的处理逻辑过程,实际上对于data的处理实际上是分为两种:

  • data为对象
  • data为数组

当data为数组是实际上会重复调用observe函数,即:

  • 递归将Array中元素平铺成非数组来处理,即对数组中每个元素都调用observe函数
    walk中的处理实际上是:
  • 循环遍历data中属性,调用defineReactive函数来实现监听

defineReactive就是实现对data选项数据进行响应式的具体实现,具体逻辑如下:

defineReactive

主要逻辑代码如下:

function defineReactive (
  obj,
  key,
  val,
  customSetter,
  shallow
) {
  // 创建dep对象
  var dep = new Dep();
  // 获取属性描述符
  var property = Object.getOwnPropertyDescriptor(obj, key);
  // 对于configurable为false即不可配置的不进行数据劫持
  if (property && property.configurable === false) {
    return
  }

  // cater for pre-defined getter/setters
  var getter = property && property.get;
  var setter = property && property.set;
  // 除了$attrs和$listeners,任何其他的拦截shallow值都是false,这里逻辑就是通过递归实现深层拦截即对对象中所有属性都进行数据劫持
  var childOb = !shallow && observe(val);
  // 使用defineProperty实现对属性进行劫持
  Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    get: function reactiveGetter () {
      // 执行getter获取对应的value值
      var value = getter ? getter.call(obj) : val;
      // Dep.target值正常是Watcher对象
      if (Dep.target) {
        dep.depend();
        if (childOb) {
          childOb.dep.depend();
          if (Array.isArray(value)) {
            dependArray(value);
          }
        }
      }
      return value
    },
    set: function reactiveSetter (newVal) {
      var value = getter ? getter.call(obj) : val;
      // 如果值没有变化就不需要后续逻辑
      if (newVal === value || (newVal !== newVal && value !== value)) {
        return
      }
      if (setter) {
        setter.call(obj, newVal);
      } else {
        val = newVal;
      }
      childOb = !shallow && observe(newVal);
      dep.notify();
    }
  });
}

通过上面实现响应式的代码逻辑总结如下几点:

  • 使用Object.defineProperty实现数据拦截,这是响应式原理的本质
  • 如果数据属性配置configurable为false,就不会对数据进行响应式处理
  • 在defineReactive中调用Observe实现递归,从而对多层对象实现深层劫持
  • 每一个属性进行数据拦截都会对应一个Dep对象

Object.defineProperty对数据劫持后:

  • 当对对象属性进行获取时会触发get函数
  • 当对对象属性进行赋值时会触发set函数

Vue响应式原理正是基于Object.defineProperty的特性建立起来的,以此来看实际上defineReactive的代码可以简单认为是:

Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    get: function reactiveGetter () {
      if (Dep.target) {
        dep.depend();
        // 子对象的处理
      }
      return value
    },
    set: function reactiveSetter (newVal) {
      // 其他逻辑
      dep.notify();
    }
  });

每一个属性都对应一个Dep对象,当对属性获取时会触发dep.depend即依赖收集,当对属性进行赋值时会触发dep.notify即视图更新。

Dep对象的depend和notify方法
Dep.prototype.depend = function depend () {
  if (Dep.target) {
    Dep.target.addDep(this);
  }
};

Dep.prototype.notify = function notify () {
  // stabilize the subscriber list first
  var subs = this.subs.slice();
  for (var i = 0, l = subs.length; i < l; i++) {
    subs[i].update();
  }
};

这部分逻辑实际上涉及到Watcher对象,Vue.js官网给出的响应式的具体流程:
这里写图片描述
上面逻辑图具体逻辑如下:

  • render函数触发了data响应式属性的get函数
  • get函数开启Dep与Watcher之间的关联
  • set函数触发视图更新

每个组件实例都对应一个 watcher 实例,这个watcher实例负责视图渲染。它会在组件渲染的过程中把“接触”过的数据 property(即Dep对象)记录为依赖,之后当依赖项的 setter 触发时,会通知 watcher,从而使它关联的组件重新渲染:

  • Dep对象表示需要响应式的属性,即从Watcher角度来看就是一个依赖
  • Dep对象会保存所有监听该依赖的Watcher对象
  • Watcher对象就是一个监听器,该监听器会保存所有需要该监听器监听的Dep依赖对象

Dep的depend方法就是建立Dep与Watcher对象之间的联系,而notify是触发Watcher对象的update方法来实现视图更新,重新渲染出最新的视图。

Vue响应式原理就基于Object.defineProperty、Watcher对象与Dep对象建立起来的。

总结

从实例过程中data选项处理以及响应式处理可知如下信息:

  • Object.defineProperty实现对对象属性进行拦截,这是Vue响应式原理的基础
  • 由于Object.defineProperty API的缺点(只能对对象属性进行设置),所以需要递归遍历处理多层对象结构,数组的处理则也是如此
  • 每个组件实例都对应一个 watcher 实例,它会在组件渲染的过程中把“接触”过的数据 property(即Dep对象)记录为依赖,之后当依赖项的 setter 触发时会通知 watcher,从而使它关联的组件重新渲染
Logo

前往低代码交流专区

更多推荐