一道字节面试题
这是之前面试字节的时候,出的第一道题,里面涉及的知识点还挺多的,跟大家分享下
<!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>
上面这段代码依次输出什么?
先看答案:
是不是跟你想的不一样呢?
这道题涉及到两个知识点,第一个是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之前的