vue源码学习--vue源码学习入门

本文为开始学习vue源码的思路整理。在拿到vue项目源码的以后看到那些项目中的文件夹,会很困惑,不知道每一个文件夹内的世界,怎么变换,怎样的魔力,最后产生了vue框架。学习源码也无从学起。我解决了这些困惑以后,造成了这篇文章----vue源码学习入门。文章会根据我本身的学习思路,理清vue项目的大致框架。文章先介绍vue项目中各个文件夹中内包含的功能,而后是从package.json文件开始咱们的寻找vue构造函数之旅,最后再从vue构造函数返回入口,看看旅程中的各个节点都为vue加了什么“戏”。前端

1、Vue项目结构vue

在GitHub中下载vue项目的源码后解压咱们会获得以下的目录。webpack

每一个文件夹是作什么的,在项目中的CONTRIBUTING有介绍。我翻译了一下,造成了如下脑图:git

 

咱们源码学习,学习的是vue框架内容的源码,而项目中包含的许多构建vuejs的配置文件和辅助vue代码编写的功能模块是咱们几乎接触不到的。es6

咱们须要着重注意的是,包含vue源码的【src】文件夹。我把src的内容单独整理,造成了又一脑图,以下:github

 

如今咱们大体知道vue项目的各个文件夹的用处了。并且,对于咱们须要着重关注的源码src也大体掌握了每一个文件夹负责的功能模块。接下来,咱们就要开始啃食vue源码了。作一只饥渴的寄生虫吧~web

 

2、寻找构造函数之旅npm

要了解一个前端项目,通常状况下咱们均可以从查看项目的package.json文件开始。从package.json咱们能够知道项目有哪些依赖包,定义了哪些脚本字段。其实在CONTRIBUTING中还提到了经常使用的npm命令。如下是个人截图:json

 

其中说起能够用npm run dev来达到监控和自动重建dist目录下的vue.js文件以及还有一些npm命令在package.json的script中,你们能够按需执行。浏览器

那咱们找到vue项目中的package.json,看看npm run dev执行了什么命令

rollup指的是一个专一于es6模块构建的工具,会对其构建的代码进行tree-shaking,是tree-shaking的提出者。它与webpack的区别是rollup没作uplify,其构建后的代码尽可能保持源代码的样子。

rollup -c指定配置文件为build/config.js,最后将TARGET的值设置成web-full-dev。

打开build文件夹下的config.js。简化后代码以下:

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

function genConfig (name) {
  const opts = builds[name]
  const config = {
    input: opts.entry,
    external: opts.external,
    plugins: [
      replace({
        __WEEX__: !!opts.weex,
        __WEEX_VERSION__: weexVersion,
        __VERSION__: version
      }),
      flow(),
      buble(),
      alias(Object.assign({}, aliases, opts.alias))
    ].concat(opts.plugins || []),
    output: {
      file: opts.dest,
      format: opts.format,
      banner: opts.banner,
      name: opts.moduleName || 'Vue'
    }
  }

  if (opts.env) {
    config.plugins.push(replace({
      'process.env.NODE_ENV': JSON.stringify(opts.env)
    }))
  }

  Object.defineProperty(config, '_name', {
    enumerable: false,
    value: name
  })

  return config
}

if (process.env.TARGET) {
  module.exports = genConfig(process.env.TARGET)
} else {
  exports.getBuild = genConfig
  exports.getAllBuilds = () => Object.keys(builds).map(genConfig)
}

 

咱们能够看出,该js中包含着一个builds常量对象,对象的属性名为以前package.json中npm对应命里里设置的TAGET值。js的最后根据是否有TAGET值返回不一样的值。咱们npm run dev的命令对应的是:

module.exports = genConfig({
    entry: resolve('web/entry-runtime-with-compiler.js'),
    dest: resolve('dist/vue.js'),
    format: 'umd',
    env: 'development',
    alias: { he: './entity-decoder' },
    banner
  });

并最终返回一个config对象,也就是执行rollup的配置对象。

从builds对象中能够知道,vue的入口文件为:web/entry-runtime-with-compiler.js。在项目结构的介绍中,咱们也说起了dist、build的入口文件在platform中。在通过resolve函数以后,路径就是:\vue-dev\src\platforms\web\entry-runtime-with-compiler.js。

 

