看一道前端面试题

339 阅读2分钟

一道字节面试题

这是之前面试字节的时候,出的第一道题,里面涉及的知识点还挺多的,跟大家分享下

<!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" />
    <title>Document</title>
  </head>
  <script>
    console.log("1", document.getElementById("root"));
    setTimeout(() => {
      console.log("2", document.getElementById("root"));
    }, 0);
    Promise.resolve().then((res) => console.log("3", document.getElementById("root")));
    console.log("4", document.getElementById("root"));
  </script>
  <body id="root">
    <div id="div">test</div>
  </body>
</html>

上面这段代码依次输出什么?

先看答案:

image.png

是不是跟你想的不一样呢?

这道题涉及到两个知识点,第一个是html渲染,第二个是js的事件循环机制。

html渲染

html渲染简单来说,从上往下依次渲染,当碰到script标签时,会阻塞,先执行script标签里面的内容,所以说,上面这段代码在执行的时候,整个html页面还没有渲染完,下面的 body 节点还没有渲染,所以document.getElementById("root")就取不到元素,返回null,至于为什么放在setTimeout里面的函数可以拿到root节点,这与事件循环机制有关,等会再解释。

那有没有什么办法让script不阻塞页面的渲染呢?

可以在script标签上增加async或者defer属性,并且把js脚本用src的方式引用,这样script就不用阻塞页面的渲染的,因为这段代码比较简单,我自己试了一下,不管是使用async还是deder,上面的都能打印出结果

这里还要说一下async和defer的区别,async是渲染到script标签时,就开始执行里面的js代码,而defer需要等document页面渲染完成之后再执行

事件循环

为什么js会有事件循环机制?

js是一门单线程的语言,这就导致在执行一些耗时间比较长的代码的时候,会发生阻塞,比如说我们去发一个请求,要执行3秒,这时候就会一直卡在那儿,为了解决这种问题,有了js的异步机制,把这种需要耗时间比较长的任务放在一个队列里面,等主流程走完之后,我们再去执行异步队列里面的代码

而异步队列又分为宏任务和微任务,执行顺序是先执行一边同步任务,然后执行微任务,再执行宏任务,微任务和宏任务交替执行,这就是js的事件循环机制

其中 setTimeout是宏任务,promise.then是微任务

我们在看上面的代码,先输出1,4,这是同步任务,再执行3,这是微任务,最后执行2(宏任务)

为什么放在setTimeout里面的函数可以拿到root呢?可能是setTimeout里面的函数要等dom都渲染完毕之后再执行吧,这一点其实我也不是太清楚,只能说render是在setTimeout之前的