Vue 源码剖析之二:Vue Class

这段时刻折腾了一个vue的日期挑选的组件,为了杀青我一向的运用惬意优先准绳,我决议运用directive来完成,然则经由历程这个完成有一个难点就是我怎样把时刻挑选的组件插进去到dom中,所以题目来了,我是否是又要看Vue的源码?

vue2.0行将到来,改了一大堆,Fragment没了,所以vue社区中为数不多的组件又有一批不能在2.0中运用,vue的官方插件也是毁得只剩vuex兼容,所以在我正在折腾我的组件的时刻看到这个音讯我是崩溃的。。。但没方法,照样得继承。愿望2.0出来以后官方能圆满一下文档,1.0中太多东西基础没在文档里提到,比方Fragment,比方Vue的util要领,这给第三方组件以及插件开发者带来了无数的玛法,你只能去看源码了,费时辛苦,刚研讨透又来个大更新,我真的想哭/(ㄒoㄒ)/~~

———-回归正题——–

Vue Class

vue的中心就是他的Vue class,component到终究实在也就是一个Vue的实例,包含了一些component独占的属性罢了,我们来看看这个Class做了什么:

function Vue (options) {
  this._init(options)
}

恩,他挪用了_init,而在_init内里就是初始化了一大堆属性,这些不主要,最主要的是最下面他有这么一句代码:

if (options.el) {
  this.$mount(options.el)
}

这个el是我们在挪用new Vue({...})时传入的,即这个vue对象的挂载点,好了,我们找到方法去动态得把一个Vue的实例挂载到dom内里了,因而就有了以下代码:

const vm = new Vue({
    template: '<div>我是天赋</div>',
    data: {
        hehe: 'haha'
    }
})
vm.$mount(document.body)

愉快得翻开页面,等等,为何全部页面上就剩下我是天赋这句异常准确的话呢?哦~本来$mount默许是替代全部全部元素的,呵呵哒

那末我们要怎样把节点插进去到body内里呢?这里有许多方法,比方你直接挪用$mount()不传任何参数,这个时刻他不会实行插进去操纵,然后你把他编译过的节点(也就是vm.$el)拿出来手动经由历程dom操纵来举行插进去,固然我们一定不能用这么low的要领O(∩_∩)O~,继承撸源码,很快我们找到了这么一个文件:

// instance/api/dom.js
Vue.prototype.$appendTo = function(target, cb, withTransition) {...}
Vue.prototype.$before = function(target, cb, withTransition) {...}

是的,Vue的实例自带一些dom操纵的协助,那末我们随意选一个用就是了,不细说

但是我们照样遇到了题目

运用这类体式格局动态插进去的节点会有一个题目,那就是$el并非我们真正想要的节点,而是一个解释节点,这是为啥?照样看源码,我们随着$mount进去看看他做了什么:

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对象时的一些要领的实行递次,以及一个组件怎样从字符串模板终究到一个节点的历程,讲得比较粗拙,发起有兴致的列位照样自行去看源代码吧~

    原文作者:Jokcy
    原文地址: https://segmentfault.com/a/1190000005059686
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