Vue 2.6.13 源码解析(三) 初始化


前言

首篇提到initProvideinitInjections中间为何调用initState, 那么initState里做了什么?
这篇因为函数调用很多所以篇幅长, 但因为只是一个子模块, 所以函数的复杂性不及上篇.

思路的话, 这些函数的大部都是在处理数据格式问题, 核心基本就是最后的一两次调用.
比如initDatainitPropsobserveObserver判断和处理数据格式问题, 响应式核心依靠defineReactive()(核心也是defineProperty), initComputed核心defineProperty().

规范了一下结构, 第一篇感觉写的很乱, 这次采用了多级标题, 子标题函数父标题函数的内部调用.

不用自己写例子, Vue源码example目录下的页面基本能满足本章的输出测试需求, 记得修改引入的vue.min.jsdist下的vue.js, 打包完用LiveServer打开页面即可.


一、initState

初始化Props, methods, data, computed, watch

export function initState (vm: Component) {
  vm._watchers = []
  const opts = vm.$options
  
  if (opts.props) initProps(vm, opts.props) 
  // 初始化props
  if (opts.methods) initMethods(vm, opts.methods)
  // 初始化methods
  if (opts.data) { 
  // 如果data存在, 那么初始化data
    initData(vm)
  } else { 
  // data不存在则返回defineReactive({})的返回值
    observe(vm._data = {}, true /* asRootData */)
  }
  if (opts.computed) initComputed(vm, opts.computed)
  // computed存在则初始化computed
  if (opts.watch && opts.watch !== nativeWatch) {
  // watch存在则初始化watch
    initWatch(vm, opts.watch)
  }
}

二、支线

2.1.initProps

function initProps (vm: Component, propsOptions: Object) { // vm, vm.$options.props
  const propsData = vm.$options.propsData || {}
  /*
    propsData :[
      todoList: [
        0: {
          id: 0,
          text: '工作'
        },
        1: {
          id: 1,
          text: '早饭'
        }
      ]
    ]
  */
  const props = vm._props = {}
  
  const keys = vm.$options._propKeys = []
  const isRoot = !vm.$parent
  if (!isRoot) {
    toggleObserving(false)
  }
  for (const key in propsOptions) {
  /* 
    propsOptions: {
      color: { type: null },
      visible: { type: null }
    }
 */
    keys.push(key) // keys内为所有props参数名
    const value = validateProp(key, propsOptions, propsData, vm) // 所有遍历的props的新值
	/* value: [
	  0: {
	    id: 1,
	    text: 'Hello',
	  },
	  0: {
	    id: 2,
	    text: 'World',
	  }
	] */
    if (process.env.NODE_ENV !== 'production') {
      // 生产模式略
    } else {
    // 上面提到如果没有data就直接将data设为defineReactive({}), 现在有的话将每个值设置为响应式
      defineReactive(props, key, value)
    }
    if (!(key in vm)) { // 如果指定的属性在指定的对象或其原型链中, in运算符返回true
      proxy(vm, `_props`, key) // // 代理key到vm
    }
  }
  toggleObserving(true)
}

2.1.1.initProps—defineReactive

/**
 * 在一个对象上定义响应式属性
 */
