Vue源码分析——生命周期

数字逸云
• 阅读 1610

背景

vue版本

2.5.21

本篇内容

Vue的生命周期

源码开始

1. package.json

scrpit内,npm run dev的命令:

"dev": "rollup -w -c scripts/config.js --environment TARGET:web-full-dev"
  • 其中rollup是一个打包工具,类似webpack。

    rollup: Rollup 是一个 JavaScript 模块打包器,可以将小块代码编译成大块复杂的代码,例如 library 或应用程序。
  • 打包的代码在scripts/config.js

2. scripts/config.js

根据TARGET:web-full-dev找到如下代码:

// Runtime+compiler development build (Browser)
'web-full-dev': {
  entry: resolve('web/entry-runtime-with-compiler.js'),
  dest: resolve('dist/vue.js'),
  format: 'umd',
  env: 'development',
  alias: { he: './entity-decoder' },
  banner
}

找到入口文件在web/entry-runtime-with-compiler.js

3. src\platforms\web\entry-runtime-with-compiler.js

vue来自于./runtime/index

import Vue from './runtime/index'

4. src\platforms\web\runtime\index.js

Vue来自于core/index

import Vue from 'core/index'
...
// public mount method
Vue.prototype.$mount = function (
  el?: string | Element,
  hydrating?: boolean
): Component {
  el = el && inBrowser ? query(el) : undefined
  return mountComponent(this, el, hydrating)
}

5. src\core\index.js

Vue来自于./instance/index

import Vue from './instance/index'

6. src\core\instance\index.js

一直跟到这里,Vue终于露出庐山真面目。

function Vue (options) {
  if (process.env.NODE_ENV !== 'production' &&
    !(this instanceof Vue)
  ) {
    warn('Vue is a constructor and should be called with the `new` keyword')
  }
  this._init(options)
}

在定义了一个函数对象Vue后,接下来的代码:

initMixin(Vue)
stateMixin(Vue)
eventsMixin(Vue)
lifecycleMixin(Vue)
renderMixin(Vue)

我们一一分析。

6.1 initMixin src\core\instance\init.js

这里代码的作用是给Vue的原型链上定义_init方法。而这个_init方法在Vue对象创建时被调用(回看6里的代码this._init(options))。
接下来我们分析_init方法里做了什么,就明白了Vue对象创建时,到底经历了什么。
刚开始是一些参数的初始化,直到merge options。

export function initMixin (Vue: Class<Component>) {
  Vue.prototype._init = function (options?: Object) {
    ...
    // merge options
    if (options && options._isComponent) {
      // optimize internal component instantiation
      // since dynamic options merging is pretty slow, and none of the
      // internal component options needs special treatment.
      initInternalComponent(vm, options)
    } else {
      vm.$options = mergeOptions(
        resolveConstructorOptions(vm.constructor),
        options || {},
        vm
      )
    }
    
    // expose real self
    vm._self = vm
    initLifecycle(vm)
    initEvents(vm)
    initRender(vm)
    callHook(vm, 'beforeCreate')
    initInjections(vm) // resolve injections before data/props
    initState(vm)
    initProvide(vm) // resolve provide after data/props
    callHook(vm, 'created')
    
    ...
    if (vm.$options.el) {
      vm.$mount(vm.$options.el)
    }
  }
}
6.1.1 src\core\util\options.js

mergeOptions的方法做了什么?顾名思义,将options挂到Vue对象上。看下面的图:
Vue源码分析——生命周期
merge前vm.$options是空的;merge后,$options已经有值了。
执行完mergeOptions后,我们继续往下看。

6.1.2 initLifecycle src\core\instance\lifecycle.js

initLifecycle顾名思义,肯定与Vue的生命周期有关!真的是吗?同样看图:
Vue源码分析——生命周期
发现,只是多了一些参数,还没有到我们熟悉的created,mounted。但是这里是为生命周期做准备,做了一下初始化工作。

6.1.3 initEvents src\core\instance\events.js

这个方法的代码不多,主要是调用了updateComponentListeners方法。这个方法的作用是更新组件的侦听事件,与生命周期无关,暂不分析。

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)
  }
}
6.1.4 initRender src\core\instance\render.js

initRender方法初始化了渲染的参数和方法(此时还没有渲染)。如下图:
Vue源码分析——生命周期

6.1.5 callHook(vm, 'beforeCreate')

终于到了第一个生命周期beforeCreate
接下来肯定是create了吧。

6.1.6 initInjections src\core\instance\inject.js

initInjections与provide/inject有关,与生命周期无关,这里暂不介绍。《Vue官方文档:provide/inject》

6.1.7 initState src\core\instance\state.js

代码不多,但是一看就知道是初始化Props,Methods,data,computed,watch的。

