js代码在载入完后,是立即执行的。
Document
var item = document.getElementById("item")
console.log(item)//null
我是html结构,我要展示到页面上!
通过这个简单的例子我们可以看到,js代码在加载完后,是立即执行的,执行完后,body才开始解析渲染,所以是找不到item,所以为null。
js代码执行时会阻塞页面渲染(由于GUI渲染线程和js引擎线程互斥。具体原理可以看这)
Document
while (true) {
console.log(1)
}
我是html结构,我要展示到页面上!
测试结果,浏览器一直处于加载过程,一会就卡死了。html整个结构(包括html标签)都无法渲染出来。js的下载和执行会阻塞Dom树的构建。
86250c123e53
1523429832(1).jpg
所以,我们一般采用以下方法
将所有的script标签放到页面底部,也就是body闭合标签之前,这能确保在脚本执行前页面已经完成了DOM树渲染。
如果放在上面,将js包裹在$(document).ready(function(){})或者$(function(){})里面
,或者window.οnlοad=function(){}里面。
具体这几句代码什么意思,可以看一下文章
ready和onload的区别
1.浏览器线程浏览器有这么几大线程:UI渲染线程(用于页面的渲染),javascript引擎线程(用于处理js),GUI事件触发线程(用于交互)。
有时会开启的线程:http传输线程,定时触发线程(定时器)
它们之间的关系是什么呢?
(1)UI渲染线程 与 javascript引擎线程 互斥
由于javascript可以操纵页面的DOM,所以如果UI渲染线程与javascript引擎线程 不互斥的话,在UI渲染线程进行页面渲染的同时,javascript引擎线程进行DOM修改,最终会造成DOM状态不一致的现象。所以,当javascript引擎线程运行的时候,UI渲染线程处于冻结状态。
(2)javascript引擎线程 与 GUI事件触发线程(用于交互) 异步
浏览器开启事件触发线程,等待用户动作,事件触发线程解析为响应事件,转移到javascript引擎线程,排队等候,等待javascript引擎的处理。
(3)javascript引擎线程 与 http传输线程 异步
网页get,post等请求,xhr异步请求都通过http传输线程,传送到javascript引擎排队,等候处理。
(4)javascript引擎线程 与 定时触发线程(定时器) 异步
setTimeout(),setInterval()由单独的线程 定时触发线程 触发,传送到javascript引擎排队等候,等待处理。
上述的所有的异步操作有不同的浏览器分配线程执行,那个先执行完就先将那个加入到异步队列中,利用事件的轮询执行异步队列中的回调函数
react在16+的版本中使用fiber架构解决了很多的问题。react在调用this.setState()的过程中使用的脏处理(从root节点开始调用),在以前的版本中就会导致卡顿,尽管有虚拟dom,diff算法,但是如果递归的足够深,如div-div一直有100个div,在重新渲染阶段会造成卡顿现象,如在渲染过程中不能触发点击事件等,也正是因为这个原因,fiber架构的出现就是为了解决递归过深,会造成无可避免的卡顿这个问题。
fiber架构采用了chrome浏览器的一个api requestIdleCallback(callback[, options]),在浏览器的空闲阶段调用,这里可能会扯很多浏览器的概念,由于这个api目前只支持谷歌,所以react也对此方法进行了重写。
浏览器的空闲阶段调用究竟是怎么回事呢?就必须要说一说浏览器了。
浏览器是一个多进程。Browser 进程,第三方插件进程,gpu进程,和重中之重浏览器内核。
下面我们说一下浏览器内核,一个进程可以有多个线程。那浏览器内核也不为过
如gui渲染线程,js引擎线程,定时器线程,事件触发线程,http请求线程。
我们经常会说gui渲染线程和js引擎线程互斥,因为js在浏览器中运行也可以操作dom,为了防止渲染出现问题,所以互斥,就说到这里。
浏览器在每一帧(时间片)都是在工作的,和我们人一样,上班都需要工作,只是在工作的时候没有活,我们可以偷懒一会。
一个看上去不卡的浏览器,必须需要1s中完成60帧,也就是16ms为一帧,当你的显卡更好的时候,肯定是不止60帧这个数值。但是这里就使用60帧,这个概念,所以说打游戏的时候fps低帧数低,每秒加载的帧数低就算网络不卡,你也会玩起来很卡。
每一帧
1.首先会先进行事件处理,如果有事件触发了且有回调函数,如触发点击事件,且有回调函数,会把回调函数放到eventLoop的宏任务队列中。
2.然后执行js,在js调用栈为空,先去执行宏任务队列最先执行的,再执行所有微任务队列里面的方法,执行完再去执行js调用栈,来回循环调用。
3.执行requestanimationframe,再重绘之前执行
4.解析html --先把html解析成dom树,构建渲染树,布局渲染树,绘制渲染树,这也是一个经常会出的面试题,重绘 -重排(重绘-回流)。
5.剩余时间片,好比人工作总会有没事的时候。requsetIdleCallback就是再剩余时间片执行,如果这个帧没有剩余时间片,那就下一个帧寻找是否有剩余时间片。
我们渲染阶段放到requestIdleCallback中,之前的递归必须递归到底,但是现在我可以放到每一个时间片之中,requestIdleCallback(workLoop,{timeout:1000}),再workloop方法中我再调用requestIdleCallback(workLoop,{timeout:1000}),这样每一帧的空闲时间我都会去调用workloop方法,这样我就不需要一次性递归到底,也就避免了造成卡顿。
下面我来说一说fiber是怎么实现的。
这里说的肯定不可能太具体,只能讲一点思路,下次写博客深入理解fiber机构。
如果了解过一点fiber的数据结构知道,fiber的数据结构中有
React.createElement是为了帮我们创建虚拟dom的。我们需要通过虚拟dom去创建对应的fiber。
componentDidmounted是从外到内执行的,好比先序遍历,而componentWillUnmounted是从里往外执行,后序遍历。
那么fiber调用的时候肯定就会出现这两个阶段。
在先序遍历的时候我们调用beginWork 根据先给fiber.stateNode=dom/new实例,在给其子节点创建fiber
在后续遍历中,我们调用completeUnitOfWork,完成effectlist,
effectlist 先调用根节点的firstEffect即d,然后一次nexteffect,即d-b-c-a
最后在commit阶段根据effectList,依次appendChild,removeChild,或者更新属性,完成渲染。