从入口开始
src/platfroms/web/entry-runtime-with-compiler.js
通过源码解决这道面试题
<!-- examples/02-debug/index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Runtime+Compiler</title>
</head>
<body>
<div id="app">
Hello World
</div>
<script src="../../dist/vue.js"></script>
<script>
// 如果同时设置template和render此时会渲染什么?
const vm = new Vue({
el: '#app',
template: '<h1>Hello Template</h1>',
render(h) {
return h('h1', 'Hello Render')
}
})
</script>
</body>
</html>
观察以上代码,通过阅读源码,回答在页面上输出的结果
entry-runtime-with-compiler.js 都做了哪些事情
可以看出 这里重写了vue原型上的 $mount 方法
重写 $mount 方法
- 🔲 获取 el 对象,判断是否是body 或者 html
//./util/index.js
/* @flow */
import { warn } from 'core/util/index'
export * from './attrs'
export * from './class'
export * from './element'
/**
* Query an element selector if it's not an element already.
*/
export function query (el: string | Element): Element {
if (typeof el === 'string') {
const selected = document.querySelector(el)
if (!selected) {
process.env.NODE_ENV !== 'production' && warn(
'Cannot find element: ' + el
)
return document.createElement('div')
}
return selected
} else {
return el
}
}
找到el这个选择器对应的 DOM元素,如果没有找到,且当前处于开发环境,控制台打印警告,并且返回一个div元素。
拿到el 对应的 DOM元素之后判断是否是 body 和 html
// entry-runtime-with-compiler.js
// 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
}
- 🔲 判断 options 有无传入 render
- 🔲 没有 则把template 转成 render函数
- 🔲 有 则直接调用 mount 挂载 DOM
...
const options = this.$options
// resolve template/el and convert to render function
// 把 template/el 转换成 render 函数
if (!options.render) {
...
}
// 调用 mount 方法,渲染 DOM
return mount.call(this, el, hydrating)
...
调试代码
<!-- examples/02-debug/index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Runtime+Compiler</title>
</head>
<body>
<div id="app">
Hello World
</div>
<script src="../../dist/vue.js"></script>
<script>
// 如果同时设置template和render此时会渲染什么?
const vm = new Vue({
el: '#app',
template: '<h1>Hello Template</h1>',
render(h) {
return h('h1', 'Hello Render')
}
})
</script>
</body>
</html>
运行到浏览器打开控制台,打开代码地图,找到src下platform/web 中的 entry-runtime-with-compiler.js 打断点
Call Stack 中可以看到,$mount 在vue的 _init 函数中被调用,_init 函数 在Vue 的构造函数中被调用, 构造函数在 new Vue实例的时候触发。
F10 进行下一步,这里el 对应的DOM 不是body也不是html,if 判断不会成立。
接下来判断 options中是否传入了render 函数, F10 继续
new Vue的时候有传入 render ,故而 if 判断不成立,直接执行mount函数挂载 DOM
按F10继续,mount函数的执行之后页面发生了视图的变化
总结
通过阅读源码以及调试代码的过程可以得到
- el 不能是html 和 body
- 如果没有render函数,将template转化为 render函数
- 如果有render函数, 直接调用mount挂载DOM
// 1. 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
if (!options.render) {
// 2. 把 template/el 转换成 render 函数
……
}
// 3. 调用 mount 方法,挂载 DOM
return mount.call(this, el, hydrating)
下一章
通过调试入口文件 entry-runtime-with-compiler.js 已经找出Vue的构造函数的位置
// 构造函数的位置
src/core/instance/index.js
接下来继续阅读源码梳理Vue初始化的过程以及Vue实例的成员以及静态成员从哪里被定义的。