超详细的vue2源码解析(二)

208 阅读3分钟

上一篇文章我们说到下次我们会从VUE对象引入我们项目时所做的一些事开始全面的去解析vue相关的实现,我们在项目中使用的vue大部分是只运行时版本,也就是去除了compiler编译器的部分,通过vue插件在编译期间就将我们的template字符串转为render渲染函数,减少了这个转换的时间。但是在解析源码时我们还是从带有compiler的入口来进行解析,这样能更加全面的了解vue是如何实现的。

上面这块就是我们引入Import Vue 对象时的入口文件,我会在里面加上一些注释来帮助理解后面会进行大致的总结

import config from 'core/config'  //一些不区分平台的一些vue基础配置
import { 
warn,  //vue内部 进行报错警告组件位置信息的函数
cached  //利用闭包 创建纯函数的缓存版本。
} from 'core/util/index' 

import { mark, measure } from 'core/util/perf' //性能检测的一些方法
 
import Vue from './runtime/index'  //全局引入VUE对象
import { query } from './util/index' //// 封装后的查询元素的方法 
import { compileToFunctions } from './compiler/index' //用于生成渲染函数的方法
import { shouldDecodeNewlines, shouldDecodeNewlinesForHref } from './util/compat' //一些浏览器解析属性内容时 可能会对属性换行符进行编码 此时需要特殊处理


//通过元素的id 返回元素的innerHtml
const idToTemplate = cached(id => {
  const el = query(id)
  return el && el.innerHTML
})

//这段代码首先缓存了原型上的 $mount 方法,再重新定义该方法
const mount = Vue.prototype.$mount

//调用 vm.$mount 方法挂载 vm,挂载的目标就是把模板渲染成最终的DOM,
//这里不是运行时runtime的vue包 所以需要一些特殊处理
Vue.prototype.$mount = function (
  el?: string | Element,
  hydrating?: boolean
): Component {

 //获取当前实例需要挂载到的真实dom
  el = el && query(el)

  if (el === document.body || el === document.documentElement) {
    process.env.NODE_ENV !== 'production' && warn(
      `Do not mount Vue to <html> or <body> - mount to normal elements instead.`
    )
    return this
  }

  const options = this.$options
  
  //当没有render函数时就会进行一个基本的兼容
  if (!options.render) {
    let template = options.template
    if (template) {
      if (typeof template === 'string') {
        if (template.charAt(0) === '#') {
          template = idToTemplate(template)
          /* istanbul ignore if */
          if (process.env.NODE_ENV !== 'production' && !template) {
            warn(
              `Template element not found or is empty: ${options.template}`,
              this
            )
          }
        }
      } else if (template.nodeType) {
        template = template.innerHTML
      } else {
        if (process.env.NODE_ENV !== 'production') {
          warn('invalid template option:' + template, this)
        }
        return this
      }
    } else if (el) {
     //如果将vue实例进行了挂载 但是 没有传template此时 获取元素的外部HTML 就是整个挂载对象的结构
      template = getOuterHTML(el)
    }
    if (template) {
      if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
       //开始标记时间
        mark('compile')
      }

     // 将template生成 render函数 
      const { render, staticRenderFns } = compileToFunctions(template, {
        outputSourceRange: process.env.NODE_ENV !== 'production',
        shouldDecodeNewlines,
        shouldDecodeNewlinesForHref,
        delimiters: options.delimiters,
        comments: options.comments
      }, this)
      options.render = render
      options.staticRenderFns = staticRenderFns

      if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
        mark('compile end')
        //标记下当前vue实例的编译时间
        measure(`vue ${this._name} compile`, 'compile', 'compile end')
      }
    }
  }
  
  //生成了渲染函数后继续去调用原有的通用mount函数
  return mount.call(this, el, hydrating)
}

我们可以看到这个入口文件主要的工作就是将VUE对象进行暴露,并重写了VUE原型上的$mount方法,由此可知vue2继承相关的实现是通过原型链来实现的,这个$mount方法就会存在于每一个vue对象实例的原型中。

$mount方法的作用就是将我们编写的vue组件挂载到指定的dom元素之上,我们可以看到这里先对$mount进行了缓存,接着重新实现,主要就是为了重新实现前的$mount逻辑是全平台全环境通用的逻辑。这个思想也可以应用到我们的项目之中来实现通用逻辑复用的场景。这里的重新实现是针对web环境带有compiler编译器版本的实现。这个版本的$mount函数很重要的一个逻辑就是将template通过编译函数转换成渲染的render函数。

我们也可以注意到vue中使用了cached这个工具函数用于提供函数的缓存版本,这一特性在我们自己的项目中也完全可以借鉴使用,来提高性能。 还进行了关键节点的性能检测,可以让用户更好的排查问题。

好了这次就先讲这么多,下次我们会从这个入口文件从 runtime/index引入的Vue对象来进一步讲解Vue对象是如何被设计的。