Vue源码分析之Vue实例初始化详解


Posted in Javascript onAugust 25, 2019

这一节主要记录一下:Vue 的初始化过程

以下正式开始:

Vue官网的生命周期图示表

Vue源码分析之Vue实例初始化详解

重点说一下 new Vue()后的初始化阶段,也就是created之前发生了什么。

Vue源码分析之Vue实例初始化详解

initLifecycle 阶段

export function initLifecycle (vm: Component) {
 const options = vm.$options

 // locate first non-abstract parent
 let parent = options.parent
 if (parent && !options.abstract) {
  while (parent.$options.abstract && parent.$parent) {
   parent = parent.$parent
  }
  parent.$children.push(vm) // 自己把自己添加到父级的$children数组中
 }

 vm.$parent = parent // 父组件实例
 vm.$root = parent ? parent.$root : vm // 根组件 如果不存在父组件,则本身就是根组件

 vm.$children = [] // 用来存放子组件
 vm.$refs = {}

 vm._watcher = null 
 vm._inactive = null 
 vm._directInactive = false
 vm._isMounted = false
 vm._isDestroyed = false
 vm._isBeingDestroyed = false
}

接下来是initEvents 阶段

// v-on如果写在平台标签上如:div,则会将v-on上注册的事件注册到浏览器事件中
// v-on如果写在组件标签上,则会将v-on注册的事件注册到子组件的事件系统
// 子组件(Vue实例)在初始化的时候,有可能接收到父组件向子组件注册的事件。
// 子组件(Vue实例)自身模板注册的事件,只要在渲染的时候才会根据虚拟DOM的对比结果
// 来确定是注册事件还是解绑事件

// 这里初始化的事件是指父组件在模板中使用v-on注册的事件添加到子组件的事件系统也就是vue的事件系统。
export function initEvents (vm: Component) {
 vm._events = Object.create(null) // 初始化
 vm._hasHookEvent = false
 // init parent attached events 初初始化腹肌组件添加的事件
 const listeners = vm.$options._parentListeners
 if (listeners) {
  updateComponentListeners(vm, listeners)
 }
}

export function updateComponentListeners (
 vm: Component,
 listeners: Object,
 oldListeners: ?Object
) {
 target = vm
 updateListeners(listeners, oldListeners || {}, add, remove, createOnceHandler, vm)
 target = undefined
}

initjections 阶段

export function initInjections (vm: Component) {
 // 自下而上读取inject
 const result = resolveInject(vm.$options.inject, vm)
 if (result) {
  // 设置为false 避免defineReactive函数把数据转换为响应式
  toggleObserving(false)
  Object.keys(result).forEach(key => {
    defineReactive(vm, key, result[key])
  })
  // 再次更改回来
  toggleObserving(true)
 }
}

export function resolveInject (inject: any, vm: Component): ?Object {
 if (inject) {
  // inject is :any because flow is not smart enough to figure out cached
  const result = Object.create(null)
  // 如果浏览器支持Symbol,则使用Reflect.ownkyes(),否则使用Object.keys()
  const keys = hasSymbol
   ? Reflect.ownKeys(inject)
   : Object.keys(inject)

  for (let i = 0; i < keys.length; i++) {
   const key = keys[i]
   // #6574 in case the inject object is observed...
   if (key === '__ob__') continue
   const provideKey = inject[key].from
   let source = vm
   // 当provided注入内容的时候就是把内容注入到当前实例的_provided中
   // 刚开始的时候 source就是实本身,挡在source._provided中找不到对应的值
   // 就会把source设置为父实例
   // Vue实例化的第一步就是规格化用户传入的数据,所以inject不管时数组还是对象
   // 最后都会变成对象
   while (source) {
    if (source._provided && hasOwn(source._provided, provideKey)) {
     result[key] = source._provided[provideKey]
     break
    }
    source = source.$parent
   }
   // 处理默认值的情况
   if (!source) {
    if ('default' in inject[key]) {
     const provideDefault = inject[key].default
     result[key] = typeof provideDefault === 'function'
      ? provideDefault.call(vm)
      : provideDefault
    } else if (process.env.NODE_ENV !== 'production') {
     warn(`Injection "${key}" not found`, vm)
    }
   }
  }
  return result
 }
}

