2

学习vue源码时,我们首先需要看的是package.json文件,该文件里配置了vue的依赖以及开发环境和生产环境的编译的启动脚本等其他信息。首先我们需要关注的是script。我们这里先看第一个dev脚本:

"dev": "rollup -w -c scripts/config.js --environment TARGET:web-full-dev"

我们可以看到vue是采用了rollup编译的脚本,然后对应的查看其配置文件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
  },
...
}

在该文件中我们可以找到web-full-dev的配置项,并且知道其编译的文件为'web/entry-runtime-with-compiler.js'
那么我们就需要去找到该文件了。
我们可以发现entry-runtime-with-compiler.js的其中有一行代码是:

import Vue from './runtime/index'

然后继续跟着代码往上找,我们会发现还是嵌套了好几层,最后在'/instance/index'中找到我们vue的定义:最终其路劲如下:

/src/platforms/web/web-runtime-with-compiler.js   
=> /src/platforms/web/runtime/index.js 
=> /src/core/index.js 
=> /src/core/instance/index.js 

最终我们在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._init(options)
}

我们可以发现它并没有使用class的,只是一个普通的构造函数,通过 !(this instanceof Vue) 来强制使用new来构建。
之所以不采用class,个人理解是为了更好的把代码拆分。原型上的方法只需要通过prototype来添加。如下

initMixin(Vue) // 这里主要注册了_init
stateMixin(Vue) // $set,$delete,$watch
eventsMixin(Vue) // $on, $once, $off, $emit
lifecycleMixin(Vue) //  _update, $forceUpdate,$destroy
renderMixin(Vue) // $nextTick, _render

这边都是基于在Vue上扩展方法,这样就把代码分离开发,方便维护。不需要全部写到Vue函数内部。
然后我们往回走,我们可以看到/src/core/index.js 中

initGlobalAPI(Vue)
Object.defineProperty(Vue.prototype, '$isServer', {
  get: isServerRendering
})
Object.defineProperty(Vue.prototype, '$ssrContext', {
  get () {
    /* istanbul ignore next */
    return this.$vnode && this.$vnode.ssrContext
  }
})

Object.defineProperty(Vue, 'FunctionalRenderContext', {
  value: FunctionalRenderContext
})

Vue.version = '__VERSION__'

该文件中主要就是注册全局API,以供我们内部或外部使用
再往上/src/platforms/web/runtime/index.js,我们可以看到

// install platform specific utils
Vue.config.mustUseProp = mustUseProp
Vue.config.isReservedTag = isReservedTag
Vue.config.isReservedAttr = isReservedAttr
Vue.config.getTagNamespace = getTagNamespace
Vue.config.isUnknownElement = isUnknownElement

// install platform runtime directives & components
extend(Vue.options.directives, platformDirectives)
extend(Vue.options.components, platformComponents)

// install platform patch function 
Vue.prototype.__patch__ = inBrowser ? patch : noop // 用于把vNode显示到dom上的方法,这边要区分是浏览器环境还是weex环境

// public mount method
Vue.prototype.$mount = function () {
.....
} // 把dom挂在到页面上

这边就是注册一些全局的工具,以及patch方法
那么再往上:src/platforms/web/web-runtime-with-compiler.js, 这个文件中主要就是重写$mount

const mount = Vue.prototype.$mount
Vue.prototype.$mount = function (
){
...
}

那么为什么要重写呢?原来在runtime里面的$mount方法是没有编译功能的,而最后一个重写就是增加了编译。
在vue脚手架我们会让我们选择哪个版本:如下图

clipboard.png

这就是2个版本的区别,是否包含编译功能。一个是完整版,一个是运行时。
vue官方属于解释了:

完整版:同时包含编译器和运行时的版本。
编译器:用来将模板字符串编译成为 JavaScript 渲染函数的代码。
运行时:用来创建 Vue 实例、渲染并处理虚拟 DOM 等的代码。基本上就是除去编译器的其它一切。

如果你需要在客户端编译模板 (比如传入一个字符串给 template 选项,或挂载到一个元素上并以其 DOM 内部的 HTML 作为模板),就将需要加上编译器,即完整版:
// 需要编译器
new Vue({
  template: '<div>{{ hi }}</div>'
})

// 不需要编译器
new Vue({
  render (h) {
    return h('div', this.hi)
  }
})

重写$mount方法就是对template就行了编译转换为render方法
好了这边就简要的介绍了vue的入口。

您的点赞是我继续努力的动力!


DanielDemi
159 声望8 粉丝

开始前端之旅