从源码解惑-$mount执行后,被挂载的节点最后是如何处理的

2020/5/13 11:26:14

本文主要是介绍从源码解惑-$mount执行后,被挂载的节点最后是如何处理的,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

问题描述

当你用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')**报错,可以很轻松的得出结论。

但是本着知其然,知其所以然的态度,我们打开源码验证一下猜想

验证猜想

为了方便查找到我们要看的源码,所以选择在浏览器打断点调试。那么断点打在哪里比较合适呢,有两种思路

  1. 将断点打在app.js的new Vue()这行代码,顺着vue初始化的流程去追,追到页面dom节点渲染出来时,就可以往回找,通过打新的断点不断往下追,但是因为vue项目非常庞大,这种方法追踪起来效率很低,很容易迷失。(别问我怎么知道的)
  2. 第二种方法需要对vue源码项目的目录结构有一定了解,因为这种大型项目的文件分组都是有一定规律的。vue将所有dom更新的方法都放在了patch中,所以我们打开patch.js。这里需要做一个设想,声明了两个#app,最后渲染出来只有一个,那么肯定移除了另一个。所以我们要在这个文件中找一下有没有关于移除dom的方法,果然找到了removeNode,于是我们将断点打在这个方法上,刷新页面,果然进入了这个断点,这也验证了我刚才的猜想
  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中找到相应的方法打上断点直接调试

remove node

结论

讲到这里,结论基本上已经出来了,那就是在index.html和App.vue中存在两个#app,经过$mount挂载后,最终渲染在body中的是App.vue中的那个#app,index.html中的#app则会被移除。

引申

不知道大家有没有想过作者为什么要这么设计呢?乍一看有点多此一举的感觉。我说一下我的想法。

  1. 我们现在的spa项目越做越大,即使做了懒加载,但是首屏以然需要加载很多基础环境地方js包,所以避免不了需要等待,那么这个时候,如果我们在index.html的#app里面写一个loading动画,那么在实际的App.vue内容没有被渲染出来,我们就能在页面上看到一个loading状态,能够显著的提升用户体验。当App.vue中的内容渲染完后,之前的节点就会被移除,loading动画自然也就消失了。
  2. 第二点其实还是关于页面等待体验优化的地方,那就是骨架屏。现在的很多h5页面都采用了骨架屏,不管方案如何,最终骨架屏在正式的内容加载出来后都应该自己消失的,那么写在这里也很合适、
  3. 至于其他的点,我暂时还没想出来,欢迎大家评论补充

另外,有了完整的调用堆栈信息,想要深挖$mount过程就很容易了



这篇关于从源码解惑-$mount执行后,被挂载的节点最后是如何处理的的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!


扫一扫关注最新编程教程