initState 阶段

在 Vue 中,我们经常会用到 props 、methods 、 watch 、computed 、data 。这些状态在使用前都需要初始化。而初始化的过程正是在 initState 阶段完成。

因为 injects 是在 initState 之前完成,所以可以在 State 中使用 injects 。

export function initState (vm: Component) {
 vm._watchers = []
 // 获取到经过初始化的用户传进来的options
 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)
 }
}

initProps

function initProps (vm: Component, propsOptions: Object) {
 const propsData = vm.$options.propsData || {}
 const props = vm._props = {}
 // cache prop keys so that future props updates can iterate using Array
 // instead of dynamic object key enumeration.
 // 缓存props的key值
 const keys = vm.$options._propKeys = []
 const isRoot = !vm.$parent
 // root instance props should be converted
 // 如果不是跟组件则没必要转换成响应式数据
 if (!isRoot) {
  // 控制是否转换成响应式数据
  toggleObserving(false)
 }
 for (const key in propsOptions) {
  keys.push(key)
  // 获取props的值
  const value = validateProp(key, propsOptions, propsData, vm)
  defineReactive(props, key, value)
  // static props are already proxied on the component's prototype
  // during Vue.extend(). We only need to proxy props defined at
  // instantiation here.
  // 把props代理到Vue实例上来,可以直接通过this.props访问
  if (!(key in vm)) {
   proxy(vm, `_props`, key)
  }
 }
 toggleObserving(true)
}

initMethods

function initMethods (vm: Component, methods: Object) {
 const props = vm.$options.props
 for (const key in methods) {
  if (process.env.NODE_ENV !== 'production') {
   // 如果key不是一个函数 报错
   if (typeof methods[key] !== 'function') {
    warn(
     `Method "${key}" has type "${typeof methods[key]}" in the component definition. ` +
     `Did you reference the function correctly?`,
     vm
    )
   }
   // 如果props中存在同名的属性 报错
   if (props && hasOwn(props, key)) {
    warn(
     `Method "${key}" has already been defined as a prop.`,
     vm
    )
   }
   // isReserved判断是否以$或_开头
   if ((key in vm) && isReserved(key)) {
    warn(
     `Method "${key}" conflicts with an existing Vue instance method. ` +
     `Avoid defining component methods that start with _ or $.`
    )
   }
  }
  // 把methods的方法绑定到Vue实例上
  vm[key] = typeof methods[key] !== 'function' ? noop : bind(methods[key], vm)
 }
}

initData

function initData (vm: Component) {
 let data = vm.$options.data
 data = vm._data = typeof data === 'function'
  ? getData(data, vm)
  : data || {}
 // isPlainObject监测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
  )
 }
 // proxy data on instance
 const keys = Object.keys(data)
 const props = vm.$options.props
 const methods = vm.$options.methods
 let i = keys.length
 // 循环data
 while (i--) {
  const key = keys[i]
  if (process.env.NODE_ENV !== 'production') {
   // 如果在methods中存在和key同名的属性 则报错
   if (methods && hasOwn(methods, key)) {
    warn(
     `Method "${key}" has already been defined as a data property.`,
     vm
    )
   }
  }
  // 如果在props中存在和key同名的属性 则报错
  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)) {
   // isReserved判断是否以$或_开头
   // 代理data,使得可以直接通过this.key访问this._data.key
   proxy(vm, `_data`, key)
  }
 }
 // observe data
 // 把data转换为响应式数据
 observe(data, true /* asRootData */)
}

initComputed

