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监测ActiveX控件是否已经安装过的代码
Sep 02 Javascript
轻松学习jQuery插件EasyUI EasyUI创建树形菜单
Nov 30 Javascript
JS实现部分HTML固定页面顶部随屏滚动效果
Dec 24 Javascript
jquery实现上传文件大小类型的验证例子(推荐)
Jun 25 Javascript
BOM系列第二篇之定时器requestAnimationFrame
Aug 17 Javascript
js鼠标按键事件和键盘按键事件用法实例汇总
Oct 03 Javascript
如何使用Bootstrap创建表单
Mar 29 Javascript
js 将canvas生成图片保存,或直接保存一张图片的实现方法
Jan 02 Javascript
聊聊JS动画库 Velocity.js的使用
Mar 13 Javascript
面试题:react和vue的区别分析
Apr 08 Javascript
mui js控制开关状态、修改switch开关的值方法
Sep 03 Javascript
javascript实现弹幕墙效果
Nov 28 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
【动漫杂谈】关于《请在T台上微笑》
2020/03/03 日漫
PHP将DateTime对象转化为友好时间显示的实现代码
2011/09/20 PHP
探讨PHP使用eAccelerator的API开发详解
2013/06/09 PHP
php制作圆形用户头像的实例_自定义封装类源代码
2017/09/18 PHP
JavaScript prototype对象的属性说明
2010/03/13 Javascript
jquery动态更换设置背景图的方法
2014/03/25 Javascript
cocos2dx骨骼动画Armature源码剖析(一)
2015/09/08 Javascript
跟我学习javascript的call(),apply(),bind()与回调
2015/11/16 Javascript
javascript实现无缝上下滚动特效
2015/12/16 Javascript
javascript的几种写法总结
2016/09/30 Javascript
Bootstrap table使用方法记录
2017/08/23 Javascript
Vue按需加载的具体实现
2017/12/02 Javascript
nodejs使用redis作为缓存介质实现的封装缓存类示例
2018/02/07 NodeJs
JavaScript this绑定过程深入详解
2018/12/07 Javascript
vue组件命名和props命名代码详解
2019/09/01 Javascript
jQuery实现消息弹出框效果
2019/12/10 jQuery
全面解析js中的原型,原型对象,原型链
2021/01/25 Javascript
[00:55]2015国际邀请赛中国区预选赛5月23日——28日约战上海
2015/05/25 DOTA
[57:22]2018DOTA2亚洲邀请赛 4.7总决赛 LGD vs Mineski 第五场
2018/04/10 DOTA
Python基于递归和非递归算法求两个数最大公约数、最小公倍数示例
2018/05/21 Python
Python for循环生成列表的实例
2018/06/15 Python
Django 中使用流响应处理视频的方法
2018/07/20 Python
对Django外键关系的描述
2019/07/26 Python
PyCharm取消波浪线、下划线和中划线的实现
2020/03/03 Python
pandas中read_csv、rolling、expanding用法详解
2020/04/21 Python
Python3以GitHub为例来实现模拟登录和爬取的实例讲解
2020/07/30 Python
在终端启动Python时报错的解决方案
2020/11/20 Python
浅析两列自适应布局的3种思路
2016/05/03 HTML / CSS
浅谈HTML5 &amp; CSS3的新交互特性
2016/07/19 HTML / CSS
英国女性时尚品牌:Apricot
2018/12/04 全球购物
英国邮购活的植物主要供应商:Gardening Direct
2019/01/28 全球购物
青年创业培训欢迎词
2014/01/08 职场文书
先进个人获奖感言
2014/01/24 职场文书
北京奥运会口号
2014/06/21 职场文书
竞聘演讲稿开场白
2014/08/25 职场文书
机械设计专业大学生职业生涯规划书范文
2014/09/13 职场文书