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.$options.render) { vm.$options.render = createemptyvnode if (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 ) } } } callhook(vm, 'beforemount') let updatecomponent /* istanbul ignore if */ if (process.env.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.newdeps = [] this.depids = new set() this.newdepids = new set() this.expression = process.env.node_env !== 'production' ? exporfn.tostring() : '' // parse expression for getter if (typeof exporfn === 'function') { this.getter = exporfn } else { this.getter = parsepath(exporfn) if (!this.getter) { this.getter = noop process.env.node_env !== 'production' && warn( `failed watching path: "${exporfn}" ` + 'watcher only accepts simple dot-delimited paths. ' + 'for full control, use a function instead.', vm ) } } this.value = this.lazy ? undefined : this.get() }
其他方法暂时不提,可以看到,但是我们也能大致猜到他在做些什么,这里只是截取了部分watcher的构造方法,,重点是最后执行了this.get 而this.get则执行了this.getter,最后等于执行了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 (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 } } // if the returned array contains only a single node, allow it if (array.isarray(vnode) && vnode.length === 1) { vnode = vnode[0] } // return empty vnode in case the render function errored out if (!(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 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__)) { process.env.node_env !== 'production' && warn( `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 (process.env.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) || config.gettagnamespace(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
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持移动技术网。
如对本文有疑问, 点击进行留言回复!!
蒲公英 · JELLY技术周刊 Vol.14: Vue 3 新特性详解
keepalived+haproxy+mycat+mysql高可用搭建配制
vue中子组件的created、mounted生命周期钩子中获取不到props中的值问题
网友评论