export function defineReactive (
  obj: Object,
  key: string, // 当前遍历的props属性名
  val: any, // 当前遍历的props值, 不一定最新
  customSetter?: ?Function,
  shallow?: boolean
) {
  const dep = new Dep() // 实例化Dep
  // Dep接收Observer的变化通知并向订阅它的Watcher分发变化通知
  const property = Object.getOwnPropertyDescriptor(obj, key)
  if (property && property.configurable === false) {
    return
  }

  // cater for pre-defined getter/setters
  const getter = property && property.get // getter setter这俩在很多时候是undefined, 也可以是undefined
  const setter = property && property.set
  if ((!getter || setter) && arguments.length === 2) {
    val = obj[key]
  }

  let childOb = !shallow && observe(val) // val每次是data中的一个属性值
  Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    get: function reactiveGetter () {
      const value = getter ? getter.call(obj) : val 
      // props情况和computed情况因为有更新值的需求所以有set和get方法, 由sharedPropertyDefinition所设, 参考2.1.2和2.4.1
      if (Dep.target) {
        dep.depend() // depend()加入依赖
        if (childOb) {
          childOb.dep.depend()
          if (Array.isArray(value)) {
            dependArray(value)
          }
        }
      }
      return value
    },
    set: function reactiveSetter (newVal) { // 现在不清楚怎么拿到的新值
      const value = getter ? getter.call(obj) : val
      /* eslint-disable no-self-compare */
      if (newVal === value || (newVal !== newVal && value !== value)) {
        return
      }
      /* eslint-enable no-self-compare */
      if (process.env.NODE_ENV !== 'production' && customSetter) {
        customSetter()
      }
      // #7981: for accessor properties without setter
      if (getter && !setter) return
      if (setter) {
        setter.call(obj, newVal) // 如果有setter就将当前newVal更新到当前key上
      } else { 
      // 没有setter那就不是props或者computed情况, data之类直接设置当前值为最新值
        val = newVal
      }
      childOb = !shallow && observe(newVal) // 见2.3.2.1
      dep.notify() // 见2.4.1.1
    }
  })
}

2.1.2.initProps—proxy

props属性名定义到vm上并且挨个对应他们自己的setget, sharedPropertyDefinition全局数组.

const sharedPropertyDefinition = {
  enumerable: true,
  configurable: true,
  get: noop,
  set: noop
}
export function proxy (target: Object, sourceKey: string, key: string) { // vm, `_props`, 当前props名
  sharedPropertyDefinition.get = function proxyGetter () {
    return this[sourceKey][key] // sharedPropertyDefinition[_`props`][props名]
  }
  sharedPropertyDefinition.set = function proxySetter (val) {
    this[sourceKey][key] = val // sharedPropertyDefinition[_`props`][props名] = val
  }
  Object.defineProperty(target, key, sharedPropertyDefinition)
}

这种情况下defineReactive中的property.getset就不是undefined了.


2.1.3.initProps—dependArray

特殊的 用于收集数组元素依赖的方法.

/**
 * Collect dependencies on array elements when the array is touched, since
 * we cannot intercept array element access like property getters.
 */
function dependArray (value: Array<any>) {
  for (let e, i = 0, l = value.length; i < l; i++) {
    e = value[i]
    e && e.__ob__ && e.__ob__.dep.depend()
    if (Array.isArray(e)) { 
    // 如果还有数组就继续重来, 不是就到上一行三个值纳入依赖depend()结束
      dependArray(e)
    }
  }
}

2.2. initMethods

前面都在判重, 核心是最后的bind().

function initMethods (vm: Component, methods: Object) { // vm, vm.$options.methods
  const props = vm.$options.props
  /* props
  {
    color: {
      id: 1,
      text: 'Hello'
    },
    text: {
      id: 2,
      text: 'World'
    }
  },
  */
  for (const key in methods) {
    if (process.env.NODE_ENV !== 'production') {
      if (typeof methods[key] !== 'function') {
        warn(
          `Method "${key}" has type "${typeof methods[key]}" in the component definition. ` +
          `Did you reference the function correctly?`,
          vm
        )
      }
      if (props && hasOwn(props, key)) {
        warn(
          `Method "${key}" has already been defined as a prop.`,
          vm
        )
      }
      if ((key in vm) && isReserved(key)) {
        warn(
          `Method "${key}" conflicts with an existing Vue instance method. ` +
          `Avoid defining component methods that start with _ or $.`
        )
      }
    }
    vm[key] = typeof methods[key] !== 'function' ? noop : bind(methods[key], vm)
    // vm[key]即当前遍历的key对应的methods函数, 判定其是否为函数类型并判重
    // 重的话直接vm[key] = noop, 不重的话
  }
}

