浏览器必备知识
XSS(跨站脚本攻击)
xss攻击是跨站脚本攻击,是一种代码注入攻击。攻击者通过在网站注入恶意脚本,使之在用户的浏览器上运行,从而盗取用户的信息如cookie等。
避免方式
- 不使用服务端渲染
- 对一些敏感信息进行保护,如cookie使用http-only,使得脚本无法获取
- 对用户输入的地方和变量都进行字符的过滤
CSRF(跨站请求伪造)
CSRF攻击的本质是利用cookie会在同源请求中携带发送给服务器的特点,以此来实现用户的冒充。
避免方式
- 添加验证码验证
- 使用token验证
- 限制cookie不能作为第三方使用
- 进行同源检测
什么是进程和线程?有什么区别?
进程(process)
进程是计算机中正在运行的程序的实例,一个进程就是一个程序运行的实例。每个进程在运行时都会分配独立的内存空间,不同进程之间的内存是隔离的,一个进程的错误不会直接影响其他进程。进程间通信方式有管道,消息队列,共享内存等。进程的切换开销较大。
线程(thread)
线程是进程的子任务,一个进程可以包含多个线程。多个线程可以在同一个进程内并发执行,共享进程的资源。线程间的通信很方便,因为他们共享相同的地址空间。线程的切换开销较小,因为线程共享进程的地址空间,切换时不需要切换内存页表,速度较快。
区别
- 进程和线程都可以实现并发执行,但进程是独立的执行实体,而线程是依赖于进程的。
- 进程之间资源相互隔离,线程共享所属进程的资源
- 创建和销毁线程的开销较小,而创建和销毁进程的开销较大
- 多线程可以提高程序的执行效率
浏览器有哪些进程?
- 主进程: 负责处理用户输入,渲染页面等主要任务
- 渲染进程:渲染进程负责解析HTML,CSS和Javascript,并将网页渲染成可视化内容
- GPU进程:负责处理浏览器中的GPU加速任务
- 网络进程:网络进程负责处理浏览器中的网络请求和响应,包括下载网页和资源等
- 插件进程:负责浏览器插件运行
协商缓存和强缓存的区别
强缓存
使用强缓存时,如果缓存资源有效,浏览器会从本地读取缓存资源并返回200,不必再向服务器发起请求。强缓存策略可以通过两种方式来设置,分别是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状态,让浏览器使用本地缓存。如果资源发生修改,则返回修改后的内容。
- last-modified:上次修改的时间,当浏览器发起请求时,会在请求头上添加一个
IF-Modified-Since属性,值为上一次资源请求的Last-Modified的值。服务器会通过这个属性和最后修改时间来进行比较,以此来判断资源是否修改。如果没有资源修改,返回304状态,使用本地缓存。如果资源修改,就返回最新资源,200状态。 - etag:文件资源改动时,这个值也会改变。下次请求资源时,会在请求头中添加
If-None-Match属性,为上一次请求的资源的Etag值。服务端会通过这个属性和资源最后一次修改时间进行对比,以此来判断资源是否修改。这种方式比Last-Modified更加准确。
区别
- 强缓存优先级高于协商缓存
- 协商缓存不论是否命中都会发起一次请求
- 强缓存返回200,协商缓存命中返回304
- ctrl+F5会强制刷新跳过所有缓存,而F5刷新跳过强缓存,但是会检查协商缓存
为什么需要浏览器缓存
- 减少服务器的负担,提高了网站的性能
- 加快了客户端网页的加载速度
- 减少了多余网络数据传输
浏览器内核
- IE:Trident内核
- Chrome:Webkit内核,现在是Blink内核
- Firefox:Gecko内核
- Safari:Webkit内核
- 360浏览器:IE+Chrome双内核
浏览器的渲染过程
-
浏览器自动补全协议和端口号,如果地址不合法,则会执行默认引擎
-
浏览器根据url地址查找本地缓存,根据缓存规则查看是否命中缓存,若命中缓存,则直接使用缓存,不再发出请求
- 如果是https,先去找service worker,查看是否有离线缓存
- 如果没有,再找浏览器的内存缓存(memory cache)
- 如果还没有,再找硬盘缓存:强缓存和协商缓存
-
通过DNS解析找到服务器的地址
- 递归查询有没有dns缓存,如果没有则进行迭代查询获得ip地址
- 首先,根据dns根域名(.)服务器查询,返回顶级域名服务器ip
- 再根据返回的ip去顶级域名(.com)服务器查询,返回权威域名服务器ip(xxx.com)
- 再根据返回的ip去权威域名服务器查询,返回解析到的服务器ip
- 如果此时配置了cdn的话,权威域名服务器会返回一个CName别名记录,它指向CDN网络中的负载均衡系统, 然后通过智能算法,返回最佳节点的IP
-
建立TCP链接
- 三次握手,第一次客户端发送syn=1和一个初始化序列号seq=x;第二次服务端返回ACK=1,SYN=1,ack=x+1,seq=y;第三次握手,ACK=1,ack=y+1,seq=x+1
- 如果是https,则多了一个TLS连接建立的过程:
- 客户端给服务器发送支持的加密方法和TLS版本,以及一个随机字符串
- 服务器返回加密方法,TLS版本号,数字证书以及一个随机字符串
- 客户端验证数字证书,确保服务器的合法性;检查数字签名,检查证书有效期,检查证书是否撤销
- 验证完合法性后,客户端向服务器发送另一个随机字符串,这个字符串是由服务器的公钥加密的,只有对应的私钥才可以解开
- 服务器使用私钥解开字符串
- 现在客户端和服务器都拥有三个随机字符串,通过相同的算法生成相同的会话密钥
- 使用会话密钥进行双方通信
-
浏览器决定要附带那些cookie到请求头中
-
浏览器自动设置好请求头,协议版本,cookie,发出GET请求
-
服务器处理请求,返回一个HTTP响应报文给浏览器
-
浏览器根据状态码决定如何处理这一响应:
- 1xx 信息状态码 100 继续
- 2xx 成功状态码 200 成功 204 请求成功,但是没有资源返回 206 需要下载文件,由用户决定是否下载
- 3xx 重定向状态 301 永久重定向 302 临时重定向 304 调用缓存,不必重新请求数据
- 4xx 客户端错误码 400 请求的语法错误 404 服务器无法找到请求的url
- 5xx 服务器错误码 500 服务器内部出错 503 服务器正在维护 504 请求超时
-
浏览器根据响应头中的Content-Type字段识别响应类型,如果是text/html,则对响应体的内容进行HTML解析
-
dom树构建
- html解析器对html进行解析,对字符词法分析,将字符解析成token,对token进行语法分析,转成dom节点对象并定义属性和规则
- html解析器会维护一个解析栈,栈底为document对象,也就是dom树的根节点。然后根据根节点关系将节点一次推入栈中,形成dom树。
- 如果解析过程中没有async或defer的script标签引用时,会暂停解析,同时加载js文件,执行相应的代码,代码执行完之后再返回渲染引擎继续渲染流程
- 在解析过程中还会触发一系列的事件,当dom树完成后会触发domcontentloaded事件。当所有资源加载完毕后会触发load事件
-
CSSOM规则树构建
- 首先将CSS进行格式化成StyleSheets,然后对计算好的样式进行标准化操作
- 样式计算规则主要有继承和层叠。计算阶段就是计算出dom树中的每个节点的位置信息,样式数据,文本节点数据,然后css和html可以同时解析,但是css会阻塞js
-
layout布局
- 首先创建布局树,遍历DOM树中的节点,将可见节点添加到布局树中,然后根据DOM结构和元素样式对布局树中节点的几何位置信息进行计算
-
分层
- 因为脱离了文档流的对象会形成一个层叠上下文,所以有了分层的概念。就类似于ps中的图层,然后主线程为每个图层计算样式,把每一个图层的绘制拆分成很多小的绘制指令,生成绘制表。
-
栅格化
- 一个图层可能很大,需要进行分块。另外渲染引擎维护了一个栅格化线程,合成线程将分割好的图块发给栅格化线程,然后分别栅格化每个图块,再将栅格化之后的图块存储在GPU内存中。
-
合成和显示
浏览器渲染优化
-
优化js,js会阻塞html的解析,改变js的加载方式可以防止阻塞
- 将js尽量放在body最后面
- 尽量使用异步加载js资源,async,defer
-
优化css加载
- css样式少使用内嵌样式
- 导入外部样式使用link,而不是@import,因为它会阻塞渲染
-
减少重绘和回流
- 避免频繁操作样式
- 避免频繁操作dom
- 复杂动画使用绝对定位的方式脱离文档流
- 使用transform代替动画
Cokie,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,脱离标准文档流
回流必将引起重绘,而重绘不一定会引起回流。
对浏览器事件循环的理解
事件循环是一种机制,它会不断的轮询任务队列,并将队列中的任务依次执行。 js的任务分为同步和异步两种:
- 同步任务:在主线程上排队执行的任务,只有一个任务执行完毕,才能执行下一个任务。
- 异步任务:不进入主线程,而是放在异步队列中,若有多个异步任务则需要在任务队列中排队等待,任务队列类似于缓冲区,任务下一步会被移到执行栈然后主线程执行调用栈的任务。
宏观任务、微观任务
- 宏任务:script全部代码,setTimeout,setInterval,I/O,UI渲染
- 微任务:Promise.then、Process.nextTick、MutationObserver
任务队列中的任务分为宏任务和微任务,当执行栈清空后,会先检查任务队列中是否有微任务,如果有就按照先进先出的原则,压入执行栈中执行。微任务中产生了新的微任务不会推迟到下个循环中,而是在当前循环中继续执行。当执行这一轮微任务完毕之后,开启下一轮循环,执行任务队列中的宏任务。
一次 Eventloop 循环会处理一个宏任务和所有这次循环中产生的微任务。
执行顺序
- 执行宏任务中的同步代码,遇到宏任务或微任务,分别放入对应的任务队列,等待执行
- 当所有同步任务执行完毕之后,执行栈为空,首先执行微任务队列中的任务
- 微任务执行完毕后,检查这次执行中是否产生新的微任务,如果存在,重复执行步骤,直到微任务执行完毕
- 开始下一轮Event Loop,执行宏任务中的代码
Node的事件循环
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阶段执行回调。
- check阶段:执行setImmediate回调
- colse callbacks阶段:执行一些关闭回调,比如 socket.on(‘close’,。。。)等。
node和浏览器事件循环机制的区别
- 浏览器事件循环会在宏任务结束后,检查微任务。而node的微任务是在两个阶段之间执行。
- 浏览器的process.nextTick和其他微任务优先级一样,而node中要高于其他优先级。