const computedWatcherOptions = { lazy: true }
function initComputed (vm: Component, computed: Object) {
 // $flow-disable-line
 const watchers = vm._computedWatchers = Object.create(null)
 // computed properties are just getters during SSR
 // 判断是不是服务端渲染
 const isSSR = isServerRendering()

 for (const key in computed) {
  const userDef = computed[key]
  const getter = typeof userDef === 'function' ? userDef : userDef.get
  if (process.env.NODE_ENV !== 'production' && getter == null) {
   warn(
    `Getter is missing for computed property "${key}".`,
    vm
   )
  }
  // 如果不是ssr,则创建Watcher实例
  if (!isSSR) {
   // create internal watcher for the computed property.
   watchers[key] = new Watcher(
    vm,
    getter || noop,
    noop,
    computedWatcherOptions
   )
  }

  // component-defined computed properties are already defined on the
  // component prototype. We only need to define computed properties defined
  // at instantiation here.
  // 如果vm不存在key的同名属性
  if (!(key in vm)) {
   defineComputed(vm, key, userDef)
  } 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)
   }
  }
 }
}
sharedPropertyDefinition = {
  enumerable: true,
  cnfigurable: true,
  get: noop,
  set: noop
}
export function defineComputed (
 target: any,
 key: string,
 userDef: Object | Function
) {
 // 如果是服务端渲染,则computed不会有缓存,因为数据响应式的过程在服务器是多余的
 const shouldCache = !isServerRendering()
 // createComputedGetter返回计算属性的getter
 // createGetterInvoker返回userDef的getter
 if (typeof userDef === 'function') {
  sharedPropertyDefinition.get = shouldCache
   ? createComputedGetter(key)
   : createGetterInvoker(userDef)
  sharedPropertyDefinition.set = noop
 } else {
  // 当userDef为一个对象时
  sharedPropertyDefinition.get = userDef.get
   ? shouldCache && userDef.cache !== false
    ? createComputedGetter(key)
    : createGetterInvoker(userDef.get)
   : noop
  sharedPropertyDefinition.set = userDef.set || noop
 }
 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
   )
  }
 }
 // 在tearget上定义一个属性, 属性名为key, 属性描述符为sharedPropertyDefinition
 Object.defineProperty(target, key, sharedPropertyDefinition)
}

function createComputedGetter (key) {
 return function computedGetter () {
  // 查找是否存在key的Watcher
  const watcher = this._computedWatchers && this._computedWatchers[key]
  if (watcher) {
   // 如果dirty为true,则重新计算,否则返回缓存
   if (watcher.dirty) {
    watcher.evaluate()
   }
   if (Dep.target) {
    watcher.depend()
   }
   return watcher.value
  }
 }
}

function createGetterInvoker(fn) {
 return function computedGetter () {
  return fn.call(this, this)
 }
}

initWatch

function initWatch (vm: Component, watch: Object) {
 for (const key in watch) {
  const handler = watch[key]
  // 处理数组类型
  if (Array.isArray(handler)) {
   for (let i = 0; i < handler.length; i++) {
    createWatcher(vm, key, handler[i])
   }
  } else {
   createWatcher(vm, key, handler)
  }
 }
}

function createWatcher (
 vm: Component,
 expOrFn: string | Function,
 handler: any,
 options?: Object
) {
  // isPlainObject检查是否是对象
 if (isPlainObject(handler)) {
  options = handler
  handler = handler.handler
 }
 if (typeof handler === 'string') {
  handler = vm[handler]
 }
 // 最后调用$watch
 return vm.$watch(expOrFn, handler, options)
}

initProvide阶段

export function initProvide (vm: Component) {
 const provide = vm.$options.provide
 if (provide) {
  // 把provided存到_provided上
  vm._provided = typeof provide === 'function'
   ? provide.call(vm)
   : provide
 }
}

到这里 Vue 的初始化就结束了,接下来就是触发生命周期函数 created 。

总结一下:new Vue() 执行之后,Vue 进入初始化阶段。

初始化流程如下:

  • 规格化 $options ,也就是用户自定义的数据
  • initLifecycle 注入生命周期
  • initEvents 初始化事件,注意:这里的事件是值在父组件在子组件上定义的事件
  • initRender
  • initjections 初始化 jetction
  • initProps 初始化props
  • initState 包括props 、methods 、data 、computed 、watch
  • initProvided 初始化 provide

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,谢谢大家对三水点靠木的支持。