2.2.1.initMethods----bind

src/core/observer/index.js

export const bind = Function.prototype.bind ? nativeBind : polyfillBind

function nativeBind (fn: Function, ctx: Object): Function { // methods函数, vm
  return fn.bind(ctx)
}

function polyfillBind (fn: Function, ctx: Object): Function {
  function boundFn (a) {
    const l = arguments.length
    return l
      ? l > 1
        ? fn.apply(ctx, arguments)
        : fn.call(ctx, a)
      : fn.call(ctx)
  }

  boundFn._length = fn.length
  return boundFn
}

2.3.initData

完整代码:

function initData (vm: Component) { // vm
  let data = vm.$options.data // 函数mergedInstanceDataFn ()
  data = vm._data = typeof data === 'function'
    ? getData(data, vm)
    : data || {}
  /* data格式: {
	   options: [
		 { id: 0, name: 'color' },
		 { id: 1, name: 'color' }
	   ],
	   selected: 0
     }
  */
  if (!isPlainObject(data)) { // !true
    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
    )
  }

  const keys = Object.keys(data) // data中的属性名构成数组keys
  const props = vm.$options.props
  /* props
    {
      color: {
        id: 1,
        text: 'Hello'
      },
      text: {
        id: 2,
        text: 'World'
      }
    },
  */
  const methods = vm.$options.methods
  /* methods:
    {
  	  handleClick: function () {},
  	  handleBlur: function() {}
  	  ...
	}
  */
  let i = keys.length
  while (i--) { // 挨个检查data中的属性名有没有和props或者methods重名
    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
  observe(data, true /* asRootData */)
  /*
    data最终被传入new Obsever(value), 然后因为是一个对象走了else线this.walk(value),
    内部属性被循环defineReactive() 
  */
}

2.3.1.initData—observe

需要过一遍这里基本是因为格式可能不能直接处理, 需要判定.

export function observe (value: any, asRootData: ?boolean): Observer | void {
/* value格式: 
  {
    options: [
	  { id: 0, name: 'color' },
	  { id: 1, name: 'color' }
	],
	selected: 0
  }
*/
// asRootData: true
  if (!isObject(value) || value instanceof VNode) { 
  // 这一句导致进到下面的只有对象数组
    return
  }
  let ob: Observer | void
  if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
    // 如果value上有__ob__说明该value已经进行过观察, 直接返回value.__ob__
    ob = value.__ob__
  } else if (
    shouldObserve &&
    !isServerRendering() &&
    (Array.isArray(value) || isPlainObject(value)) &&
    Object.isExtensible(value) &&
    !value._isVue
  ) {
    // 如果没被观察过, 那么创建观察者实例, 劫持监听所有属性并向Dep通知变化
    ob = new Observer(value)
  }
  if (asRootData && ob) {
    ob.vmCount++
  }
  return ob
}

2.3.2.1.initData—observe—Observer类

export class Observer {
  value: any;
  dep: Dep;
  vmCount: number; // number of vms that have this object as root $data

  constructor (value: any) {
    this.value = value
    this.dep = new Dep() // 实例化一个Dep
    this.vmCount = 0
    def(value, '__ob__', this) // 为value defineProperty: __ob__
    /* value格式:
      {
        input: "Hello"
        __ob__: { 
          value: {
            input: "Hello",
            dep: Dep,
            vmCount: 1 
          }
        }
      }
    */
    if (Array.isArray(value)) { // 由上不是value
      if (hasProto) {
        protoAugment(value, arrayMethods)
      } else {
        copyAugment(value, arrayMethods, arrayKeys)
      }
      this.observeArray(value)
    } else {
      this.walk(value) 
      // 提取value全部键组成数组, 然后循环将每个值传入defineReactive(obj, keys[i])做响应式处理(见下walk)
    }
  }

  walk (obj: Object) {
    const keys = Object.keys(obj)
    for (let i = 0; i < keys.length; i++) {
      defineReactive(obj, keys[i])
    }
  }

