探索Vue.js底层源码——响应式的数据对象原理

响应式对象

    在 Vue 中响应式的原理是基于 ES5 的 Object.defineProperty(而 Vue 3.0 的响应式原理是基于 ES6 中的 Proxy)。

数据初始化

    在 Vue 的初始化过程中,数据初始化的完成是由 initState 执行的,initState.js 定义在 src/core/instance/state.js。

	export function initState (vm: Component) {
	  vm._watchers = []
	  const opts = vm.$options
	  if (opts.props) initProps(vm, opts.props)
	  if (opts.methods) initMethods(vm, opts.methods)
	  if (opts.data) {
	    initData(vm)
	  } else {
	    observe(vm._data = {}, true /* asRootData */)
	  }
	  if (opts.computed) initComputed(vm, opts.computed)
	  if (opts.watch && opts.watch !== nativeWatch) {
	    initWatch(vm, opts.watch)
	  }
	}

    可以看到在 initState 中分别对 props、methods、data、computed、watch等选项进行了初始化.

props

    在处理 props 的过程中,会先遍历 props,再对相应的 key 进行 defineReactive 处理,把每一个 props 属性都变成响应式的,以及调用 proxy 函数,进行代理映射数据访问。

data

    在处理 data 的过程,先遍历 data,再通过 proxy 做代理属性映射。然后调用 observer 方法观测整个 data 的变化,将 data 的属性变成响应式和可监测的。

    小插曲:面试常常会问,为什么 data 中返回的是对象?

    从源码可以看出,这个问题根本就是一个坑,因为完全可以很简单地回答说源码中专门为这个数据类型设置了 try catch 捕获异常,并且会通过 Object.keys 遍历 data 对象,获得 data 对象中的属性数组,从而将每一个属性代理到实例上,即直接通过 vm.attrName 访问。并且在 initData 中对 data 的 flow 静态类型检测都是 Object ,也就是说如果不是 Object 连类型检测都过不了。下面大家可以参考几处关键的代码增强记忆:

// state.js
	// initData
	data = vm._data = typeof data === 'function'
	    ? getData(data, vm)
	    : data || {}
	// getData
	export function getData (data: Function, vm: Component): any {
	  // #7573 disable dep collection when invoking data getters
	  // 调用数据获取时禁止dep收集
	  pushTarget()
	  try {
	    // 返回data中的对象
	    return data.call(vm, vm)
	  } catch (e) {
	    handleError(e, vm, `data()`)
	    return {}
	  } finally {
	    popTarget()
	  }
	}

proxy 函数

    在 Vue.js 源码中自定义了一个 proxy 函数,将 vm._props.a 映射到了 vm.a(对于data也是同样的道理),proxy 定义在 src/core/instance/state.js 中。

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

observe 函数

    observer 方法的作用是数据类型对象添加一个 Observer,如果已经添加过则直接返回,否则实例化一个 Observer 实例,形成发布者订阅模式。

	/**
	 * Attempt to create an observer instance for a value,
	 * returns the new observer if successfully observed,
	 * or the existing observer if the value already has one.
	 */
	export function observe (value: any, asRootData: ?boolean): Observer | void {
	  if (!isObject(value) || value instanceof VNode) {
	    return
	  }
	  let ob: Observer | void
	  if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
	    ob = value.__ob__
	  } else if (
	    shouldObserve &&
	    !isServerRendering() &&
	    (Array.isArray(value) || isPlainObject(value)) &&
	    Object.isExtensible(value) &&
	    !value._isVue
	  ) {
	    ob = new Observer(value)
	  }
	  if (asRootData && ob) {
	    ob.vmCount++
	  }
	  return ob
	}

    其实这段代码中最重要的就是实例化了 Observer 这个类。

Observer 类

    Observer 的作用主要是将对象实现数据响应以及数据的更新的通知。

扫描二维码关注公众号,回复: 9828133 查看本文章
	export class Observer {
		value: any;
		dep: Dep;
		vmCount: number;

		constructor (value: any) {
			this.value = value	
			this.dep = new Dep()
			this.vmCount = 0
			def(value, '__ob__', this)
			if (Array.isArray(value)) {
				const augment = hasProto	
					? protoAugmengt
					: copyAugment
				augment(value, arrayMethods, arrayKeys)
				this.observerArray(value)
			} else {
				this.walk(value)	
			}
		}
		....
	}

    在 Observer 这个类中最关键的代码都在构造函数中,其中区别于 props 只实现数据响应,Observer 还做了几件事:实例化 Dep(主题) 对象,添加数据到数据对象的__ob__上。

data 和 props 初始化时候的区别

  • props 直接初始化。
  • data 初始化时会查看其他选项中是否存在重名,例如methods、props、watch等。
  • props 直接 defineReactive。
  • data 需要通过 Observer 这个类,虽然最终都会走 defineReactive,但是不同于 props 的是,对于 data 属性会实例化一个 Dep 类,绑定到对应的 vm 实例上。

————未完待续…

发布了140 篇原创文章 · 获赞 16 · 访问量 4万+

猜你喜欢

转载自blog.csdn.net/qq_42049445/article/details/103004472