export function initState (vm: Component) {
  vm._watchers = []
  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)
  }
}

如图,将data(demo中没有methods等)的值注入:
Vue源码分析——生命周期

6.1.8 initProvide src\core\instance\inject.js

initProvide与provide/inject有关,与生命周期无关,这里暂不介绍。《Vue官方文档:provide/inject》
initProvide

6.1.9 callHook(vm, 'created')

第二个生命周期created

6.1.10 vm.$mount(vm.$options.el)

回看第4步中,给Vue的原型链上挂上了$mount方法:

import { mountComponent } from 'core/instance/lifecycle'
// public mount method
Vue.prototype.$mount = function (
  ...
}
6.1.10.1 mountComponent src\core\instance\lifecycle.js
export function mountComponent (
  vm: Component,
  el: ?Element,
  hydrating?: boolean
): Component {
  ...
  callHook(vm, 'beforeMount')
  ...
  // updateComponent
  new Watcher(vm, updateComponent, noop, {
    before () {
      if (vm._isMounted && !vm._isDestroyed) {
        callHook(vm, 'beforeUpdate')
      }
    }
  }, true /* isRenderWatcher */)
  ...
  if (vm.$vnode == null) {
    vm._isMounted = true
    callHook(vm, 'mounted')
  }
  return vm
}

这段代码里,有关生命周期的就很多了。一共三个:beforeMount,beforeUpdate,mounted
真正mount的方法在updateComponent

updateComponent = () => {
  vm._update(vm._render(), hydrating)
}

那么updateComponent何时调用?updateComponentnew Watcher时传进去,作为getter方法,在每次获取vm时执行,其中执行了vm._update(vm._render(), hydrating),用以mount渲染。
TODO: 这块代码具体会另起文章详细介绍。

原创说明

点赞
收藏
评论区
推荐文章
blmius blmius
4年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
Oracle 分组与拼接字符串同时使用
SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(
Wesley13 Wesley13
4年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
Stella981 Stella981
4年前
Python+Selenium自动化篇
本篇文字主要学习selenium定位页面元素的集中方法,以百度首页为例子。0.元素定位方法主要有:id定位:find\_element\_by\_id('')name定位:find\_element\_by\_name('')class定位:find\_element\_by\_class\_name(''
Wesley13 Wesley13
4年前
VBox 启动虚拟机失败
在Vbox(5.0.8版本)启动Ubuntu的虚拟机时,遇到错误信息:NtCreateFile(\\Device\\VBoxDrvStub)failed:0xc000000034STATUS\_OBJECT\_NAME\_NOT\_FOUND(0retries) (rc101)Makesurethekern
Wesley13 Wesley13
4年前
FLV文件格式
1.        FLV文件对齐方式FLV文件以大端对齐方式存放多字节整型。如存放数字无符号16位的数字300(0x012C),那么在FLV文件中存放的顺序是:|0x01|0x2C|。如果是无符号32位数字300(0x0000012C),那么在FLV文件中的存放顺序是:|0x00|0x00|0x00|0x01|0x2C。2.  
Stella981 Stella981
4年前
SpringBoot整合Redis乱码原因及解决方案
问题描述:springboot使用springdataredis存储数据时乱码rediskey/value出现\\xAC\\xED\\x00\\x05t\\x00\\x05问题分析:查看RedisTemplate类!(https://oscimg.oschina.net/oscnet/0a85565fa
Wesley13 Wesley13
4年前
mysql设置时区
mysql设置时区mysql\_query("SETtime\_zone'8:00'")ordie('时区设置失败,请联系管理员!');中国在东8区所以加8方法二:selectcount(user\_id)asdevice,CONVERT\_TZ(FROM\_UNIXTIME(reg\_time),'08:00','0
Wesley13 Wesley13
4年前
PHP创建多级树型结构
<!lang:php<?php$areaarray(array('id'1,'pid'0,'name''中国'),array('id'5,'pid'0,'name''美国'),array('id'2,'pid'1,'name''吉林'),array('id'4,'pid'2,'n
Easter79 Easter79
4年前
SpringBoot整合Redis乱码原因及解决方案
问题描述:springboot使用springdataredis存储数据时乱码rediskey/value出现\\xAC\\xED\\x00\\x05t\\x00\\x05问题分析:查看RedisTemplate类!(https://oscimg.oschina.net/oscnet/0a85565fa
Wesley13 Wesley13
4年前
Java日期时间API系列36
  十二时辰,古代劳动人民把一昼夜划分成十二个时段,每一个时段叫一个时辰。二十四小时和十二时辰对照表:时辰时间24时制子时深夜11:00凌晨01:0023:0001:00丑时上午01:00上午03:0001:0003:00寅时上午03:00上午0