查找入口文件
当我们运行 npm run dev 启动项目的时候,实际是执行:
"rollup -w -c scripts/config.js --sourcemap --environment TARGET:web-full-dev"
其中定义了 web-full-dev 这个环境,在 scripts/config.js 配置文件中定义了 web-full-dev 对应的入口文件:
'web-full-dev': {
entry: resolve('web/entry-runtime-with-compiler.js'),
dest: resolve('dist/vue.js'),
...
}
即入口文件为:platforms/web/entry-runtime-with-compiler.js,接下来,我们通过调试来查看入口文件中的内容。
调试入口文件
写一个 demo ,其中引入我们打包后的 vue.js:
<body>
<div id="app">
vue
</div>
<script src="../../dist/vue.js"></script>
<script>
var vm = new Vue({
el: '#app',
template: '<h1>template</h1>',
render(h) {
return h('h1', 'render')
}
})
</script>
</body>
在浏览器中打开,并在控制台的 sources 栏下,找到入口文件 platforms/web/entry-runtime-with-compiler.js,在 $mount 中设置断点,然后刷新界面。
此时我们看到右侧的 Call Stack 调用栈,这里可以看到方法的执行的过程:
- 当前正在执行的是
Vue.$mount方法, - 上一个是
Vue._init方法,点击这个方法可以进入,表示在这个方法中调用了$mount方法。 - 再往下是
Vue函数,表示在Vue中调用了Vue._init。 - 最后是一个匿名函数,这个匿名函数是在
index.html中调用new Vue的时候执行的构造函数,表示在这里调用了Vue。
查看入口文件
- 根据
el参数选项找到对应的界面元素,判断el不能是body或者html标签 - 如果没有
render,把template转换成render函数 - 如果有
render方法,直接调用mount挂载DOM
我们可以对比一下 entry-runtime.js 文件,其中只是非常简单的导入导出了 runtime 目录下的 index 中的 Vue,虽然 entry-runtime-with-compiler.js 中也是导入导出了 runtime 目录下的 index 中的 Vue,但是添加了一些代码,重写了 Vue.prototype.$mount 方法,其中核心是将 template 转换为了 render 函数,也就是添加了编译的过程。
接下来,看看 runtime 目录下的 index 中的 Vue: platforms\web\runtime\index.js。
platforms\web\runtime\index.js
-
在
Vue.config上注册了一些和平台相关的特定通用的方法 -
通过
extend注册了一些全局的指令和组件
包含 web 平台特有的指令(v-model/v-show)和组件(<transition>/<transition-group>)
- 在
Vue的原型上注册了__patch__方法
- 通过
inBrowser来判断是否是浏览器环境
typeof window !== 'undefined' // inBrowser 通过是否有 window 全局变量来判断是否是浏览器环境
- 非浏览器环境会返回
noop,noop是一个空函数
export function noop (a?: any, b?: any, c?: any) {}
- 当前是浏览器环境,直接返回
patch
patch 的功能是将虚拟 dom 转换成真实 dom,在学习 snabbdom 的时候,看过这个方法。
- 在
Vue的原型上注册了$mount方法
$mount 中调用了 mountComponent,它的作用就是渲染 DOM
- 最后是一段关于
devtools调试相关的代码,我们不关心
platforms 目录下的文件主要包含和平台相关的代码,接下来顺着 Vue 导入的地方,继续找到 core\index.js。
core\index.js
initGlobalAPI给Vue的构造函数添加一些静态属性和方法:
Vue.config
Vue.util:存储公用的内部方法
Vue.set
Vue.delete
Vue.nextTick
Vue.observable:设置响应式数据
Vue.options
Vue.use
Vue.mixin
...
- 往
Vue的原型上添加了一些成员,这些成员都是和服务的渲染相关的,可以忽略 - 设置
Vue的版本
Vue.version = '__VERSION__'
继续顺着 Vue 导入的地方,找到 core\instance\index.js。
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._init(options)
}
initMixin(Vue) // 给 Vue 原型上添加 _init 方法
stateMixin(Vue) // 混入 $data,$props 等属性
eventsMixin(Vue)
lifecycleMixin(Vue)
renderMixin(Vue)
- 创建了
Vue构造函数。非生产环境,必须使用new关键字调用Vue,否则会提示警告 - 设置
Vue实例的成员,调用_init方法,这个方法是在initMixin中初始化的。
为什么用构造函数实现,而不用类实现 Vue: 因为需要往 Vue 原型上挂载很多属性方法,如果用类的话,再使用原型就很不搭。
总结
四个导出 Vue 的模块:
src/platforms/web/entry-runtime-with-compiler.js
web平台相关的入口- 重写了平台相关的
$mount()方法,添加将template转换为了render函数的代码,注册了Vue.compile()方法,传递一个HTML字符串返回render函数 - 主要就是增加了编译的功能
src/platforms/web/runtime/index.js
- web 平台相关
- 注册和平台相关的全局指令:
v-model、v-show, 注册和平台相关的全局组件:transition、transition-group, 都挂载到了Vue.options.directives和Vue.options.components上 - 原型上注册了两个全局方法:
__patch__用于把虚拟DOM转换成真实DOM,$mount是挂载方法
src/core/index.js
- 与平台无关
- 设置了
Vue的静态方法initGlobalAPI,给Vue的构造函数添加了很多静态属性和方法
src/core/instance/index.js
- 与平台无关
- 定义了构造函数,调用了
this._init(options)方法,给Vue中混入了常用的实例成员