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 相关文章推荐
jquery中ajax学习笔记4
Oct 16 Javascript
Jquery.addClass始终无效原因分析
Sep 08 Javascript
JS截取url中问号后面参数的值信息
Apr 29 Javascript
如何利用Promises编写更优雅的JavaScript代码
May 17 Javascript
深入理解MVC中的时间js格式化
May 19 Javascript
JSON对象转化为字符串详解
Aug 11 Javascript
使用JS和canvas实现gif动图的停止和播放代码
Sep 01 Javascript
使用SVG基本操作API的实例讲解
Sep 14 Javascript
微信小程序支付及退款流程详解
Nov 30 Javascript
Vuex入门到上手教程
Jun 20 Javascript
原生Js 实现的简单无缝滚动轮播图的示例代码
May 10 Javascript
vue的项目如何打包上线
Apr 13 Vue.js
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
DIY一个适配电脑声卡的动圈话筒放大器
2021/03/02 无线电
配置PHP使之能同时支持GIF和JPEG
2006/10/09 PHP
通过PHP CLI实现简单的数据库实时监控调度
2009/07/01 PHP
在Linux系统下一键重新安装WordPress的脚本示例
2015/06/30 PHP
suggestion开发小结以及对键盘事件的总结(针对中文输入法状态)
2011/12/20 Javascript
用JQuery实现表格隔行变色和突出显示当前行的代码
2012/02/10 Javascript
8个实用的jQuery技巧
2014/03/04 Javascript
jquery.idTabs 选项卡使用示例代码
2014/09/03 Javascript
JavaScript随机生成信用卡卡号的方法
2015/04/07 Javascript
JS+CSS实现仿触屏手机拨号盘界面及功能模拟完整实例
2015/05/16 Javascript
简介AngularJS的HTML DOM支持情况
2015/06/17 Javascript
JS操作JSON方法总结(推荐)
2016/06/14 Javascript
JS正则RegExp.test()使用注意事项(不具有重复性)
2016/12/28 Javascript
js实现字符全排列算法的简单方法
2017/05/01 Javascript
微信小程序 rich-text的使用方法
2017/08/04 Javascript
JavaScript实现三级级联特效
2017/11/05 Javascript
详解vue-router数据加载与缓存使用总结
2018/10/29 Javascript
Angular父子组件通过服务传参的示例方法
2018/10/31 Javascript
vue前后分离调起微信支付
2019/07/29 Javascript
[51:26]DOTA2上海特级锦标赛主赛事日 - 2 胜者组第一轮#3Secret VS OG第二局
2016/03/03 DOTA
在Python中处理时间之clock()方法的使用
2015/05/22 Python
pygame实现烟雨蒙蒙下彩虹雨
2019/11/11 Python
LTD Commodities:礼品,独特发现,家居装饰,家用器皿
2017/08/11 全球购物
欧洲领先的电子和电信零售商和服务提供商:Currys PC World Business
2017/12/05 全球购物
欧洲最大的高尔夫零售商:American Golf
2019/09/02 全球购物
美国优质马术服装购买网站:Breeches.com
2019/12/16 全球购物
Java面试中常遇到的问题,也是需要注意的几点
2013/08/30 面试题
早会主持词
2014/03/17 职场文书
目标责任书范本
2014/04/16 职场文书
反腐倡廉警示教育活动心得体会
2014/09/04 职场文书
2014年公务员工作总结
2014/11/18 职场文书
党的群众路线教育实践活动先进个人材料
2014/12/24 职场文书
司机个人年终总结
2015/03/03 职场文书
《你在为谁工作》心得体会(共8篇)
2016/01/20 职场文书
第四次工业革命,打工人与机器人的竞争
2022/04/21 数码科技
Mybatis-Plus 使用 @TableField 自动填充日期
2022/04/26 Java/Android