  observeArray (items: Array<any>) { 
  /* 为数组的每一项new Observer()设置观察, 如果数组内部不是对象就继续到这再来一次, 看着像个递归, 这步只是不停的依靠循环不断深入提取数据, 最终目的还是达到walk()的执行条件去执行walk(), 只有walk()里面有defineReactive(). */
    for (let i = 0, l = items.length; i < l; i++) {
      observe(items[i])
    }
  }
}

2.4.initComputed

const computedWatcherOptions = { lazy: true } // watcher类的options, 有lazy的话会懒执行

function initComputed (vm: Component, computed: Object) { // vm, vm.$options.computed

  const watchers = vm._computedWatchers = Object.create(null) // 创造一个没有原型的干净空对象

  const isSSR = isServerRendering() // 是否为后端渲染(SSR)决定computedWatcher的创建

  for (const key in computed) { // 遍历computed对象
  /* computed
    {
      clickPermission: function() {},
      submitPermission: function() {},
      ...
    } 
  */
    const userDef = computed[key] // clickPermission函数, 内部有get和set
    const getter = typeof userDef === 'function' ? userDef : userDef.get
    // 似乎是getter可以省略的原因? 如果写了get()就不是函数类型那么就将get()作为getter, 是函数类型就将整个函数作getter
    if (process.env.NODE_ENV !== 'production' && getter == null) {
      warn(
        `Getter is missing for computed property "${key}".`,
        vm
      )
    }

    if (!isSSR) { // 非后端渲染情况
      watchers[key] = new Watcher( // 空对象内创建与计算属性函数同名的key对应自己的watch实例
        vm,
        getter || noop,
        noop,
        computedWatcherOptions
      ) // 懒执行的watcher
    }

    if (!(key in vm)) { // 如果vm里有当前这个key
      defineComputed(vm, key, userDef)
      // 将这个计算属性的名字和函数定义到vm里, 最终依靠还是Object.defineProperty()
    } else if (process.env.NODE_ENV !== 'production') {
    // 非生产环境判重
      if (key in vm.$data) {
        warn(`The computed property "${key}" is already defined in data.`, vm)
      } else if (vm.$options.props && key in vm.$options.props) {
        warn(`The computed property "${key}" is already defined as a prop.`, vm)
      } else if (vm.$options.methods && key in vm.$options.methods) {
        warn(`The computed property "${key}" is already defined as a method.`, vm)
      }
    }
  }
}

2.4.1.initComputed—defineComputed

export function defineComputed (
  target: any, // vm
  key: string, // 计算属性名
  userDef: Object | Function // 计算属性函数
) {
  const shouldCache = !isServerRendering() // SSR就赋值false
  if (typeof userDef === 'function') { // 如果是函数, 里面不分get()和set()
    sharedPropertyDefinition.get = shouldCache 
    // 如果该缓存就以createComputedGetter(计算属性名)的返回值作为sharedPropertyDefinition.get
      ? createComputedGetter(key)
      : createGetterInvoker(userDef)
    sharedPropertyDefinition.set = noop // 这个空函数, 防止undefined造成报错
  } else {
  // 如果不是个函数, 分为get()和set()两部分
    sharedPropertyDefinition.get = userDef.get
      ? shouldCache && userDef.cache !== false
        ? createComputedGetter(key)
        : createGetterInvoker(userDef.get)
      : noop
      // 如果userDef.get为true就判断shouldCache && userDef.cache !== false, 如果还为true就将createComputedGetter(key)返回值赋值到sharedPropertyDefinition.get
    sharedPropertyDefinition.set = userDef.set || noop //然后这时候因为有set就不能给set赋noop了, 将userDef的set()赋值到set
  }
  if (process.env.NODE_ENV !== 'production' &&
      sharedPropertyDefinition.set === noop) {
    sharedPropertyDefinition.set = function () {
      warn(
        `Computed property "${key}" was assigned to but it has no setter.`,
        this
      )
    }
  }
  Object.defineProperty(target, key, sharedPropertyDefinition)
  // Object.defineProperty(需定义或修改属性的对象, 需要定义或者修改的属性名, 含存取描述符和数据描述符的对象)
  // Object.defineProperty(vm, 计算属性名, { get: xxx, set: xxx })
}

