Vue源码系列 -- 数据驱动流程分析(1)

141 阅读2分钟

此系列文章是学习huangyi大佬的视频所做的学习笔记,并参考了cherish553大佬的笔记分析

Vue的核心思想是数据驱动。所谓数据驱动,是指视图是由数据驱动生成的,我们对视图的修改,不会直接操作 DOM,而是通过修改数据。所以这一块主要是为了弄清楚模板和数据如何渲染成最终的 DOM。

new Vue的时候做了什么?

在我们在做项目的时候,以脚手架的代码为例,我们会再main.js中进行Vue的实例化

var vm = new Vue({
	el: "#app",
	data(){
		return {
			message: 'hello world'
		}
	}
})

实际调用的是源码中src/core/instancs/index文件中的Vue方法

function Vue (options) {
  if (process.env.NODE_ENV !== 'production' &&
    !(this instanceof Vue)
  ) {
    warn('Vue is a constructor and should be called with the `new` keyword')
  }
  this._init(options)
}

// 各种初始化
initMixin(Vue) 
stateMixin(Vue)
eventsMixin(Vue)
lifecycleMixin(Vue)
renderMixin(Vue)

当我们在实例化Vue的时,最后他会调用this._init(options)方法并把options传入

_init方法是定义在src/core/instancs/init中 在initMixin方法中,_init被挂到了Vue.prototype 原型中。并在方法中对options进行了merge合并,然后进行了一系列的初始化操作, 最终执行 vm.$mount(vm.$options.el) 挂载到dom上

export function initMixin (Vue: Class<Component>) {
  Vue.prototype._init = function (options?: Object) {
    // ...
    // merge options
    if (options && options._isComponent) {
      // ...
    } else {
		// 对options进行merge合并操作
      vm.$options = mergeOptions(
        resolveConstructorOptions(vm.constructor),
        options || {},
        vm
      )
    }
    // ...
	  // 进行了一些列的初始化 
    initLifecycle(vm) // 初始化生命周期
    initEvents(vm) // 初始化事件中心
    initRender(vm) 
    callHook(vm, 'beforeCreate')
    initInjections(vm) // resolve injections before data/props
    initState(vm)
    initProvide(vm) // resolve provide after data/props
    callHook(vm, 'created')

	  // ...	
	  // 最终将el进行挂载操作
	  if (vm.$options.el) {
      vm.$mount(vm.$options.el)
    }
  }
}

以上代码是new Vue的整体流程,是非常清晰的。然后我们来分析data()中的数据是如何通过this.message拿到的

他是执行了 initState() 这个方法

export function initState (vm: Component) {
  // ...
	// 获取options,然后对options中的props、methods、data进行相对应的初始化
  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 */)
  }
  // ...
}

很清晰的看到他如果有 data() 的话会执行 initData() 方法

function initData (vm: Component) {
	// 这里是将data执行并挂载到了vm._data上,并对他进行了校验(data必须返回一个对象)
  let data = vm.$options.data
  data = vm._data = typeof data === 'function'
    ? getData(data, vm)
    : data || {}
  if (!isPlainObject(data)) {
    data = {}
    process.env.NODE_ENV !== 'production' && warn(
      'data functions should return an object:\n' +
      'https://vuejs.org/v2/guide/components.html#data-Must-Be-a-Function',
      vm
    )
  }
	// 这里是取到data的keys 然后跟循环遍历跟props、methods中的名称进行对比,防止命名冲突
  const keys = Object.keys(data)
  const props = vm.$options.props
  const methods = vm.$options.methods
  let i = keys.length
  while (i--) {
    const key = keys[i]
    if (process.env.NODE_ENV !== 'production') {
      if (methods && hasOwn(methods, key)) {
        warn(
          `Method "${key}" has already been defined as a data property.`,
          vm
        )
      }
    }
    if (props && hasOwn(props, key)) {
      process.env.NODE_ENV !== 'production' && warn(
        `The data property "${key}" is already declared as a prop. ` +
        `Use prop default value instead.`,
        vm
      )
    } else if (!isReserved(key)) {
		// 最终执行了这个方法
      proxy(vm, `_data`, key)
    }
  }
  // 进行响应式数据绑定
  observe(data, true )
}

Proxy方法,分别传入了vm、_data、key参数 并将vm中的_data的属性添加了get、set方法,其中get方法返回了 this._data[key] 也就是说将data中的属性全部挂载到了vm上面

const sharedPropertyDefinition = {
  enumerable: true,
  configurable: true,
  get: noop,
  set: noop
}
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)
}

到从我们就明白了访问 this.message实际上就是在访问this._data.message 因为_data是一个私有属性,所以直接this即可

Object.defineProperty() 方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象。

// 例子
const object = {};
Object.defineProperty(object, 'property', {
  value: 42
})
console.log(object.property)  // 44