资讯专栏INFORMATION COLUMN

Vue 源码分析之二:Vue Class

toddmark / 1631人阅读

摘要:但没办法,还是得继续。因为这边返回的是一个,所以会执行如下代码然后回到刚才的里面,,额,好吧。。。

这段时间折腾了一个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: "
我是天才
", 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 = ""
    }
    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 " +
          ". This will replace  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对象时的一些方法的执行顺序,以及一个组件如何从字符串模板最终到一个节点的过程,讲得比较粗糙,建议有兴趣的各位还是自行去看源代码吧~

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/79365.html

相关文章

  • vue-loader 源码解析系列之 整体分析

    摘要:笔者系贡献者之一官方说明简单来说就是将文件变成,然后放入浏览器运行。部分首先分析部分从做右到左,也就是被先后被和处理过了。源码解析之二源码解析之三写作中源码解析之四写作中作者博客作者微博 笔者系 vue-loader 贡献者(#16)之一 官方说明 vue-loader is a loader for Webpack that can transform Vue components ...

    icattlecoder 评论0 收藏0
  • JavaScript - 收藏集 - 掘金

    摘要:插件开发前端掘金作者原文地址译者插件是为应用添加全局功能的一种强大而且简单的方式。提供了与使用掌控异步前端掘金教你使用在行代码内优雅的实现文件分片断点续传。 Vue.js 插件开发 - 前端 - 掘金作者:Joshua Bemenderfer原文地址: creating-custom-plugins译者:jeneser Vue.js插件是为应用添加全局功能的一种强大而且简单的方式。插....

    izhuhaodev 评论0 收藏0
  • 4月份前端资源分享

    摘要:更多资源请文章转自月份前端资源分享关于的思考一款有趣的动画效果跨站资源共享之二最流行的编程语言能做什么到底什么是闭包的第三个参数跨域资源共享详解阮一峰前端要给力之语句在中的值周爱民中国第二届视频花絮编码规范前端工程师手册奇舞周刊被忽视的 更多资源请Star:https://github.com/maidishike... 文章转自:https://github.com/jsfron...

    jsdt 评论0 收藏0
  • vue2.0源码分析之理解响应式架构

    摘要:分享前啰嗦我之前介绍过如何实现和。我们采用用最精简的代码,还原响应式架构实现以前写的那篇源码分析之如何实现和可以作为本次分享的参考。到现在为止,我们再看那张图是不是就清楚很多了总结我非常喜欢,以上代码为了好展示,都采用最简单的方式呈现。 分享前啰嗦 我之前介绍过vue1.0如何实现observer和watcher。本想继续写下去,可是vue2.0横空出世..所以 直接看vue2.0吧...

    chenatu 评论0 收藏0
  • vue源码分析系列之响应式数据(四)

    摘要:执行当时传入的回调,并将新值与旧值一并传入。文章链接源码分析系列源码分析系列之环境搭建源码分析系列之入口文件分析源码分析系列之响应式数据一源码分析系列之响应式数据二源码分析系列之响应式数据三 前言 上一节着重讲述了initComputed中的代码,以及数据是如何从computed中到视图层的,以及data修改后如何作用于computed。这一节主要记录initWatcher中的内容。 ...

    GHOST_349178 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<