2.4.1.1.initComputed—defineComputed—createComputedGetter与缓存
function createComputedGetter (key) { // 计算属性名
  return function computedGetter () { // return watcher.value
    const watcher = this._computedWatchers && this._computedWatchers[key]
    if (watcher) {
      if (watcher.dirty) { 
      /* 
        前面new Watcher()的时候传入的配置里有lazy, 那么options.lazy为true, options.dirty为lazy取反(见new Watcher), 为false 
      */
        watcher.evaluate()
      }
      if (Dep.target) {
        watcher.depend()
      }
      return watcher.value
    }
  }
}

两个计算属性会输出两次, 每次处理一个计算属性:
计算属性名compiledMarkdown, this._computedWatchers:

在这里插入图片描述
Dep是根据Observer的通知来了解页面状况从而通知Watcher的, 观察到Dep内正好有个notify通知函数:
src/core/observer/dep.js

notify () {
  // stabilize the subscriber list first
  const subs = this.subs.slice()
  if (process.env.NODE_ENV !== 'production' && !config.async) {
    // subs aren't sorted in scheduler if not running async
    // we need to sort them now to make sure they fire in correct
    // order
    subs.sort((a, b) => a.id - b.id)
  }
  for (let i = 0, l = subs.length; i < l; i++) {
    subs[i].update()
  }
}

另外看到defineReactive()内的set()最后也会调用这个进行通知.
看起来收到一次Observer的通知, 它会将所有订阅Dep的Watcher都调用一次update(), 那么去到Watcherupdate(), lazy有判断, 应该可以看出一点computed缓存的原理, 主要是控制get()的执行, 只要get()不执行就不会获取新值, 栈也不会改变, 而只要lazytrue, get()就不会被执行, update()中两个调用get()的途径都受到lazy控制, constructor中的直接调用途径也受到lazy控制.
但是lazy其实只是规定需要懒更新, 以此来启动dirty的更新判定机制.

Watcherconstructor末有这一项:

this.value = this.lazy ? undefined : this.get()

结合以下:

/**
 * Subscriber interface.
 * Will be called when a dependency changes.
 */
update () {
  /* istanbul ignore else */
  if (this.lazy) {
    this.dirty = true
  } else if (this.sync) {
    this.run()
  } else {
    queueWatcher(this)
  }
}
  /**
   * Scheduler job interface.
   * Will be called by the scheduler.
   */
run () {
  if (this.active) {
    const value = this.get()
    if (
      value !== this.value ||
      // Deep watchers and watchers on Object/Arrays should fire even
      // when the value is the same, because the value may
      // have mutated.
      isObject(value) ||
      this.deep
    ) {
      // set new value
      const oldValue = this.value
      this.value = value
      if (this.user) {
        const info = `callback for watcher "${this.expression}"`
        invokeWithErrorHandling(this.cb, this.vm, [value, oldValue], this.vm, info)
      } else {
        this.cb.call(this.vm, value, oldValue)
      }
    }
  }
}
/**
 * Evaluate the value of the watcher.
 * This only gets called for lazy watchers.
*/
evaluate () {
// 每次成功更新完值将dirty设置为false, 产生了误导让我觉得是dirty在直接控制懒更新, 实际上是辅助
  this.value = this.get()
  this.dirty = false
}

evaluate()dirty绝对为false, 然后createComputedGetter里调evaluate()之前判定了一下watcher.dirty, dirtytrue才调evaluate().
watcher.lazy只要在new Watcher()的时候规定了true就不会变false, 在watcher.lazytrue的情况下, watcher.dirty只有在evaluate()执行完, update()还没执行的时间段是false, 那么此处:
首次evaluate()一定会执行.
第二次及后续不好说, 如果每次之前执行了update(), 那么会执行, 否则判定为源状态未改变, 不执行.

