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 Validation插件防止重复提交表单的解决方法
Mar 05 Javascript
js控制滚动条缓慢滚动到顶部实现代码
Mar 20 Javascript
javascript 面向对象封装与继承
Nov 27 Javascript
JavaScript 异常处理 详解
Feb 06 Javascript
常用jQuery选择器汇总
Feb 02 Javascript
微信小程序利用co处理异步流程的方法教程
May 20 Javascript
vue-cli 默认路由再子路由选中下的选中状态问题及解决代码
Sep 06 Javascript
vue构建动态表单的方法示例
Sep 22 Javascript
浅谈Vue.js中如何实现自定义下拉菜单指令
Jan 06 Javascript
vue实现带复选框的树形菜单
May 27 Javascript
JS实现滚动条触底加载更多
Sep 19 Javascript
Jquery属性的获取/设置及样式添加/删除操作技巧分析
Dec 23 jQuery
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.NET的入门教程
2006/10/09 PHP
PR值查询 | PageRank 查询
2006/12/20 PHP
PHP 翻页 实例代码
2009/08/07 PHP
一步一步学习PHP(8) php 数组
2010/03/05 PHP
PHP生成指定长度随机数最简洁的方法
2014/07/14 PHP
PHP图片处理之使用imagecopyresampled函数实现图片缩放例子
2014/11/19 PHP
php jsonp单引号转义
2014/11/23 PHP
php通过文件流方式复制文件的方法
2015/03/13 PHP
你可能不再需要JQUERY
2021/03/09 Javascript
js关闭模态窗口刷新父页面或跳转页面
2012/12/13 Javascript
学习JavaScript设计模式之单例模式
2016/01/19 Javascript
微信小程序url与token设置详解
2017/09/26 Javascript
ES6解构赋值实例详解
2017/10/31 Javascript
Vue使用高德地图搭建实时公交应用功能(地图 + 附近站点+线路详情 + 输入提示+换乘详情)
2018/05/16 Javascript
微信小程序使用form表单获取输入框数据的实例代码
2018/05/17 Javascript
在小程序/mpvue中使用flyio发起网络请求的方法
2018/09/13 Javascript
mpvue将vue项目转换为小程序
2018/09/30 Javascript
利用Promise自定义一个GET请求的函数示例代码
2019/03/20 Javascript
vue中created和mounted的区别浅析
2019/08/13 Javascript
利用numpy+matplotlib绘图的基本操作教程
2017/05/03 Python
浅析PHP与Python进行数据交互
2018/05/15 Python
python实现中文文本分句的例子
2019/07/15 Python
python中hasattr()、getattr()、setattr()函数的使用
2019/08/16 Python
Pytorch Tensor 输出为txt和mat格式方式
2020/01/03 Python
Python 实现判断图片格式并转换,将转换的图像存到生成的文件夹中
2020/01/13 Python
Django实现任意文件上传(最简单的方法)
2020/06/03 Python
Python 执行矩阵与线性代数运算
2020/08/01 Python
挪威太阳镜和眼镜网上商城:SmartBuyGlasses挪威
2016/08/20 全球购物
Hanro官网:奢华男士和女士内衣、睡衣和家居服
2018/10/25 全球购物
银行员工辞职信范文
2014/01/20 职场文书
《放小鸟》教学反思
2014/04/20 职场文书
医院志愿者活动总结
2015/05/06 职场文书
阿凡达观后感
2015/06/10 职场文书
蜗居观后感
2015/06/11 职场文书
大学生支教感言
2015/08/01 职场文书
党员廉政准则心得体会
2016/01/20 职场文书