打开这个文件,在import中看到了Vue又来自./runtime/index即\vue-dev\src\platforms\web\runtime\index.js。按照这个Vue是来自于import的套路咱们接着又找到了core/index.js、instance/index.js。拨开层层迷雾,终识庐山真面目,最终找到了最终的vue构造函数,以下:(\vue-dev\src\core\instance\index.js)

1 function Vue (options) {
2   if (process.env.NODE_ENV !== 'production' &&
3     !(this instanceof Vue)
4   ) {
5     warn('Vue is a constructor and should be called with the `new` keyword')
6   }
7   this._init(options)
8 }

因此寻找Vue构造函数之旅以下图:

 

3、回首来路,旅程给Vue加了哪些戏

  若是直接从Vue实例化开始阅读源码(通常思路)在阅读的过程当中经常会碰见一些没有见过的属性和方法。心中就会产生疑惑,这些属性的值是啥,在哪赋值定义的。为了解决这个疑问,本节经过“回顾旅程”,其实也是vuejs执行顺序,来看看程序对Vue构造函数都加了哪些“戏”。

  Vue(options)做为函数本质上也是一个对象。做为构造函数其原型对象将会对实例产生影响。因此整理出了vuejs执行时挂载在函数上和函数原型上的属性方法有哪些。

vue的构造函数的文件在\vue-dev\src\core\instance\index.js,源码以下:

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._init(options)
}

initMixin(Vue)
stateMixin(Vue)
eventsMixin(Vue)
lifecycleMixin(Vue)
renderMixin(Vue)

export default Vue

 

vue构造函数先是作一个安全模式的处理,告诉使用者必须经过new字符串调用Vue方法,而后用了一个_init(options)方法。能够推断出_init方法是在Vue原型对象上的方法。

构造函数以外还执行了许多从几个依赖中引入的函数,并将构造函数做为参数传入方法。整理事后再instance/index.js中对Vue以及Vue原型的改变以下:

 

往上一层core/index.js中,这个js导入了已经在原型上挂载了方法合属性后的Vue,而后导入initGlobalAPI为Vue导入许多全局API(咱们在阅读vue官方文档中API分为全局配置、全局API以及实例属性等全局API正是这时导入的)。initGlobalAPI中最后调用依赖引入的initUse、initMixin、initExtend、initAssetRegisters等方法

而后和在Vue原型对象上定义$isServer、$ssrContext两个访问器属性和为Vue添加version属性。具体添加见下图:

 

 

再往回看,咱们回到runtime/index.js。这个js显示为Vue下的config属性添加属性和赋值,再是将平台相关的directives和components扩展给Vue.options下的directives和components中也就是安装平台特有的指令和组件。

而后为判断是否在浏览器中,若是是就为Vue原型对象安装平台patch功能。接着定义公共的$mount方法。

Vue.prototype.$mount = function (
  el?: string | Element,
  hydrating?: boolean
): Component {
  el = el && inBrowser ? query(el) : undefined
  return mountComponent(this, el, hydrating)
}

$mount方法先是根据是否在浏览器决定是否query(el),最后都将参数传给core/instance/lifecycle.js中的mountComponent方法,该方法负责挂载更新组件。

具体添加以下:

 

最后是runtime/entry-runtime-with-compiler.js,这个文件首先是缓存原型对象中存在的$mount方法,而后用一个能够将template/el转化为render函数的方法覆盖原型对象的$mount方法。而后定义了Vue.compile属性为compileToFunctions 函数。这个函数的做用就是将模板 template 编译为render函数。以下图所示:

汇总一下:

第一步:instance/index.js 为Vue.prototype定义实例属性、实例方法/数据、实例方法/事件以及生命周期等实例相关的属性方法。

第二步:core/index.js  主要是经过initGlobalAPI挂载平台相关的全局API 和Vue须要用到的工具函数

第三部:runtime/index.js   是添加平台相关的指令、组件和配置参数和$mount方法

第四部    runtime/entry-runtime-with-compiler.js 给Vue添加了能够支持template的$mount方法和compiler编译器。

回首之旅结束,但愿你获得你想获得的。

如此一路走来,我想咱们应该是知道vue源码学习的大体框架了。知道了去哪找对应功能模块的代码,以及vue对象上的属性和vue原型对象上的属性有粗略的了解。若是碰见也知道要去哪认识他们,而不是带着苦恼前行了。

但愿能帮到你~

 注:本文章中学习的源码版本为vue  2.5.2. 文章中涉及的观点和理解均是我的的理解,若有偏颇或是错误还请大神指出,不吝赐教,万分感谢~

相关文章
相关标签/搜索