这段时间折腾了一个vue的日期选择的组件,为了达成我一向的使用舒服优先原则,我决定使用directive
来实现,可是经过这个实现有一个难点就是我如何把时间选择的组件插入到dom中,因此问题来了,我是否是又要看Vue的源码?vue
vue2.0即将到来,改了一大堆,Fragment没了,因此vue社区中为数很少的组件又有一批不能在2.0中使用,vue的官方插件也是毁得只剩vuex兼容,因此在我正在折腾个人组件的时候看到这个消息我是崩溃的。。。但没办法,仍是得继续。但愿2.0出来以后官方能完善一下文档,1.0中太多东西根本没在文档里提到,好比Fragment,好比Vue的util方法,这给第三方组件以及插件开发者带来了无数的玛法,你只能去看源码了,费时费力,刚研究透又来个大更新,我真的想哭/(ㄒoㄒ)/~~node
----------回归正题--------vuex
vue的核心就是他的Vue class,component到最终其实也就是一个Vue的实例,包含了一些component独有的属性而已,咱们来看看这个Class作了什么:api
function Vue (options) { this._init(options) }
恩,他调用了_init
,而在_init
里面就是初始化了一大堆属性,这些不重要,最重要的是最下面他有这么一句代码:app
if (options.el) { this.$mount(options.el) }
这个el是咱们在调用new Vue({...})
时传入的,即这个vue对象的挂载点,好了,咱们找到办法去动态得把一个Vue的实例挂载到dom里面了,因而就有了以下代码:dom
const vm = new Vue({ template: '<div>我是天才</div>', data: { hehe: 'haha' } }) vm.$mount(document.body)
愉快得打开页面,等等,为何整个页面上就剩下我是天才这句很是正确的话呢?哦~原来$mount
默认是替换整个整个元素的,呵呵哒this
那么咱们要如何把节点插入到body里面呢?这里有不少办法,好比你直接调用$mount()
不传任何参数,这个时候他不会执行插入操做,而后你把他编译过的节点(也就是vm.$el
)拿出来手动经过dom操做来进行插入,固然咱们确定不能用这么low的方法O(∩_∩)O~,继续撸源码,很快咱们找到了这么一个文件:prototype
// instance/api/dom.js Vue.prototype.$appendTo = function(target, cb, withTransition) {...} Vue.prototype.$before = function(target, cb, withTransition) {...}
是的,Vue的实例自带一些dom操做的帮助,那么咱们随便选一个用就是了,不细说插件
使用这种方式动态插入的节点会有一个问题,那就是$el
并非咱们真正想要的节点,而是一个注释节点,这是为啥?仍是看源码,咱们跟着$mount
进去看看他作了什么:code
Vue.prototype.$mount = function (el) { if (this._isCompiled) { process.env.NODE_ENV !== 'production' && warn( '$mount() should be called only once.' ) return } el = query(el) if (!el) { el = document.createElement('div') } this._compile(el) this._initDOMHooks() if (inDoc(this.$el)) { this._callHook('attached') ready.call(this) } else { this.$once('hook:attached', ready) } return this }
显然咱们的el是没有的,那么这里的el就变成了一个div
,而后进行了_compile
,再继续:
// 源码太长不贴了 // 文件位置:instance/internal/lifecycle.js
这里面他作了一个el = transclude(el, options)
,以及this._initElement(el)
,咱们重点看一下this._initElement(el)
:
Vue.prototype._initElement = function (el) { if (el instanceof DocumentFragment) { this._isFragment = true this.$el = this._fragmentStart = el.firstChild this._fragmentEnd = el.lastChild // set persisted text anchors to empty if (this._fragmentStart.nodeType === 3) { this._fragmentStart.data = this._fragmentEnd.data = '' } this._fragment = el } else { this.$el = el } this.$el.__vue__ = this this._callHook('beforeCompile') }
咱们发现这里的el以及不是以前咱们可亲的div
了,那么他是什么呢?咱们倒回去看transclude
:
... if (options) { if (options._asComponent && !options.template) { options.template = '<slot></slot>' } if (options.template) { options._content = extractContent(el) el = transcludeTemplate(el, options) } } ...
咱们是有template的,因此执行了transcludeTemplate
:
function transcludeTemplate (el, options) { var template = options.template var frag = parseTemplate(template, true) if (frag) { var replacer = frag.firstChild var tag = replacer.tagName && replacer.tagName.toLowerCase() if (options.replace) { /* istanbul ignore if */ if (el === document.body) { process.env.NODE_ENV !== 'production' && warn( 'You are mounting an instance with a template to ' + '<body>. This will replace <body> entirely. You ' + 'should probably use `replace: false` here.' ) } // there are many cases where the instance must // become a fragment instance: basically anything that // can create more than 1 root nodes. if ( // multi-children template frag.childNodes.length > 1 || // non-element template replacer.nodeType !== 1 || // single nested component tag === 'component' || resolveAsset(options, 'components', tag) || hasBindAttr(replacer, 'is') || // element directive resolveAsset(options, 'elementDirectives', tag) || // for block replacer.hasAttribute('v-for') || // if block replacer.hasAttribute('v-if') ) { return frag } else { options._replacerAttrs = extractAttrs(replacer) mergeAttrs(el, replacer) return replacer } } else { el.appendChild(frag) return el } } else { process.env.NODE_ENV !== 'production' && warn( 'Invalid template option: ' + template ) } }
这边生成了一个Fragment
,好吧,咱们最终仍是回到了这里。。。由于这边返回的是一个Fragment
,因此会执行以下代码:
if (el instanceof DocumentFragment) { // anchors for fragment instance // passing in `persist: true` to avoid them being // discarded by IE during template cloning prepend(createAnchor('v-start', true), el) el.appendChild(createAnchor('v-end', true)) }
而后回到刚才的_initElement
里面,this.$el = this._fragmentStart = el.firstChild
,额,好吧。。。我表示无力吐槽
那么回到咱们刚才的问题,想要让$el
正确,只须要在new Vue({...})
的时候传入replace: false
就好了,可是外面就多包了一层div
,怎么样都不以为完美
到这里咱们基本了解了初始化一个Vue对象时的一些方法的执行顺序,以及一个组件如何从字符串模板最终到一个节点的过程,讲得比较粗糙,建议有兴趣的各位仍是自行去看源代码吧~