数据驱动
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 技术揭秘
这里query
el
接下来会执行这里的挂载函数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
这个方法的实现是和平台、构建方式都相关的。
render
src/core/instance/render.jsVue 的 _render 方法是实例的一个私有方法,它用来把实例渲染成一个虚拟 Node。
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变化侦测原理
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。