详解vue挂载到dom上会发⽣什么
vue 挂载到dom 元素后发⽣了什么
前⼀篇⽂章分析了new vue() 初始化时所执⾏的操作,主要包括调⽤vue._init 执⾏⼀系列的初始化,包括⽣命周期,事件系统,beforeCreate和Created hook,在在这⾥发⽣,重点分析了 initState,即对我们常⽤到的data props computed 等等进⾏的初始化,最后,执⾏$mount 对dom进⾏了挂载,本篇⽂章将对挂载后所发⽣的事情进⾏进⼀步阐述,
Vue.prototype.$mount = function (
el?: string | Element,
hydrating?: boolean
): Component {
el = el && inBrowser ? query(el) : undefined
return mountComponent(this, el, hydrating)
}
mount 的代码很简单,直接执⾏了moutComponent⽅法,
export function mountComponent (
vm: Component,
el: ?Element,
hydrating?: boolean
): Component {
vm.$el = el
if (!vm.$der) {
vm.$der = createEmptyVNode
if (v.NODE_ENV !== 'production') {
/
* istanbul ignore if */
if ((vm.$plate && vm.$plate.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
)
}
}
}
callHook(vm, 'beforeMount')
let updateComponent
/* istanbul ignore if */
if (v.NODE_ENV !== 'production' && config.performance && mark) {
updateComponent = () => {
const name = vm._name
const id = vm._uid
const 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
if (vm.$vnode == null) {
vm._isMounted = true
callHook(vm, 'mounted')
}
return vm
}
moutComponent 这⾥判断了render函数,正常开发过程中,对于dom的写法有很多种,可以直接写templete,也可以写render函数,也可以直接把dom写在挂载元素⾥⾯,但是在编译阶段(通常是通过webpack执⾏的),统统会把这些写法都编译成render函数,所以,最后执⾏的都是render函数,判断完render可以看到,beforeMount hook在这⾥执⾏,最后执⾏了new Watcher()我们进⼊new Watcher
export default class Watcher {
vm: Component;
expression: string;
cb: Function;
id: number;
deep: boolean;
user: boolean;
lazy: boolean;
sync: boolean;
dirty: boolean;
active: boolean;
deps: Array<Dep>;
newDeps: Array<Dep>;
depIds: SimpleSet;
newDepIds: SimpleSet;
before: ?Function;
getter: Function;
value: any;
constructor (
vm: Component,
expOrFn: string | Function,
cb: Function,
options?: ?Object,
isRenderWatcher?: boolean
) {
this.vm = vm
if (isRenderWatcher) {
vm._watcher = this
}
vm._watchers.push(this)
// options
if (options) {
this.deep = !!options.deep
this.user = !!options.user
this.lazy = !!options.lazy
this.sync = !!options.sync
this.before = options.before
} else {
this.deep = this.user = this.lazy = this.sync = false
}
this.cb = cb
this.id = ++uid // uid for batching
this.active = true
this.dirty = this.lazy // for lazy watchers
this.deps = []
this.depIds = new Set()
: ''
// parse expression for getter
if (typeof expOrFn === 'function') {
< = expOrFn
} else {
< = parsePath(expOrFn)
if (!) {
< = noop
`Failed watching path: "${expOrFn}" ` +
'Watcher only accepts simple dot-delimited paths. ' +
'For full control, use a function instead.',
vm
)
}
}
this.value = this.lazy
undefined
: ()
}
其他⽅法暂时不提,可以看到,但是我们也能⼤致猜到他在做些什么,这⾥只是截取了部分Watcher的构造⽅法,,重点是最后执⾏了 ⽽则执⾏了,最后等于执⾏了Watcher构造⽅法中传⼊的第⼆个参数,也就是上⼀环节moutComponent中的updateComponent⽅法,updateComponent⽅法也是在moutComponent⽅法中定义updateComponent = () => {
vm._update(vm._render(), hydrating)
}
这⾥先是执⾏编译⽽成的render⽅法,然后作为参数传到_update⽅法中执⾏,render⽅法执⾏后返回⼀个vnode 即Virtual dom,然后将这个Virtual dom作为参数传到update⽅法中,这⾥我们先介绍⼀下Virtual dom 然后在介绍最后执⾏挂载的update⽅法,
render函数
Vue.prototype._render = function (): VNode {
const vm: Component = this
const { render, _parentVnode } = vm.$options
if (_parentVnode) {
vm.$scopedSlots = normalizeScopedSlots(
_parentVnode.data.scopedSlots,
vm.$slots
)
}
// set parent vnode. this allows render functions to have access
// to the data on the placeholder node.
vm.$vnode = _parentVnode
// render self
let vnode
try {
vnode = 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 (v.NODE_ENV !== 'production' && vm.$derError) {
try {
vnode = vm.$derError.call(vm._renderProxy, vm.$createElement, e)
} catch (e) {
handleError(e, vm, `renderError`)
vnode = vm._vnode
}
} else {
vnode = vm._vnode
}
}
// if the returned array contains only a single node, allow it
if (Array.isArray(vnode) && vnode.length === 1) {
vnode = vnode[0]
}
/
reactnative开发/ return empty vnode in case the render function errored out
if (!(vnode instanceof VNode)) {
if (v.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 parent
vnode.parent = _parentVnode
return vnode
}
根据flow 的类型定义,我们可以看到,_render函数最后返回⼀个vnode,_render主要代码在第⼀个try catch中,vnode = render.call(vm._renderProxy,vm.$CREATRElement) ,第⼀个参数为当前上下⽂this 其实就是vm本⾝,第⼆个参数是实际执⾏的⽅法,当我们在⼿写render函数时,⽐如这样
render:h=>{
return h(
"div",
123
)
}
这时候我们使⽤的h 就是传⼊的$createElement⽅法,然后我们来看⼀下createElement⽅法,在看creatElement之前,我们先简单介绍⼀下vdom,因为createElement返回的就是⼀个vdom,vdom其实就是真实dom对象的⼀个映射,主要包含标签名字tag 和在它下⾯的标签 children 还有⼀些属性的定义等等,当我们在进⾏dom改变时⾸先是数据的改变,数据的改变映射到vdom中,然后改变vdom,改变vdom是对js数据层⾯的改变所以说代价很⼩,在这⼀过程中我们还可以进⾏针对性的优化,复⽤等,最后把优化后的改变部分通过dom操作操作到真实的dom上去,另外,通过vdom这层的定义我们不仅仅可以把vdom 映射到web⽂档流上,甚⾄可以映射到app端的⽂档流,桌⾯应⽤的⽂档流多种,这⾥引⽤⼀下vue js作者对vdom的评价:Virtual DOM真正价值从来不是性能,⽽是它 1: 为函数式的ui编程⽅式打开了⼤门,2 :可以渲染到dom以外的backend ⽐如ReactNative 。
下⾯我们来继续介绍creatElement
export function _createElement (
context: Component,
tag?: string | Class<Component> | Function | Object,
data?: VNodeData,
children?: any,
normalizationType?: number
): VNode | Array<VNode> {
if (isDef(data) && isDef((data: any).__ob__)) {
`Avoid using observed data object as vnode data: ${JSON.stringify(data)}\n` +
'Always create fresh vnode data objects in each render!',
context
)
return createEmptyVNode()
}
// object syntax in v-bind
if (isDef(data) && isDef(data.is)) {
tag = data.is
}
if (!tag) {
// in case of component :is set to falsy value
return createEmptyVNode()
}
// warn against non-primitive key
if (v.NODE_ENV !== 'production' &&
isDef(data) && isDef(data.key) && !isPrimitive(data.key)
) {
if (!__WEEX__ || !('@binding' in data.key)) {
warn(
'Avoid using non-primitive value as key, ' +
'use string/number value instead.',
context
)
}
}
// support single function children as default scoped slot
if (Array.isArray(children) &&
typeof children[0] === 'function'
) {
data = data || {}
data.scopedSlots = { default: children[0] }
children.length = 0
}
if (normalizationType === ALWAYS_NORMALIZE) {
children = normalizeChildren(children)
} else if (normalizationType === SIMPLE_NORMALIZE) {
children = simpleNormalizeChildren(children)
}
let vnode, ns
if (typeof tag === 'string') {
let Ctor
ns = (context.$vnode && context.$vnode.ns) || TagNamespace(tag)
if (config.isReservedTag(tag)) {
// platform built-in elements
vnode = new VNode(
config.parsePlatformTagName(tag), data, children,
undefined, undefined, context
)
} else if ((!data || !data.pre) && isDef(Ctor = resolveAsset(context.$options, 'components', tag))) {
// component
vnode = createComponent(Ctor, data, context, children, tag)
} else {
// unknown or unlisted namespaced elements
// check at runtime because it may get assigned a namespace when its
// parent normalizes children
vnode = new VNode(
tag, data, children,
undefined, undefined, context
)
}
} else {
// direct component options / constructor
vnode = createComponent(tag, data, context, children)
}
if (Array.isArray(vnode)) {
return vnode
} else if (isDef(vnode)) {
if (isDef(ns)) applyNS(vnode, ns)
if (isDef(data)) registerDeepBindings(data)
return vnode
} else {
return createEmptyVNode()
}
}
creatElement 最后结果时返回⼀个new VNode,并将craete时传⼊的参数,经过处理,传到VNode的初始化中,这⾥有⼏种情况,createEmptyVNode,没有传参数,或参数错误,会返回⼀个空的vnode,如果tag 时浏览器的标签如div h3 p等,会返回⼀个保留VNode,等等,最后,回到上⾯,vnode 创建完毕,_render会返回这个vnode,最后⾛回vm._update(),update 中,便是将vnode 通过dom操作插⼊到真正的⽂档流中,下⼀节我们聊聊update
以上就是本⽂的全部内容,希望对⼤家的学习有所帮助,也希望⼤家多多⽀持。
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论