2021春季前端知识系统总结
2021-09-19 20:11:28 1 举报
AI智能生成
上半年准备春招的前端知识整理
作者其他创作
大纲/内容
手撕代码
排序、查找
二叉树
原理
let、const
call、bind、apply
深拷贝、浅拷贝
手写 new、instanceof
防抖
节流
事件委托
委托:把事件托付给别人或别的机构办理
事件流:确定页面接收事件的顺序;事件冒泡和事件捕获
事件代理通过事件冒泡,只指定一个事件处理程序,就可以管理某一类型的所有事件。
手写xhr请求
各种promise场景题、原理题(promise、promise.then、promise.all、promise.race、promise.catch)
场景
数组扁平化
发布订阅模式
下拉加载图片
手写元素拖拽
图片懒加载
URL拆解
将HTTP header转换成JS对象
将数组转化为树形结构
前端优化
性能优化
资源获取阶段
本地存储数据
分页加载、数据分批请求
懒加载
响应式请求(如通过滚定条判断接口请求)
组件缓冲、减少资源加载
开发代码习惯类/解析阶段
CSS文件放头部;
CSS代码多利用CSS的继承属性,减少不必要的重写,占用体积
脚本文件放body标签上一行,防止阻塞,根据场景进行必要的async和defer
CSS代码多利用CSS的继承属性,减少不必要的重写,占用体积
脚本文件放body标签上一行,防止阻塞,根据场景进行必要的async和defer
使用服务端渲染
静态资源缓存、CDN、压缩文件
图片类
压缩图片、base64格式
使用webp格式的图片、减少容量
首页白屏、页面卡顿
使用浏览器控制台查看执行性能
页面性能知识点
重排、重绘
重排:改变元素的边框大小、内容、内外边距( 几何属性 )、调用window.getComputeStyle属性也会触发回流( 获取祖先节点的一些信息进行计算:offsetxxx、scrollxxx、clientxxx )、浏览器窗口大小发生变化等会导致回流。即改变dom元素在布局树的坐标,会影响到其他dom节点的坐标更改(页面布局发生变化),会导致页面重新样式计算,布局树、分层树、绘制(分图层、分图块、栅格化、合成、显示)
重绘:修改元素的背景、边框、字体颜色、修改cursor鼠标样式
性能优化:
· 使用class替代style,减少style的使用,只引起一次回流。
· 使用resize、scroll时进行防抖和节流处理,这两者会直接导致回流
· 使用visibility替换display: none,因为前者只会引起重绘,后者会引发回流
· 批量修改元素时,可以先让元素脱离文档流,等修改完毕后,再放入文档流
· 避免触发同步布局事件,我们在获取offsetWidth这类属性的值时,可以使用变量将查询结果存起来,避免多次查询,每次对offset/scroll/client等属性进行查询时都会··`· 建立一个图层,让回流在图层里面进行,限制回流和重绘的范围,减少浏览器的运算工作量。
触发回流:
· 对于复杂动画效果,使用绝对定位让其脱离文档流,复杂的动画效果会频繁地触发回流重绘,我们可以将动画元素设置绝对定位从而脱离文档流避免反复回流重绘。
· 使用class替代style,减少style的使用,只引起一次回流。
· 使用resize、scroll时进行防抖和节流处理,这两者会直接导致回流
· 使用visibility替换display: none,因为前者只会引起重绘,后者会引发回流
· 批量修改元素时,可以先让元素脱离文档流,等修改完毕后,再放入文档流
· 避免触发同步布局事件,我们在获取offsetWidth这类属性的值时,可以使用变量将查询结果存起来,避免多次查询,每次对offset/scroll/client等属性进行查询时都会··`· 建立一个图层,让回流在图层里面进行,限制回流和重绘的范围,减少浏览器的运算工作量。
触发回流:
· 对于复杂动画效果,使用绝对定位让其脱离文档流,复杂的动画效果会频繁地触发回流重绘,我们可以将动画元素设置绝对定位从而脱离文档流避免反复回流重绘。
dom0、dom2、dom3
事件是基于发布订阅模式,浏览器加载的时候会读取事件相关的代码,但是只有实际等到具体的事件触发才会执行。
事件流:事件捕获、处于目标阶段、事件冒泡阶段
DOM0 级事件,直接在 html 元素上绑定 on-event,比如 onclick,取消的话,dom.onclick = null,同一个事件只能有一个处理程序,后面的会覆盖前面的。
DOM2 级事件,通过 addEventListener 注册事件,通过 removeEventListener 来删除事件,一个事件可以有多个事件处理程序,按顺序执行,捕获事件和冒泡事件。
DOM3级事件,增加了事件类型,比如 UI 事件,焦点事件,鼠标事件、滚动事件
addEventListener
添加的方法可以是具名函数和匿名函数,但是删除的时候只能删除具名函数
第三个参数是事件捕获,默认是false
添加的方法可以是具名函数和匿名函数,但是删除的时候只能删除具名函数
第三个参数是事件捕获,默认是false
浏览器缓存机制
缓存的位置
from merory cache
form dish cache
浏览器每次发起请求,都会现在浏览器缓存中查找该请求的结果以及缓存标识
浏览器每次拿到返回的请求结果都会将该结果和缓存标识存入浏览器缓存中
浏览器每次拿到返回的请求结果都会将该结果和缓存标识存入浏览器缓存中
强缓存
Expires / Last-modified
缓存过期时间,用来指定资源到期的时间,是服务器端的具体的时间点。
受限于本地时间,如果修改了本地时间,可能会造成缓存失效。
Cache-Control
public
所有内容都将被缓存(客户端和代理服务器都可缓存)
private
所有内容只有客户端可以缓存
max-age
表示缓存内容将在xxx秒后失效
no-store
所有内容都不会被缓存,即不使用强制缓存,也不使用协商缓存
no-cache
客户端缓存内容,是否使用缓存则需要经过协商缓存来验证决定。表示不使用 Cache-Control的缓存控制方式做前置验证,而是使用 Etag 或者Last-Modified字段来控制缓存。需要注意的是,no-cache这个名字有一点误导。设置了no-cache之后,并不是说浏览器就不再缓存数据,只是浏览器在使用缓存数据时,需要先确认一下数据是否还跟服务器保持一致。
max-state
在一定时间内,就是缓存过期,也是用缓存
min-fresh
希望在xx秒内获取最新的响应
总结
强缓存判断是否缓存的依据来自于是否超出某个时间或者某个时间段,而不关心服务器端文件是否已经更新,这可能会导致加载文件不是服务器端最新的内容,那我们如何获知服务器端内容是否已经发生了更新呢?此时我们需要用到协商缓存策略。
强缓存判断是否缓存的依据来自于是否超出某个时间或者某个时间段,而不关心服务器端文件是否已经更新,这可能会导致加载文件不是服务器端最新的内容,那我们如何获知服务器端内容是否已经发生了更新呢?此时我们需要用到协商缓存策略。
协商缓存
Last-Modified / If-Modified-Since
如果本地打开缓存文件,即使没有对文件进行修改,但还是会造成 Last-Modified 被修改,服务端不能命中缓存导致发送相同的资源
因为 Last-Modified 只能以秒计时,如果在不可感知的时间内修改完成文件,那么服务端会认为资源还是命中了,不会返回正确的资源
既然根据文件修改时间来决定是否缓存尚有不足,能否可以直接根据文件内容是否修改来决定缓存策略?所以在 HTTP / 1.1 出现了 ETag 和If-None-Match
因为 Last-Modified 只能以秒计时,如果在不可感知的时间内修改完成文件,那么服务端会认为资源还是命中了,不会返回正确的资源
既然根据文件修改时间来决定是否缓存尚有不足,能否可以直接根据文件内容是否修改来决定缓存策略?所以在 HTTP / 1.1 出现了 ETag 和If-None-Match
etag / if none match
Etag是服务器响应请求时,返回当前资源文件的一个唯一标识(由服务器生成),只要资源有变化,Etag就会重新生成。
浏览器在下一次加载资源向服务器发送请求时,会将上一次返回的Etag值放到request header里的If-None-Match里,服务器只需要比较客户端传来的If-None-Match跟自己服务器上该资源的ETag是否一致,就能很好地判断资源相对客户端而言是否被修改过了。如果服务器发现ETag匹配不上,那么直接以常规GET 200回包形式将新的资源(当然也包括了新的ETag)发给客户端;如果ETag是一致的,则直接返回304知会客户端直接使用本地缓存即可。
总结:
频繁变动的资源:Cache-Control: no-cache
不常变化的资源:Cache-Control: max-age=31536000
从一个url到页面渲染过程
浏览器命中缓存
强制缓存
协商缓存
启发式缓存
解析域名、dns查询
解析域名先查找缓存
浏览器搜索自己的 DNS 缓存(维护一张域名与 IP 地址的对应表)
搜索操作系统中的 DNS 缓存(维护一张域名与 IP 地址的对应表)
搜索操作系统的 hosts 文件( Windows 环境下,维护一张域名与 IP 地址的对应表)
本地域名服务器缓存( 查询成功率为80% )、根域名服务器缓存、顶级域名服务器缓存、主域名服务器缓存
搜索操作系统中的 DNS 缓存(维护一张域名与 IP 地址的对应表)
搜索操作系统的 hosts 文件( Windows 环境下,维护一张域名与 IP 地址的对应表)
本地域名服务器缓存( 查询成功率为80% )、根域名服务器缓存、顶级域名服务器缓存、主域名服务器缓存
创建网络请求线程、三次握手
页面下载资源,页面渲染过程
构建dom、同时构建cssom、样式计算,生成渲染树、计算节点坐标构建布局树、填表记录绘制顺序,生成分层树、绘制(分图层、图块、栅格化、合成、显示)
绘制阶段:在多个层上绘制DOM元素的文字、颜色、图像、边框和阴影等。
渲染层合并:按照合理的顺序合并图层然后显示到屏幕上。
绘制阶段:在多个层上绘制DOM元素的文字、颜色、图像、边框和阴影等。
渲染层合并:按照合理的顺序合并图层然后显示到屏幕上。
构建DOM树、然后同时构建CSSOM树、转换样式表中的属性值、使其标准化
构建渲染树、样式计算;计算出 DOM 树中每个节点的具体样式;遵守CSS的继承和层叠两个规则
总之,样式计算阶段的目的是为了计算出 DOM 节点中每个元素的具体样式,在计算过程中需要遵守 CSS 的继承和层叠两个规则。这个阶段最终输出的内容是每个 DOM 节点的样式,并被保存在 ComputedStyle 的结构内。
总之,样式计算阶段的目的是为了计算出 DOM 节点中每个元素的具体样式,在计算过程中需要遵守 CSS 的继承和层叠两个规则。这个阶段最终输出的内容是每个 DOM 节点的样式,并被保存在 ComputedStyle 的结构内。
布局阶段:根据渲染树构建布局树、布局计算
遍历DOM树中的所有可见节点,并确定dom节点坐标
遍历DOM树中的所有可见节点,并确定dom节点坐标
分层
构建出布局树,而且每个元素的具体位置信息都计算出来了,那么接下来还是不能开始着手绘制页面。
因为页面中有很多复杂的效果,如一些复杂的 3D 变换、页面滚动,或者使用 z-index 做 z 轴排序等,为了更加方便地实现这些效果,「「渲染引擎还需要为特定的节点生成专用的图层,并生成一棵对应的图层树」」(LayerTree)。
渲染引擎给页面分了很多图层,这些图层按照一定顺序叠加在一起,就形成了最终的页面。通常情况下,「「并不是布局树的每个节点都包含一个图层,如果一个节点没有对应的层,那么这个节点就从属于父节点的图层」」。
渲染引擎通常会为满足下面两点中任意一点的元素创建新的图层。
因为页面中有很多复杂的效果,如一些复杂的 3D 变换、页面滚动,或者使用 z-index 做 z 轴排序等,为了更加方便地实现这些效果,「「渲染引擎还需要为特定的节点生成专用的图层,并生成一棵对应的图层树」」(LayerTree)。
渲染引擎给页面分了很多图层,这些图层按照一定顺序叠加在一起,就形成了最终的页面。通常情况下,「「并不是布局树的每个节点都包含一个图层,如果一个节点没有对应的层,那么这个节点就从属于父节点的图层」」。
渲染引擎通常会为满足下面两点中任意一点的元素创建新的图层。
拥有层叠上下文属性的元素会被提升为单独的一层
文档根元素(<html>)
position 值为 absolute(绝对定位)或 relative(相对定位)且 z-index 值不为 auto 的元素
position 值为 fixed(固定定位)或 sticky(粘滞定位)的元素(沾滞定位适配所有移动设备上的浏览器,但老的桌面浏览器不支持)
flex (flexbox) 容器的子元素,且 z-index 值不为 auto
grid (grid) 容器的子元素,且 z-index 值不为 auto
opacity 属性值小于 1 的元素(参见 the specification for opacity)
position 值为 absolute(绝对定位)或 relative(相对定位)且 z-index 值不为 auto 的元素
position 值为 fixed(固定定位)或 sticky(粘滞定位)的元素(沾滞定位适配所有移动设备上的浏览器,但老的桌面浏览器不支持)
flex (flexbox) 容器的子元素,且 z-index 值不为 auto
grid (grid) 容器的子元素,且 z-index 值不为 auto
opacity 属性值小于 1 的元素(参见 the specification for opacity)
需要剪裁(clip)的地方也会被创建为图层
如果把 div 的大小限定为 200 * 200 像素,而 div 里面的文字内容比较多,文字所显示的区域肯定会超出 200 * 200 的面积,这时候就产生了剪裁,渲染引擎会把裁剪文字内容的一部分用于显示在 div 区域,出现这种裁剪情况的时候,渲染引擎会为文字部分单独创建一个层,如果出现滚动条,滚动条也会被提升为单独的层。
所以说:元素有了层叠上下文的属性或者需要被剪裁,满足其中任意一点,就会被提升成为单独一层
图层绘制
在完成图层树的构建之后,渲染引擎会对图层树中的每个图层进行绘制,那么接下来我们看看渲染引擎是怎么实现图层绘制的?
起初浏览器是下拉新可视区域,再合成图层。缺点是有延迟加载,用户体验差
渲染引擎实现图层的绘制是把一个图层的绘制拆分成很多小的**「绘制指令」**,然后再把这些指令按照顺序组成一个待绘制列表,如下图所示
从图中可以看出,绘制列表中的指令其实非常简单,就是让其执行一个简单的绘制操作,比如绘制粉色矩形或者黑色的线等。而绘制一个元素通常需要好几条绘制指令,因为每个元素的背景、前景、边框都需要单独的指令去绘制。所以在图层绘制阶段,输出的内容就是这些待绘制列表。
栅格化(raster)操作
绘制列表只是用来记录绘制顺序和绘制指令的列表,而实际上绘制操作是由渲染引擎中的合成线程来完成的。你可以结合下图来看下渲染主线程和合成线程之间的关系:
如上图所示,当图层的绘制列表准备好之后,主线程会把该绘制列表提交(commit)给合成线程。
通常一个页面可能很大,但是用户只能看到其中的一部分,我们把用户可以看到的这个部分叫做**「视口」**(viewport)。
在有些情况下,有的图层可以很大,比如有的页面你使用滚动条要滚动好久才能滚动到底部,但是通过视口,用户只能看到页面的很小一部分,所以在这种情况下,要绘制出所有图层内容的话,就会产生太大的开销,而且也没有必要。
基于这个原因,「「合成线程会将图层划分为图块(tile)」」,这些图块的大小通常是 256x256 或者 512x512。
「「然后合成线程会按照视口附近的图块来优先生成位图,实际生成位图的操作是由栅格化来执行的。所谓栅格化,是指将图块转换为位图」」。而图块是栅格化执行的最小单位。渲染进程维护了一个栅格化的线程池,所有的图块栅格化都是在线程池内执行的,运行方式如下图所示:
从图中可以看出,渲染进程把生成图块的指令发送给 GPU,然后在 GPU 中执行生成图块的位图,并保存在 GPU 的内存中。
合成与显示
一旦所有图块都被光栅化,合成线程就会生成一个绘制图块的命令——“DrawQuad”,然后将该命令提交给浏览器进程。
浏览器进程里面有一个叫 viz 的组件,用来接收合成线程发过来的 DrawQuad 命令,然后根据 DrawQuad 命令,将其页面内容绘制到内存中,最后再将内存显示在屏幕上。
到这里,经过这一系列的阶段,编写好的 HTML、CSS、JavaScript 等文件,经过浏览器就会显示出漂亮的页面了。
浏览器进程里面有一个叫 viz 的组件,用来接收合成线程发过来的 DrawQuad 命令,然后根据 DrawQuad 命令,将其页面内容绘制到内存中,最后再将内存显示在屏幕上。
到这里,经过这一系列的阶段,编写好的 HTML、CSS、JavaScript 等文件,经过浏览器就会显示出漂亮的页面了。
浏览器渲染流程大致如下:
渲染进程将 HTML 内容转换为能够读懂的**「DOM 树」**结构。
渲染引擎将 CSS 样式表转化为浏览器可以理解的 「「styleSheets」」,计算出 DOM 节点的样式。
创建**「布局树」**,并计算元素的布局信息。
对布局树进行分层,并生成**「分层树」**。
为每个图层生成**「绘制列表」**,并将其提交到合成线程。
合成线程将图层分成**「图块」「,并在」「光栅化线程池」**中将图块转换成位图。
合成线程发送绘制图块命令 「「DrawQuad」」 给浏览器进程。
浏览器进程根据 DrawQuad 消息**「生成页面」「,并」「显示」**到显示器上。
渲染进程将 HTML 内容转换为能够读懂的**「DOM 树」**结构。
渲染引擎将 CSS 样式表转化为浏览器可以理解的 「「styleSheets」」,计算出 DOM 节点的样式。
创建**「布局树」**,并计算元素的布局信息。
对布局树进行分层,并生成**「分层树」**。
为每个图层生成**「绘制列表」**,并将其提交到合成线程。
合成线程将图层分成**「图块」「,并在」「光栅化线程池」**中将图块转换成位图。
合成线程发送绘制图块命令 「「DrawQuad」」 给浏览器进程。
浏览器进程根据 DrawQuad 消息**「生成页面」「,并」「显示」**到显示器上。
计算机网络
http
http版本优化
概念
由 http+url+html 组成互联网
HTTP协议是Hyper Text Transfer Protocol(超文本传输协议),用于从WWW服务器传输超文本到本地浏览器的传送协议;
HTTP属于应用层的面向对象的协议,建立在TCP协议基础之上的,当浏览器需要从服务器获取网页数据的时候,会发出一次Http请求。Http会通过TCP建立起一个到服务器的连接通道,当本次请求的数据完毕后,Http会立即将TCP连接断开。
HTTP协议是Hyper Text Transfer Protocol(超文本传输协议),用于从WWW服务器传输超文本到本地浏览器的传送协议;
HTTP属于应用层的面向对象的协议,建立在TCP协议基础之上的,当浏览器需要从服务器获取网页数据的时候,会发出一次Http请求。Http会通过TCP建立起一个到服务器的连接通道,当本次请求的数据完毕后,Http会立即将TCP连接断开。
http0.9
最初设想的系统里的文档都是只读的,所以只允许用“GET”动作从服务器上获取 HTML 文档,并且在响应请求之后立即关闭连接,功能非常有限。
http1.0
- 增加了 HEAD、POST 等新方法;
- 增加了响应状态码,标记可能的错误原因;
- 引入了协议版本号概念;
- 引入了 HTTP Header(头部)的概念,让 HTTP 处理请求和响应更加灵活;
- 传输的数据不再仅限于文本,支持音频、图像格式。
http1.1
- 增加了 PUT、DELETE 等新的方法;
- 增加了缓存管理和控制;
- 明确了连接管理,允许持久连接(这里是指TCP在一定时间内维护连接,而不是http);
- 允许响应数据分块(chunked),利于传输大文件;
- 强制要求 Host 头,让互联网主机托管成为可能。
Transfer-Encoding响应头用于告诉客户端发送内容的编码格式;
chunked 数据分块发送、gzip 压缩格式、identity 表示身份函数;
在Web Socket没出来前,可利用响应式数据分块实现长连接的效果;
后来在Http/2 已经不支持 chunked这一格式,因为其本身提供了更加高级的流机制来实现类似功能。
chunked 数据分块发送、gzip 压缩格式、identity 表示身份函数;
在Web Socket没出来前,可利用响应式数据分块实现长连接的效果;
后来在Http/2 已经不支持 chunked这一格式,因为其本身提供了更加高级的流机制来实现类似功能。
http2.0
- 二进制帧流传输,不再是纯文本;
- 可发起多个请求,废弃了 1.1 里的管道( 多路复用 );
- 使用专用算法压缩头部,减少数据传输量;
- 允许服务器主动向客户端推送数据;
- 增强了安全性,“事实上”要求加密通信。
- 请求优先级
二进制帧与流传输的优点
原因:
如果是文本传输、服务器接收后不确定解析文本内容需要多大的内存空间,且一发一收机制会出现队头阻塞情况;
方案:
采用二进制帧、将所有的消息分割为更小的消息和帧,并对他们进行二进制格式的编码,首部消息被封装到Headers帧,request body则封装带Data帧里
每个帧都有标识符、确定顺序和帧的资源大小;HTTP/2通信都在一个连接上完成,支持双向数据流、并发多请求;
而消息由一个或多个帧组成,可以乱序发送,然后再根据每个帧额首部的流标识重新组装。
原因:
如果是文本传输、服务器接收后不确定解析文本内容需要多大的内存空间,且一发一收机制会出现队头阻塞情况;
方案:
采用二进制帧、将所有的消息分割为更小的消息和帧,并对他们进行二进制格式的编码,首部消息被封装到Headers帧,request body则封装带Data帧里
每个帧都有标识符、确定顺序和帧的资源大小;HTTP/2通信都在一个连接上完成,支持双向数据流、并发多请求;
而消息由一个或多个帧组成,可以乱序发送,然后再根据每个帧额首部的流标识重新组装。
多路复用的好处
HTTP性能优化的关键并不在于高带宽,而是低延迟。TCP连接会随着时间进行自我 调谐( 慢启动、拥塞控制、快恢复快重传 )
HTTP性能优化的关键并不在于高带宽,而是低延迟。TCP连接会随着时间进行自我 调谐( 慢启动、拥塞控制、快恢复快重传 )
http3.0
概念:基于 UDP 实现了类似于 TCP 的多路数据流、传输可靠性等功能,我们把这套功能称为QUIC 协议
尽管HTTP/2解决了很多1.1的问题,但HTTP/2仍然存在一些缺陷,这些缺陷并不是来自于HTTP/2协议本身,而是来源于底层的TCP协议,我们知道TCP链接是可靠的连接,如果出现了丢包,那么整个连接都要等待重传,HTTP/1.1可以同时使用6个TCP连接,一个阻塞另外五个还能工作,但HTTP/2只有一个TCP连接,阻塞的问题便被放大了。
由于TCP协议已经被广泛使用,我们很难直接修改TCP协议,基于此,HTTP/3选择了一个折衷的方法——UDP协议,HTTP/2在UDP的基础上实现多路复用、0-RTT、TLS加密、流量控制、丢包重传等功能。
由于TCP协议已经被广泛使用,我们很难直接修改TCP协议,基于此,HTTP/3选择了一个折衷的方法——UDP协议,HTTP/2在UDP的基础上实现多路复用、0-RTT、TLS加密、流量控制、丢包重传等功能。
特点:
实现了类似 TCP 的流量控制、传输可靠性的功能。虽然 UDP 不提供可靠性的传输,但 QUIC 在 UDP 的基础之上增加了一层来保证数据可靠性传输。它提供了数据包重传、拥塞控制以及其他一些 TCP 中存在的特性。
集成了 TLS 加密功能。目前 QUIC 使用的是 TLS1.3,相较于早期版本 TLS1.3 有更多的优点,其中最重要的一点是减少了握手所花费的 RTT 个数。
实现了 HTTP/2 中的多路复用功能。和 TCP 不同,QUIC 实现了在同一物理连接上可以有多个独立的逻辑数据流。实现了数据流的单独传输,就解决了 TCP 中队头阻塞的问题。
实现了快速握手功能。由于 QUIC 是基于 UDP 的,所以 QUIC 可以实现使用 0-RTT 或者 1-RTT 来建立连接,这意味着 QUIC 可以用最快的速度来发送和接收数据,这样可以大大提升首次打开页面的速度。
QUIC协议不需要三次握手,优化了连接建立的握手延迟,在应用层实现了TCP的可靠性、TLS安全性和HTTP2的并发性。
实现了类似 TCP 的流量控制、传输可靠性的功能。虽然 UDP 不提供可靠性的传输,但 QUIC 在 UDP 的基础之上增加了一层来保证数据可靠性传输。它提供了数据包重传、拥塞控制以及其他一些 TCP 中存在的特性。
集成了 TLS 加密功能。目前 QUIC 使用的是 TLS1.3,相较于早期版本 TLS1.3 有更多的优点,其中最重要的一点是减少了握手所花费的 RTT 个数。
实现了 HTTP/2 中的多路复用功能。和 TCP 不同,QUIC 实现了在同一物理连接上可以有多个独立的逻辑数据流。实现了数据流的单独传输,就解决了 TCP 中队头阻塞的问题。
实现了快速握手功能。由于 QUIC 是基于 UDP 的,所以 QUIC 可以实现使用 0-RTT 或者 1-RTT 来建立连接,这意味着 QUIC 可以用最快的速度来发送和接收数据,这样可以大大提升首次打开页面的速度。
QUIC协议不需要三次握手,优化了连接建立的握手延迟,在应用层实现了TCP的可靠性、TLS安全性和HTTP2的并发性。
http报文结构
起始行 start line:描述请求或响应的基本信息;由三部分构成:
请求行:请求方法、请求目标、版本号
状态行:版本号、状态码、原因
头部字段集合 header:使用key-value 形式更详细地说明报文
通用字段
Date、cookie
请求字段
Host、User-Agent、Accept、Accept-Encoding(浏览器支持的压缩编码类型 )、Accept-language、Referer( 先前网页的地址,即来路)
响应字段
Server、Expires、Content-Encoding
实体字段
Content-Length:标识报文里body的长度、Content-type:返回的内容类型
消息正文 entity:实际传输的数据
请求方法
GET、HEAD、POST、PUT、DELETE、CONNECT、TRACE
http状态码
不熟:
403:禁止,服务器拒绝请求,常见于客户端权限不足。
502:网关或代理服务器无效
503:服务不可用(处于停机或超载状态)
403:禁止,服务器拒绝请求,常见于客户端权限不足。
502:网关或代理服务器无效
503:服务不可用(处于停机或超载状态)
http进阶
传输大文件
数据压缩、分块传输、范围请求
连接管理
长连接、短连接
HTTP的长连接和短连接本质上是TCP长连接和短连接。HTTP属于应用层协议,在传输层使用TCP协议,在网络层使用IP协议。 IP协议主要解决网络路由和寻址问题,TCP协议主要解决如何在IP层之上可靠地传递数据包,使得网络上接收端收到发送端所发出的所有包,并且顺序与发送顺序一致。TCP协议是可靠的、面向连接的。
http协议无状态:
指的是协议对于事务处理没有记忆能力,服务器不知道客户端是什么状态。也就是说,打开一个服务器上的网页和上一次打开这个服务器上的网页之间没有任何联系。HTTP是一个无状态的面向连接的协议,无状态不代表HTTP不能保持TCP连接,更不能代表HTTP使用的是UDP协议(无连接)。
指的是协议对于事务处理没有记忆能力,服务器不知道客户端是什么状态。也就是说,打开一个服务器上的网页和上一次打开这个服务器上的网页之间没有任何联系。HTTP是一个无状态的面向连接的协议,无状态不代表HTTP不能保持TCP连接,更不能代表HTTP使用的是UDP协议(无连接)。
短连接:,客户端和服务器每进行一次HTTP操作,就建立一次连接,任务结束就中断连接。
长连接:这里的长不是一直连接,而是有个时间、过了这个时间就询问是否有请求,有的话就刷新,重新计算时间。(心跳机制)
在使用长连接的情况下,当一个网页打开完成后,客户端和服务器之间用于传输HTTP数据的TCP连接不会关闭,客户端再次访问这个服务器时,会继续使用这一条已经建立的连接。Keep-Alive不会永久保持连接,它有一个保持时间,可以在不同的服务器软件(如Apache)中设定这个时间。实现长连接需要客户端和服务端都支持长连接。
在使用长连接的情况下,当一个网页打开完成后,客户端和服务器之间用于传输HTTP数据的TCP连接不会关闭,客户端再次访问这个服务器时,会继续使用这一条已经建立的连接。Keep-Alive不会永久保持连接,它有一个保持时间,可以在不同的服务器软件(如Apache)中设定这个时间。实现长连接需要客户端和服务端都支持长连接。
长连接短连接的区别:
长连接多用于操作频繁,点对点的通讯,而且连接数不能太多情况,。每个TCP连接都需要三步握手,这需要时间,如果每个操作都是先连接,再操作的话那么处理速度会降低很多,所以每个操作完后都不断开,次处理时直接发送数据包就OK了,不用建立TCP连接。例如:数据库的连接用长连接, 如果用短连接频繁的通信会造成socket错误,而且频繁的socket 创建也是对资源的浪费。
而像WEB网站的http服务一般都用短链接,因为长连接对于服务端来说会耗费一定的资源,而像WEB网站这么频繁的成千上万甚至上亿客户端的连接用短连接会更省一些资源,如果用长连接,而且同时有成千上万的用户,如果每个用户都占用一个连接的话,那可想而知吧。所以并发量大,但每个用户无需频繁操作情况下需用短连好。
长连接多用于操作频繁,点对点的通讯,而且连接数不能太多情况,。每个TCP连接都需要三步握手,这需要时间,如果每个操作都是先连接,再操作的话那么处理速度会降低很多,所以每个操作完后都不断开,次处理时直接发送数据包就OK了,不用建立TCP连接。例如:数据库的连接用长连接, 如果用短连接频繁的通信会造成socket错误,而且频繁的socket 创建也是对资源的浪费。
而像WEB网站的http服务一般都用短链接,因为长连接对于服务端来说会耗费一定的资源,而像WEB网站这么频繁的成千上万甚至上亿客户端的连接用短连接会更省一些资源,如果用长连接,而且同时有成千上万的用户,如果每个用户都占用一个连接的话,那可想而知吧。所以并发量大,但每个用户无需频繁操作情况下需用短连好。
队头阻塞
因为HTTP规定报文“一发一收”,所以形成了一个先进先出的串行队列。队列里的请求没有轻重缓急的优先级,只有入队的先后顺序,排在最前面的请求被最优先处理。
方案:并发连接、域名分片
长轮询、短轮询
tcp/udp
三次握手
四次挥手
tcp、udp结构
tcp如何保证可靠性
检验和
三次握手四次挥手
序列号、确认号
滑动窗口
流量控制
慢启动、拥塞控制、快重传、快恢复
应用场景
https
概念
概念:
- HTTP + TLS / SSL,默认端口是 443
- HTTPS协议是通过加入SSL(Secure Sockets Layer)层来加密HTTP数据进行安全传输的HTTP协议,同时启用默认的443端口进行数据传输。
- 因为 HTTP 是明文传输,所以不安全,容易被黑客窃听或篡改
- 通信须保证安全:机密性 + 完整性 + 身份认证 + 不可否认
SSL/TLS是什么(处于会话层):
它把 HTTP 下层的传输协议由 TCP/IP 换成了 SSL/TLS,由“HTTP over TCP/IP”变成了“HTTP over SSL/TLS”,让 HTTP 运行在了安全的 SSL/TLS 协议上,收发报文不再使用 Socket API,而是调用专门的安全接口。
它把 HTTP 下层的传输协议由 TCP/IP 换成了 SSL/TLS,由“HTTP over TCP/IP”变成了“HTTP over SSL/TLS”,让 HTTP 运行在了安全的 SSL/TLS 协议上,收发报文不再使用 Socket API,而是调用专门的安全接口。
SSL/TSL
SSL:secure socket layer 安全套接层
TSL:transport secure layer 安全传输层
TSL:transport secure layer 安全传输层
对称加密和非对称加密
对称加密
- 发送端和接收端首先要共享相同的密钥;才能进行通信。
- 发送端和接收端首先要共享相同的密钥k(即通信前双方都需要知道对应的密钥)才能进行通信。发送端用共享密钥k对明文p进行加密,得到密文c,并将得到的密文发送给接收端,接收端收到密文后,并用其相同的共享密钥k对密文进行解密,得出明文p。
- 对称加密算法是公开的
优点:
只使用一个密钥,运算速度快,密钥必须保证加密,但是无法做到安全的密钥交换。
只使用一个密钥,运算速度快,密钥必须保证加密,但是无法做到安全的密钥交换。
缺点:
- 对称加密是一对一的使用方式,若一方要跟n方通信,则需要维护n对密钥。即服务端为所有客户端都应用同一个密钥A,破解了一个用户,所有的用户信息都会被盗取。
非对称加密
使用两个密钥:公钥和私钥
公钥加密的内容,私钥可以解开
私钥加密的内容,所有的公钥可以解开
私钥只保存在服务端,公钥可以发送给所有的客户端。
公钥可以任意分发而私钥保密,解决了密钥交换问题但速度慢,常用的有 RSA 和 ECC
公钥加密的内容,私钥可以解开
私钥加密的内容,所有的公钥可以解开
私钥只保存在服务端,公钥可以发送给所有的客户端。
公钥可以任意分发而私钥保密,解决了密钥交换问题但速度慢,常用的有 RSA 和 ECC
非对称加密可以解决“密钥交换”的问题。网站秘密保管私钥,在网上任意分发公钥,你想要登录网站只要用公钥加密就行了,密文只能由私钥持有者才能解密。而黑客因为没有私钥,所以就无法破解密文,但速度慢。
数字签名
非对称加密的缺点:
- 黑客虽然拿不到会话密钥,无法破解密文,但可以通过窃听收集到足够多的密文,再尝试着修改、重组后发给网站。因为没有完整性保证,服务器只能“照单全收”,然后他就可以通过服务器的响应获取进一步的线索,最终就会破解出明文。
- 另外,黑客也可以伪造身份发布公钥。如果你拿到了假的公钥,混合加密就完全失效了。你以为自己是在和“某宝”通信,实际上网线的另一端却是黑客,银行卡号、密码等敏感信息就在“安全”的通信过程中被窃取了。
保证信息完整性:摘要算法
- 摘要算法,也是常说的散列函数、哈希函数。
- 你可以把摘要算法近似地理解成一种特殊的压缩算法,它能够把任意长度的数据“压缩”成固定长度、而且独一无二的“摘要”字符串,就好像是给这段数据生成了一个数字“指纹”。
摘要算法是把数据从一个大空间映射到了小空间,所以存在冲突的可能性。
数字签名:
- 加密算法结合摘要算法,可以解决通信过程的安全性和信息的完整性,但无法确保是否是服务端,所以得确保身份认证。
- 数字证书 = 网站信息 + 数字签名;即信息先进行摘要算法加密,再用私钥进行加密。接收到信息后再用公钥解密。
- 公钥被掉包,是因为客户端无法分辨穿回来的公钥是中间人还是服务器。
- 现实生活中,解决身份认证的手段是签名和印章,只要在纸上写下签名或者盖个章,就能够证明这份文件确实是由本人而不是其他人发出的。
数字签名缺点:
- 存在“公钥的信任”问题。
证书
证书 ( 第三方认证 ):
这个“第三方”就是我们常说的 CA(Certificate Authority,证书认证机构)。它就像网络世界里的公安局、教育部、公证中心,具有极高的可信度,由它来给各个公钥签名,用自身的信誉来保证公钥无法伪造,是可信的。
即公钥的分发需要使用数字证书,必须由 CA 的信任链来验证,否则就是不可信的;
这个“第三方”就是我们常说的 CA(Certificate Authority,证书认证机构)。它就像网络世界里的公安局、教育部、公证中心,具有极高的可信度,由它来给各个公钥签名,用自身的信誉来保证公钥无法伪造,是可信的。
即公钥的分发需要使用数字证书,必须由 CA 的信任链来验证,否则就是不可信的;
SSL协议通信过程
双向认证 SSL 协议的具体通讯过程,服务器和用户双方必须都有证书。由此可见,SSL协议是通过非对称密钥机制保证双方身份认证,并完成建立连接,在实际数据通信时通过对称密钥机制保障数据安全性
SSL协议通信过程
浏览器首先要从URI里提取出协议和域名。协议名是https、所以默认端口号是 443,再用DNS解析,得到目标的IP地址,然后就可以使用三次握手与网站建立TCP连接了。
(1) 浏览器发送一个连接请求给服务器;服务器将自己的证书(包含服务器公钥S_PuKey)、对称加密算法种类及其他相关信息返回客户端;
(2) 客户端浏览器检查服务器传送到CA证书是否由自己信赖的CA中心签发。若是,执行4步;否则,给客户一个警告信息:询问是否继续访问。
(3) 客户端浏览器比较证书里的信息,如证书有效期、服务器域名和公钥S_PK,与服务器传回的信息是否一致,如果一致,则浏览器完成对服务器的身份认证。
(4) 服务器要求客户端发送客户端证书(包含客户端公钥C_PuKey)、支持的对称加密方案及其他相关信息。收到后,服务器进行相同的身份认证,若没有通过验证,则拒绝连接;
(5) 服务器根据客户端浏览器发送到密码种类,选择一种加密程度最高的方案,用客户端公钥C_PuKey加密后通知到浏览器;
(6) 客户端通过私钥C_PrKey解密后,得知服务器选择的加密方案,并选择一个通话密钥key,接着用服务器公钥S_PuKey加密后发送给服务器;
(7) 服务器接收到的浏览器传送到消息,用私钥S_PrKey解密,获得通话密钥key。
(8) 接下来的数据传输都使用该对称密钥key进行加密。
浏览器首先要从URI里提取出协议和域名。协议名是https、所以默认端口号是 443,再用DNS解析,得到目标的IP地址,然后就可以使用三次握手与网站建立TCP连接了。
(1) 浏览器发送一个连接请求给服务器;服务器将自己的证书(包含服务器公钥S_PuKey)、对称加密算法种类及其他相关信息返回客户端;
(2) 客户端浏览器检查服务器传送到CA证书是否由自己信赖的CA中心签发。若是,执行4步;否则,给客户一个警告信息:询问是否继续访问。
(3) 客户端浏览器比较证书里的信息,如证书有效期、服务器域名和公钥S_PK,与服务器传回的信息是否一致,如果一致,则浏览器完成对服务器的身份认证。
(4) 服务器要求客户端发送客户端证书(包含客户端公钥C_PuKey)、支持的对称加密方案及其他相关信息。收到后,服务器进行相同的身份认证,若没有通过验证,则拒绝连接;
(5) 服务器根据客户端浏览器发送到密码种类,选择一种加密程度最高的方案,用客户端公钥C_PuKey加密后通知到浏览器;
(6) 客户端通过私钥C_PrKey解密后,得知服务器选择的加密方案,并选择一个通话密钥key,接着用服务器公钥S_PuKey加密后发送给服务器;
(7) 服务器接收到的浏览器传送到消息,用私钥S_PrKey解密,获得通话密钥key。
(8) 接下来的数据传输都使用该对称密钥key进行加密。
连接太慢,如何优化
HTTPS与HTTP的区别
socket
概念:应用层通过传输层进行数据通信时,TCP和UDP会遇到同时为多个应用程序进程提供并发服务的问题。为了区别不同的应用程序进程和连接,许多计算机操作系统为应用程序与TCP/IP协议交互提供了称为套接字(Socket)的接口,区分不同应用程序进程间的网络通信和连接。
Socket与TCP/IP的关系:TCP/IP只是一个协议栈,就像操作系统的运行机制一样,必须要具体实现,同时还要提供对外的操作接口。这个就像操作系统会提供标准的编程接口,比如win32编程接口一样,TCP/IP也要提供可供程序员做网络开发所用的接口,这就是Socket编程接口。比如一些最基本的函数接口create、 listen、connect、accept、send、read和write等等
跨域
什么是跨域
同源策略
指协议、域名、端口三者相同。
安全策略
防止XSS、CSRF攻击
同源策略限制内容有:
· Cookie、LocalStorage、IndexedDB 等存储性内容
· DOM 节点
· AJAX 请求发送后,结果被浏览器拦截了
· Cookie、LocalStorage、IndexedDB 等存储性内容
· DOM 节点
· AJAX 请求发送后,结果被浏览器拦截了
但是有三个标签允许跨域加载资源:
· <img src=XXX>
· <link href=XXX>
· <script src=XXX>
· <img src=XXX>
· <link href=XXX>
· <script src=XXX>
jsonp
利用 <script> 标签没有跨域限制的漏洞,网页可以得到从其他来源动态产生的 JSON 数据。JSONP请求一定需要对方的服务器做支持才可以。
JSONP和AJAX相同,都是客户端向服务器端发送请求,从服务器端获取数据的方式。但AJAX属于同源策略,JSONP属于非同源策略(跨域请求)
缺点:仅支持get方法
JQ的JSONP形式
corf
服务端设置 Access-Control-Allow-Origin 就可以开启 CORS。 该属性表示哪些域名可以访问资源,如果设置通配符则表示所有网站都可以访问资源。
虽然设置 CORS 和前端没什么关系,但是通过这种方式解决跨域问题的话,会在发送请求时出现两种情况,分别为简单请求和复杂请求。
postmessage
可解决的问题
页面和其打开的新窗口的数据传递
多窗口之间消息传递
页面与嵌套的iframe消息传递
上面三个场景的跨域数据传递
多窗口之间消息传递
页面与嵌套的iframe消息传递
上面三个场景的跨域数据传递
示例
// a.html
<iframe src="http://localhost:4000/b.html" frameborder="0" id="frame" onload="load()"></iframe> //等它加载完触发一个事件
//内嵌在http://localhost:3000/a.html
<script>
function load() {
let frame = document.getElementById('frame')
frame.contentWindow.postMessage('我爱你', 'http://localhost:4000') //发送数据
window.onmessage = function(e) { //接受返回数据
console.log(e.data) //我不爱你
}
}
// b.html
window.onmessage = function(e) {
console.log(e.data) //我爱你
e.source.postMessage('我不爱你', e.origin)
}
<iframe src="http://localhost:4000/b.html" frameborder="0" id="frame" onload="load()"></iframe> //等它加载完触发一个事件
//内嵌在http://localhost:3000/a.html
<script>
function load() {
let frame = document.getElementById('frame')
frame.contentWindow.postMessage('我爱你', 'http://localhost:4000') //发送数据
window.onmessage = function(e) { //接受返回数据
console.log(e.data) //我不爱你
}
}
// b.html
window.onmessage = function(e) {
console.log(e.data) //我爱你
e.source.postMessage('我不爱你', e.origin)
}
websocket
Websocket是HTML5的一个持久化的协议,它实现了浏览器与服务器的全双工通信,也是跨域的一种解决方案。
WebSocket和HTTP都是应用层协议,都基于 TCP 协议。但是 WebSocket 是一种双向通信协议,在建立连接之后,WebSocket 的 server 与 client 都能主动向对方发送或接收数据。同时,WebSocket 在建立连接时需要借助 HTTP 协议,连接建立好了之后 client 与 server 之间的双向通信就与 HTTP 无关了。
node中间件反向代理(两次跨域)
get和post的区别
正向代理、反向代理
dns
沙盒与IFF
- 概念:
一小块的真实环境,里面发生的事情不会影响到外面。相同的操作,相同的数据都不会和外面发生冲突(但是声明变量没有使用let会变量提升);
作用:
避免命名冲突;
网络协议栈
TCP/IP四层、五层、OSI ( 开放式系统互联通信参考模型”(Open System Interconnection Reference Model) ) 七层
网络安全
XSS攻击
网页被注入恶意代码,使用户加载并执行攻击者恶意制造的网页程序。
盗取用户的 cookie 信息从而伪装成用户去操作,危害数据安全。
盗取用户的 cookie 信息从而伪装成用户去操作,危害数据安全。
存储型
将恶意代码存储在服务端,持久型
反射型
通过URL请求、返回恶意代码
DOM型
将恶意代码注入到DOM中,执行时发起攻击
如何防范:对输入(和URL参数)进行过滤,对输出进行转义
设置cookie的HttpOnly属性为 true
纯前端渲染
设置cookie的HttpOnly属性为 true
纯前端渲染
CSRF攻击
跨站请求伪造( Cross-site request forgery ),是一种挟制用户在当前已登录的 Web 应用程序上执行非本意的操作的攻击方法。
攻击者诱导受害者进入第三方网站,在第三方网站中,向被攻击网站发送跨站请求。利用受害者在被攻击网站已经获取的注册凭证,绕过后台的用户验证,达到冒充用户对被攻击的网站执行某项操作的目的。
攻击者诱导受害者进入第三方网站,在第三方网站中,向被攻击网站发送跨站请求。利用受害者在被攻击网站已经获取的注册凭证,绕过后台的用户验证,达到冒充用户对被攻击的网站执行某项操作的目的。
中间人攻击
请求劫持
知识扩展
serveless
小程序云开发
ssr
cdn
react
nodeJS
rollup、vite打包工具
react
概念
核心思想
类组件
生命周期
四大属性
state
props
refs
context
函数组件
展示组件(纯组件、props)
HOC高阶组件
Hooks组件
概念:
给予函数式组件类似于类组件生命周期的概念,扩大了函数式组件的应用范围。
给予函数式组件类似于类组件生命周期的概念,扩大了函数式组件的应用范围。
提出的原由:
类组件难以 实现复用性(redux、mobx),使用 hooks更面向函数式编程(声明、过滤添加业务逻辑、调用)
Hooks 鼓励开发者将业务通用的逻辑封装成React Hooks,而不是根据类组件。
目前函数式组件基本用于 纯展示组件 ,一旦函数式组件耦合有业务逻辑,就需要通过 Props 的传递,通过子组件触发父组件方法的方式来实现业务逻辑的传递,Hooks 的出现使得函数组件也有了自己的状态与业务逻辑,简单逻辑在自己内部处理即可,不再需要通过 Props 的传递,使简单逻辑组件抽离更加方便,也使使用者无需关心组件内部的逻辑,只关心 Hooks 组件返回的结果即可。
类组件难以 实现复用性(redux、mobx),使用 hooks更面向函数式编程(声明、过滤添加业务逻辑、调用)
Hooks 鼓励开发者将业务通用的逻辑封装成React Hooks,而不是根据类组件。
目前函数式组件基本用于 纯展示组件 ,一旦函数式组件耦合有业务逻辑,就需要通过 Props 的传递,通过子组件触发父组件方法的方式来实现业务逻辑的传递,Hooks 的出现使得函数组件也有了自己的状态与业务逻辑,简单逻辑在自己内部处理即可,不再需要通过 Props 的传递,使简单逻辑组件抽离更加方便,也使使用者无需关心组件内部的逻辑,只关心 Hooks 组件返回的结果即可。
钩子函数
基本钩子
useState
useEffect
useContext
扩展钩子
useCallback
useMemo
useStore
useContext
生命周期
受控组件与非受控组件
状态提升
redux 全局状态管理器
react-router 路由管理
组件通信
事件合成系统
考虑到浏览器的兼容性和性能问题,与原生事件直接在元素上注册的方式不同的是,react的合成事件不会直接绑定到目标dom节点上,用事件委托机制,
以队列的方式,从触发事件的组件向父组件回溯直到document节点,因此React组件上声明的事件最终绑定到了document 上。
用一个统一的监听器去监听,这个监听器上保存着目标节点与事件对象的映射,当组件挂载或卸载时,只是在这个统一的事件监听器上插入或删除一些对象;
当事件发生时,首先被这个统一的事件监听器处理,然后在映射里找到真正的事件处理函数并调用。这样做的好处:
1.减少内存消耗,提升性能,不需要注册那么多的事件了,一种事件类型只在 document 上注册一次
2.统一规范,解决 ie 事件兼容问题,简化事件逻辑
3.对开发者友好
以队列的方式,从触发事件的组件向父组件回溯直到document节点,因此React组件上声明的事件最终绑定到了document 上。
用一个统一的监听器去监听,这个监听器上保存着目标节点与事件对象的映射,当组件挂载或卸载时,只是在这个统一的事件监听器上插入或删除一些对象;
当事件发生时,首先被这个统一的事件监听器处理,然后在映射里找到真正的事件处理函数并调用。这样做的好处:
1.减少内存消耗,提升性能,不需要注册那么多的事件了,一种事件类型只在 document 上注册一次
2.统一规范,解决 ie 事件兼容问题,简化事件逻辑
3.对开发者友好
详细笔记见链接:
React Fiber
TS
基本数据类型
类
接口
泛型
数据类型规范
移动端
概念
- web app ( H5 )
- 混合 app (uni-app,flutter)
- native app ( 安卓、ios )
- 原生JS开发原生app:react-native flutter
响应式开发
- viewport
- @media媒体查询
- rem、百分比、em、px、vh、vw尺寸
- 对HTML字体大小动态设置
微信原生小程序
uni-app
使用vue 和微信小程序 结合开发前端应用的框架,开发者吧编写一套代码,可发布到OPS、Android、H5以及各种小程序。
HTML
SEO
概念:“搜索引擎蜘蛛”根据url资源,分析、抠出资源的内容,进行分析提炼,找到其中的关键词。
meta
模拟请求头http-equiv
expires:过期时间
refresh:定时刷新
X-UA-Compatible:使用浏览器版本
set-cookie:设置cookie
页面关键词keywords
页面描述内容description
定义网页作者author
媒体查询viewport
前端SEO规范
导航优化
网站结构布局
利用布局,把重要的内容HTML代码放在前面
网页代码优化
突出重要内容,合理设置title、meta
语义化HTML代码
a标签,加title属性说明,imge标签加alt说明
重要内容不要用JS输出
尽量少使用iframe框架,因为蜘蛛读取不到其中的内容。
网站性能优化
新语义化标签
header、footer、nav、section、actical、progress等语义元素。
新增媒体标签
canvas、svg、video、audio、拖放API、地理定位、Web worker、WebSocket
Canvas 画布标签:用于在网页上绘制自定义图形
getContext()方法返回一个对象,提供用于在画布上绘图的方法和属性。
getContext()方法返回一个对象,提供用于在画布上绘图的方法和属性。
async、defer异步加载JS代码
async和defer,这两个属性使得script都不会阻塞DOM的渲染
- async:异步加载资源、加载完成立即执行,不保证在DOMConentLoad后执行,但一定在onload事件之前。
- 多个脚本文件使用async,不保证其执行顺序,先下载完成则先执行
- 下载快的先执行,这导致async属性下的脚本是乱序的,对于script有先后依赖关系的情况,并不适⽤
defer:异步加载完成并不会立刻执行,等待HTML load钩子函数触发再执行,按照加载顺序执行脚本
onload和DomContentLoad的区别
1、当 onload 事件触发时,页面上所有的DOM,样式表,脚本,图片,flash都已经加载完成了
2、DomContentLoad是Dom加载完成后执行,不必等待样式脚本和图片加载
1、当 onload 事件触发时,页面上所有的DOM,样式表,脚本,图片,flash都已经加载完成了
2、DomContentLoad是Dom加载完成后执行,不必等待样式脚本和图片加载
前端存储
cookie
概念:cookie 是服务器提供的一种用于维护会话状态信息的数据,通过服务器发送到浏览器,浏览器保存在本地的一种纯文本文件,当下一次有同源的请求(path、domain、secure)时,将保存的 cookie 值添加到请求头部,发送给服务端。这可以用来实现记录用户登录状态等功能。
客户端保持状态的方案。这种生命期为浏览会话期的cookie被称为会话cookie。会话cookie一般不保存在硬盘上而是保存在内存里。
cookie 一般可以存储 4kb 大小的数据,并且只能够被同源的网页所共享访问。且默认为会话级别,需设置maxAge生存周期实现本地存储。
客户端保持状态的方案。这种生命期为浏览会话期的cookie被称为会话cookie。会话cookie一般不保存在硬盘上而是保存在内存里。
cookie 一般可以存储 4kb 大小的数据,并且只能够被同源的网页所共享访问。且默认为会话级别,需设置maxAge生存周期实现本地存储。
属性:name=value、maxAge、expires、path、domain、httpOnly、securice、sameSite
使用场景:实现自动登录、记录用户习惯(网页主题、保留浏览页数)
session
概念:session技术是基于cookie,cookie技术存储session编号--jsessionid,是存储在服务端的,记录服务器和客户端会话状态的机制。
服务器端保持状态的方案。
生命周期由服务端设置,而不是网页关闭后会话ID就失效!
服务器端保持状态的方案。
生命周期由服务端设置,而不是网页关闭后会话ID就失效!
一个常见的误解是以为session在有客户端访问时就被创建,然而事实是直到某server端程序调用HttpServletRequest.getSession(true)这样的语句时才被创建。
session在下列情况下被删除:
A.程序调用HttpSession.invalidate()
B.距离上一次收到客户端发送的session id时间间隔超过了session的最大有效时间
C.服务器进程被停止
A.程序调用HttpSession.invalidate()
B.距离上一次收到客户端发送的session id时间间隔超过了session的最大有效时间
C.服务器进程被停止
使用:发起请求时运行携带cookie传递sessionID( axios.default.withCredentials = true )
后端设置 Access-Control-Allow-Credentials:true
后端设置 Access-Control-Allow-Credentials:true
使用场景:安全级别更高,用户账号信息,敏感信息
cookie与sesion的区别
- cookie和session都是用来跟踪浏览器用户身份的会话方式。
- session保存在服务器,cookie保存在客户端
- session中保存的时对象,cookie保存的是字符串
- session不能区分路径,同一个用户访问一个网站期间,所有的session在任何一个地方都可以访问
- cookie如果设置路径,则在某些地方不能访问 session需要借助cookie才能正常工作,
- 如果禁用cookie,session则失效
- 客户端会在发送请求的时候,自动将本地存活的cookie封装在信息头发送给服务器
token、jwt
session cookie和session对象的生命周期是一样的吗:
当用户关闭了浏览器虽然session cookie已经消失,但session对象仍然保存在服务器端,直到其失效时间。
当用户关闭了浏览器虽然session cookie已经消失,但session对象仍然保存在服务器端,直到其失效时间。
sessionStorage
会话存储,属于同一会话级别有效,即有当前页面跳转的新页面,可数据共享。
localStorage
本地存储,永久有效。会出现超出配额现象。可采取降级处理
IndexdDB
Web SQL
href、src、@import
href
超文本引用,会并行的下载资源,不会阻塞HTML解析。因此建议使用link标签而不是@import来导入到html文档里。
src
资源下载,阻塞HTML解析。
@import
@import是css提供的语法规则,只有导入样式表的作用;
@impoer引入的css文件在页面加载完毕后才被加载;
@impoer引入的css文件在页面加载完毕后才被加载;
浏览器内核
浏览器类型 内核 JS引擎
IE Trident JScript
Firefox Gecko TraceMonkey
Chrome WebKit, Blink V8
Safari WebKit SquirrelFish Extreme
Opera Presto Carakan
CSS
box-sizing
content-box:宽度等于content
border-box:宽度等于content、padding、border
定位布局
包含块
两栏布局
左浮动 + 负边距左偏移(响应式用inline-block)
左绝对定位 + 负边距左偏移
flex、inline-table、grip
三栏布局
左浮动右浮动,中间水平居中
两边使用绝对定位,中间水平居中
两边使用float和负margin
table、flex
如何修改节点的样式
FC ( Formatting Context )格式化上下文
它是页面中一块独立的渲染区域,并有一套渲染规则
它决定了其子元素如何定位,以及和其他元素的关系
和作用。
它是页面中一块独立的渲染区域,并有一套渲染规则
它决定了其子元素如何定位,以及和其他元素的关系
和作用。
BFC:块级格式化是上下文
布局规则
内部的Box会在垂直方向,一个接一个地放置;
Box垂直方向的距离由margin决定。属于同一个BFC的两个相邻Box的margin会发生重叠;
BFC的区域不会与float box重叠;
BFC就是页面上的一个隔离的独立容器,容器里面的子元素不会影响到外面的元素。反之也如此;
计算BFC的高度时,浮动元素也参与计算。
Box垂直方向的距离由margin决定。属于同一个BFC的两个相邻Box的margin会发生重叠;
BFC的区域不会与float box重叠;
BFC就是页面上的一个隔离的独立容器,容器里面的子元素不会影响到外面的元素。反之也如此;
计算BFC的高度时,浮动元素也参与计算。
触发条件
根元素,即HTML元素
float不为none
overflow不为visible
display值为inline-block、flex、inline-flex、table-cell
应用
清除浮动
阻止被浮动元素覆盖
解决同一个BFC中外边距重叠问题
IFC:行内格式化上下文
FFC:自适应格式化上下文
GFC:网格布局格式化上下文
水平垂直居中
定宽高
absolute + 负margin
absolute + calc
position: absolute;
top: calc(50% - 高度一半);
left: calc(50% - 宽度一半);
top: calc(50% - 高度一半);
left: calc(50% - 宽度一半);
不定宽高
absolute + transform
flex
浮动
层叠上下文、渲染图层、复合图层
复合图层(硬件加速)
概念
把一个元素变成一个复合图层、就是传说中的硬件加速技术,它会单独分配资源
- 最常用的方式:translate3d、translateZ
- opacity属性/过渡动画(需要动画执行的过程中才会创建合成层,动画没有开始或结束后元素还会回到之前的状态)
- will-chang属性(这个比较偏僻),一般配合opacity与translate使用(而且经测试,除了上述可以引发硬件加速的属性外,其它属性并不会变成复合层),作用是提前告诉浏览器要变化,这样浏览器会开始做一些优化工作(这个最好用完后就释放)
- <video><iframe><canvas><webgl>等元素
- 其它,譬如以前的flash插件
absolute和硬件加速的区别
可以看到,absolute虽然可以脱离普通文档流,但是无法脱离默认复合层。
所以,就算absolute中信息改变时不会改变普通文档流中render树,但是,浏览器最终绘制时,是整个复合层绘制的,所以absolute中信息的改变,仍然会影响整个复合层的绘制。
(浏览器会重绘它,如果复合层中内容多,absolute带来的绘制信息变化过大,资源消耗是非常严重的)
而硬件加速直接就是在另一个复合层了(另起炉灶),所以它的信息改变不会影响默认复合层(当然了,内部肯定会影响属于自己的复合层),仅仅是引发最后的合成(输出视图)
所以,就算absolute中信息改变时不会改变普通文档流中render树,但是,浏览器最终绘制时,是整个复合层绘制的,所以absolute中信息的改变,仍然会影响整个复合层的绘制。
(浏览器会重绘它,如果复合层中内容多,absolute带来的绘制信息变化过大,资源消耗是非常严重的)
而硬件加速直接就是在另一个复合层了(另起炉灶),所以它的信息改变不会影响默认复合层(当然了,内部肯定会影响属于自己的复合层),仅仅是引发最后的合成(输出视图)
复合图层的作用
一般一个元素开启硬件加速后会变成复合图层,可以独立于普通文档流中,改动后可以避免整个页面重绘,提升性能
但是尽量不要大量使用复合图层,否则由于资源消耗过度,页面反而会变的更卡
但是尽量不要大量使用复合图层,否则由于资源消耗过度,页面反而会变的更卡
硬件加速时请使用index
使用硬件加速时,尽可能的使用index,防止浏览器默认给后续的元素创建复合层渲染
具体的原理时这样的:
webkit CSS3中,如果这个元素添加了硬件加速,并且index层级比较低,
那么在这个元素的后面其它元素(层级比这个元素高的,或者相同的,并且releative或absolute属性相同的),会默认变为复合层渲染,如果处理不当会极大的影响性能
简单点理解,其实可以认为是一个隐式合成的概念:如果a是一个复合图层,而且b在a上面,那么b也会被隐式转为一个复合图层,这点需要特别注意
具体的原理时这样的:
webkit CSS3中,如果这个元素添加了硬件加速,并且index层级比较低,
那么在这个元素的后面其它元素(层级比这个元素高的,或者相同的,并且releative或absolute属性相同的),会默认变为复合层渲染,如果处理不当会极大的影响性能
简单点理解,其实可以认为是一个隐式合成的概念:如果a是一个复合图层,而且b在a上面,那么b也会被隐式转为一个复合图层,这点需要特别注意
层叠上下文
每个层叠上下文都完全独立于它的兄弟元素:当处理层叠时只考虑子元素
个层叠上下文都是自包含的:当一个元素的内容发生层叠后,该元素将被作为整体在父级层叠上下文中按顺序进行层叠
没有创建自己的层叠上下文的元素会被父层叠上下文同化
实现三角形、梯形、圆形( border-raduis:50% )
css3新特性
权重
!important>style属性内联>ID选择器>(类选择器=属性选择器=伪类)>标签选择器>通配选择器*
ES6
var、let、const定义声明变量
词法作用域指的是定义在词法阶段的作用域,即作用域是由你在写代码时将变量和块级作用域写在哪里来决定的。
无论函数在哪里被调用,也无论它如何被调用,它的词法作用域都只由函数被声明时所处的位置决定。
所以函数的作用域链也是在一开始写下代码的时候就已经定下来了,而非后面的编译阶段才定下来的。
无论函数在哪里被调用,也无论它如何被调用,它的词法作用域都只由函数被声明时所处的位置决定。
所以函数的作用域链也是在一开始写下代码的时候就已经定下来了,而非后面的编译阶段才定下来的。
var声明的缺点:函数作用域、可重复声明、循环计数泄漏为全局变量
特性:暂时性死区、块级作用域、无法变量提升
暂时性死区
如果区块(花括号)中存在let命令,这个区块对这些命令声明的变量,从一开始就形成了封闭作用域,凡是在声明之前就使用这些变量,就会报错,所以在代码块内,使用let命令声明变量之前,该变量都是不可用的
块级作用域
花括号内,即为块级作用域,里面的变量为私有变量,不会影响外部
const定义常量,必须有初始值,不可修改地址,通过Obj.defineProperty设置其writable:false 不可修改值,但可扩展对象的属性。
解构赋值、扩展操作符、字符串模板、参数默认值
symbol对象
声明独一无二的值,常用来定义对象的属性名,防止与对象中的原来的属性或方法名冲突
Symbol没有办法被for..in遍历,但可以使用Object.getOwnPropertySymbols查看对象上所有的symbol属性。
箭头函数
普通函数的this指向于最近的调用对象。
没有构造器,无法new 操作;没有原型、没有super、没有argments、没有变量提升
没有this属性,内部的this就是一个普通变量,指向定义时上层函数所在的对象,也就是说箭头函数内部的this执行是固定的;
无法通过call()或 apply()来改变其运行的作用域来改变其tihi指向
无法通过call()或 apply()来改变其运行的作用域来改变其tihi指向
使用场景:( 让this指向固定化 ),封装回调函数、事件处理、定时器
new一个对象
- 向内存申请空间,创建新的空对象
- 将新对象的原型对象设置为与构造函数的原型对象同一地址,即继承构造函数原型上的属性和方法。
- 指向构造函数,方法内的this被指定为该新实例
- 返回新实例,若有设置返回对象,则返回对象,否则返回非对象类型,则返回新对象
class
- 构造函数可定义构造函数属性与方法;
- 在构造函数外、class内定义的属性和方法为原型对象上的属性和方法;
- 通过static定义私有方法和属性。可通过类名.xx访问使用。
若没有定义构造器在执行时会添加一个空的构造方法
ES5继承与ES6继承
ES5继承
构造函数继承
通过call()、bind()或apply 执行父类构造函数,绑定this
缺点:只能访问父类构造函数属性与方法,无法访问父类的原型上属性和方法
原型对象继承
在子类的原型对象设置为 new 父类实例。
缺点;无法访问父类构造函数中的属性和方法
组合继承
子类构造函数中绑定父类构造函数,设置子类原型对象为父类实例
缺点:每个子类实例两次父类构造函数,内存占用高
寄生组合继承
将子类原型对象设置为父类的原型对象,从新绑定原型对象的构造函数指向为子类构造函数
ES6继承
extends父类、在构造器中首行super父类构造器
Mpa、Set、WeakMap、WeakSet
Map
WeakMap
Set
WeakSet
promise
概念
起初异步函数之间存在依赖关系,通过层层嵌套回调,可满足这种顺序执行、相互之间的依赖关系,但是缺点是当层次太多时,会代码臃肿、难易维护;
后来提出一种用同步的流程表示异步的操作,串行执行有依赖关系的异步函数。即 promise的 链式调用,增强可读性与可维护性;缺点是无法取消,一旦开始执行,中途无法取消。
后来提出一种用同步的流程表示异步的操作,串行执行有依赖关系的异步函数。即 promise的 链式调用,增强可读性与可维护性;缺点是无法取消,一旦开始执行,中途无法取消。
构造函数内的属性和方法
value、reason、state、succeedArr、failArr
resolve()、reject()
resolve()、reject()
原型对象上的方法
then 、catch、finnally 监听
- 通过Promise.prototype.then和Promise.prototype.catch方法将观察者方法注册到被观察者Promise对象中,同时返回一个新的Promise对象,以便可以链式调用。
- 被观察者管理内部pending、fulfilled和rejected的状态转变,同时通过构造函数中传递的resolve和reject方法以主动触发状态转变和通知观察者。
Promise.prototype.catch是 .then(null,rejection) 或是 .then(undefined, rejection),用于指定发生错误时的回调函数。
如果Promise 对象状态变为resolved,则会调用then方法指定的回调函数;如果异步操作抛出错误,状态就会变为rejected,就会调用catch方法指定的回调函数,处理这个错误。另外,then方法指定的回调函数,如果运行中抛出错误,也会被catch方法捕获。
如果Promise 对象状态变为resolved,则会调用then方法指定的回调函数;如果异步操作抛出错误,状态就会变为rejected,就会调用catch方法指定的回调函数,处理这个错误。另外,then方法指定的回调函数,如果运行中抛出错误,也会被catch方法捕获。
Promise.all、Promise.race
将接收到的promise列表的结果返回,区别是,all是等待所有的promise都触发成功了,才会返回,而arce有一个成功了就会返回结果。其中任何一个promise执行失败了,都会直接返回失败的结果。
Promise.resolve
返回一个状态由给定 value 决定的 Promise 对象。如果该值是 thenable(即,带有 then 方法的对象),返回的 Promise 对象的最终状态由 then 方法执行决定;否则的话(该 value 为空,基本类型或者不带 then 方法的对象),返回的 Promise 对象状态为 fulfilled,并且将该 value 传递给对应的 then 方法。通常而言,如果你不知道一个值是否是 Promise 对象,使用 Promise.resolve(value) 来返回一个 Promise 对象,这样就能将该 value 以 Promise 对象形式使用。
promise为ES6重点掌握,所以promise相关的知识点都要掌握、会手写原理
async/await
声明一个函数为 async,里面异步函数需等待其执行完,再顺序执行。即把异步当同步执行。增强可读性
模块发展
模块开发概念
闭包、立即执行函数、一个脚本文件一个功能模块,实现单一职责原则与多人开发合作
CommonJS
commonJS用同步的方式加载模块。在服务端,模块文件都存在本地磁盘,读取非常快,所以这样做不会有问题。但是在浏览器端,限于网络原因,会出现队头阻塞情况,更合理的方案是使用异步加载。
动态只读引用(import命令指向一个只读引用),浅拷贝。使用设置缓存,再次使用会去缓存中的值,所以导入的对象为缓存中的同一对象,会存在相互影响。
AMD,requireJS
依赖前置,提前执行。模块依赖异步加载,需等待所有的依赖模块加载成功后才会运行。所以不保证模块加载的顺序性
CMD,seaJS
依赖就近,延迟执行。解决AMD在声明依赖的模块时会在第一时间加载并执行模块内的代码。
AMD与CMD总结
最大的区别是对依赖模块执行时机处理不同,加载模块都是异步加载。
同样都是异步加载模块,AMD在加载模块完成后就会执行改模块,所有模块都加载执行完后会进入require的回调函数,执行主逻辑,这样的效果就是依赖模块的执行顺序和书写顺序不一定一致,看网络速度,哪个先下载下来,哪个先执行,但是主逻辑一定在所有依赖加载完成后才执行。
CMD加载完某个依赖模块后并不执行,只是下载而已,在所有依赖模块加载完成后进入主逻辑,遇到require语句的时候才执行对应的模块,这样模块的执行顺序和书写顺序是完全一致的。
同样都是异步加载模块,AMD在加载模块完成后就会执行改模块,所有模块都加载执行完后会进入require的回调函数,执行主逻辑,这样的效果就是依赖模块的执行顺序和书写顺序不一定一致,看网络速度,哪个先下载下来,哪个先执行,但是主逻辑一定在所有依赖加载完成后才执行。
CMD加载完某个依赖模块后并不执行,只是下载而已,在所有依赖模块加载完成后进入主逻辑,遇到require语句的时候才执行对应的模块,这样模块的执行顺序和书写顺序是完全一致的。
这也是很多人说AMD用户体验好,因为没有延迟,依赖模块提前执行了,CMD性能好,因为只有用户需要的时候才执行的原因。
缺点:
AMD提高了开发成本,阅读性与书写比较困难,语义不顺畅;不符合通用模块化思维方式
CMD依赖SPM打包,模块的加载逻辑偏重
AMD提高了开发成本,阅读性与书写比较困难,语义不顺畅;不符合通用模块化思维方式
CMD依赖SPM打包,模块的加载逻辑偏重
UMD
跨平台模块开发,浏览器服务端都可支持
ESM
ES6模块不是对象,import命令静态分析。
ComJ、ESM
语法区别:
一个使用import/export 语法
另一个使用require/module 语法
一个使用import/export 语法
另一个使用require/module 语法
另一个 ESM 与 CommonJS 显著的差异在于,ESM 导入模块的变量都是强绑定,导出模块的变量一旦发生变化,对应导入模块的变量也会跟随变化,而 CommonJS 中导入的模块都是值传递与引用传递,类似于函数传参(基本类型进行值传递,相当于拷贝变量,非基础类型【对象、数组】,进行引用传递)。
JS
基础数据类型与引用类型
基础数据类型:number、string、null、undefined、boolen、symbol
引用数据类型:Object、Function、Array、Date
判断数据类型方法
typedof:判断基础数据类型
一般用来判断基本数据类型:除了Null(object)、Symbol(function)之外的其他5个,
还可以判断函数:typeof 方法名(function)
还可以判断函数:typeof 方法名(function)
instanceOf:判断实例对象与构造函数的原型对象是否一致。
一般用来判断自定义的类实例对象,原理是以arr instanceof Array来说
(arr.__proto__一直往上__proto__直到找到Array.prototype为止)
(arr.__proto__一直往上__proto__直到找到Array.prototype为止)
Object.prototype.toString.call()
最可靠的方法。例如:Object.prototype.toString.call(null)=>[object Null]
==和===的区别
==:类型不同,进行隐式转换
1、如果两个值类型相同,进行 === 比较。
2、如果两个值类型不同,他们可能相等。根据下面规则进行类型转换再比较:
3、如果一个是null、一个是undefined,那么[相等]。
4、如果一个是字符串,一个是数值,把字符串转换成数值再进行比较。
5、如果任一值是 true,把它转换成 1 再比较;如果任一值是 false,把它转换成 0 再比较。
6、如果一个是对象,另一个是数值或字符串,把对象转换成基础类型的值再比较。
对象转换成基础类型,利用它的toString或者valueOf方法。
js核心内置类,会尝试valueOf先于toString;例外的是Date,Date利用的是toString转换。
7、任何其他组合,都[不相等]。
总结:对象类型(object、array、function)会转为原始类型(number、string、boolean)的值去比较,
原始类型的值会转为数值类型(0、1)去比较
2、如果两个值类型不同,他们可能相等。根据下面规则进行类型转换再比较:
3、如果一个是null、一个是undefined,那么[相等]。
4、如果一个是字符串,一个是数值,把字符串转换成数值再进行比较。
5、如果任一值是 true,把它转换成 1 再比较;如果任一值是 false,把它转换成 0 再比较。
6、如果一个是对象,另一个是数值或字符串,把对象转换成基础类型的值再比较。
对象转换成基础类型,利用它的toString或者valueOf方法。
js核心内置类,会尝试valueOf先于toString;例外的是Date,Date利用的是toString转换。
7、任何其他组合,都[不相等]。
总结:对象类型(object、array、function)会转为原始类型(number、string、boolean)的值去比较,
原始类型的值会转为数值类型(0、1)去比较
例子:
[]==0 //true
[]==false //true
''==false //true
[]==[] //false不同空间地址
[]==![] //true=>右边=!toBealean([])=>!true=>false=左边
[]==0 //true
[]==false //true
''==false //true
[]==[] //false不同空间地址
[]==![] //true=>右边=!toBealean([])=>!true=>false=左边
===:类型相同值相同
1、如果类型不同,就[不相等]
2、如果两个都是数值,并且是同一个值,那么[相等];
例外的是,如果其中一个是NaN,那么[不相等]。(判断一个值是否是NaN,只能用isNaN()来判断)
3、如果两个都是字符串,每个位置的字符都一样,那么[相等];否则[不相等]。
4、如果两个值都是true,或者都是false,那么[相等]。
5、如果两个值都引用同一个对象或函数,那么[相等];否则[不相等]。
6、如果两个值都是null,或者都是undefined,那么[相等]。
2、如果两个都是数值,并且是同一个值,那么[相等];
例外的是,如果其中一个是NaN,那么[不相等]。(判断一个值是否是NaN,只能用isNaN()来判断)
3、如果两个都是字符串,每个位置的字符都一样,那么[相等];否则[不相等]。
4、如果两个值都是true,或者都是false,那么[相等]。
5、如果两个值都引用同一个对象或函数,那么[相等];否则[不相等]。
6、如果两个值都是null,或者都是undefined,那么[相等]。
例子:
0==='0'===new String('0') //false
undefined===null //false
+0===-0 //true
0==='0'===new String('0') //false
undefined===null //false
+0===-0 //true
区别
"=="表示只要值相等即可为真,而"==="则要求不仅值相等,而且也要求类型相同。
注意事项
对于明确数据类型的用===更为可靠,JavaScript是一门弱类型语言,表达式运算赋值等操作都会导致类型转换。
而一些隐式转换会带来一些意想不到的后果。
而一些隐式转换会带来一些意想不到的后果。
对象属性
创建对象
Object.create()
字面量对象
new Function
属性
数据属性
enumerable可枚举,是否可以被Object.keys()和for..in遍历
writable可写:是否可改变引用地址
configurable可配置:是否可以被删除(delete)
value 值
访问属性
setter
getter
熟悉数组操作的api
事件循环机制
浏览器多进程
浏览器主进程、GPU进程、网络进程、插件进程、渲染进程
渲染主进程
功能:
核心任务是将HTML、CSS、JS转换为用户可与之交互的网页,排版引擎Blink和V8引擎都运行在线程中;
谷歌默认每个Tab标签创建一个渲染进程,渲染进程运行在沙箱模式下
核心任务是将HTML、CSS、JS转换为用户可与之交互的网页,排版引擎Blink和V8引擎都运行在线程中;
谷歌默认每个Tab标签创建一个渲染进程,渲染进程运行在沙箱模式下
GUI渲染进程、JS引擎线程、事件触发线程、定时触发线程、异步http请求线程
执行机制
- 主线程执行完毕,查询任务队列,取出一个任务,推入主线程处理
- 当有异步任务时,提交给对应的异步进程处理
- 异步任务完毕,推入任务队列队尾,等待JS引擎线程的执行
- 主线程执行完当前任务后,会查询微任务队列,微任务队列取完后,会先进行一定时间的UI渲染,再继续取任务队列队头。一次循环;
消息队列
宏任务、微任务
宏任务
setTimeout、setInterval、 setImmediate、script(整体代码)、 I/O 操作、UI 渲染
微任务
宏任务无法处理高级优先任务、无法权衡效率,实时性。即消息队列机制并不是太灵活,为了适应效率和 实时性,引入微任务。
process.nextTick、new Promise().then(回调)、MutationObserver(html5 新特性)、async/await
异步操作解决了同步操作的性能问题,微任务解决了实时性问题
作用域和作用域链
作用域
概念
它决定代码区块中变量和其他资源的可见性,它的最大用处就是隔离变量,不同作用域下同名变量不会有冲突
分类
全局作用域
最外层函数 和在最外层函数外面定义的变量拥有全局作用域
所有末定义直接赋值的变量自动声明为拥有全局作用域
所有 window 对象的属性拥有全局作用域
所有末定义直接赋值的变量自动声明为拥有全局作用域
所有 window 对象的属性拥有全局作用域
函数作用域
声明在函数内部的变量
只在函数内部可以访问的到
只在函数内部可以访问的到
块级作用域
通过let、const实现所声明的变量只在该块级作用域中可访问
特点:
不存在变量提升
禁止重复声明
存在暂时性死区
特点:
不存在变量提升
禁止重复声明
存在暂时性死区
作用域链
在当前作用域中查找变量,若当前作用域中没有定义该变量,那么就会向父级作用域中继续查找,
一层一层往上查找,直到找到全局作用域,这种关系叫做作用域链
一层一层往上查找,直到找到全局作用域,这种关系叫做作用域链
执行上下文
概念:JS属于解释型语言,所以它的执行分为两个部分:
解释(词法分析、语法分析、作用域规则确定)和执行部分(创建执行上下文、执行函数代码、垃圾回收)
所以作用域在函数定义的时候就已经确定了,而执行上下文是在函数执行的前一步才确定的。
解释(词法分析、语法分析、作用域规则确定)和执行部分(创建执行上下文、执行函数代码、垃圾回收)
所以作用域在函数定义的时候就已经确定了,而执行上下文是在函数执行的前一步才确定的。
分类
全局执行上下文
函数执行上下文
Eval函数执行上下文
this指向
概念:this对象是在运行时基于函数的执行环境绑定的
(this 实际上是在函数被调用时发生的绑定,它指向什么完全取决于函数在哪里被调用,也就是函数的调用位置)
(this 实际上是在函数被调用时发生的绑定,它指向什么完全取决于函数在哪里被调用,也就是函数的调用位置)
绑定规则
默认绑定
显示绑定( call、apply、bind )
new绑定
闭包
概念
定义:当一个嵌套的内部函数引用了外部函数的变量或者函数时,外部函数在执行时就产生了闭包。
在JS中,当变量存在于一个函数内部,那么该变量的作用域属于函数作用域,在该函数执行后作用域会被销毁,内存也随之被回收,这样我们在函数外部无法访问到函数内部变量。
但由于闭包是建立在一个函数内部的子函数,其可访问父级作用域,所以即便父级函数执行完,它的作用域也不会被销毁(因为它的子函数正在访问它的作用域的变量)
所以:闭包就是能够读取其他函数内部变量的函数
在JS中,当变量存在于一个函数内部,那么该变量的作用域属于函数作用域,在该函数执行后作用域会被销毁,内存也随之被回收,这样我们在函数外部无法访问到函数内部变量。
但由于闭包是建立在一个函数内部的子函数,其可访问父级作用域,所以即便父级函数执行完,它的作用域也不会被销毁(因为它的子函数正在访问它的作用域的变量)
所以:闭包就是能够读取其他函数内部变量的函数
作用
可以读取函数内部的变量
可以让这些变量的值始终保持在内存中
可以让这些变量的值始终保持在内存中
- 封装对象的私有属性
- 避免变量的全局污染
- 缓存结果,闭包可以延长外部函数的局部变量的生命周期,可以实现计数器,累加器这类,让变量始终保持在内存中
应用
设计单例模式
编写防抖函数
事件回调
封装变量
编写防抖函数
事件回调
封装变量
内存泄漏
使用后应该清除内存空间,但是有可能使用不当,没有及时清理内存空间
垃圾回收
副垃圾回收器(新生代垃圾回收器)
使用Scavenge算法:
1.把新生代空间对半划分为两个区域,一半是对象区域,一半是空闲区域;
2.新加入的对象都会存放到对象区域,当对象区域快被写满时,就需要执行一次垃圾清理操作;
3.首先要对对象区域中的垃圾做标记;标记完成之后,就进入垃圾清理阶段,副垃圾回收器会把这些存活的对象复制到空闲区域中,同时它还会把这些对象有序地排列起来,所以这个复制过程,也就相当于完成了内存整理操作,复制后空闲区域就没有内存碎片了;
4.完成复制后,对象区域与空闲区域进行角色翻转,也就是原来的对象区域变成空闲区域,原来的空闲区域变成了对象区域。这样就完成了垃圾对象的回收操作,同时这种角色翻转的操作还能让新生代中的这两块区域无限重复使用下去。
1.把新生代空间对半划分为两个区域,一半是对象区域,一半是空闲区域;
2.新加入的对象都会存放到对象区域,当对象区域快被写满时,就需要执行一次垃圾清理操作;
3.首先要对对象区域中的垃圾做标记;标记完成之后,就进入垃圾清理阶段,副垃圾回收器会把这些存活的对象复制到空闲区域中,同时它还会把这些对象有序地排列起来,所以这个复制过程,也就相当于完成了内存整理操作,复制后空闲区域就没有内存碎片了;
4.完成复制后,对象区域与空闲区域进行角色翻转,也就是原来的对象区域变成空闲区域,原来的空闲区域变成了对象区域。这样就完成了垃圾对象的回收操作,同时这种角色翻转的操作还能让新生代中的这两块区域无限重复使用下去。
主垃圾回收器(老生代垃圾回收器)
老生代的对象特点:
1.对象占用空间大
2.对象存活时间长
使用标记-清除算法:
1.首先是标记过程阶段。标记阶段就是从一组根元素开始,递归遍历这组根元素,在这个遍历过程中,能到达的元素称为活动对象,没有到达的元素就可以判断为垃圾数据;
2.接下来就是垃圾的清除过程。它和副垃圾回收器的垃圾清除过程完全不同,你可以理解这个过程是清除掉红色标记数据(是步骤1中标记的垃圾数据)的过程;
3.对一块内存多次执行标记 - 清除算法后,会产生大量不连续的内存碎片。而碎片过多会导致大对象无法分配到足够的连续内存,于是又产生了另外一种算法——标记 - 整理(Mark-Compact),这个标记过程仍然与标记 - 清除算法里的是一样的,但后续步骤不是直接对可回收对象进行清理,而是让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存。
括展:
由于 JavaScript 是运行在主线程之上的,一旦执行垃圾回收算法,都需要将正在执行的 JavaScript 脚本暂停下来,待垃圾回收完毕后再恢复脚本执行。我们把这种行为叫做全停顿(Stop-The-World)。
为了降低老生代的垃圾回收而造成的卡顿,V8 将标记过程分为一个个的子标记过程,同时让垃圾回收标记和 JavaScript 应用逻辑交替进行,直到标记阶段完成,我们把这个算法称为增量标记(Incremental Marking)算法。
使用增量标记算法,可以把一个完整的垃圾回收任务拆分为很多小的任务,这些小的任务执行时间比较短,可以穿插在其他的 JavaScript 任务中间执行,这样当执行上述动画效果时,就不会让用户因为垃圾回收任务而感受到页面的卡顿了。
1.对象占用空间大
2.对象存活时间长
使用标记-清除算法:
1.首先是标记过程阶段。标记阶段就是从一组根元素开始,递归遍历这组根元素,在这个遍历过程中,能到达的元素称为活动对象,没有到达的元素就可以判断为垃圾数据;
2.接下来就是垃圾的清除过程。它和副垃圾回收器的垃圾清除过程完全不同,你可以理解这个过程是清除掉红色标记数据(是步骤1中标记的垃圾数据)的过程;
3.对一块内存多次执行标记 - 清除算法后,会产生大量不连续的内存碎片。而碎片过多会导致大对象无法分配到足够的连续内存,于是又产生了另外一种算法——标记 - 整理(Mark-Compact),这个标记过程仍然与标记 - 清除算法里的是一样的,但后续步骤不是直接对可回收对象进行清理,而是让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存。
括展:
由于 JavaScript 是运行在主线程之上的,一旦执行垃圾回收算法,都需要将正在执行的 JavaScript 脚本暂停下来,待垃圾回收完毕后再恢复脚本执行。我们把这种行为叫做全停顿(Stop-The-World)。
为了降低老生代的垃圾回收而造成的卡顿,V8 将标记过程分为一个个的子标记过程,同时让垃圾回收标记和 JavaScript 应用逻辑交替进行,直到标记阶段完成,我们把这个算法称为增量标记(Incremental Marking)算法。
使用增量标记算法,可以把一个完整的垃圾回收任务拆分为很多小的任务,这些小的任务执行时间比较短,可以穿插在其他的 JavaScript 任务中间执行,这样当执行上述动画效果时,就不会让用户因为垃圾回收任务而感受到页面的卡顿了。
Vue原理
vue两个核心思想: 数据驱动(响应式)、组件化
vue生命周期、构建流程
vm._init 阶段
beforeCreate
initLifecycle(vm):确定父子组件关系
initEvents(vm):将父组件自定义事件传递给子组件
initRender(vm):挂载vm._render方法
beforeCreate:调用beforeCreate钩子函数
initEvents(vm):将父组件自定义事件传递给子组件
initRender(vm):挂载vm._render方法
beforeCreate:调用beforeCreate钩子函数
概念:
组件实例已经完全创建:data已绑定,methods已初始化,但是真实的dom还没有生成,所以$el还不可以用
组件实例已经完全创建:data已绑定,methods已初始化,但是真实的dom还没有生成,所以$el还不可以用
Created
initInjections(vm):主要是初始化inject,可以访问到对应的依赖
initState:props、methods、data、computed、watch
initProvide(vm): 主要作用是初始化`provide`为子组件提供依赖
调用用户自定义的created钩子函数
initState:props、methods、data、computed、watch
initProvide(vm): 主要作用是初始化`provide`为子组件提供依赖
调用用户自定义的created钩子函数
vm._mount 阶段
beforeMount
概念:
在挂载开始之前被调用,相关的 render 函数首次被调用
在挂载开始之前被调用,相关的 render 函数首次被调用
CompileToFunction,将渲染函数转化为VNode
mounted
mountCompoment,实例Watcher、vm._update,调用path算法,将VNode转换为真实Dom
MVVM模式 和 MVC模式
MVVM模式,vm作为视图层和模型层的桥梁,实现数据绑定和DOM监听
MVVM是一个MVC的增强版,正式连接了视图和控制器,并将表示逻辑从Controller移出放到一个新的对象里,即ViewModal。它实现了View和Modal的自动同步,即当Modal的属性改变时,我们不用再自己手动操作Dom元素来改变View的显示,而是改变属性后该属性对应的View层显示会自动改变。
MVC模式:.Modal和View永远不能相互通信,只能通过Controller传递。
响应式原理
Object.definedProperty的setter/getter进行数据劫持、用于依赖收集和派发更新、发布订阅模式
Observer : 它的作用是给对象的属性添加 getter 和 setter,用于依赖收集和派发更新
Dep : 用于收集当前响应式对象的依赖关系,每个响应式对象包括子对象都拥有一个 Dep 实例(里面 subs 是 Watcher 实例数组),当数据有变更时,会通过 dep.notify()通知各个 watcher。
Watcher : 观察者对象 , 实例分为渲染 watcher (render watcher),计算属性 watcher (computed watcher),侦听器 watcher(user watcher)三种
Observer : 它的作用是给对象的属性添加 getter 和 setter,用于依赖收集和派发更新
Dep : 用于收集当前响应式对象的依赖关系,每个响应式对象包括子对象都拥有一个 Dep 实例(里面 subs 是 Watcher 实例数组),当数据有变更时,会通过 dep.notify()通知各个 watcher。
Watcher : 观察者对象 , 实例分为渲染 watcher (render watcher),计算属性 watcher (computed watcher),侦听器 watcher(user watcher)三种
computed、watch原理
三类watch对象:组件、computed懒更新、watch
computed原理
设计初衷
Vue中我们不需要在template里面直接计算{{this.firstName + ' ' + this.lastName}},因为在模版中放入太多声明式的逻辑会让模板本身过重,尤其当在页面中使用大量复杂的逻辑表达式处理数据时,会对页面的可维护性造成很大的影响,而computed的设计初衷也正是用于解决此类问题。
initComputed
1、new Watcher,调用vm._computedWatchers,遍历computed中的属性,为每个属性创建watcer对象;
lazy:true(默认懒加载),默认dirty:true;默认watcher不执行(不执行用户方法),同时持有一个 dep 实例,收集依赖。
lazy:true(默认懒加载),默认dirty:true;默认watcher不执行(不执行用户方法),同时持有一个 dep 实例,收集依赖。
2、dedineComputed:将属性定义到实例上,作为vm.data
3、createComputedGetter:创建getter当取值会执行此方法(调用计算属性时会触发其Object.defineProperty的get访问器函数)
4、当用户取值、dirty为false时返回上次计算的结果;dirty为true时、计算结果会进行依赖收集,重新计算结果,再把dirty更改为false
惰性求值,使用缓存
computed 在第一次完成计算的时候就会把结果缓存起来,只有当他的依赖数据发生了改变,才会重新计算新的值,并且更新缓存。这个应该就是惰性求值
computed 本质是一个惰性求值的观察者。
computed 内部实现了一个惰性的 watcher,也就是 computed watcher,computed watcher 不会立刻求值,同时持有一个 dep 实例。
其内部通过 this.dirty 属性标记计算属性是否需要重新求值。
当 computed 的依赖状态发生改变时,就会通知这个惰性的 watcher,
computed watcher 通过 this.dep.subs.length 判断有没有订阅者,
有的话,会重新计算,然后对比新旧值,如果变化了,会重新渲染。 (Vue 想确保不仅仅是计算属性依赖的值发生变化,而是当计算属性最终计算的值发生变化时才会触发渲染 watcher 重新渲染,本质上是一种优化。)
没有的话,仅仅把 this.dirty = true。 (当计算属性依赖于其他数据时,属性并不会立即重新计算,只有之后其他地方需要读取属性的时候,它才会真正计算,即具备 lazy(懒计算)特性。)
computed 内部实现了一个惰性的 watcher,也就是 computed watcher,computed watcher 不会立刻求值,同时持有一个 dep 实例。
其内部通过 this.dirty 属性标记计算属性是否需要重新求值。
当 computed 的依赖状态发生改变时,就会通知这个惰性的 watcher,
computed watcher 通过 this.dep.subs.length 判断有没有订阅者,
有的话,会重新计算,然后对比新旧值,如果变化了,会重新渲染。 (Vue 想确保不仅仅是计算属性依赖的值发生变化,而是当计算属性最终计算的值发生变化时才会触发渲染 watcher 重新渲染,本质上是一种优化。)
没有的话,仅仅把 this.dirty = true。 (当计算属性依赖于其他数据时,属性并不会立即重新计算,只有之后其他地方需要读取属性的时候,它才会真正计算,即具备 lazy(懒计算)特性。)
conputed和watch的差异
- computed是计算一个新的属性,并将该属性挂载到vm(Vue实例)上,而watch是监听已经存在且已挂载到vm上的数据,所以用watch同样可以监听computed计算属性的变化(其它还有data、props)
- computed本质是一个惰性求值的观察者,具有缓存性,只有当依赖变化后,第一次访问 computed 属性,才会计算新的值,而watch则是当数据发生变化便会调用执行函数
- 从使用场景上说,computed适用一个数据被多个数据影响,而watch适用一个数据影响多个数据;
key的作用
key 是给每一个 vnode 的唯一 id,依靠 key,我们的 diff 操作可以更准确、更快速 (对于简单列表页渲染来说 diff 节点也更快,但会产生一些隐藏的副作用,比如可能不会产生过渡效果,或者在某些节点有绑定数据(表单)状态,会出现状态错位。)
diff 算法的过程中,先会进行新旧节点的首尾交叉对比,当无法匹配的时候会用新节点的 key 与旧节点进行比对,从而找到相应旧节点.
更准确 : 因为带 key 就不是就地复用了,在 sameNode 函数 a.key === b.key 对比中可以避免就地复用的情况。所以会更加准确,如果不加 key,会导致之前节点的状态被保留下来,会产生一系列的 bug。
更快速 : key 的唯一性可以被 Map 数据结构充分利用,相比于遍历查找的时间复杂度 O(n),Map 的时间复杂度仅仅为 O(1)
diff 算法的过程中,先会进行新旧节点的首尾交叉对比,当无法匹配的时候会用新节点的 key 与旧节点进行比对,从而找到相应旧节点.
更准确 : 因为带 key 就不是就地复用了,在 sameNode 函数 a.key === b.key 对比中可以避免就地复用的情况。所以会更加准确,如果不加 key,会导致之前节点的状态被保留下来,会产生一系列的 bug。
更快速 : key 的唯一性可以被 Map 数据结构充分利用,相比于遍历查找的时间复杂度 O(n),Map 的时间复杂度仅仅为 O(1)
监听数组变化
重写方法,拦截、通知数组更新
push、pop、shift、unshift、reverse、sort、splice
push、pop、shift、unshift、reverse、sort、splice
Vue 通过原型拦截的方式重写了数组的 7 个方法,首先获取到这个数组的ob,也就是它的 Observer 对象,如果有新的值,就调用 observeArray 对新的值进行监听,然后手动调用 notify,通知 render watcher,执行 update
也就是它的 Observer 对象,如果有新的值,就调用 observeArray 对新的值进行监听,然后手动调用 notify,通知 render watcher,执行 update
arr[index] = val 不是响应式的
无法监听数组变化、使用watch deep也无法监听
无法监听数组变化、使用watch deep也无法监听
Vue.set()和this.$set()实现原理监听数组变化
this.isReply.splice(index, 1, Math.random().toFixed(1))
this.$set(this.isReply, index, 1);
this.isReply.splice(index, 1, Math.random().toFixed(1))
this.$set(this.isReply, index, 1);
SPA 与 路由器原理
sap应用
hash
history
nextTick原理
异步更新Dom队列
Vue 在更新 DOM 时是异步执行的。只要侦听到数据变化,Vue 将开启一个队列,并缓冲在同一事件循环中发生的所有数据变更。
如果同一个 watcher 被多次触发,只会被推入到队列中一次。这种在缓冲时去除重复数据对于避免不必要的计算和 DOM 操作是非常重要的。
然后,在下一个的事件循环“tick”中,Vue 刷新队列并执行实际 (已去重的) 工作。queueWatcher函数,对watch.id进行标记、去重、push更新队列,最后将queueWatcher中的队列传入flushSchedulerQueue函数中;flushSchedulerQueue是下一个tick时的回调函数,主要目的是执行Watcher的run函数,用来更新视图。
Vue 在内部对异步队列尝试使用原生的 Promise.then、MutationObserver 和 setImmediate,如果执行环境不支持,则会采用 setTimeout(fn, 0) 代替。
在 vue2.5 的源码中,macrotask 降级的方案依次是:setImmediate、MessageChannel、setTimeout
vue 的 nextTick 方法的实现原理:
vue 用异步队列的方式来控制 DOM 更新和 nextTick 回调先后执行
microtask 因为其高优先级特性,能确保队列中的微任务在一次事件循环前被执行完毕
考虑兼容问题,vue 做了 microtask 向 macrotask 的降级方案
如果同一个 watcher 被多次触发,只会被推入到队列中一次。这种在缓冲时去除重复数据对于避免不必要的计算和 DOM 操作是非常重要的。
然后,在下一个的事件循环“tick”中,Vue 刷新队列并执行实际 (已去重的) 工作。queueWatcher函数,对watch.id进行标记、去重、push更新队列,最后将queueWatcher中的队列传入flushSchedulerQueue函数中;flushSchedulerQueue是下一个tick时的回调函数,主要目的是执行Watcher的run函数,用来更新视图。
Vue 在内部对异步队列尝试使用原生的 Promise.then、MutationObserver 和 setImmediate,如果执行环境不支持,则会采用 setTimeout(fn, 0) 代替。
在 vue2.5 的源码中,macrotask 降级的方案依次是:setImmediate、MessageChannel、setTimeout
vue 的 nextTick 方法的实现原理:
vue 用异步队列的方式来控制 DOM 更新和 nextTick 回调先后执行
microtask 因为其高优先级特性,能确保队列中的微任务在一次事件循环前被执行完毕
考虑兼容问题,vue 做了 microtask 向 macrotask 的降级方案
diff算法
真实dom和虚拟dom
深度遍历、同级比较、同步更新dom
四指针
组件通信
父子组件通信
props和emit
$parent/$children与ref
父子/隔代组件通信
provide/inject
所有组件通信(父子、隔代、兄弟)
$emit和$on
v-on注册事件,$emit(自定义事件,传递值给父组件)
Vuex、会话存储、本地存储
JQ和Vue的区别
Query 专注视图层,通过操作 DOM 去实现页面的一些逻辑渲染; Vue 专注于数据层,通过数据的双向绑定,最终表现在 DOM 层面,减少了 DOM 操作Vue 使用了组件化思想,使得项目子集职责清晰,提高了开发效率,方便重复利用,便于协同开发
Vue3
Webpack原理
模块发展、webpack
模块化发展历史:立即执行函数、脚本文件、ComJS、AMD、CMD、UMD、ES6模块
webpacK是一个静态模块打包工具。处理应用程序、它会在内部构件一个依赖图,此依赖图对应映射到项目所需要的每个模块,并生成一个或多个bundle。
webpack的优点:编译兼容、压缩代码、ESLint语法判断、模块开发、清除语句
配置文件属性:entry、output、module、plugin、mode
构建流程
Tabable、Complier、Compliation
初始化阶段、编译阶段、打包优化阶段;( 熟悉 )
bable和AST代码转换
AST( 抽象语法树 ) -- 带标记信息的对象
@babel/parser 将源代码解析成 AST
@babel/traverse 对AST节点进行递归遍历,生成一个便于操作、转换的path对象
@babel/generator 将AST解码生成js代码
@babel/types通过该模块对具体的AST节点进行进行增、删、改、查
@babel/traverse 对AST节点进行递归遍历,生成一个便于操作、转换的path对象
@babel/generator 将AST解码生成js代码
@babel/types通过该模块对具体的AST节点进行进行增、删、改、查
loader(加载器)
loader编写原则
单一原则
链式调用
统一原则
链式调用
统一原则
单一原则: 每个 Loader 只做一件事;
链式调用: Webpack 会按顺序链式调用每个 Loader;
统一原则: 遵循 Webpack 制定的设计规则和结构,输入与输出均为字符串,各个 Loader 完全独立,即插即用;
链式调用: Webpack 会按顺序链式调用每个 Loader;
统一原则: 遵循 Webpack 制定的设计规则和结构,输入与输出均为字符串,各个 Loader 完全独立,即插即用;
常用的loader
babel-loader:把 ES6 转换成 ES5
css-loader:加载 CSS,支持模块化、压缩、文件导入等特性
style-loader:把 CSS 代码注入到 JavaScript 中,通过 DOM 操作去加载 CSS
eslint-loader:通过 ESLint 检查 JavaScript 代码
file-loader:把文件输出到一个文件夹中,在代码中通过相对 URL 去引用输出的文件
url-loader:和 file-loader 类似,但是能在文件很小的情况下以 base64 的方式把文件内容注入到代码中去
image-loader:加载并且压缩图片文件
plugin(插件)
clean-webpack-plugin、html-webpack-plugin、mini-css-extract-plugin、uglifyjs-webpack-plugin
loader和plugin的区别
Loader像一个"翻译官"把读到的源文件内容转义成新的文件内容,并且每个Loader通过链式操作,将源文件一步步翻译成想要的样子。
编写Loader时要遵循单一原则,每个Loader只做一种"转义"工作。 每个Loader的拿到的是源文件内容(source),可以通过返回值的方式将处理后的内容输出,也可以调用this.callback()方法,将内容返回给webpack。 还可以通过 this.async()生成一个callback函数,再用这个callback将处理后的内容输出出去。 此外webpack还为开发者准备了开发loader的工具函数集——loader-utils。
相对于Loader而言,Plugin的编写就灵活了许多。 webpack在运行的生命周期中会广播出许多事件,Plugin 可以监听这些事件,在合适的时机通过 Webpack 提供的 API 改变输出结果。
编写Loader时要遵循单一原则,每个Loader只做一种"转义"工作。 每个Loader的拿到的是源文件内容(source),可以通过返回值的方式将处理后的内容输出,也可以调用this.callback()方法,将内容返回给webpack。 还可以通过 this.async()生成一个callback函数,再用这个callback将处理后的内容输出出去。 此外webpack还为开发者准备了开发loader的工具函数集——loader-utils。
相对于Loader而言,Plugin的编写就灵活了许多。 webpack在运行的生命周期中会广播出许多事件,Plugin 可以监听这些事件,在合适的时机通过 Webpack 提供的 API 改变输出结果。
热更新原理
HMR:Hot Module Replacement;无需完全刷新整个页面的同时,更新模块。
触发新的编译中,控制台会有三个值
新的Hash值:a61bdd6e82294ed06fa3;代表每一次编译的标识。
新的json文件: a93fd735d02d98633356.hot-update.json;有c和h属性,h是本地新生成的hash值、c是当前要热更新的文件对应的js模块。
新的js文件:index.a93fd735d02d98633356.hot-update.js;本次修改的代码,重新编译打包后的。
新的json文件: a93fd735d02d98633356.hot-update.json;有c和h属性,h是本地新生成的hash值、c是当前要热更新的文件对应的js模块。
新的js文件:index.a93fd735d02d98633356.hot-update.js;本次修改的代码,重新编译打包后的。
webpack Compile: 将js编译成Bundle
HMR Server: 将热更新的文件输出给 HMR Runtime
Bundle Server: 提供文件在浏览器访问
HMR Runtime:会被注入到浏览器,更新文件变化
bundle.js : 构建输出的文件
HMR Server: 将热更新的文件输出给 HMR Runtime
Bundle Server: 提供文件在浏览器访问
HMR Runtime:会被注入到浏览器,更新文件变化
bundle.js : 构建输出的文件
1. webpack-dev-server启动本地服务
启动webpack,生成compiler实例。compiler上有很多方法,比如可以启动 webpack 所有编译工作,以及监听本地文件的变化。
使用express框架启动本地server,让浏览器可以请求本地的静态资源。
本地server启动之后,再去启动websocket服务,如果不了解websocket,建议简单了解一下websocket速成。通过websocket,可以建立本地服务和浏览器的双向通信。这样就可以实现当本地文件发生变化,立马告知浏览器可以热更新代码啦!
使用express框架启动本地server,让浏览器可以请求本地的静态资源。
本地server启动之后,再去启动websocket服务,如果不了解websocket,建议简单了解一下websocket速成。通过websocket,可以建立本地服务和浏览器的双向通信。这样就可以实现当本地文件发生变化,立马告知浏览器可以热更新代码啦!
2. 修改webpack.config.js的entry配置
3. 监听webpack编译结束
当监听到一次webpack编译结束,就会调用_sendStats方法通过websocket给浏览器发送通知,ok和hash事件,这样浏览器就可以拿到最新的hash值了,做检查更新逻辑。
4. webpack监听文件变化
5.浏览器接收到热更新的通知
监听到文件的变化了,当文件发生变化,就触发重新编译。同时还监听了每次编译结束的事件。当监听到一次webpack编译结束,_sendStats方法就通过websoket给浏览器发送通知,检查下是否需要热更新。下面重点讲的就是_sendStats方法中的ok和hash事件都做了什么。
socket方法建立了websocket和服务端的连接,并注册了 2 个监听事件。
hash事件,更新最新一次打包后的hash值。
ok事件,进行热更新检查。
hash事件,更新最新一次打包后的hash值。
ok事件,进行热更新检查。
6. HotModuleReplacementPlugin
7. moudle.hot.check 开始热更新
8. hotApply 热更新模块替换
①删除过期的模块,就是需要替换的模块
②将新的模块添加到 modules 中
③通过__webpack_require__执行相关模块的代码
其他打包工具:rollup、vite、grunt
Rollup(英文名称为 归纳、卷曲)
Rollup提出 Tree Shaking
rollup的解释是在构建代码时,在使用ES6模块化的代码中,会对你的代码进行静态分析,只打包使用到的代码。这样的好处是减少代码的体积
构建后的代码的组织形式被一个立即执行函数包裹
webpack构建够的代码
输出代码有三种:
- 业务逻辑代码
- Runtime - 代码执行的引导
- Manifest - 模块依赖关系的记录
对构建代码分析:
- 可以看到构建结果中的业务逻辑代码,Runtime和Manifest
- 在Manifest中记录中依赖关系,通过__webpack_require__加载
- 构建结果中包含了没有使用到的square
- 构建体积明显比rollup中iife格式大
- 代码执行的时候,rollup中iife输出格式,代码执行的速度更快,webpack构建出来的还有依赖查找,而且每个模块通过一个函数包裹形式,执行的时候,就形成了一个个的闭包,占用了内存,当然可以在webpack3使用ConcatenationPlugin插件优化这样的输出格式,打包到一个依赖中
Rollup与webpack的区别
- webpack 拆分代码, 静态资源按需加载;
- Rollup 所有资源放在同一个地方,一次性加载,利用 tree-shake 特性来剔除项目中未使用的代码,减少冗余,但是webpack2已经逐渐支持tree-shake(压缩代码)
Rollup和Webpack的选择
官方简介
Rollup官方解析:Rollup 是一个 JavaScript 模块打包器,可以将小块代码编译成大块复杂的代码,例如 library 或应用程序
webpack官方解析:webpack 是一个现代 JavaScript 应用程序的静态模块打包器(module bundler)。当 webpack 处理应用程序时,它会递归地构建一个依赖关系图(dependency graph),其中包含应用程序需要的每个模块,然后将所有这些模块打包成一个或多个 bundle。
webpack官方解析:webpack 是一个现代 JavaScript 应用程序的静态模块打包器(module bundler)。当 webpack 处理应用程序时,它会递归地构建一个依赖关系图(dependency graph),其中包含应用程序需要的每个模块,然后将所有这些模块打包成一个或多个 bundle。
应用使用 webpack,对于类库使用 Rollup
从上面使用场景可以大概分析出,Rollup偏向应用于js库,webpack偏向应用于前端工程,UI库;
如果你的应用场景中只是js代码,希望做ES转换,模块解析,可以使用Rollup。如果你的场景中涉及到css、html,涉及到复杂的代码拆分合并,建议使用webpack。
从上面使用场景可以大概分析出,Rollup偏向应用于js库,webpack偏向应用于前端工程,UI库;
如果你的应用场景中只是js代码,希望做ES转换,模块解析,可以使用Rollup。如果你的场景中涉及到css、html,涉及到复杂的代码拆分合并,建议使用webpack。
form
place
收藏
0 条评论
下一页