前端铜面试必备八股文——浏览器
XSS(跨站脚本攻击)
XSS
攻击指的是跨站脚本攻击,是一种代码注入攻击。攻击者通过在网站注入恶意脚本,使之在用户的浏览器上运行,从而盗取用户的信息如 cookie
等。
避免方式
- 不用服务器端拼接后返回(不使用服务端渲染)。
- 对一些敏感信息进行保护,比如
cookie
使用http-only
,使得脚本无法获取。 - 对用户输入的地方和变量都需要仔细检查长度和对
”<”,”>”,”;”,”’”
等字符做过滤
CSRF(跨站请求伪造)
CSRF
攻击的本质是利用 cookie
会在同源请求中携带发送给服务器的特点,以此来实现用户的冒充。
避免方式
- 添加验证码验证
- 使用
token
验证 - 限制
cookie
不能作为被第三方使用 - 进行同源检测
什么进程和线程?有什么区别
进程(Process)
进程是计算机中正在运行的程序的实例,一个进程就是一个程序运行实例。它拥有独立的内存空间、代码和数据,并且由操作系统负责调度和管理。每个进程在执行时都会分配独立的内存空间,不同进程之间的内存是隔离的,一个进程的错误不会直接影响其他进程。 进程之间通过进程间通信(IPC)机制来交换数据和进行通信,常见的IPC
方式包括管道、消息队列、共享内存等。进程的切换开销较大,因为需要保存和恢复进程的完整状态,涉及到内存保护和虚拟内存的切换。
线程(Thread)
线程是进程的子任务,一个进程可以包含多个线程。它们共享相同的代码和数据,但拥有独立的执行栈和寄存器集合。多个线程可以在同一进程内并发执行,共享进程的资源,如内存空间、打开的文件等。线程间的通信和数据交换比进程间的通信更加方便,因为它们共享相同的地址空间。线程的切换开销较小,因为线程共享进程的地址空间,切换时不需要切换内存页表,速度较快。
区别
- 进程和线程都可以实现并发执行,但进程是独立的执行实体,而线程是依赖于进程的。
- 进程之间资源相互隔离,线程共享所属进程的资源。
- 创建和销毁线程的开销较小,而创建和销毁进程的开销较大。
- 多线程程序的编程复杂度通常比单线程程序高,但多线程可以更好地利用多核处理器来提高程序的执行效率。
浏览器有哪些进程
- 主进程:负责处理用户输入、渲染页面等主要任务。
- 渲染进程:渲染进程负责解析
HTML
、CSS
和JavaScript
,并将网页渲染成可视化内容。 - GPU进程:负责处理浏览器中的
GPU
加速任务。 - 网络线程:网络进程负责处理浏览器中的网络请求和响应,包括下载网页和资源等。
- 插件进程:负责浏览器插件运行。
协商缓存和强缓存的区别
强缓存
使用强缓存策略时,如果缓存资源有效,浏览器会从本地读取缓存资源并返回200,不必再向服务器发起请求。强缓存策略可以通过两种方式来设置,分别是 http
头信息中的 Expires
属性和 Cache-Control
属性。
Expires指定资源的过期时间。在过期时间以内,改资源可以被缓存使用,不需要向浏览器发送请求。这个时间依赖于服务器时间,会存在服务器时间和客户端时间不一致。
Cache-Control属性:
private
: 仅浏览器可以缓存public
:浏览器和代理服务器都可以缓存max-age=xxx
过期时间,单位为秒no-cache
不进行强缓存,但会有协商缓存no-store
不强缓存,也不协商缓存
当两种方式一起使用时,Cache-Control 的优先级要高于 Expires。
协商缓存
如果设置强缓存,无需发起请求,直接使用缓存内容。如果没有命中强缓存,设置了协商缓存,也不需要发起请求,使用缓存。
命中协商缓存条件:
Cache-Control: no-cache
max-age
时间过期
在使用协商缓存时,会先向服务器发送一个请求,如果资源没有发生修改,则请求返回304状态,让浏览器使用本地缓存。如果资源发生修改,则返回修改后的内容
在request headers
中的Etag
属性和Last-Modified
属性,来进行设置。其中,ETage优先于Last-Modified。
Etag文件改动 服务器在返回资源的时候,在头信息中添加Etag
属性,这个属性是资源的唯一标识符。当资源改变,这个值也会改变。下次请求资源时,会在请求头中添加If-None-Match
属性,为上一次请求的资源的Etag
值。服务端会通过这个属性和资源最后一次修改时间进行对比,以此来判断资源是否修改。这种方式比Last-Modified
更加准确。
Last-Modified 上次修改时间 服务器通过在响应头上添加Last-Modified
属性,来指出资源最后一次修改时间。当浏览器发起请求时,会在请求头上添加一个IF-Modified-Since
属性,值为上一次资源请求的Last-Modified
的值。服务区会通过这个属性和最后修改时间来进行比较,以此来判断资源是否修改。如果没有资源修改,返回304
状态,使用本地缓存。如果资源修改,就返回最新资源,200
状态。
这种方式有个缺点,Last-Modified
标记的时间只能精确到1秒,如果文件在1秒内修改,但是 Last-Modified
却没有改变,这样会造成缓存命中的不准确。
区别
- 强缓存优先级高于协商缓存
- 协商缓存不论命中与否都会发送一次请求
- 强缓存返回
200
,协商缓存命中返回304
Ctrl+F5
会强制刷新会跳过所有缓存,而F5刷新跳过强缓存,但是会检查协商缓存。
为什么需要浏览器缓存?
使用浏览器缓存,有以下优点:
- 减少了服务器的负担,提高了网站的性能
- 加快了客户端网页的加载速度
- 减少了多余网络数据传输
常见浏览器所用内核
- IE浏览器内核,
Trident
内核,也是俗称的IE
内核; Chrome
浏览器内核,以前是Webkit
内核,现在是Blink
内核;Firefox
浏览器内核:Gecko
内核,俗称Firefox
内核;Safari
浏览器内核:Webkit
内核;- 360浏览器、猎豹浏览器内核:
IE + Chrome
双内核;
浏览器的渲染过程
- 解析文档,生成
DOM
树 - 解析
CSS
,根据CSS
规则生成CSSOM
规则树 - 在
CSSOM
树和DOM
树生成完后,合并DOM
、CSSO
M树构建渲染树 - 渲染树构建完成后,开始计算元素大小和位置【回流发生在这个阶段】
- 根据计算好的位置信息将内容渲染到屏幕上【重绘发生在这个阶段】
浏览器渲染优化
-
优化
javaScript
,
JavaScript
会阻塞HTML的解析,改变
JavaScrip
加载方式。
- 将
JavaScript
放到body
最后面 - 尽量使用异步加载
JS
资源,这样不会阻塞DOM
解析,如defer、async
- 将
-
优化CSS加载,
- CSS样式少,使用内嵌样式
- 导入外部样式使用
link
,而不是@import
,因为它会阻塞渲染。
-
减少回流重绘
- 避免频繁操作样式
- 避免频繁操作DOM
- 复杂动画使用定位脱离文当流
- 使用
transform
替代动画
Cookie、LocalStorage、SessionStorage区别
Cookie
- 大小只有4kb
- 跨域不能共享
- 不安全,容易被劫持
- 只存在请求头中
SessionStorage
- 存储在内存中,体积相对较大
- 页面关闭,数据会消失
- 相对Cookie安全
LocalStorage
- 体积大,可以存储更多内容。
- 生命周期长,除非手动删除,不然会一直存在
- 存储在硬盘中,不会像Cookie一样被请求携带
什么是同源策略
跨域问题其实就是浏览器的同源策略造成的。 同源指的是:协议、端口号、域名必须一致。
如何解决跨越问题
- CORS:服务器开启跨域资源共享
- JSONP:利用
<script>
标签不存在跨域限制,只支持GET请求,且不安全。 - nginx代理跨域
- nodejs 中间件代理跨域,通过node开启一个代理服务器。
事件流
事件流分为三个阶段:捕获阶段、目标阶段、冒泡阶段。
过程如下:
- 捕获阶段:事件从最外层的节点,也就是文档对象开始,逐级向下传播,直到事件的目标节点上。
- 目标阶段:事件到达目标节点,触发目标节点上的事件处理函数。
- 冒泡阶段:事件从目标节点开始,逐级向上传播,直到到达最外层节点(文档对象)
事件冒泡和捕获的区别?
事件冒泡和事件捕获是两种不同的事件传播方式,默认是冒泡,它们的区别在于传播方向不同:
- 事件冒泡是从自下而上,从子元素冒泡到父元素,执行父元素上的事件处理。
- 事件捕获是事件从文档的根元素开始,逐级向下传播到较为具体的元素(即从父元素到子元素)。
如何阻止事件冒泡
- 普通浏览器:
event.stopPropagation()
- IE浏览器:
event.cancelBubble = true
;
对事件委托的理解
利用浏览器事件冒泡机制。事件在冒泡的过程中会传到父节点,并且父节点可以通过事件对象获取到目标节点,可以吧子节点的监听函数定义在父节点上,由父节点的监听函数统一处理多个子元素的事件
回流与重绘
回流当DOM变化影响了元素,比如元素的尺寸、布局、显示隐藏等改变了,需要重写构建。每个页面至少需要一次回流,就是在页面第一次加载的时候,这个时候一定会发生回流。
重绘当一个元素的外观发生变化,但是没有改变布局,重新渲染元素的外观。比如background-color
、color
如何避免回流重绘:
- 避免使用
table
布局 - 尽可能在
DOM
树的最末端改变class
- 不要频繁的操作元素的样式
- 避免设置多层内联样式
- 开启GPU加速
- 使用
absolute
或者fixed
,脱离标准文档流
回流必将引起重绘,而重绘不一定会引起回流
对浏览器事件循环的理解
事件循环是一种机制,它会不断的轮询任务队列,并将队列中的任务依此执行。
JavaScript的任务分为两种同步和异步:
- 同步任务:在主线程上排队执行的任务,只有一个任务执行完毕,才能执行下一个任务,
- 异步任务:不进入主线程,而是放在任务队列中,若有多个异步任务则需要在任务队列中排队等待,任务队列类似于缓冲区,任务下一步会被移到执行栈然后主线程执行调用栈的任务。
因为js是单线程,在执行代码的时候将所有函数压入执行栈中。同步任务会按照后进先出的原则以此执行。遇到异步任务时,将其放入任务队列中。当前执行栈里事件执行完毕后,就会从任务队列中取出对应异步任务的回调函数放入执行栈中继续执行。
宏观任务(MacroTask|Task)
、微观任务(MicorTask)
。
- 宏任务:
script全部代码
、setTimeout
、setInterval
、I/O
、UI
渲染 - 微任务:
Promise.then
、Process.nexTick(Node独有)
、MutationObserver
任务队列中的任务分为宏任务和微任务,当执行栈清空后,会先检查任务队列中是否有微任务,如果有就按照先进先出的原则,压入执行栈中执行。微任务中产生了新的微任务不会推迟到下一个循环中,而是在当前循环中继续执行。 当执行这一轮的微任务完毕后,开启下一轮循环,执行任务队列中的宏任务。
一次 Eventloop 循环会处理一个宏任务和所有这次循环中产生的微任务。
执行顺序
- 执行宏任务中的同步代码,遇到宏任务或微任务,分别放入对应的任务队列,等待执行。
- 当所有同步任务执行完毕后,执行栈为空,首先执行微任务队列中的任务
- 微任务执行完毕后,检查这次执行中是否产生新的微任务,如果存在,重复执行步骤,直到微任务执行完毕。
- 开始下一轮
Event Loop
,执行宏任务中的代码
Node.js的事件循环
Node事件循环分为6个阶段,每进入一个阶段,都会去对应的回调队列中取出函数执行。
-
Timers阶段:执行
timer
(setTimeout
、setInterval
)的回调,由poll
阶段控制; -
I/O callbacks阶段:系统调用相关的回调
-
idle prepare阶段:Nodejs内部执行,可以忽略
-
poll阶段:轮询 在该阶段如果没有timer的话,会出现一下情况
-
poll队列不为空,会遍历回调队列并同步执行,直到队列为空或者达到系统限制;
-
poll队列对空
,会出现以下两种情况
- 如果有 setImmediate 回调需要执行,
poll
阶段会停止并且进入到check
阶段执行回调; - 如果没有
setImmediate
回调需要执行,就会等待回调被天加到队列中,然后立即执行。 如果设置里有timer
,并且poll
队列为空,就会判断是否有timer
超时,如果有就回到timers
阶段执行回调。
- 如果有 setImmediate 回调需要执行,
-
-
check阶段:执行
setImmediate
回调 -
colse callbacks阶段:执行一些关闭回调,比如
socket.on('close', ...)
等。
process.nextTick
它会在轮询的各个阶段结束时,进入到下一个阶段之前立即执行。
setImmediate 和 setTimeout
js复制代码setTimeout(() => {
console.log('setTimeout')
}, 0)
setImmediate(() => {
console.log('setImmediate')
})
这种情况下,定时器的执行顺序是随机的。
如果把这两个函数放入一个 I/O 循环内调用,setImmediate 总是被优先调用
js复制代码const fs = require('fs')
fs.readFile(__filename, () => {
setTimeout(() => {
console.log('timeout');
}, 0)
setImmediate(() => {
console.log('immediate')
})
})
// immediate
// timeout
Node和浏览器事件循环机制的区别
- 浏览器事件循环会在宏任务结束后,检查微任务。而Node的微任务是在两个阶段之间执行。
**
js复制代码const fs = require('fs')
fs.readFile(__filename, () => {
setTimeout(() => {
console.log('timeout');
}, 0)
setImmediate(() => {
console.log('immediate')
})
})
// immediate
// timeout
Node和浏览器事件循环机制的区别
- 浏览器事件循环会在宏任务结束后,检查微任务。而Node的微任务是在两个阶段之间执行。
- 浏览器的
process.nextTick
和其他微任务优先级一样,而node中要高于其他优先级。