当你用vue-cli创建一个工程后,会看到index-html文件里有一个div,id叫app
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width,initial-scale=1.0"> <link rel="icon" href="<%= BASE_URL %>favicon.ico"> <title></title> </head> <body> <noscript> <strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong> </noscript> <div id="app">app</div> <!-- built files will be auto injected --> </body> </html> 复制代码
此时App.vue文件中也有一个div,id也叫app
<template> <div id="app"> hello world! </div> </template> 复制代码
但是根据规定,id应该具有唯一性,那么问题来了,我们在main.js里面挂载的那个#app是哪个?最终渲染到页面上的那个#app又是哪一个呢?
new Vue({ router, render: (h: any) => h(App), }).$mount('#app'); 复制代码
想要知道这些问题的答案,我们可以做一个简单的实验,比如把这两个文件中的某一个id改掉,看一下页面中最终渲染的是哪个,看一下哪个文件中的id改变会导致执行**$mount('#app')**报错,可以很轻松的得出结论。
但是本着知其然,知其所以然的态度,我们打开源码验证一下猜想
为了方便查找到我们要看的源码,所以选择在浏览器打断点调试。那么断点打在哪里比较合适呢,有两种思路
function removeNode (el) { const parent = nodeOps.parentNode(el) // element may have already been removed due to v-html / v-text if (isDef(parent)) { nodeOps.removeChild(parent, el) } } 复制代码
我们将鼠标悬浮在入参el上,可以看到,要被移除的元素是index.html里的#app,而且此时App组件中的内容已经渲染到了页面上,所以另一个#app很大概率是被append到body里面的,如果想分析另一个元素挂载过程,同样可以在patch中找到相应的方法打上断点直接调试
讲到这里,结论基本上已经出来了,那就是在index.html和App.vue中存在两个#app,经过$mount挂载后,最终渲染在body中的是App.vue中的那个#app,index.html中的#app则会被移除。
不知道大家有没有想过作者为什么要这么设计呢?乍一看有点多此一举的感觉。我说一下我的想法。
另外,有了完整的调用堆栈信息,想要深挖$mount过程就很容易了