源码解读vue实例化过程"/>
从vue2.6.11源码解读vue实例化过程
1.vue2.6.11文件结构
vue文件结构下的详情:
dist | vue本身构建生成的文件 | |
src | ||
--compiler | 编译器 | |
--codegen | 键盘事件生成 | |
--event.js | 声明键盘事件和在Weex上生成具有绑定参数的处理程序代码 | |
--index.js | codegen入口文件,结合ASTElemen、键盘事件状态、插槽作用域确定规范(涉及: sub-trees、v-once、v-if、v-for、key、pre、component、props、event handlers、v-model、inline-template、v-bind、v-on、data:finction、directives等等 )并生成渲染code | ASTElement树 |
--directives | 指令 | |
--bind.js | v-bind语法糖的实现(结合ASTElement和ASTDirective) | ast详解 |
--index.js | 1.暴露v-bind和v-model语法糖出去 2.引入并暴露shared/util.js 的noop方法(不执行任何操作,不留下无用的传输代码,严格检查函数调用 Arity) | |
--model.js | v-model语法糖的实现(结合ASTElement和ASTModifiers) | ast详解 |
--on.js | v-on语法糖的实现(结合ASTElement和ASTDirective) | ast详解 |
--parser | 解析器 | |
--entity-decoder.js | 通过创建div标签来解码传入的html内容 | |
--filter-parser.js | 过滤函数解析(filter允许用在两个地方,一个是双括号插值,一个是v-bind表达式后面,如果解析到这两种情况,执行parseFilters解析filter) | 解析思路 |
--html-parser.js | html解析器 | 不检查此文件的类型,因为它主要是供应商代码。 |
--index.js | parser入口文件,主要结合entity-decoder.js、filter-parser.js和text-parser.js生成ASTElement树 | |
--text-parser.js | 将text里的文本内容 和 {{}}插值内容一并存入TextParseResult类型的对象中并返回, 插值里的内容先进行过滤器解析 | |
--codeframe.js | code生成模板方法 | |
--create-compiler.js | 创建编译器 | |
--error-detector.js | 错误检测器(定义错误规则并检测) | |
--helpers.js | 编译助手(给el添加、绑定属性、指令) | |
--index.js | 创建编译器生成ast树且渲染 | |
--optimizer.js | 优化器 | /目标:遍历生成的模板AST树 并检测纯静态的子树,即 永远不需要改变的DOM。 一旦我们检测到这些子树: 1.将它们提升为常数,这样我们就不再需要在每次重新渲染时为其创建新节点; 2、在修补过程中完全跳过它们。 |
--to-function.js | 编译器将模板转换成函数 | |
--core | 核心实现 | |
--components | 核心组件 | |
--index.js | components入口文件,引入KeepAlive并导出 | |
--keep-alive.js | KeepAlive(缓存组件)的实现 | |
--global-api | 核心接口 | |
--assets.js | Vue组件资源 | 创建资源注册方法 |
--extend.js | Vue.extend()扩展接口 | 每个实例构造函数(包括Vue)都有一个唯一的cid。这使我们能够创建包装的“子对象”构造函数”,并缓存它们。 对于PROP和计算属性,我们在上定义代理getter扩展时的Vue实例,在扩展原型上。为创建的每个实例调defineProperty。 允许进一步扩展/混合/插件使用 创建资产寄存器,以便扩展类 也可以拥有他们的私人资源。 启用递归自查找,在扩展时保留引用。在实例化时,可以检查Super的选项是否已更新。并缓存构造函数 |
--index.js | 全局api入口文件 | |
--mixin.js | Vue.mixin()合并接口 | 通过调用../util/index中的mergeOptions来实现 |
--use.js | Vue.use()插件接口 | 通过调用../util/index中的 toArray来实现 |
--instance | 核心实例 | |
--render-helpers | 渲染助手 | |
--bind-dynamic-keys.js | 帮助处理v-bind和v-on中动态参数的动态键 | 将vue模板(如: <div id="app" :[key]="value"> )编译(如:_c('div', { attrs: bindDynamicKeys({ "id": "app" }, [key, value]) }) ) |
--bind-object-listeners.js | 对象侦听器 | 处理v-on=’{}'到vnode对象 data上 |
--bind-object-props.js | 用于将v-bind=“object”合并到VNode数据中的运行时助手 | |
--check-keycodes.js | 用于从配置中检查键代码的运行时帮助程序。作为Vue.prototype公开,将eventKeyName作为最后一个参数单独传递给向后兼容。 | |
--index.js | render-helpers(入口文件) | |
--render-list.js | 用于呈现v-for列表的运行时助手 | |
--render-slot.js | 插槽渲染助手 | |
--render-static.js | 渲染静态树运行助手 | |
--resolve-filter.js | filters过滤器渲染助手 | |
--resolve-scoped-slots.js | 同resolve-slots.js作用一样,不同的是编译父组件模板时,会生成一个返回结果为VNode的函数。当子组件匹配到父组件传递作用域插槽函数时,调用该函数生成对应VNode | |
--resolve-slots.js | 将children VNodes解析为插槽对象运行助手 | |
--events.js | 核心事件($on、$off、$emit、$once)接口 | 通过'../vdom/helpers/index'的 updateListeners监听来实现 |
--index.js | 核心实例(入口文件)接口:(导入)代理,状态,渲染,事件,生命周期,基础工具 | |
--init.js | 封装vue初始化(实例化)init接口 | |
--inject.js | provide和inject接口的实现 | |
--lifecycle.js | 生命周期接口的实现 | |
--proxy.js | 核心代理接口(判断平台是否支持Proxy功能;注册Proxy的处理函数;初始化代理initProxy() 注册vm._renderProxy属性) | |
--render.js | 核心渲染接口 | |
--state.js | 核心状态接口(初始化顺序:props属性,methods属性,data属性,computed属性watch属性) | |
--observer | 监视器接口 | |
--array.js | 数组监控 | 数组方法:'push','pop','shift', 'unshift','splice',sort','reverse' |
--dep.js | 消息订阅器 | |
--index.js | 数据监控器observer | |
--scheduler.js | 事件(队列)侦听器 | |
--traverse.js | 对象侦听 | 递归遍历对象以调用所有已转换的getter,使对象内的每个嵌套属性,作为“深度”依赖项收集。 |
--watcher.js | 消息订阅 | 观察者解析表达式,收集依赖项,并在表达式值更改时激发回调。这用于$watch()api和指令。 |
--util | 核心工具 | |
--debug.js | debug工具 | |
--env.js | 环境工具 | |
--error.js | 错误工具(在处理错误程序时,停用deps跟踪避免无限渲染) | |
--index.js | 入口文件(导入工具接口) | |
--lang.js | 语言扩展工具 | |
--next-tick.js | nextTick工具 | 微任务异步延迟包装器,nextTick行为利用了可以访问的微任务队列;添加空计时器“强制”刷新微任务队列;策略 Promise、MutationObserver、setImmediate、setTimeout |
--options.js | 选项覆盖(父值和子值的合并策略及转换为最终结果) | |
--perf.js | 忽略工具 | |
--props.js | props属性工具 | |
--vdom | ||
--helpers | 助手 | |
--extract-props.js | prop提取助手 | |
--get-first-component-child.js | 获取第一个组件 | |
--index.js | 入口文件 | |
--is-async-placeholder.js | 异步占位符判断 | |
--merge-hook.js | 合并钩子 | |
--normalize-children.js | 规范化子项 | |
--normalize-scoped-slots.js | 规范化作用域插槽 | |
--resolve-async-component.js | 解析异步组件 | |
--update-listeners.js | 更新侦听器 | |
--modules | 模块 | |
--directives.js | 指令 | |
--index.js | 入口文件 | |
--ref.js | ref | |
--create-component.js | 提供createComponent方法创建组件返回vnode | |
--create-element.js | 提供createElement方法 | |
--create-functional-component.js | 提供createFunctionalComponent方法 | |
--patch.js | 基于Snabbdom的虚拟DOM修补算法 | |
--vnode.js | 入口文件(提供VNode类) | |
--config.js | 核心配置文件 | |
--index.js | 核心入口文件,引入和导出vue api接口 | |
--platforms | 应用平台 | |
--web | web平台 | |
--compiler | 编译器 | |
--directives | 指令 | |
--html.js | 添加html元素及内容 | |
--index.js | 入口文件 | |
--model.js | model指令 | |
--text.js | text指令 | |
--modules | 模块 | |
--class.js | dom节点样式class解析 | |
--index.js | 入口文件 | |
--model.js | 表单v-model解析 | |
--style.js | dom节点样式style解析 | |
--index.js | 入口文件 | |
--options.js | 编译器选项 | |
--util.js | 标签及属性判断归类方法 | |
--runtime | 运行 | |
--components | 组件 | |
--transition-group.js | 提供列表项支持 | |
--transition.js | 提供单个元素/组件支持 | |
--directives | 指令 | |
--index.js | 入口文件 | |
--model.js | model指令 | |
--show.js | show指令 | |
--modules | 模块 | |
--attrs.js | attrs属性 | |
--class.js | class属性 | |
--dom-props.js | dom的props属性 | |
--events.js | 事件 | |
--index.js | 入口文件 | |
--style.js | 样式优先级判断及更新 | |
--transition.js | dom节点的过渡 | |
--class-util.js | 封装添加/移除与SVG兼容的样式class | |
--index.js | 入口文件 | |
--node-ops.js | node节点的方法集成封装 | |
--patch.js | 内置模块(最后应用指令模块) | |
--transition-util.js | 过渡集成封装 | |
--server | 服务端 | |
--directives | 指令 | |
--index.js | 入口文件 | |
--model.js | model指令 | |
--show.js | show指令 | |
--modules | 模块 | |
--attrs.js | attrs属性 | |
--class.js | class属性 | |
--dom-props.js | dom的props属性 | |
--index.js | 入口文件 | |
--style.js | 样式优先级判断及更新 | |
--compiler.js | 重命名并暴露编译器方法 | |
--util.js | 标签属性及样式属性判断归类方法 | |
--util | 应用 | |
--attrs.js | web保留的attrs属性编译处理 | |
--class.js | 样式class编译处理 | |
--compat.js | 检查当前浏览器是否在属性值内编码字符 | |
--element.js | html dom元素编译处理 | |
--index.js | 入口文件 | |
--style.js | 样式style元素编译处理 | |
--entry-compiler.js | 条目编译器 | |
--entry-runtime-with-compiler.js | 带编译器的入口运行 | |
--entry-runtime.js | 编译器运行入口文件 | |
--entry-server-basic-renderer.js | 服务端基本渲染器 | |
--entry-server-renderer.js | 服务端渲染器 | |
--weex | weex平台 | |
--compiler | ||
--directives | ||
--index.js | 入口文件 | |
--model.js | model指令 | |
--modules | 模块 | |
--recycle-list | 回收列表 | |
--component-root.js | 根组件 | |
--component.js | 组件 | |
--index.js | 入口文件 | |
--recycle-list.js | 回收列表 | |
--text.js | 文本解析器 | |
--v-bind.js | v-bind解析器 | |
--v-for.js | v-for解析器 | |
--v-if.js | v-if解析器 | |
--v-on.js | v-on解析器 | |
--v-once.js | v-once解析器 | |
--append.js | 追加 | |
--class.js | 样式class解析器 | |
--index.js | 入口文件 | |
--props.js | props解析器 | |
--style.js | 样式style解析器 | |
--index.js | 入口文件 | |
--runtime | ||
--components | ||
--index.js | 入口文件 | |
--richtext.js | 富文本 | |
--transition-group.js | 过渡组 | |
--transition.js | 过渡 | |
--directives | 指令 | |
--index.js | 入口文件 | |
--modules | 模块 | |
--attrs.js | attrs属性编译处理 | |
--class.js | 样式class编译处理 | |
--event.js | 事件 | |
--index.js | 入口文件 | |
--style.js | 样式style元素编译处理 | |
--transition.js | 过渡编译处理 | |
--recycle-list | 回收列表 | |
--render-component-template.js | 渲染组件模板 | |
--virtual-component.js | 虚拟组建 | |
--index.js | 入口文件 | |
--node-ops.js | node节点的方法集成封装 | |
--patch.js | 内置模块(最后应用指令模块) | |
--node-ops.js | 节点文本 | |
--util | 应用 | |
--element.js | html dom元素编译处理 | |
--index.js | 入口文件 | |
--parser.js | 解析器 | |
--entry-compiler.js | 条目编译器 | |
--entry-framework.js | 入口框架 | |
--entry-runtime-factory.js | 函数构建包装 | 用于为每个Weex实例生成Vue的新副本 |
--server | 支持服务器端渲染,所有服务器端渲染相关的逻辑 | |
--bundle-renderer | ||
--create-bundle-renderer.js | 创建结束渲染器 | |
--create-bundle-runner.js | 创建结束运行程序 | |
--source-map-support.js | 源地图支持 | |
--optimizing-compiler | ||
--codegen.js | 通过扩展默认codegen来进行SSR优化节点 | |
--index.js | 入口文件 | |
--modules.js | 模块 | |
--optimizer.js | 在SSR中,vdom树只生成一次,从不修补,因此我们可以将大多数元素/树优化为纯字符串渲染函数。 *SSR优化器遍历AST树以检测可优化的元素和树。SSR优化的标准比静态树要宽松一些检测(设计用于客户端重新渲染)。在SSR中,我们只为组件/插槽/自定义指令。 | |
--runtime-helpers.js | 运行帮助程序 | |
--template-renderer | ||
--create-async-file-mapper.js | 创建异步映射器 | 创建映射器,映射服务器端渲染期间使用的组件在客户端构建中异步区块文件,以便我们可以内联它们直接在呈现的HTML中,以避免瀑布式请求。 |
--index.js | 入口文件 | |
--parse-template.js | 分析模板 | |
--template-stream.js | 模板流 | |
--webpack-plugin | webpack插件 | |
--client.js | 客户端 | |
--server.js | 服务端 | |
--util.js | 应用 | |
--create-basic-renderer.js | 创建基本渲染器 | |
--create-renderer.js | 创建渲染器 | |
--render-context.js | 渲染上下文 | |
--render-stream.js | 渲染流 | |
--render.js | 服务器渲染函数 | |
--util.js | 判断js、css文件并创建promise回调 | |
--write.js | 创建写入函数(会判断堆栈) | |
--sfc | single-file components(单文件组件) | 简单说:转换单文件组件(*.vue) 解析成一个javascript对象 |
--parser.js | 主要内容:将单个文件组件(*.vue)文件解析为SFC描述对象。 | |
--shared | 共享 | |
--constants.js | 常量:数据渲染,组件,指令,过滤器,生命周期,错误机制和SSR服务端渲染(组件在服务器上呈现之前解析,异步) | |
--util.js | 封装了些简单实用明确的js方法(检查是是否为js原始值,undefined判断,对象判断,两个值是否大致相等,值在数组中的第一个索引,等) | |
types | 一些ts文件 | |
LICENSE | 作者尤雨溪及版权声明 | |
package.json | vue2配置文件 | |
README.md | vue的一些赞助商 |
2.vue实例化过程
vue构造函数,源码位置:src\core\instance\index.js
import { initMixin } from './init'
import { stateMixin } from './state'
import { renderMixin } from './render'
import { eventsMixin } from './events'
import { lifecycleMixin } from './lifecycle'
import { warn } from '../util/index'/*** * @param {*} options 是用户传递过来的配置项,如data、methods等常用的方法*/
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)
}
// 定义 _init
initMixin(Vue)
// 定义 $set $get $delete $watch 等
stateMixin(Vue)
// 定义事件$on $once $off $emit
eventsMixin(Vue)
// 定义 _update $forceUpdate $destroy
lifecycleMixin(Vue)
// 定义 _render 返回虚拟dom
renderMixin(Vue)export default Vue
initMixin方法,源码位置:src\core\instance\init.js
/* @flow */import config from '../config'
import { initProxy } from './proxy'
import { initState } from './state'
import { initRender } from './render'
import { initEvents } from './events'
import { mark, measure } from '../util/perf'
import { initLifecycle, callHook } from './lifecycle'
import { initProvide, initInjections } from './inject'
import { extend, mergeOptions, formatComponentName } from '../util/index'let uid = 0export function initMixin (Vue: Class<Component>) {Vue.prototype._init = function (options?: Object) {const vm: Component = this// a uidvm._uid = uid++let startTag, endTag/* istanbul ignore if */if (process.env.NODE_ENV !== 'production' && config.performance && mark) {startTag = `vue-perf-start:${vm._uid}`endTag = `vue-perf-end:${vm._uid}`mark(startTag)}// a flag to avoid this being observedvm._isVue = true// merge options// 合并属性,判断初始化的是否是组件,这里合并主要是 mixins 或 extends 的方法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 {// 合并vue属性vm.$options = mergeOptions(resolveConstructorOptions(vm.constructor),options || {},vm)}/* istanbul ignore else */if (process.env.NODE_ENV !== 'production') {// 初始化proxy拦截器initProxy(vm)} else {vm._renderProxy = vm}// expose real selfvm._self = vm// 初始化组件生命周期标志位initLifecycle(vm)// 初始化组件事件侦听initEvents(vm)// 初始化渲染方法initRender(vm)callHook(vm, 'beforeCreate')// 初始化依赖注入内容,在初始化data、props之前initInjections(vm) // resolve injections before data/props// 初始化props/data/method/watch/methodsinitState(vm)initProvide(vm) // resolve provide after data/propscallHook(vm, 'created')/* istanbul ignore if */if (process.env.NODE_ENV !== 'production' && config.performance && mark) {vm._name = formatComponentName(vm, false)mark(endTag)measure(`vue ${vm._name} init`, startTag, endTag)}// 挂载元素if (vm.$options.el) {vm.$mount(vm.$options.el)}}
}
结论:可以看出,
1)在调用beforeCreate
之前,数据初始化并未完成,像data
、props
这些属性无法访问到
2)到了created
的时候,数据已经初始化完成,能够访问data
、props
这些属性,但这时候并未完成dom
的挂载,因此无法访问到dom
元素
3)挂载方法是调用vm.$mount
方法
initState
方法,完成props/methods/data/computed/watch的初始化,源码位置:src\core\instance\state.js
export function initState(vm: Component) {// 初始化组件的watcher列表vm._watchers = []const opts = vm.$options// 初始化props属性if (opts.props) initProps(vm, opts.props)// 初始化methods方法if (opts.methods) initMethods(vm, opts.methods)// 初始化data函数/对象if (opts.data) {initData(vm)} else {observe(vm._data = {}, true /* asRootData */)}// 初始化computed方法if (optsputed) initComputed(vm, optsputed)// 初始化watch 方法if (opts.watch && opts.watch !== nativeWatch) {initWatch(vm, opts.watch)}
}
结论: 可以看出,初始化顺序为:
props => methods => data => computed => watch
可以根据这个初始化顺序,我们可以定义一些策略,如:data直接调用methods方法,computed调用methods方法,而不会产生undefine
initData方法,源码位置:src\core\instance\state.js
function initData (vm: Component) {let data = vm.$options.data// 获取组件上的datadata = vm._data = typeof data === 'function'? getData(data, vm): data || {}if (!isPlainObject(data)) {data = {}process.env.NODE_ENV !== 'production' && warn('data functions should return an object:\n' +'.html#data-Must-Be-a-Function',vm)}// proxy data on instanceconst keys = Object.keys(data)const props = vm.$options.propsconst methods = vm.$options.methodslet i = keys.lengthwhile (i--) {const key = keys[i]if (process.env.NODE_ENV !== 'production') {// 属性名不能与方法名重复if (methods && hasOwn(methods, key)) {warn(`Method "${key}" has already been defined as a data property.`,vm)}}// 属性名不能与props中的名称重复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)) { // 验证key值的合法性// 将_data中的数据挂载到组件vm上,这样就可以通过this.xxx访问到组件上的数据proxy(vm, `_data`, key)}}
结论:可以看出,
1)data定义的时候可选择函数形式或者对象形式(组件只能为函数形式)
2)data的属性名会在methods方法和props属性中判断是否重名
3)data会被响应式监听数据变化
vm.$mount挂载方法,源码位置:
web平台:src/platforms/web/runtime/index.js
weex平台:src/platform/weex/runtime/index.js
Vue.prototype.$mount = function (el?: string | Element,hydrating?: boolean
): Component {el = el && inBrowser ? query(el) : undefined// 渲染组件return mountComponent(this, el, hydrating)
}
mountComponent渲染组件,源码位置src/core/instance/lifecycle.js
export function mountComponent (vm: Component,el: ?Element,hydrating?: boolean
): Component {vm.$el = el// 如果没有获取解析的render函数,则会抛出警告// render是解析模板文件生成的if (!vm.$options.render) {vm.$options.render = createEmptyVNodeif (process.env.NODE_ENV !== 'production') {/* istanbul ignore if */if ((vm.$options.template && vm.$options.template.charAt(0) !== '#') ||vm.$options.el || el) {warn('You are using the runtime-only build of Vue where the template ' +'compiler is not available. Either pre-compile the templates into ' +'render functions, or use the compiler-included build.',vm)} else {warn('Failed to mount component: template or render function not defined.',vm)}}}// 执行beforeMount钩子callHook(vm, 'beforeMount')let updateComponent/* istanbul ignore if */if (process.env.NODE_ENV !== 'production' && config.performance && mark) {updateComponent = () => {const name = vm._nameconst id = vm._uidconst startTag = `vue-perf-start:${id}`const endTag = `vue-perf-end:${id}`mark(startTag)const vnode = vm._render()mark(endTag)measure(`vue ${name} render`, startTag, endTag)mark(startTag)vm._update(vnode, hydrating)mark(endTag)measure(`vue ${name} patch`, startTag, endTag)}} else {// 定义更新函数updateComponent = () => {vm._update(vm._render(), hydrating)}}// we set this to vm._watcher inside the watcher's constructor// since the watcher's initial patch may call $forceUpdate (e.g. inside child// component's mounted hook), which relies on vm._watcher being already defined// 监听当前组件状态,当有数据变化时,更新组件new Watcher(vm, updateComponent, noop, {before () {if (vm._isMounted && !vm._isDestroyed) {callHook(vm, 'beforeUpdate')}}}, true /* isRenderWatcher */)hydrating = false// manually mounted instance, call mounted on self// mounted is called for render-created child components in its inserted hook//手动装载实例,调用自行装载//在其插入的挂钩中为渲染创建的子组件调用mountedif (vm.$vnode == null) {vm._isMounted = truecallHook(vm, 'mounted')}return vm
}
结论:可以看出,
1) 会触发beforeCreate钩子
2) 定义updateComponent渲染页面视图的方法
3) 监听组件数据,一旦发生变化,触发beforeUpdate生命钩子
4) updateComponent方法主要执行在vue初始化时声明的render,update方法
render方法的主要作用是生成vnode, 源码位置:src/core/instance/render.js
// 定义vue 原型上的render方法Vue.prototype._render = function (): VNode {const vm: Component = this// render函数来自于组件的optionconst { render, _parentVnode } = vm.$optionsif (_parentVnode) {vm.$scopedSlots = normalizeScopedSlots(_parentVnode.data.scopedSlots,vm.$slots,vm.$scopedSlots)}// set parent vnode. this allows render functions to have access// to the data on the placeholder node.vm.$vnode = _parentVnode// render selflet vnodetry {// There's no need to maintain a stack because all render fns are called// separately from one another. Nested component's render fns are called// when parent component is patched.currentRenderingInstance = vm// 调用render方法,自己的独特的render方法, 传入createElement参数,生成vNodevnode = render.call(vm._renderProxy, vm.$createElement)} catch (e) {handleError(e, vm, `render`)// return error render result,// or previous vnode to prevent render error causing blank component/* istanbul ignore else */if (process.env.NODE_ENV !== 'production' && vm.$options.renderError) {try {vnode = vm.$options.renderError.call(vm._renderProxy, vm.$createElement, e)} catch (e) {handleError(e, vm, `renderError`)vnode = vm._vnode}} else {vnode = vm._vnode}} finally {currentRenderingInstance = null}// if the returned array contains only a single node, allow itif (Array.isArray(vnode) && vnode.length === 1) {vnode = vnode[0]}// return empty vnode in case the render function errored outif (!(vnode instanceof VNode)) {if (process.env.NODE_ENV !== 'production' && Array.isArray(vnode)) {warn('Multiple root nodes returned from render function. Render function ' +'should return a single root node.',vm)}vnode = createEmptyVNode()}// set parentvnode.parent = _parentVnodereturn vnode}
_update方法通过调用patch,将vnode转换为真实DOM,并且更新到页面中;源码位置:
src/core/instance/lifecycle.js
Vue.prototype._update = function (vnode: VNode, hydrating?: boolean) {const vm: Component = thisconst prevEl = vm.$elconst prevVnode = vm._vnode// 设置当前激活的作用域const restoreActiveInstance = setActiveInstance(vm)vm._vnode = vnode// Vue.prototype.__patch__ is injected in entry points// based on the rendering backend used.if (!prevVnode) {// initial render// 执行具体的挂载逻辑vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false /* removeOnly */)} else {// updatesvm.$el = vm.__patch__(prevVnode, vnode)}restoreActiveInstance()// update __vue__ referenceif (prevEl) {prevEl.__vue__ = null}if (vm.$el) {vm.$el.__vue__ = vm}// if parent is an HOC, update its $el as wellif (vm.$vnode && vm.$parent && vm.$vnode === vm.$parent._vnode) {vm.$parent.$el = vm.$el}// updated hook is called by the scheduler to ensure that children are// updated in a parent's updated hook.}
$mount在web平台具体实现(src/platform/web/entry-runtime-with-compiler.js)
const mount = Vue.prototype.$mount
Vue.prototype.$mount = function (el?: string | Element,hydrating?: boolean
): Component {// 获取或查询元素el = el && query(el)/* istanbul ignore if */// vue 不允许直接挂载到body或页面文档上if (el === document.body || el === document.documentElement) {process.env.NODE_ENV !== 'production' && warn(`Do not mount Vue to <html> or <body> - mount to normal elements instead.`)return this}const options = this.$options// resolve template/el and convert to render functionif (!options.render) {let template = options.template// 存在template模板,解析vue模板文件if (template) {if (typeof template === 'string') {if (template.charAt(0) === '#') {template = idToTemplate(template)/* istanbul ignore if */if (process.env.NODE_ENV !== 'production' && !template) {warn(`Template element not found or is empty: ${options.template}`,this)}}} else if (template.nodeType) {template = template.innerHTML} else {if (process.env.NODE_ENV !== 'production') {warn('invalid template option:' + template, this)}return this}} else if (el) {// 通过选择器获取元素内容template = getOuterHTML(el)}if (template) {/* istanbul ignore if */if (process.env.NODE_ENV !== 'production' && config.performance && mark) {mark('compile')}// 将temmplate解析ast tree, 将ast tree转换成render语法字符串, 生成render方法const { render, staticRenderFns } = compileToFunctions(template, {outputSourceRange: process.env.NODE_ENV !== 'production',shouldDecodeNewlines,shouldDecodeNewlinesForHref,delimiters: options.delimiters,comments: optionsments}, this)options.render = renderoptions.staticRenderFns = staticRenderFns/* istanbul ignore if */if (process.env.NODE_ENV !== 'production' && config.performance && mark) {mark('compile end')measure(`vue ${this._name} compile`, 'compile', 'compile end')}}}return mount.call(this, el, hydrating)
}
结论:可以看出,
1)不要将根元素放到body或者html上
2)可以在对象中定义template/render或者直接使用template、el表示元素选择器
3)最终都会解析成render函数,调用compileToFunctions,会将template解析成render函数
4)template解析步骤:
将html文档片段解析成ast描述符
将ast描述符解析成字符串
生成render函数
生成render函数,挂载到vm上后,会再次调用mount方法
initLifecycle方法初始化一些生命周期相关属性,源码位置:
src/core/instance/lifecycle.js
export function initLifecycle(vm: Component) {// 初始化参数$options const options = vm.$options// locate first non-abstract parent/*** 定位第一个“非抽象”的父组件* 抽象组件:<keep-alive>包裹动态组件时,会缓存不活动的组件实例,而不销毁。和<transition>相似* <keep-alive>是一个抽象组件:它自身不会渲染一个dom元素,也不会出现在父组件链中*/let parent = options.parentif (parent && !options.abstract) {// 持续找组件的父组件直到找到非抽象组件while (parent.$options.abstract && parent.$parent) {parent = parent.$parent}parent.$children.push(vm)}// 指定创建的实例的父实例,两者之间建立父子关系vm.$parent = parent// 当前组件树的根Vue实例;如果当前实例没有父实例,此实例将会是自己vm.$root = parent ? parent.$root : vm// 当前实例的直接子组件。但并不保证顺序,也不响应式vm.$children = []// 注册过ref的所有子组件vm.$refs = {}// 当前实例组件的watcher实例对象vm._watcher = null// keep-alive组件状态vm._inactive = null// keep-alive组件状态的属性vm._directInactive = false// 当前实例是否完成挂载vm._isMounted = false// 当前实例是否已被销毁vm._isDestroyed = false// 当前实例是否正在被销毁vm._isBeingDestroyed = false
}
结论:可以看出,
1)确定父子组件关系
2)初始化ref,watcher,ref属性,判断组件是否挂载、是否销毁和销毁中
stateMixin方法,源码位置:src/core/instance/state.js
export function stateMixin (Vue: Class<Component>) {// flow somehow has problems with directly declared definition object// when using Object.defineProperty, so we have to procedurally build up// the object here.const dataDef = {}dataDef.get = function () { return this._data }const propsDef = {}propsDef.get = function () { return this._props }if (process.env.NODE_ENV !== 'production') {dataDef.set = function () {warn('Avoid replacing instance root $data. ' +'Use nested data properties instead.',this)}propsDef.set = function () {warn(`$props is readonly.`, this)}}Object.defineProperty(Vue.prototype, '$data', dataDef)Object.defineProperty(Vue.prototype, '$props', propsDef)Vue.prototype.$set = setVue.prototype.$delete = delVue.prototype.$watch = function (expOrFn: string | Function,cb: any,options?: Object): Function {const vm: Component = thisif (isPlainObject(cb)) {return createWatcher(vm, expOrFn, cb, options)}options = options || {}options.user = trueconst watcher = new Watcher(vm, expOrFn, cb, options)if (options.immediate) {const info = `callback for immediate watcher "${watcher.expression}"`pushTarget()invokeWithErrorHandling(cb, vm, [watcher.value], vm, info)popTarget()}return function unwatchFn () {watcher.teardown()}}
}
结论:可以看出,通过定义dataDef和propsDef,来设置响应式data和data和props的get和set方法,并给vue实例添加set、delete和watch方法
eventsMixin方法:源码位置:src/core/instance/events.js
export function eventsMixin (Vue: Class<Component>) {const hookRE = /^hook:/Vue.prototype.$on = function (event: string | Array<string>, fn: Function): Component {const vm: Component = thisif (Array.isArray(event)) {for (let i = 0, l = event.length; i < l; i++) {vm.$on(event[i], fn)}} else {(vm._events[event] || (vm._events[event] = [])).push(fn)// optimize hook:event cost by using a boolean flag marked at registration// instead of a hash lookupif (hookRE.test(event)) {vm._hasHookEvent = true}}return vm}Vue.prototype.$once = function (event: string, fn: Function): Component {const vm: Component = thisfunction on () {vm.$off(event, on)fn.apply(vm, arguments)}on.fn = fnvm.$on(event, on)return vm}Vue.prototype.$off = function (event?: string | Array<string>, fn?: Function): Component {const vm: Component = this// allif (!arguments.length) {vm._events = Object.create(null)return vm}// array of eventsif (Array.isArray(event)) {for (let i = 0, l = event.length; i < l; i++) {vm.$off(event[i], fn)}return vm}// specific eventconst cbs = vm._events[event]if (!cbs) {return vm}if (!fn) {vm._events[event] = nullreturn vm}// specific handlerlet cblet i = cbs.lengthwhile (i--) {cb = cbs[i]if (cb === fn || cb.fn === fn) {cbs.splice(i, 1)break}}return vm}Vue.prototype.$emit = function (event: string): Component {const vm: Component = thisif (process.env.NODE_ENV !== 'production') {const lowerCaseEvent = event.toLowerCase()if (lowerCaseEvent !== event && vm._events[lowerCaseEvent]) {tip(`Event "${lowerCaseEvent}" is emitted in component ` +`${formatComponentName(vm)} but the handler is registered for "${event}". ` +`Note that HTML attributes are case-insensitive and you cannot use ` +`v-on to listen to camelCase events when using in-DOM templates. ` +`You should probably use "${hyphenate(event)}" instead of "${event}".`)}}let cbs = vm._events[event]if (cbs) {cbs = cbs.length > 1 ? toArray(cbs) : cbsconst args = toArray(arguments, 1)const info = `event handler for "${event}"`for (let i = 0, l = cbs.length; i < l; i++) {invokeWithErrorHandling(cbs[i], vm, args, vm, info)}}return vm}
}
结论:可以看出,定义实现Vue实例的$on、$once、$off和$emit方法
lifecycleMixin方法,源码位置:src/core/instance/lifecycle.js
export function lifecycleMixin (Vue: Class<Component>) {Vue.prototype._update = function (vnode: VNode, hydrating?: boolean) {const vm: Component = thisconst prevEl = vm.$elconst prevVnode = vm._vnodeconst restoreActiveInstance = setActiveInstance(vm)vm._vnode = vnode// Vue.prototype.__patch__ is injected in entry points// based on the rendering backend used.if (!prevVnode) {// initial render// 执行具体的挂载逻辑vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false /* removeOnly */)} else {// updatesvm.$el = vm.__patch__(prevVnode, vnode)}restoreActiveInstance()// update __vue__ referenceif (prevEl) {prevEl.__vue__ = null}if (vm.$el) {vm.$el.__vue__ = vm}// if parent is an HOC, update its $el as wellif (vm.$vnode && vm.$parent && vm.$vnode === vm.$parent._vnode) {vm.$parent.$el = vm.$el}// updated hook is called by the scheduler to ensure that children are// updated in a parent's updated hook.}Vue.prototype.$forceUpdate = function () {const vm: Component = thisif (vm._watcher) {vm._watcher.update()}}Vue.prototype.$destroy = function () {const vm: Component = thisif (vm._isBeingDestroyed) {return}callHook(vm, 'beforeDestroy')vm._isBeingDestroyed = true// remove self from parentconst parent = vm.$parentif (parent && !parent._isBeingDestroyed && !vm.$options.abstract) {remove(parent.$children, vm)}// teardown watchersif (vm._watcher) {vm._watcher.teardown()}let i = vm._watchers.lengthwhile (i--) {vm._watchers[i].teardown()}// remove reference from data ob// frozen object may not have observer.if (vm._data.__ob__) {vm._data.__ob__.vmCount--}// call the last hook...vm._isDestroyed = true// invoke destroy hooks on current rendered treevm.__patch__(vm._vnode, null)// fire destroyed hookcallHook(vm, 'destroyed')// turn off all instance listeners.vm.$off()// remove __vue__ referenceif (vm.$el) {vm.$el.__vue__ = null}// release circular reference (#6759)if (vm.$vnode) {vm.$vnode.parent = null}}
}
结论:可以看出,定义实现vue实例的_update,$forceUpdate,$destroy方法
renderMixin方法,源码位置:src/core/instance/render.js
export function renderMixin (Vue: Class<Component>) {// install runtime convenience helpers// 安装运行助手installRenderHelpers(Vue.prototype)// 定义$nextTickVue.prototype.$nextTick = function (fn: Function) {return nextTick(fn, this)}// 定义vue 原型上的render方法Vue.prototype._render = function (): VNode {const vm: Component = this// render函数来自于组件的optionconst { render, _parentVnode } = vm.$optionsif (_parentVnode) {vm.$scopedSlots = normalizeScopedSlots(_parentVnode.data.scopedSlots,vm.$slots,vm.$scopedSlots)}// set parent vnode. this allows render functions to have access// to the data on the placeholder node.// 设置父vnode。这允许渲染函数具有访问权限到占位符节点上的数据。vm.$vnode = _parentVnode// render selflet vnodetry {// There's no need to maintain a stack because all render fns are called// separately from one another. Nested component's render fns are called// when parent component is patched.currentRenderingInstance = vm// 调用render方法,自己的独特的render方法, 传入createElement参数,生成vNodevnode = render.call(vm._renderProxy, vm.$createElement)} catch (e) {handleError(e, vm, `render`)// return error render result,// or previous vnode to prevent render error causing blank component/* istanbul ignore else */if (process.env.NODE_ENV !== 'production' && vm.$options.renderError) {try {vnode = vm.$options.renderError.call(vm._renderProxy, vm.$createElement, e)} catch (e) {handleError(e, vm, `renderError`)vnode = vm._vnode}} else {vnode = vm._vnode}} finally {currentRenderingInstance = null}// if the returned array contains only a single node, allow itif (Array.isArray(vnode) && vnode.length === 1) {vnode = vnode[0]}// return empty vnode in case the render function errored outif (!(vnode instanceof VNode)) {if (process.env.NODE_ENV !== 'production' && Array.isArray(vnode)) {warn('Multiple root nodes returned from render function. Render function ' +'should return a single root node.',vm)}vnode = createEmptyVNode()}// set parentvnode.parent = _parentVnodereturn vnode}
}
结论:可以看出,定义实现vue实例的 _render 并返回返回虚拟dom
错误处理handleError方法,源码地址:src/core/util/error.js
export function handleError (err: Error, vm: any, info: string) {// Deactivate deps tracking while processing error handler to avoid possible infinite rendering.// See: // 在处理错误处理程序时停用deps跟踪,以避免可能的无限渲染。pushTarget()try {if (vm) {let cur = vmwhile ((cur = cur.$parent)) {// vue实例的errorCaptured生命周期const hooks = cur.$options.errorCapturedif (hooks) {for (let i = 0; i < hooks.length; i++) {try {const capture = hooks[i].call(cur, err, vm, info) === falseif (capture) return} catch (e) {globalHandleError(e, cur, 'errorCaptured hook')}}}}}globalHandleError(err, vm, info)} finally {popTarget()}
}
function globalHandleError (err, vm, info) {if (config.errorHandler) {try {return config.errorHandler.call(null, err, vm, info)} catch (e) {// if the user intentionally throws the original error in the handler,// do not log it twiceif (e !== err) {logError(e, null, 'config.errorHandler')}}}logError(err, vm, info)
}function logError (err, vm, info) {if (process.env.NODE_ENV !== 'production') {warn(`Error in ${info}: "${err.toString()}"`, vm)}/* istanbul ignore else */if ((inBrowser || inWeex) && typeof console !== 'undefined') {console.error(err)} else {throw err}
}
结论:可以看出,错误处理机制会触发vue实例的errorCaptured生命周期(捕获一个来自子孙组件的错误时被调用)
3.思考总结
从vue实例化的过程中,可以看出其生命周期的调用过程,而不是简单意义上理解的8大生命周期;具体如下:
生命周期 | 描述 |
beforeCreate | 组件实例被创建之初 |
created | 组件实例已经完全创建 |
beforeMount | 组件挂载之前 |
mounted | 组件挂载到实例上去之后 |
beforeUpdate | 组件数据发生变化,更新之前 |
updated | 组件数据更新之后 |
beforeDestroy | 组件实例销毁之前 |
destroyed | 组件实例销毁之后 |
activated | keep-alive 缓存的组件激活时 |
deactivated | keep-alive 缓存的组件停用时调用 |
errorCaptured | 捕获一个来自子孙组件的错误时被调用 |
更多推荐
从vue2.6.11源码解读vue实例化过程
发布评论