Javascript 相关文章推荐
javascript 得到变量类型的函数
May 19 Javascript
IE6浏览器下resize事件被执行了多次解决方法
Dec 11 Javascript
关于jQuery中.attr()和.prop()的问题探讨
Sep 06 Javascript
jQuery获取选中内容及设置元素属性的方法
Jul 09 Javascript
JQuery表格拖动调整列宽效果(自己动手写的)
Sep 01 Javascript
jquery选择器需要注意的问题
Nov 26 Javascript
jquery中获取元素里某一特定子元素的代码
Dec 02 Javascript
图解JavaScript中的this关键字
May 28 Javascript
jQuery自定义图片缩放拖拽插件imageQ实现方法(附demo源码下载)
May 27 Javascript
jQuery Validate格式验证功能实例代码(包括重名验证)
Jul 18 jQuery
vue编译打包本地查看index文件的方法
Feb 23 Javascript
深入解析微信小程序开发中遇到的几个小问题
Jul 11 Javascript
javascript导出csv文件(excel)的方法示例
Aug 25 #Javascript
JavaScript在web自动化测试中的作用示例详解
Aug 25 #Javascript
angularjs自定义过滤器demo示例
Aug 24 #Javascript
Jquery实现获取子元素的方法分析
Aug 24 #jQuery
微信小程序class封装http代码实例
Aug 24 #Javascript
微信小程序前端promise封装代码实例
Aug 24 #Javascript
node获取客户端ip功能简单示例
Aug 24 #Javascript
You might like
PHP 多维数组排序(usort,uasort)
2010/06/30 PHP
PHP中使用crypt()实现用户身份验证的代码
2012/09/05 PHP
php数组的概述及分类与声明代码演示
2013/02/26 PHP
PHP把JPEG图片转换成Progressive JPEG的方法
2014/06/30 PHP
PHP中echo,print_r与var_dump区别分析
2014/09/29 PHP
php输出xml必须header的解决方法
2014/10/17 PHP
php操作redis缓存方法分享
2015/06/03 PHP
PHP入门教程之操作符与控制结构流程详解
2016/09/09 PHP
CakePHP框架Model关联对象用法分析
2017/08/04 PHP
js自定义事件代码说明
2011/01/31 Javascript
JS格式化数字金额用逗号隔开保留两位小数
2013/10/18 Javascript
Jquery promise实现一张一张加载图片
2015/11/13 Javascript
字符串反转_JavaScript
2016/04/28 Javascript
利用node.js搭建简单web服务器的方法教程
2017/02/20 Javascript
Angular.js通过自定义指令directive实现滑块滑动效果
2017/10/13 Javascript
元素全屏的设置与监听实例
2017/11/28 Javascript
vue获取时间戳转换为日期格式代码实例
2019/04/17 Javascript
javascript实现点击按钮切换轮播图功能
2020/09/23 Javascript
在Python的一段程序中如何使用多次事件循环详解
2017/09/07 Python
python 随机数使用方法,推导以及字符串,双色球小程序实例
2017/09/12 Python
python数据封装json格式数据
2018/03/04 Python
解决Pycharm下面出现No R interpreter defined的问题
2018/10/29 Python
Windows 8.1 64bit下搭建 Scrapy 0.22 环境
2018/11/18 Python
python实现Excel文件转换为TXT文件
2019/04/28 Python
django认证系统 Authentication使用详解
2019/07/22 Python
基于python3实现倒叙字符串
2020/02/18 Python
python shapely.geometry.polygon任意两个四边形的IOU计算实例
2020/04/12 Python
一款纯css3制作的2015年元旦雪人动画特效教程
2014/12/29 HTML / CSS
英国著名的化妆品折扣网站:Allbeauty.com
2016/07/21 全球购物
英语国培研修感言
2014/02/13 职场文书
人力资源管理系自荐信
2014/05/31 职场文书
房屋认购协议书
2015/01/29 职场文书
政协工作总结2015
2015/05/20 职场文书
幼儿园大班开学寄语(2016秋季)
2015/12/03 职场文书
【海涛dota解说】DCG联赛第一周 LGD VS DH
2022/04/01 DOTA
Win11 21h2可以升级22h2吗?看看你的电脑符不符合要求
2022/07/07 数码科技