這段時間折騰了一個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對象時的一些方法的執行順序,以及一個組件如何從字符串模板最終到一個節點的過程,講得比較粗糙,建議有興趣的各位仍是自行去看源代碼吧~