介绍一下EventLoop
因为JS是单线程的,如果一个事件执行时间过长,则会导致整个程序卡死,所以对于异步任务,浏览器会维护消息队列。
在执行代码的时候,如果遇见异步任务,那么浏览器将交给对应的浏览器线程处理,然后再将结果放在对应的队列中。(渲染进程将定时器添浏览器延迟执行的任务列表,将网络请求交网络进程处理。等他们满足条件时,再加入到浏览器的消息队列中)。
每个宏任务在执行时,V8 都会重新创建栈,然后随着宏任务中函数调用,栈也随之变化。这也是为什么宏任务调用自己时,不会造成栈溢出的原因。
const foo = () => { setTimeout(foo, 1000); } foo()
如果主线程的代码执行完毕,那么就回去对应的任务队列中取出事件,并执行。
宏任务
- setTimeout
- setInteval
- 网络请求
- UI Rendering
- DOM监听 微任务
- promise.then
- mutationObserver
- queueMicrotask
EventLoop中为什么要同时存在宏任务和微任务两个队列?设计一个行不行?一段代码在执行时,程序是如何去区分宏任务和微任务的?
主要是由于主线程执行消息队列中宏任务的时间颗粒度太粗了,无法胜任一些对精度和实时性要求较高的场景,那么微任务可以在实时性和效率之间做一个有效的权衡。
另外使用微任务,可以改变我们现在的异步编程模型, 使得我们可以使用同步形式的代码来编写异步调用。
主要是区分哪些任务是宏任务哪些是微任务。
内存泄露
项目中内存泄漏的场景
哪些方式可以造成内存泄漏
- 在函数中定义未声明的变量。导致变量作为全局window对象的属性
- 闭包的不正当使用
- 递归调用,没有终止条件。
- js引用dom节点。以前写过一篇文章
setTimeout为什么会造成内存泄露?如何防止setTimeout内存泄露?清除定时器为什么就不会有内存泄露?
每个宏任务在执行时,V8 都会重新创建栈,然后随着宏任务中函数调用,栈也随之变化。这也是为什么宏任务调用自己时,不会造成栈溢出的原因。
这个问题,大佬可以解释一下吗???
介绍一下http缓存
- 追问:哪些字段用做强缓存?哪些字段用做协商缓存? 强缓存:cache-control, expires。
协商缓存:if-modified-since,last-modified, if-no-match, etag
- 追问:cache-control、expires、etag等字段的属性值是什么样的?cache-control
- no-cahce: 表示缓存,但是每次需要去服务器确认该缓存是否过期。
- no-store:表示不缓存
- max-age=nums: 表示缓存资源多少秒
- s-max-age=nums:表示资源在代理服务器中缓存的时长
- public:表示可以在客户端或者代理服务器中缓存
- private: 表示只能在客户端上缓存。
- 追问:这些字段都被存放在请求的哪个部分? 请求头中
- 追问:last-modified和expires这些字段的时间有什么区别? last-modified但是是秒,而expires但是为JMP格式的时间。
- 追问:last-modified和expires能共存吗? 可以共存的。
- 追问:如果不想让某个资源使用缓存,那么应该如何设计http缓存? cache-control: no-store。
- 追问:cache-control中的no-cache和no-store的区别no-cache。是把资源进行了本地缓存,在浏览器使用缓存之前,会使用last-Modified和Etag往返浏览器进行对比,判断时间和唯一标识符和服务器的是否一致,一致的话304使用缓存,不一致的话请求服务器。
no-store。表示不缓存内容。
介绍一下宏任务和微任务
- 追问:哪些是宏任务?哪些是微任务? 宏任务: setTimeout, setInterval, 网络请求, DOM监听
微任务:mutationObserver,queueMicrotask,promise.then
- 追问:宏任务和微任务的区别是什么?为什么要设计宏任务和微任务两个队列?使用一个任务队列行不行?为什么?
宏任务时间粒度比较粗大,对于一些实时性和精度要求比较高的要求完成不了。微任务可以,并且微任务还可以满足我们现在对于异步回调函数的写法。
主要是由于主线程执行消息队列中宏任务的时间颗粒度太粗了,无法胜任一些对精度和实时性要求较高的场景,那么微任务可以在实时性和效率之间做一个有效的权衡。
另外使用微任务,可以改变我们现在的异步编程模型, 使得我们可以使用同步形式的代码来编写异步调用。
每个宏任务中都维护了一个微任务队列。执行宏任务之前,都必须把微任务列表中的任务全部执行完。
- 追问:你刚刚所说的都是根据api来识别微任务和宏任务的,那么一段完整的程序浏览器是如何区分宏任务和微任务的呢? 浏览器中除了渲染进程外,还具有很多的进程。当代码执行到对应的回调时,浏览器渲染进程自动将他们交给对应的进程处理。处理完毕后,再将他们放在消息队列中,js主线程将不断循环去取出并执行。
微任务的优先级
谁先加入队列,就先执行谁。
是这样吗???
如何理解script标签是个宏任务
因为未加async, defer属性的script的下载和执行都会影响到html文档的解析。从而影响页面加载。
http1.1和http2的区别
onload 和 DOMContentLoaded的区别
onload: 他会等到页面所有资源全部下载完毕后在执行。
DOMContentLoaded: 当html文档解析完毕后就会去执行,不会等待其他资源下载完成。
requestAnimationFrame
告诉浏览器——你希望执行一个动画,并且要求浏览器在下次重绘之前调用指定的回调函数更新动画。该方法需要传入一个回调函数作为参数,该回调函数会在浏览器下一次重绘之前执行。
我们知道 CSS 动画是由渲染进程自动处理的,所以渲染进程会让 CSS 渲染每帧动画的过程与 VSync 的时钟保持一致, 这样就能保证 CSS 动画的高效率执行。
当显示器将一帧画面绘制完成后,并在准备读取下一帧之前,显示器会发出一个垂直同步信号(vertical synchronization)给 GPU,简称 VSync。 当 GPU 接收到 VSync 信号后,会将 VSync 信号同步给浏览器进程,浏览器进程再将其同步到对应的渲染进程,渲染进程接收到 VSync 信号之后,就可以准备绘制新的一帧了。所以requestAnimationFrame执行时间就是根据浏览器每次接收到的VSync信号之后调用的。
requestAnimationFrame是在下一帧动画重绘之前执行传入的函数。能够保证传入的回调函数执行次数通常与浏览器屏幕刷新次数相匹配,一般是每秒钟60次。
但是setTimeout函数执行的间隔时间不一定是约定好的间隔时间,还与当前事件循环中的任务执行的时间有关,如果执行的时间太长的话,setTimeout里面的函数将会被延迟执行。
浏览器加载页面的过程
按照渲染的时间顺序,流水线可分为如下几个子阶段:构建 DOM 树、样式计算、布局阶段、分层、绘制、分块、光栅化和合成。
- 开始每个子阶段都有其输入的内容;
- 然后每个子阶段有其处理过程;
- 最终每个子阶段会生成输出内容。
构建 DOM 树
为什么要构建 DOM 树呢?这是因为浏览器无法直接理解和使用 HTML,所以需要将 HTML 转换为浏览器能够理解的结构——DOM 树。
DOM 和 HTML 内容几乎是一样的,但是和 HTML 不同的是,DOM 是保存在内存中树状结构,可以通过 JavaScript 来查询或修改其内容。
样式计算(Recalculate Style)
把 CSS 转换为浏览器能够理解的结构
和 HTML 文件一样,浏览器也是无法直接理解这些纯文本的 CSS 样式,所以当渲染引擎接收到 CSS 文本时,会执行一个转换操作,将 CSS 文本转换为浏览器可以理解的结构——styleSheets。可以通过document.styleSheets查看结构。
转换样式表中的属性值,使其标准化
现有的 CSS 文本转化为浏览器可以理解的结构了,那么接下来就要对其进行属性值的标准化操作。
CSS 文本中有很多属性值,如 2em、blue、bold,这些类型数值不容易被渲染引擎理解,所以需要将所有值转换为渲染引擎容易理解的、标准化的计算值,这个过程就是属性值标准化。
计算出 DOM 树中每个节点的具体样式.
样式计算阶段的目的是为了计算出 DOM 节点中每个元素的具体样式,在计算过程中需要遵守 CSS 的继承和层叠两个规则。这个阶段最终输出的内容是每个 DOM 节点的样式,并被保存在 ComputedStyle 的结构内。
这就涉及到 CSS 的继承规则和层叠规则了。
- 首先是 CSS 继承。CSS 继承就是每个 DOM 节点都包含有父节点的样式。
- css层叠。层叠是 CSS 的一个基本特征,它是一个定义了如何合并来自多个源的属性值的算法。它在 CSS 处于核心地位,CSS 的全称“层叠样式表”正是强调了这一点。
布局阶段
现在,我们有 DOM 树和 DOM 树中元素的样式,但这还不足以显示页面,因为我们还不知道 DOM 元素的几何位置信息。那么接下来就需要计算出 DOM 树中可见元素的几何位置,我们把这个计算过程叫做布局。
创建布局树
你可能注意到了 DOM 树还含有很多不可见的元素,比如 head 标签,还有使用了 display:none 属性的元素。所以在显示之前,我们还要额外地构建一棵只包含可见元素布局树。
布局计算
现在我们有了一棵完整的布局树。那么接下来,就要计算布局树节点的坐标位置了。
在执行布局操作的时候,会把布局运算的结果重新写回布局树中,所以布局树既是输入内容也是输出内容,这是布局阶段一个不合理的地方,因为在布局阶段并没有清晰地将输入内容和输出内容区分开来。
分层
现在我们有了布局树,而且每个元素的具体位置信息都计算出来了,那么接下来是不是就要开始着手绘制页面了?不可以。
因为页面中有很多复杂的效果,如一些复杂的 3D 变换、页面滚动,或者使用 z-indexing 做 z 轴排序等,为了更加方便地实现这些效果,渲染引擎还需要为特定的节点生成专用的图层,并生成一棵对应的图层树(LayerTree)。
通常情况下,并不是布局树的每个节点都包含一个图层,如果一个节点没有对应的层,那么这个节点就从属于父节点的图层。
元素应该满足什么样的条件,才会创建一个新的图层呢?
- 第一点,拥有层叠上下文属性的元素会被提升为单独的一层。
页面是个二维平面,但是层叠上下文能够让 HTML 元素具有三维概念,这些 HTML 元素按照自身属性的优先级分布在垂直于这个二维平面的 z 轴上。即z-index。
- 第二点,需要剪裁(clip)的地方也会被创建为图层。剪切这个是广义上的。比如多余的文字来控制怎么展示。即overflow属性。
图层绘制
在完成图层树的构建之后,渲染引擎会对图层树中的每个图层进行绘制。
渲染引擎实现图层的绘制会把一个图层的绘制拆分成很多小的绘制指令,然后再把这些指令按照顺序组成一个待绘制列表。
栅格化(raster)操作
绘制列表只是用来记录绘制顺序和绘制指令的列表,而实际上绘制操作是由渲染引擎中的合成线程来完成的。
当图层的绘制列表准备好之后,主线程会把该绘制列表提交(commit)给合成线程。
有可能一个图层很大,一个屏幕展示不下,那么在未看见的部分如果也渲染了,那么就产生很大的开销。基于这个原因。合成线程会将图层划分为图块。 这些图块的大小通常是 256x256 或者 512x512。
然后合成线程会按照视口附近的图块来优先生成位图, 实际生成位图的操作是由栅格化来执行的。所谓栅格化,是指将图块转换为位图。而图块是栅格化执行的最小单位。渲染进程维护了一个栅格化的线程池,所有的图块栅格化都是在线程池内执行的。
通常,栅格化过程都会使用 GPU 来加速生成,使用 GPU 生成位图的过程叫快速栅格化,或者 GPU 栅格化,生成的位图被保存在 GPU 内存中。
合成和显示
光栅化就是按照绘制列表中的指令生成图片。每一个图层都对应一张图片,合成线程有了这些图片之后,会将这些图片合成为“一张”图片,并最终将生成的图片发送到后缓冲区。这就是一个大致的分层、合成流程。
一旦所有图块都被光栅化,合成线程就会生成一个绘制图块的命令——“DrawQuad”,然后将该命令提交给浏览器进程。
浏览器进程里面有一个叫 viz 的组件,用来接收合成线程发过来的 DrawQuad 命令,然后根据 DrawQuad 命令,将其页面内容绘制到内存中,最后再将内存显示在屏幕上。
到这里,经过这一系列的阶段,编写好的 HTML、CSS、JavaScript 等文件,经过浏览器就会显示出漂亮的页面了。
需要重点关注的是,合成操作是在合成线程上完成的,这也就意味着在执行合成操作时,是不会影响到主线程执行的。这就是为什么经常主线程卡住了,但是 CSS 动画依然能执行的原因。
总结
- 渲染进程将 HTML 内容转换为能够读懂的 DOM 树结构。
- 渲染引擎将 CSS 样式表转化为浏览器可以理解的 styleSheets,计算出 DOM 节点的样式。
- 创建布局树,并计算元素的布局信息。
- 对布局树进行分层,并生成分层树。
- 为每个图层生成绘制列表,并将其提交到合成线程。
- 合成线程将图层分成图块,并在光栅化线程池中将图块转换成位图。
- 合成线程发送绘制图块命令 DrawQuad 给浏览器进程。
- 浏览器进程根据 DrawQuad 消息生成页面,并显示到显示器上。
为什么css动画比js动画效率高
因为js操作动画,会引起渲染线程的阻塞。但是css动画,渲染引擎会通过合成线程直接去处理变换,这些变换并没有涉及到主线程,这样就大大提升了渲染的效率。
通常渲染引擎生成一帧图像有三种方式:重排、重绘和合成。其中重排和重绘操作都是在渲染进程的主线程上执行的,比较耗时;而合成操作是在渲染进程的合成线程上执行的,执行速度快,且不占用主线程。
script标签为什么要放在底部
- 因为script的下载和执行都会阻塞html文档的解析。放在下面,可以让html文档解析不被js阻塞。
- 可能js内部有操作dom的代码,如果dom为被解析,那么js内部可能报错。
defer和async的区别,以及它们的加载和执行时机
defer: 异步加载js代码,不会阻塞html的解析。执行时机是DOMContentLoaded之前执行,并且如果有多个script都添加了该属性,那么他们将按照添加的顺序执行。
async: 异步加载js代码,不会阻塞html的解析。执行时机下载完毕后立即执行,但是在onload之前执行,不一定是在DOMContentLoaded之前执行。并且如果有多个script都添加了该属性,那么他们不会按照添加的顺序执行。
DOM事件模型。事件捕获和事件冒泡的使用场景
【前端 · 面试 】JavaScript 之你不一定会的基础题(一)
从输入url到页面展示的过程
如何设计css、js等文件的缓存
这个话题该如何回答呢???
204、304、404、504
- 204 No Content。它的含义与“200 OK”基本相同,但响应头后没有 body 数据。所以对于 Web 服务器来说,正确地区分 200 和 204 是很必要的。
- 304 Not Modified 它用于 If-Modified-Since 等条件请求,表示资源未修改,用于缓存控制。它不具有通常的跳转含义,但可以理解成“重定向已到缓存的文件”(即“缓存重定向”)。
- 404 Not found。表示服务器无法根据客户端的请求找到资源。
- 504 Gateway Timeout。是一种HTTP协议的服务器端错误状态代码,表示扮演网关或者代理的服务器无法在规定的时间内获得想要的响应。
描述一下同源策略、跨域及其解决方案
描述Jsonp具体的实现方案
JSONP为民间提出的一种跨域解决方案,通过客户端的script标签发出的请求方式。并且只支持get请求的跨域访问。
通过一些标签发出的请求则不会被进行同源检查,比如script标签,img标签等等,JSONP便是通过script标签做的请求。
- 需要和后端协商好回调函数的名称
- 后端取出query解析后,然后调用该函数,并传入数据。
- 前端就可以拿到这个数据,然后做页面渲染了。
<script> function download (data) { console.log("data",data) </script> <script> window.onload=function() { const oScript=document.createElement("script"); oScript.src=`xxx?callback=download`; document.body.appendChild(oScript); } </script>
xss和csrf的概念和防御方式
- sql注入。sql预编译解决。
- csrf(跨站请求伪造):主要是诱导用户点击黑客链接。然后向第三方站点发送请求。
- xss(跨站脚本攻击):
- 反射型,将恶意代码嵌入到url中发送给服务端。
- 持久性,将而已代码通过用户输入,发送给服务器,然后保存在数据库中。
- 基于dom,在客户端写入一些恶意代码。 如何预防
- 服务器对输入脚本进行过滤或转码。
- 充分利用 CSP
- cookie设置为httpOnly。
- html页面可以引入任何第三方资源,所以这就造成了不安全的隐患,所以可以使用scp来限制那些资源可以正常加载。
- 同源策略也保护了网络资源的安全。
sessionStorage、localstorage、cookie的区别?同一个系统开两个网页,两个网页的sessionStorage共享吗?
- sessionStorage: 会话级别的存储。关闭当前页面或者跳转到新的页面就会消失。
- localstorage:持久保存。 storage提供了许多api来操作数据的存储。 getItem, setItem, clear, removeItem。
- cookie。服务器返回的一段文本数据。然后保存在浏览器中,随着下一次请求一同发送给服务器。 sessionStorage不会共享。
如何设置localStorage的过期时间呢?
我们知道如果不手动删除localStorage中的数据就一直不会过期。
// 如何删除localStorage中的元素 const obj={ value: "zh", now: new Date().getTime(), // 单位为s expire: 10000 } localStorage.setItem("name_delete",JSON.stringify(obj)) setTimeout(() => { console.log("==========") const obj=JSON.parse(localStorage.getItem("name_delete")); const now=Date.now(); if(now-obj.now>obj.expire) { localStorage.removeItem("name_delete") } else { console.log(obj.value) } },12000)
http和https的区别?为什么https是相对安全的?https加密原理?
tcp三次握手和四次挥手的步骤
tcp建立三次握手的目的是确认收发双方都有接收和发送的能力。
这里写的有