Vue源码阅读
生命周期
new Vue
new Vue 发生了什么
new
关键字代表实例化一个对象, 而Vue
实际上是一个类, 源码位置是/src/core/instance/index.js。
在 new Vue()
之后。 Vue 会调用 _init
函数进行初始化,也就是这里的 init 过程,它会初始化生命周期、事件、 props、 methods、 data、 computed 与 watch 等
源码 -> _init
export function initMixin (Vue: Class<Component>) { Vue.prototype._init = function (options?: Object) { const vm: Component = this // a uid vm._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 observed vm._isVue = true // 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 ) } /* istanbul ignore else */ if (process.env.NODE_ENV !== 'production') { initProxy(vm) } else { vm._renderProxy = 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') /* 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) } } }
在初始化时,会调用以上_init
中代码,生命周期就是通过 callHook
调用的
它的定义在 src/core/instance/lifecycle 中:
export function callHook (vm: Component, hook: string) { // #7573 disable dep collection when invoking lifecycle hooks pushTarget() const handlers = vm.$options[hook] const info = `${hook} hook` if (handlers) { for (let i = 0, j = handlers.length; i < j; i++) { invokeWithErrorHandling(handlers[i], vm, null, vm, info) } } if (vm._hasHookEvent) { vm.$emit('hook:' + hook) } popTarget() }
callHook
函数的逻辑很简单,根据传入的字符串 hook
,去拿到 vm.$options[hook]
对应的回调函数数组,然后遍历执行,执行的时候把 vm
作为函数执行的上下文。
callHook
函数的功能就是调用某个生命周期钩子注册的所有回调函数。
beforeCreate & created
初始化最核心的逻辑是这段:
beforeCreate
和 created
函数都是在实例化 Vue
的阶段,在 _init
方法中执行的,它的定义在 src/core/instance/init.js 中:
beforeCreate
调用的时候,是获取不到 props
或者 data
中的数据的,因为这些数据的初始化都在 initState
中。
可以看到 beforeCreate 和 created 的钩子调用是在 initState 的前后,initState 的作用是初始化 props、data、methods、watch、computed 等属性,之后我们会详细分析。那么显然 beforeCreate 的钩子函数中就不能获取到 props、data 中定义的值,也不能调用 methods 中定义的函数。在这俩个钩子函数执行的时候,并没有渲染 DOM,所以我们也不能够访问 DOM,一般来说,如果组件在加载的时候需要和后端有交互,放在这俩个钩子函数执行都可以,如果是需要访问 props、data 等数据的话,就需要使用 created 钩子函数。
--> 此段来自Vue.js 技术揭秘
接下来会执行这里的挂载函数mountComponent
beforeMount
就是在挂载前执行的,然后开始创建 VDOM 并替换成真实 DOM,最后执行 mounted 钩子。
这里会有个判断逻辑,如果是外部 new Vue({})
的话,不会存在 $vnode
,所以直接执行 mounted 钩子了。如果有子组件的话,会递归挂载子组件,只有当所有子组件全部挂载完毕,才会执行根组件的挂载钩子。
对照生命周期图,我们看看在beforeMount
钩子和mounted
钩子之间的 Create vm.$el and replace "el" width it
具体都有做了什么:
挂载
初始化之后调用 $mount 会挂载组件,如果是运行时编译,即不存在 render function 但是存在
template 的情况,需要进行「编译」步骤。
Vue 实例挂载如何实现
Vue 中我们是通过$mount
实例方法去挂载vm
的,$mount
方法在多个文件中都有定义,如 src/platform/web/entry-runtime-with-compiler.js、src/platform/web/runtime/index.js、src/platform/weex/runtime/index.js。因为$mount
这个方法的实现是和平台、构建方式都相关的。
NextTick
双向绑定
SFC文件解析为SFCDescriptor
Virtual DOM
Virtual DOM
就是用一个原生的 JS 对象去描述一个 DOM 节点,所以它比创建一个 DOM 的代价要小很多。在 Vue.js 中,Virtual DOM 是用 VNode
这么一个 Class 去描述,它是定义在 src/core/vdom/vnode.js 中的。
Virtual DOM
其实就是一棵以 JavaScript 对象(VNode
节点)作为基础的树,用对象属性来描述节点,实际上它只是一层对真实 DOM 的抽象。最终可以通过一系列操作使这棵树映射到真实环境上。由于 Virtual DOM
是以 JavaScript 对象为基础而不依赖真实平台环境,所以使它具有了跨平台的能力,比如说浏览器平台
、Weex
、Node
等。
VNode
实现 Virtual DOM 下的一个 VNode 节点
// VNode 就是一个 JavaScript 对象,用 JavaScript 对象的属性来描述当前节点的一些状态, // 用 VNode 节点的形式来模拟一棵 Virtual DOM 树。 class VNode { constructor(tag, data, children, text, elm, context, componentOptions, asyncFactory) { // 当前节点的标签名 this.tag = tag // String // 当前节点的一些数据信息, 比如props,attrs等数据 this.data = data // VNodeData // 当前节点的子节点,是一个数组 this.children = children // Array<VNode> // 当前节点的文本 this.text = text // String // 当前虚拟节点对应的真实dom节点 this.elm = elm // Node this.ns = undefined // String | Void // rendered in this component's scope this.context = context // Component | Void // real context vm for functional nodes this.fnContext = undefined // Component | void // for SSR caching this.fnOptions = undefined // functional scope id support this.fnScopeId = undefined this.key = data && data.key // String | Number | Void this.componentOptions = componentOptions // VNodeComponentOptions | Void this.componentInstance = undefined // component instance // Component placeholder node this.parent = undefined // VNode | Void // strictly internal // contains raw HTML? (server only) this.raw = false // boolean // hoisted static node this.isStatic = false // boolean // necessary for enter transition check this.isRootInsert = true // boolean // empty comment placeholder this.isComment = false // boolean // is a cloned node ? this.isCloned = false // boolean // is a v-once node ? this.isOnce = false // boolean // aysync component factory function this.asyncFactory = asyncFactory // Function this.asyncMeta = undefined // Oject | void } get child() { return this.componentInstance } } // 对VNode进一步封装,实现一些产生常用VNode方法 // 创建空节点 const createEmptyVNode = (text) => { const node = new VNode() node.text = text node.isComment = true return node } // 创建文本节点 function createTextVNode (val) { return new VNode(undefined, undefined, undefined, String(val)) } // 克隆一个VNode节点 // optimized shallow clone // used for static nodes and slot nodes because they may be reused across // multiple renders, cloning them avoids errors when DOM manipulations rely // on their elm reference function cloneVNode(vnode) { const cloned = new VNode( vnode.tag, vnode.data, // clone children array to avoid mutating original in case of cloning a child vnode.children && vnode.children.slice() vnode.text, vnode.elm, vnode.context, vnode.componentOptions, vnode.asyncFactory ) cloned.ns = vnode.ns cloned.isStatic = vnode.isStatic cloned.key = vnode.key cloned.isComment = vnode.isComment cloned.fnContext = vnode.fnContext cloned.fnOptions = vnode.fnOptions cloned.fnScopeId = vnode.fnScopeId cloned.asyncMeta = vnode.asyncMeta cloned.isCloned = true return cloned }
如果我们有这样一个vue组件
<template> <span class="demo" v-show="isShow"> This is a span. </span> </template>
转化成render函数描述的js代码形式就是这样的:
vue-router
相关基础看这篇文章 -> Vue 路由知识点归纳总结
Vue.use
Vue 通用的插件注册原理
Vue 从它的设计上就是一个渐进式 JavaScript 框架,它本身的核心是解决视图渲染的问题,其它的能力就通过插件的方式来解决。
如何注册插件 --> Vue.use
: Vue 提供了 Vue.use 的全局 API 来注册这些插件
vue/src/core/global-api/use.js
/* @flow */ import { toArray } from '../util/index' export function initUse (Vue: GlobalAPI) { // Vue.use 接受一个 plugin 参数 Vue.use = function (plugin: Function | Object) { // 并且维护了一个 _installedPlugins 数组,它存储所有注册过的 plugin const installedPlugins = (this._installedPlugins || (this._installedPlugins = [])) if (installedPlugins.indexOf(plugin) > -1) { return this } // additional parameters const args = toArray(arguments, 1) args.unshift(this) // 判断 plugin 有没有定义 install 方法 if (typeof plugin.install === 'function') { // 如果有的话则调用该方法,并且该方法执行的第一个参数是 Vue plugin.install.apply(plugin, args) } else if (typeof plugin === 'function') { plugin.apply(null, args) } // 最后把 plugin 存储到 installedPlugins 中 installedPlugins.push(plugin) return this } } // 可以看到 Vue 提供的插件注册机制很简单, // 每个插件都需要实现一个静态的 install 方法, // 当我们执行 Vue.use 注册插件的时候,就会执行这个 install 方法, // 并且在这个 install 方法的第一个参数我们可以拿到 Vue 对象, // 这样的好处就是作为插件的编写方不需要再额外去import Vue 了
实例 component,props,slot
我们知道, 当vue库文件加载完后,vue的初始化中已有这个东西:
Vue.options={ components:{ KeepAlive:Object, Transition:Object, TransitionGroup:Object }, directives:{ show:Object, model:Object }, filter:{}, _base:function Vue$3(options){...} }
这些都是vue库内置的组件和指令,当执行Vue.component、Vue.directive、Vue.filter时就是在对这些内置组件、指令、过滤器进行扩充,所以:
var child=Vue.component('child',{ template:'<div>child</div>', props:['name'] })
执行完后
Vue.options.components={ KeepAlive:Object, Transition:Object, TransitionGroup:Object, child:function VueComponent(options) }
参考
https://yuchengkai.cn/docs/fr...
https://ustbhuangyi.github.io...
vue中SFC文件解析为SFCDescriptor的流程
Vue源码分析(11)--实例分析component,props,slot
vue变化侦测原理