vue源码剖析(一)-源码入口

vue提供不同的平台和版本,浏览器环境版本包含:Runtime only版本和Runtime + compiler版本。 Runtime + compiler版本:new Vue({template: '<div>{{ hi }}</div>'}),须要将template中的字符串编译成render函数,须要用到compiler,具体在scripts\config.js

const builds = {  'web-runtime-cjs-dev': {    entry: resolve('web/entry-runtime.js'),    dest: resolve('dist/vue.runtime.common.dev.js'),    format: 'cjs',    env: 'development',    banner  },  ...  'web-full-cjs-dev': {    entry: resolve('web/entry-runtime-with-compiler.js'),    dest: resolve('dist/vue.common.dev.js'),    format: 'cjs',    env: 'development',    alias: { he: './entity-decoder' },    banner  },  ... }

本文剖析runtime + complier版本,entry属性值就是Vue源码入口,接下来查看web/entry-runtime-with-compiler.js

import Vue from './runtime/index'...// 将runtime/index.js中定义的mount办法缓存const mount = Vue.prototype.$mount// 重写$mount// runtime + compiler版本的mountVue.prototype.$mount = function (  el?: string | Element,  hydrating?: boolean // 和服务器渲染无关): Component {  el = el && query(el)  /* istanbul ignore if */  // el不能时body和html  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  // resolve template/el and convert to render function  // 如果没有定义 render 办法,则会把 el 或者 template 字符串转换成 render 办法  if (!options.render) {    let template = options.template    // 获取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) {      template = getOuterHTML(el)    }    if (template) {      /* istanbul ignore if */      if (process.env.NODE_ENV !== 'production' && config.performance && mark) {        mark('compile')      }      // 将template转换成render函数compileToFunctions      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      /* istanbul ignore if */      if (process.env.NODE_ENV !== 'production' && config.performance && mark) {        mark('compile end')        measure(`vue ${this._name} compile`, 'compile', 'compile end')      }    }  }  // 这里调用的mount是下面被缓存的mount  return mount.call(this, el, hydrating)}

这里的Vue还是通过import引入,一路向上找,最终找到src\core\instance\index.js

// Vue构造函数function Vue (options) {  if (process.env.NODE_ENV !== 'production' &&    !(this instanceof Vue)  ) {    warn('Vue is a constructor and should be called with the `new` keyword')  }  // this是什么  // initMixin给Vue增加此办法  this._init(options)}// 初始化// if (vm.$option.el) {//   $mount负责挂载//   vm.$mount(vm.$option.el)// }initMixin(Vue)// 初始化state// Vue.prototype.$set = set// Vue.prototype.$delete = del// Vue.prototype.$watch = functionstateMixin(Vue)// 事件// $on,$emit,$once,$off和本人写的class bus没有太大区别// $emit触发以后组件的事件eventsMixin(Vue)// _update !!!!!!!!!!!!!!!!// forceUpdate,destroylifecycleMixin(Vue)// 渲染// nextTick// _render !!!!!!!!!!!!renderMixin(Vue)export default Vue

能够看到Vue的实质是一个function,之所以用function而没有用es6的class,是因为在后续会在Vue函数原型上挂载了很多api绝对于class更不便,Vue是通过xxxMixin调用不同目录下的办法组装成残缺Vue

总结

  1. package.json中的script标签中的build命令查找我的项目源码入口,实用于其余源码浏览入口
  2. vue的底层是一个构造函数,没有应用es的class不便原型挂载api扩大各个模块内容