那么update()什么时候执行, 现在不知道, 没看见有调过, 但是只要懒执行, 值的更新就必须过这里才能进行.


2.5.initWatch

function initWatch (vm: Component, watch: Object) { // vm, vm.$options.watch
/* watch格式:
  {
    currentBranch: fetchData,
    currentBranch1: { user: true, handler: function(){} }
  }
*/
  for (const key in watch) {
    const handler = watch[key] // handler = fetchData
    if (Array.isArray(handler)) { 
    // 若当前handler为数组, 那么为数组内每一个handler函数创建watcher
      for (let i = 0; i < handler.length; i++) {
        createWatcher(vm, key, handler[i])
      }
    } else { // 如果不是数组, 直接对handler创建watcher
      createWatcher(vm, key, handler)
    }
  }
}

2.5.1.initWatch—createWatcher

为一个watch对象创建watcher.

function createWatcher (
  vm: Component, // vm
  expOrFn: string | Function, // watch属性名
  handler: any, // watch对象
  options?: Object
) {
  if (isPlainObject(handler)) { // 第二种, 是对象的情况, 提出对象里的handler函数赋值给handler, 对象直接赋值给options
    options = handler
    handler = handler.handler
  }
  if (typeof handler === 'string') { // 第一种fetchData的情况, 说明方法定义在methods里, 此时initMethods已完成, 那么vm[handler]以获取该方法
    handler = vm[handler]
  }
  return vm.$watch(expOrFn, handler, options)
  //return vm.$watch(watch属性名, watch函数, watch对象)
}

2.5.1.1.initWatch—createWatcher—$watch()
Vue.prototype.$watch = function (
  expOrFn: string | Function, // watch属性名
  cb: any, // watch函数
  options?: Object // watch对象
): Function {
  const vm: Component = this
  if (isPlainObject(cb)) { 
  // 若cb为对象, 返回一个watcher
    return createWatcher(vm, expOrFn, cb, options)
  }
  options = options || {} // 没watch对象就给个空对象
  options.user = true // 如果watch是Fun不是Str的话, options里本来就有个user属性 
  const watcher = new Watcher(vm, expOrFn, cb, options)
  if (options.immediate) { // 如果watch的时候添加了immediate, 那么直接回调
    const info = `callback for immediate watcher "${watcher.expression}"`
    pushTarget() // 底层函数, 对一个数组型栈push()
    invokeWithErrorHandling(cb, vm, [watcher.value], vm, info) 
    /* 这个错误捕获函数捕获[watcher.value] ? cb.apply(vm, [watcher.value]) : handler.call(vm)中的错误报给handleError(), 它也可以捕获Promise操作中的错误 */
    popTarget() // 底层函数, 对一个数组型栈pop()
  }
  return function unwatchFn () { // 返回unwatchFn解除监听
    watcher.teardown()
  }
}
teardown () {
  if (this.active) {
  // 从vm的watcher列表中清除自己
    if (!this.vm._isBeingDestroyed) {
    // 如果vm被销毁, 那么清除vm上的watcher
      remove(this.vm._watchers, this)
    }
    let i = this.deps.length
    while (i--) {
      this.deps[i].removeSub(this)
    }
    this.active = false
  }
}

总结

快吐了…这篇先到这吧.
这篇写到一半才想起来上面好像还调了initLifeCycle(), 第二篇只能等下周补上.
因为组织一整篇要的时间比较长, 放在工作日会有点难受, 所以每轮休息日更一下, 然后工作日用来完善…
上一篇: Vue 2.6.13 源码解析(二) initLifeCycle && initEvents
下一篇: Vue 2.6.13 源码解析(四) Observer、Dep、Watcher与订阅

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值