前端学习笔记
个人前端学习笔记,不定时更新
计算机操作系统
进程、线程、协程
-
进程是操作系统资源分配的基本单位,而线程是任务调度和执行的基本单位
-
一个应用程序可以被设计为多进程,多进程的子进程会复用父进程的端口号
-
多进程开销较大,通信较麻烦,但稳定性高,
Windows
比Linux
系统多进程开销大 -
多线程比多进程较快,开销较低,但任何一个线程挂掉都可能直接造成整个进程崩溃
-
多核
cpu
支持多个线程同时执行 -
多个线程是串行还是并行取决于该时间点是否有
cpu
处于空闲,单核cpu
或该时间点无空闲cpu
则生成多个时间切片串行,线程调度器切换线程不停执行 -
多线程并行可能出现不同线程需要同一时间切片对同一资源进行操作,为了防止这种错误,会使用锁在资源被使用时加以限制
-
多线程里串行却能提高效率的原因是,线程里的操作可能除了
cpu
计算还有其他IO
等操作(任务可以分为计算密集型和IO
密集型),切换线程让其他需要cpu
操作的线程获得cpu
资源 -
线程里可以有多个协程,可以由函数实现,由开发者自己控制协程进行串行,在需要过多线程的情况下,增加线程里的协程数量,减少线程数量,这样可以减少锁的使用和过多线程自动切换开销,提高效率
不同语言通信
- 协议统一数据格式(
HTTP
里的json
) - 共享内存指针
- ABI(底层
c
通信)
编译原理
编译型语言和解释性语言
-
从编译和执行时间来看,编译型语言会编译完成后再执行,所以运行效率很快,解释型语言会一边解析一边执行,所以速度较慢
-
从编译结果来看,编译型语言会直接将源代码转化为机器码,而解释型语言需要将源代码编译转化为字节码后,再由虚拟机或解释器转为机械码执行
-
js
,python
等属于解释性语言,go
,c
属于编译型语言 -
但
java
会由.java
文件转化为.class
文件,从生成中间代码来说,他属于解释型语言,但java
却是编译完成后再执行代码,这个角度来看它又属于编译型语言
浏览器和js
JIT(即时编译)
-
js
是一种动态弱类型脚本语言,它作为解释性语言,浏览器里的解释器很快的获取代码并且执行,不需要知道全部的编译步骤,它和浏览器有着自然的契合,因为web
开发者能立即得到反馈很重要 -
解释器的弊端是当你运行相同代码时,比如执行循环会一遍又一遍的做同样的事情,因为
js
是弱类型语言,无法提前确定变量类型,解释器必须在每次循环访问时不断重新转换代码,为此浏览器开始将编译器引入,它给js
引擎添加了一个新的部分,称为监视器,监视器在js
运行时监控代码,并记录代码片段运行的次数以及使用了那些数据类型,如果相同的代码行运行了几次,这段代码被标记为warm
,如果运行次数比较多,就被标记为hot
, 被标记代码就直接扔给编译器生成机器码,这样能提升速度,如果变量类型发生了改变,那么编译器无法识别类型,则重新丢给解释器,生成中间码,再转化成机器码,这个过程就是jit
,所以减少变量类型变化,使用ts
对类型进行固定,便于jit
提高效率 -
(
aot
:运行前编译)
浏览器对js的解析过程
- 下载
js
文件 - 词法分析,语法分析,生成
ast
- 解释器生成字节码 ||
jit
和编译优化 - 内存清理
- (字节码编译成机器码)
- 执行机器码
WebAssmebly
WebAssmebly
是用于将其他语言编译成中间代码直接给浏览器执行的一种技术方案,wasm
是浏览器支持解析的编译后的文件格式,用于需要复杂计算而js
有性能瓶颈的场景中
基本使用
- 编写源代码函数
- 使用编译器编译成
wasm
文件 - 利用如
fetch
等异步请求获取代码 - 返回结果解析成一个
ArrayBuffer
(缓冲区字节数组) WebAssembly.compile
解析ArrayBuffer
生成promise
对象获取wasm
模块new WebAssembly.Instance()
接收wasm
模块实例化成一个js
模块对象,对象exports
属性上保存着所有源代码方法可供js
调用
对比js
wasm
比起js
来说文件代码更小wasm
已经是字节码,所以比起js
进行编译和优化过程少wasm
是为编译器设计的js
有自动内存管理,但wasm
一般是基于c++
等手动管理内存语言编译而来,对开发者要求更高- 当前大多数
WebAssembly
开发者使用C
、C++
或Rust
编写代码,对WebAssembly
支持最多的编译器工具链是LLVM
AST(抽象语法树)
AST
通过js
代码解析获得,在如浏览器引擎(v8
)、工具打包或js
模块化等语法转化(webpack、rollup
、babel
)、代码混淆压缩(UglifyJS
)、代码检查(Lint
)等被广泛使用,这颗树定义了代码的结构,有更好更完善的抽象能力表示代码(如正则替换可能改变语义)
浏览器基本渲染流程
浏览器基本组成
浏览器环境是多进程的:
Browser
进程:浏览器的主进程(负责协调、主控),只有一个,作用为:
- 浏览器界面显示,与用户交互(前进、后退)
- 各个页面的管理,创建和销毁其他进程
cookie
、webstroage
等数据的存储- 网络资源的管理,下载等
- 第三方插件进程:每种类型的插件对应一个进程,仅当使用该插件时才创建
GPU
进程:用于绘制和渲染Renderer
进程(浏览器内核):默认每个标签页面一个进程,避免一个标签页错误影响全部标签页,内部是多线程的,作用为页面渲染,脚本执行,事件处理等
每个标签页面都是多线程的:
GUI
渲染线程:负责生成渲染界面,解析HTML
,CSS
,构建DOM
树和Render
树,布局和描绘等(当元素大小、页面布局等不断改变,多次重新生成Render
树时,这个过程称为重排),然后将信息等传给合成器线程和栅格线程生成合成器帧(当元素文字、颜色等不断改变,多次重新生成合成器帧,这个过程称为重绘),再将合成器帧传到GPU
渲染到指定页面图层上JS
引擎线程:负责处理js
脚本计算- 事件处理线程:用于控制事件循环、事件表,存放未满足执行条件的异步任务的回调函数,当对应的事件符合触发条件被触发时,该线程会把事件添加到待处理队列的队尾,等待
js
引擎的处理 - 定时触发器线程:
setInterval
与setTimeout
所在线程,用于计时,如果放在js
引擎线程处于阻塞线程状态就会影响记计时的准确 - 异步
http
请求线程:XMLHttpRequest
或fetch
在连接后开启进程,状态变化或接受到数据时,将对应回调传入事件表再由js
引擎执行
GUI渲染线程与JS引擎线程是互斥的,所以相互阻塞
js
引擎执行是单线程的,是为了防止多线程会出现同时对dom
操作的问题,如果在不同线程js
改变了dom
,那么就会造成渲染不同步,虽然HTML5
中增加了web worker
多线程操作,但其受主线程控制,一般用于计算,且不能操作dom
,同时js
为了防止执行阻塞,所以加入了异步操作
基本过程
-
浏览器进程的
UI
线程捕获输入 -
关键字则用搜索引擎搜索,域名则判断缓存
-
无缓存或缓存失效则通过浏览器进程的网络线程
DNS
解析,安全站点判断,TCP
3握4挥,获取数据 -
创建渲染进程,浏览器进程的
UI
线程通过IPC
通信将数据传给渲染进程 -
页面渲染展示
DOMContentLoaded和load
浏览器Browser
进程拿到数据后传递给Renderer
进程,Renderer
进程里的GUI
渲染线程对html
数据便开始从上到下解析,同时会生成DOM
,如果页面中有css
,会根据css
的内容形成CSSOM
,然后DOM
和CSSOM
会生成一个渲染树,浏览器会根据渲染树的内容计算出各个节点在页面中的确切大小和位置,并将其渲染在浏览器上,当遇到script
标签时,会阻断dom
的渲染,转而去下载并执行js
,浏览器会进入阻塞,这时浏览器会进行首次渲染,这是页面从白屏到首次渲染的时间节点,所以为了提高用户体验,且有一个相对完整的dom
树结构,js
通常放在最后(css
放在最前是为了在生成dom
的时候,同时生成CSSOM
,提高性能),如果script
便签拥有async
或defer
属性,则会并行下载js
文件,async
会在下载完成后立即执行,defer
延迟到DOMContentLoaded
事件前开始执行
在DOM
树构建完成后触发DOMContentLoaded
事件,当html
文档中的图片资源,js
代码中所有异步加载的 css
、js
、图片资源都加载执行完毕之后,load
事件触发
浏览器帧
浏览器每帧里一般顺序包含以下任务:
1.处理用户的交互事件
2.JS
解析执行(可能有)
3.窗口尺寸变更,页面滚去等的处理
4.requestAnimationFrame(rAF)
5.布局
6.绘制
7.rIC
当js
执行和渲染分配不均时,如果一帧里大部分只有js
执行或渲染,就会出现掉帧卡顿现象
浏览器的渲染优化
由于每次重排都会造成计算消耗,因此现代大多数浏览器都会通过队列化批量执行来优化操作,浏览器会将操作放入到队列里,直到过了一段时间或者操作达到了一个阈值才清空队列(当受时间影响时和rAF
机制类似,但同时也会受到次数影响,更新频率可能会高过每帧),但当你获取布局信息时会强制队列刷新,比如使用以下属性或方法:
- offsetTop、offsetLeft、offsetWidth、offsetHeight
- scrollTop、scrollLeft、scrollWidth、scrollHeight
- clientTop、clientLeft、clientWidth、clientHeight
- getComputedStyle(返回元素的
CSS
样式对象) - getBoundingClientRect(返回元素大小和四边分别离左上轴的距离)
以上属性和方法都需返回最新的布局信息,因此浏览器不得不清空队列,触发回流重绘来获取正确的值,因此在我们在修改样式时,如果要多次使用它们,最好使用变量将值缓存起来
Serverless
Serverless
为“无服务架构”,是一种软件系统设计架构思想和方法,它让开发者可以更加关注业务开发,而将繁杂的运维和部署交给云厂商提供,Serverless
由Faas
和Baas
组成,Faas
为开发者提供业务运算环境,然后与Baas
提供的数据和存储服务进行交互,从而提供与传统服务一致的体验
Serverless
有两个核心特点:
- 按需收费:根据使用者的项目大小和访问次数进行收费,减少项目冷门导致的巨大成本损失
- 自动分配:普通项目部署在服务器上进程是常驻的,就算没有客户端请求,也会占用相应服务器资源,且为了考虑高流量场景,我们需要提供较高的服务器配置和多台服务进行负载均衡,这就导致服务处在低流量场景时,会多出很多额外的闲置资源浪费,
Serverless
会根据流量自动分配改变服务器资源大小
前端项目渲染类型
首先是目前最流行的spa
单页面应用,用户打开在浏览器页面,向web
服务器请求资源后,获取到一个空的html
结构,这时候页面出现白屏,浏览器开始请求静态资源和js
文件,获取到资源后在浏览器通过渲染内核和js
引擎进行渲染,但因为js
被设计成为单线程语言且js
执行会阻断渲染,所以页面可能会出现长时间白屏
基于spa
首页白屏问题和空html
结构问题,ssr
模式出现,用户在打开浏览器页面发送请求后,web
服务器在直接生成html
结构,并引入对应资源渲染,生成完整html
结构后再返回给浏览器,浏览器直接解析运行,这样既解决了spa
空html
结构不利于SEO
的问题,也因为node
的js
执行效率更高且不需要通过网络可以直接请求本服务器资源进行渲染减少了渲染时间,在首页返回后,后面ajax
请求页面逻辑等与spa
相同,也是在浏览器请求数据服务器获取数据后通过依赖框架实现页面渲染
当然还有最初的那种mpa
渲染模式,把所有页面都放在服务器里,路由和前端无关完全由服务器控制的方式,无论是请求数据还是页面跳转都需要重新向服务器发送请求获取新页面
渐进增强和优雅降级
渐进增强:主要针对低版本的浏览器进行开发,保证基本的功能情况下,再针对高级浏览器进行效果、交互等方面的改进和追加功能
优雅降级:主要针对高版本的浏览器进行开发,一开始就构建完整的功能,然后再针对低版本的浏览器进行兼容
编程范式
- 命令式:编写具体的逻辑,关注程序执行的每个步骤
- 声明式:告诉程序应该做什么或者说程序应该返回的结果应该是属于什么类型,而不关注过程
- 函数式:只关注做什么而不是怎么做,函数式编程不仅仅局限于声明式编程,将程序通过函数一层层调用生成新结果,每个函数只处理属于自己的逻辑过程
前端性能优化
首屏优化
图片
base64
图片采用base64
编码加载将图片从地址转化为字符串,可减少一次HTTP
请求,让图片随着html
下载,虽然可以被gzip
压缩,但转化后仍然会使css
文件大小增加,所以建议对在整个网站的复用性很高且基本不会被更新的小图片使用
雪碧图
通过将小图片拼接成大图片减少HTTP
请求来实现优化,但也只适用于整个网站的复用性很高且基本不会被更新的小图片
懒加载
通过监听视图实现懒加载,基于scroll
事件或IntersectionObserver
实现,但后者更加开销更小,性能更优秀
JS
Fragment
使用document.createDocumentFragment()
创建一个保存多个element
的容器对象,减少额外嵌套标签,在react
、vue
、svelte
等里实现了fragment
、多根组件、空标签都是这个原理
节流防抖
都是为了减少用户过度频繁操作带来的性能损耗和错误
节流:设置节流阀,通过节流阀判断是否应该执行,触发后一段时间里将节流阀关闭
function test() {
let sign = true
return function () {
if (sign) {
console.log(111)
sign = false
setTimeout(() => {
sign = true
}, 300)
}
}
}
let jieliu = test()
jieliu()
jieliu()
setTimeout(() => {
jieliu()
}, 3000)
防抖:设置时间间隔延迟执行,每次触发都会刷新时间间隔,只执行高频操作的最后一次
function test() {
let sign
return function () {
if (sign) {
clearTimeout(sign)
}
sign = setTimeout(() => {
console.log(111)
}, 300)
}
}
let fangdou = test()
fangdou()
fangdou()
setTimeout(() => {
fangdou()
}, 3000)
大文件上传
大文件直接上传的时间会较长,当高频次文件上传失败,失败后又需要重新上传,用户体验较差,所以可使用分片传输,使用Filereader
读取文件
Filereader
的浏览器支持性较差,且不能通过简单路径读取系统文件,还是通过系统提供给浏览器的input
接口上传
- 将需要上传的文件按照一定的分割规则,分割成相同大小的数据块
- 初始化一个分片上传任务,返回本次分片上传唯一标识
- 按照一定的策略(串行或并行)发送各个分片数据块
- 发送完成后,服务端根据判断数据上传是否完整,如果完整,则进行数据块合成得到原始文件
const input = document.querySelector('input')
input.addEventListener('change', function() {
let file = this.files[0]
})
let reader = new FileReader()
reader.readAsArrayBuffer(file)
reader.addEventListener("load", function(e) {
//每10M切割一段,这里只做一个切割演示,实际切割需要循环切割,
let slice = e.target.result.slice(0, 10*1024*1024)
})
硬件加速
transform
、opacity
等不会导致重排重绘,因为其会开启一个新的复合图层,不会导致GUI
线程重新生成render
树影响到原来的默认图层,而是属性的改变直接交给GPU
处理,这就是硬件加速
,这样减少了页面重排重绘,提高了性能,但大量使用复合图层会导致资源消耗过度,反而更卡,尽可能的使用z-index
减少过多生成不同复合图层
离线储存
<html manifest = "cache.manifest">
cache.manifest
文件
CACHE MANIFEST
#v0.11 //版本号
CACHE: //需要缓存的
js/app.js
css/style.css
NETWORK: //必须联网使用的
resourse/logo.png
FALLBACK: //访问缓存失败后的代替
offline.html
- 在线时,浏览器发现
html
头部有manifest
属性就会进行判断,如果是第一次访问,浏览器就会根据manifest
文件下载相应的资源并进行离线存储,如果已经访问过并且资源已经离线存储了,那么浏览器就会直接使用离线资源加载页面,然后浏览器会对比新旧的manifest
文件是否有改变,有变化,就会重新下载文件中的资源并进行离线存储,更新了离线资源后,需要到下次使用才会生效,如果需要资源马上就能生效,那么可以使用window.applicationCache.swapCache()
方法来使之生效,原因是浏览器会先使用离线资源加载页面,再去检查manifest
是否有更新 - 离线时,浏览器会直接使用缓存资源
- 如果服务器对离线的资源进行了更新,那么必须更新
manifest
文件后这些资源才能被浏览器重新下载,如果只是更新了资源而没有更新manifest
文件,浏览器并不会重新下载资源
HTML、CSS
meta
<meta charset="UTF-8" > //编码
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1"> //移动端视口适配
<meta name="robots" content="index,follow" /> //爬虫配置
BFC
BFC为块级格式化上下文,它是页面上的一个独立容器,子元素不会对外产生影响,计算BFC的高度时,浮动元素也会参与计算,它的产生有
-
float
不为none
的元素 -
position
为fixed
、absolute
的元素 -
display
为inline-block
、table-cell
、table-caption
、flex
、inline-flex
的元素 -
overflow
不为visible
的元素
语义化标签
语义化标签是HTML5
推出的概念,目的是让标签具有自己的结构含义,这样做有可以使代码更清晰整洁,便于团队开发、便于设备解析渲染、有利于SEO
优化
伪元素
伪元素::after
、::before
里必须设置content
flex
flex
是一种弹性布局,当父盒子设置为display:flex
时,里面的盒子都会变成它的子盒子,子盒子浮动会无效化:
容器
flex-direction
(主轴方向)flex-warp
(换行)flex-flow
(flex-direction
和flex-warp
)justify-content
(主轴方向盒子、空隙排列方式)align-items
(侧轴方向盒子排列方式)align-content
(侧轴方向盒子、空隙排列方式,换行时有效)
子单元
order
(排列顺序)flex-grow
(剩余空间放大比例,默认0,不放大)flex-shrink
(剩余空间缩小比例,默认1,等比例缩小)flex-basis
(具体分配空间,默认auto
,为元素宽度)flex
(前三总合)align-self
(单个子盒子侧轴排列方式)
flex
布局子元素不是行内块,是其布局的子元素,可以设置宽高
grid
容器
display: grid
grid-template-columns
grid-template-rows
grid-template-areas
grid-template-gap
justify-items
align-items
place-items //前两个属性之和
justify-content
align-content
place-content
子单元
grid-column-start
grid-column-end
grid-row-start
grid-row-end
grid-area
justify-self
align-self
place-self
盒子模型
盒子模型就是浏览器内核会根据CSS-box
模型,将所有元素表示为一个矩形盒子,从内到外依次为content
、padding
、border
、margin
box-sizing
为content-box
时(标准盒子模型,width对应content
,设置padding
、border
会撑大盒子)box-sizing
为border-box
时(怪异盒模型(IE盒模型),width
对应border
)
Canvas和SVG
Canvas
:Canvas
通过JavaScript
来绘制2D图形,逐像素进行渲染,一旦图形被绘制完成,它就不会继续得到浏览器的关注,如果其位置发生变化,那么整个场景也需要重新绘制,最适合图像密集型的游戏,其中的许多对象会被频繁重绘
SVG
:使用XML
描述2D
图形的语言,SVG DOM
中的每个元素都是可用的,可以为某个元素附加JavaScript
事件处理器,每个被绘制的图形均被视为对象,如果SVG
对象的属性发生变化,那么浏览器能够自动重现图形,最适合大型渲染区域,比如地图
JavaSrcipt
数据类型和类型判断
js
基本数据类型:Undefined
、Null
、Boolean
、Number
、String
、Symbol(es6)
、BigInt(es10)
引用数据类型:Object(Array,Function)
js
里所有数字都以双精度64位浮点格式(1符号位,11指数位,52尾数位)表示,所以说js
中的number
类型只能安全表示-2^53-1到2^53-1之间的整数,BigInt
数据类型的目的就是支持范围更大的整数值。
Symbol
本质上是一种唯一标识符,Symbol
数据类型的特点是唯一性和隐藏性,用同一变量生成的值也不相等,但是也能通过Object.getOwnPropertySymbols
访问
null
:逻辑上讲,null
值表示一个空对象指针,这也是给typeof
传一个null
会返回object
的原因
四种类型判断方法:
typeof
:可以识别除null
外的基本数据类型和function
,null
、Array
等都会被识别成object
类型instanceof
:判断后面参数对应的原型对象是否在前面参数的原型链上,一般用来检测对象类型和继承关系
function myInstanceof(left, right) {
let proto = Object.getPrototypeOf(left)//let proto = left.__proto__
let prototype = right.prototype;
while (true) {
if (!proto) return false;
if (proto === prototype) return true;
proto = Object.getPrototypeOf(proto);
}
}
constructor
:通过判断构造函数判断,几乎都能适用,但null
、undefined
没有constructor
方法,无法判断,且因为constructor
的指向可以改变,所以不安全Object.prototype.toString.call
:相对较全的判断方案,原理是toString
方法在不同种类对象里的实现,object
的toString
方法没有被重写
类型转化
-
+
两边为number
或boolean
则相加 -
+
两边如果有字符串或复杂数据类型,则两边toString
后拼接 -
其他运算符都转化成
number
类型运算,无法转化则NaN
window
浏览器里声明全局变量会成为window
对象属性,但node
不会,浏览器的全局对象为window
,node
为global
如果dom
元素的id
属性不与变量名重复,可以直接通过id
值操作dom
节点,因为会注册为window
的属性
console
console.time
监控时间
console.table
列表展示属性
定时器
-
setTimeout
、setInterval
的回调函数执行上下文默认指向window
-
setTimeout
、setInterval
回调函数里的变量值为时间间隔结束后作用域里的值,如果想保存之前的值,可用函数包裹缓存或者传入第三个参数缓存(推荐) -
setImmediate
:该方法把一些需要长时间运行的操作放在一个回调函数里,在浏览器事件循环到宏任务时执行,在浏览器端和setTimeout(fallback,0)
执行机制相同且支持度不高,在node
端较常用
requestAnimationFrame
requestAnimationFrame
会要求浏览器在下次重绘之前调用指定的回调函数更新动画,并且重绘或回流的时间间隔紧跟浏览器的刷新频率,一般这个频率为每秒60帧,可保证每个刷新间隔内,函数只被执行一次,这样既能保证流畅性,也能更好的节省函数执行开销,如果任务较多就会自动降低频率来防止卡顿掉帧
和定时器对比
-
定时器任务被放入异步队列,只有当
js
线程任务执行完后才会执行队列中的任务,因此实际执行时间总是比设定时间要晚,时间间隔不一定与屏幕刷新间隔时间相同,会引起丢帧容易出现卡顿、抖动,requestAnimationFrame
虽然属于宏任务,但回调函数渲染间隔完全由浏览器控制 -
定时器任务如果时间间隔设置过短,一个刷新间隔内函数执行多次是没有意义的,因为多数显示器每16.7ms刷新一次,多次绘制并不会在屏幕上体现出来
-
当页面处于不可见或不可用状态时,
SetTinterval
仍会在后台执行动画任务,而RequestAnimationFrame
则完全不同,当页面处理未激活的状态下,跟着系统走的RequestAnimationFrame
也会停止渲染
MutationObserver
用于对DOM
节点实现监听,每次监听节点发生变化都会生成一个MutationRecord
对象
使用
-
通过传入一个回调函数来创建一个
MutationObserver
实例 -
MutationObserver
对象通过observe
方法用来启动监听,接收两个参数,第一个参数为节点,第二个参数为节点MutationObserverInit
配置对象,当调用observe()
方法时,childList
,attributes
或者characterData
三个属性之中,至少有一个必须为true
,否则会抛出TypeError
异常MutationObserverInit
:{ //attributeFilter: attribute[] //监视的特定属性名称的数组 attributes: true, //观察受监视元素的属性值变更 characterData: true, //目标节点或子节点树中节点所包含的字符文本数据的变化 childList: true, //监视目标节点(如果 subtree 为 true,则包含子孙节点)添加或删除新的子节点 subtree: true, //将监视范围扩展至目标节点整个节点树中的所有节点 attributeOldValue: true, //记录任何有改动的属性的上一个值,有关观察属性更改和值记录的详细信息 characterDataOldValue: true ////记录任何有改动的字符文本数据的上一个值,有关观察字符文本数据更改和值记录的详细信息 }
IntersectionObserver
用于监听元素是否进出视图,所以也称交叉观察器
observe
方法监听元素
进出视图会产生对应Entry
对象或其对象数组
IntersectionObserverEntry
属性
time
:可见性发生变化的毫秒时间target
:被观察的目标dom元素rootBounds
:根元素的信息,如果没有根元素返回null
boundingClientRect
:目标元素信息intersectionRect
:交叉区域信息intersectionRatio
:目标元素的可见比例
IntersectionObserver
的实现采用requestIdleCallback
,所以只有线程空闲下来时才会执行观察器,所以这个观察器的优先级非常低,只在当其他任务执行完后,浏览器有了空闲才会执行
ES6模块化
- 单独作用域
cros
引入资源- 默认严格模式
- 导入导出是引用而不是复制
- 导入属于只读,无法对原模块进行更改,如果是对象,允许修改属性
script
默认是jsonp
方式引入资源,加了type=“module”
后会变成cros
(file
协议默认不同源)
对比common.js
node
和ECMAsrcipt
require
和export
- 全部导入和按需导入
- 值复制和值引用
- 服务端和浏览器
预解析、事件循环、宏微任务
-
es5
前有预解析,把声明式函数和变量声明提前,但赋值不变,变量直接调用时,函数声明优先级更高,在if
、for
语句中预解析会受到影响,预解析时函数声明会默认生成一个为undefined
的全局变量代替函数声明 -
接下来开始从上向下执行代码,同步任务依次执行,异步的进入事件处理线程
Event Table
并注册函数,当异步任务触发条件满足时,Event Table
会将对应回调函数移入执行队列,等待js
线程内同步任务执行结束为空时,会去执行队列读取对应函数执行,上述过程不断循环则为事件循环 -
js
任务又分为微任务和宏任务,js
线程从执行队列中读取异步任务时(微任务和宏任务是不同的任务队列),会先读取微任务,再读取宏任务,且微任务是一次读取全部,宏任务为一次循环读取一个执行,多次循环读取 -
由
js
控制的任务就是微任务(ES6-promise
),运行依赖宿主环境的就是宏任务(浏览器-setTimeout
),根本原因是所属规范不同
常见的微任务有:
Promise.then
MutaionObserver
process.nextTick
(Node.js
)
常见的宏任务有:
script
(可以理解为外层同步代码)setTimeout/setInterval
UI rendering
/UI事件postMessage
、MessageChannel
setImmediate
、I/O(Node.js)
闭包、纯函数、高阶函数、函数柯里化
-
闭包就是函数使用外部变量,通常配合高阶函数和柯里化使用
-
不依赖于任何外部输入,不改变任何外部数据,没有副作用(只要参数一样,结果永远一样)的函数为纯函数
-
以函数作为输出的函数被称为高阶函数
-
函数柯里化是把一个多参数函数转化成一个嵌套的一元函数的过程
-
柯里化便于生成纯函数,让函数功能解耦,每个执行步骤有更明确的意义,便于流程控制
继承
使用父构造函数apply
避免共享同一继承对象内存,根据父类生成子类单独属性
使用Object.create
减少额外构造函数性能损耗,继承父类原型
简单实现
function myExtend(Parent) {
function Child(name) {
Parent.apply(this)
this.name = name
}
Child.prototype = Object.create(Parent.prototype)
Child.prototype.constructor = Child
return Child
}
function Parent() {
this.a = 1
}
let Child = myExtend(Parent)
let tt = new Child('hh')
console.log(tt.a) // 1
console.log(tt.name) // hh
new简单实现
function myNew(Func, ...args) {
const obj = Object.create(Func.prototype)
let result = Func.apply(obj, args)
return result instanceof Object ? result : obj
}
Dom
节点复制
cloneNode
默认为浅拷贝,参数为true
才为深拷贝,返回值为新节点
滑动节点
scrollIntoview
滑动元素至滚动区某个位置
Map、Set
-
set
为成员不会重复的集合,map
为成员为键值对的列表,两者都提供了便于遍历的api
-
weakSet
里的对象只能为弱引用对象,weakMap
键只能为为弱引用对象或null
,但值为强引用,不受外部影响,弱引用外部引用清除,则对应值和键值对也消失,便于垃圾回收 -
weakMap
、weakSet
都没有生成可迭代对象的API
,无法遍历
map对比object
-
object
键只允许为字符串,map
键值允许任何类型,因此可以使用地址不同但值相同的对象 -
map
可迭代,object
不行,因此map
有序可扩展 -
map
有长度属性
深浅拷贝
浅拷贝是拷贝一层,深层次的引用类型则共享内存地址
-
object.assigin
-
扩展运算符
深拷贝开辟一个新的栈,两个对象属完成相同,但是对应两个不同的地址,修改一个对象的属性,不会改变另一个对象的属性
-
lodash
的_.cloneDeep()
-
JSON.stringify()
无法复制函数、undefined
、symbol
-
简单递归
function deepClone(obj) {
if (typeof obj === "function") return new Function('return ' + obj.toString())() // 函数
if (typeof obj !== "object") return obj; // 不是对象直接拷贝
if (obj === null) return obj; // null
if (obj instanceof Date) return new Date(obj);
if (obj instanceof RegExp) return new RegExp(obj);
let cloneObj = {}
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
cloneObj[key] = deepClone(obj[key]);
}
}
return cloneObj;
}
let a = {
a: function () {
console.log(1)
},
b: 2,
c: null,
d: { dd: 1 }
}
let b = deepClone(a)
console.log(b)
装饰器
装饰器用于类和类的属性,利用闭包传递参数,多个修饰器外层从前到后,在内层从后到前,装饰器不能用于修饰函数,因为函数存在变量声明情况
事件绑定
在标签里绑定事件会额外封装一层函数,this
指向绑定事件节点,通过js
绑定事件,this
指向window
迭代器和生成器
-
生成器函数
function*
会返回一个迭代器对象,可迭代对象上也会有Symbol.iterator
这个属性,调用后也会生成迭代器对象,迭代器对象通过next()
指向下个值,如果有下个yield
则返回{ value: any, done: false }
格式,如果是return
,则返回{ value:any, done: true }
,如果无下个值,则返回{ value: undefined, done: true }
-
yield
表达式本身没有返回值,或者说总是返回undefined
,通过调用next
方法可以带一个参数,该参数就会被当作上一个yield
表达式的返回值 -
用
for of
可以直接遍历迭代器对象拿到生成器函数yield
后的值 -
for of
无法用于原生对象,对象没有内置迭代器接口,但可以通过生成器函数添加
let jane = { first: 'Jane', last: 'Doe' }
jane[Symbol.iterator] = function*(){
let keys = Reflect.ownKeys(jane)
for(let k of keys){
yield [k,obj[k]]
}
}
for (let [key, value] of jane) {
console.log(`${key}: ${value}`)
}
// first: Jane
// last: Doe
函数作用域、执行上下文
函数声明时会绑定声明作用域,函数被赋值引用时不会改变其声明作用域,变量查找会从函数内部作用域到函数声明作用域再依次向外,对象里的函数声明默认都在外一层作用域,执行上下文为自己,this
指向执行上下文,一般为调用函数的对象,声明箭头函数会绑定外层非箭头函数的执行上下文作为自己的执行上下文,call
、setTimeout
等都无法改变
内存管理
js
自动管理内存,闭包里的执行栈里的数据会自动被标记清除法回收,但引用对象不会,所以会导致内存泄露,js
默认有个引用计数表,记录着对象的引用次数,在使用完对象后将其=null
,就会是这个引用表对应次数置0
,js
就会自动回收内存
内存泄漏可能产生途径:
1.ES5里的函数里声明全局变量
2.计时器
3.函数监听
4.闭包
5.在全局window
上声明绑定属性
6.获取dom
节点,节点被删除
原型链
BOM
-
window
:在浏览器中,window
对象有双重角色,既是浏览器窗口的一个接口,又是全局对象 -
location
:代表url
地址,除了hash
之外,只要修改location
的属性,就会导致页面重新加载新URL
-
navigator
:主要用来获取浏览器的属性,区分浏览器类型 -
screen
:代表显示器的有关信息 -
history
:操作浏览器URL
的历史记录 -
location
的href
,window
的open
,history.go
都可以用于网页跳转
事件冒泡
focus
、blur
这些事件没有事件冒泡机制,所以无法进行委托绑定事件
Drag
被拖拽的元素
-
dragstart
:开始拖拽时触发 -
darg
:拖拽时触发 -
dragend
:拖拽操作结束时触发
受到拖拽事件影响的元素
-
dragenter
:被拖拽元素进入元素时触发 -
dragover
:被拖拽在元素内移动时触发 -
dragleave
:被拖拽元素移出元素触发 -
drop
:元素完全接受被拖拽元素时触发
var、let、const
-
var
会生成全局变量 -
var
有预解析、变量提升,let
、const
是暂时性死区,但函数声明仍存在变量提升 -
var
为函数作用域,let
、const
为块级作用域 -
const
声明常量值无法改变,声明引用对象地址无法改变
错误捕获
-
try
...catch
-
window
监听error
事件
伪数组
伪数组有迭代接口,可以用于遍历,但没有继承Array
原型,无法使用数组方法
Promise
-
promise
是es6
中新增的处理异步任务的语法,它改善了回调地狱的问题,它是通过构造方法对传入异步函数封装成一个对象,封装成对象是因为对象可以保存状态,promise
有三个状态分别是:pending
(初始)--fulfilled
(成功)--rejected
(失败),执行异步任务成功后,通过resolve
接收数据,then
方法里传入成功的回调函数执行,失败后通过reject
接收数据,将失败后的回调函数传入then
里的第二个参数执行,或者传入catch
方法执行,promise
构造函数里调用resolve
或reject
后仍会向后执行,promise
构造函数会在声明后就立即执行 -
promise
对象的状态从pending
转化成fulfilled
或rejected
后无法改变,promise
对象每次调用then
和catch
都会生成新的promise
对象用于链式调用,其中函数返回值会被生成的新对象resolve
,resolve
如果接收一个fulfilled
状态的promise
对象会直接resolve
其resolve
的值,如果接收一个rejected
或pending
状态的promise
对象,则会resolve
整个对象 -
Promise
还有all
方法和race
方法,all
方法是当所有的子Promise
都完成,该Promise
完成,返回值是全部值的数组,有任何一个失败,该Promise
失败,返回值是第一个失败的子Promise
结果,race
区别是它会取第一个成功的子Promise
结果为成功结果
return Promise.resolve
和new Promise()
和直接return
比都会默认创建两次tick
,延迟两次微任务
new Promise((resolve) => {
resolve(1)
}).then((res) => {
console.log(res)
return new Promise((resolve) => {
resolve(3)
})
}).then((res) => {
console.log(res)
})
new Promise((resolve) => {
resolve(2)
}).then((res) => {
console.log(res)
return 4
}).then((res) => {
console.log(res)
return 5
}).then((res) => {
console.log(res)
return 6
}).then((res) => {
console.log(res)
})
//1 2 4 5 3 6
promise
功能简单实现
class MyPromise {
constructor(func) {
this.status = 'pending'
this.errFallback = null
this.resFallback = null
try {
func(this.resolve, this.reject)
} catch (e) {
this.status = 'rejected'
this.reason = e
if(this.errFallback){
this.errFallback(this.reason)
this.errFallback = null
}
}
}
resolve = (res) => {
if (this.status === 'pending') {
this.status = 'fulfilled'
this.reason = res
if(this.resFallback){
this.resFallback(this.reason)
this.resFallback = null
}
}
}
reject = (err) => {
if (this.status === 'pending') {
this.status = 'rejected'
this.reason = err
if(this.errFallback){
this.errFallback(this.reason)
this.errFallback = null
}
}
}
then = (back, errBack) => {
if (this.status === 'pending') {
this.resFallback = back
this.errFallback = errBack
} else {
if (this.status === 'fulfilled') {
back(this.reason)
}
if (this.status === 'rejected') {
errBack(this.reason)
}
}
}
}
new MyPromise((resolve, reject) => {
setTimeout(() => {
reject(2)
}, 5000)
}).then((res) => {
console.log(res)
}, (res) => {
console.log(res)
})
promise.all
简单实现
const myall=function(arr){
return new Promise((res,rej)=>{
let arr2=[]
arr.forEach((item)=>{
item.then((res2)=>{
arr2.push(res2)
}).catch((err)=>{
rej(err)
}).finally(()=>{
if(arr2.length===arr.length){
res(arr2)
}
})
})
})
}
const a=new Promise((res,rej)=>{
res(1)
})
const b=new Promise((res,rej)=>{
res(2)
})
const c=new Promise((res,rej)=>{
res(3)
})
const d=new Promise((res,rej)=>{
rej(2)
})
myall([a,b,c]).then((res)=>{
console.log(res);
}).catch((err)=>{
console.log(err);
})
Promise
简单实现并发机制
function together(arr, limit) {
let t = Date.now()
let index = 0
let length = 0
function callback() {
if (length < limit && index < arr.length) {
new Promise(arr[index].fuc).then((res) => {
console.log(`${res}---${Date.now() - t}`)
length--
callback()
}).catch((err) => {
console.log(err)
length--
callback()
})
index++
length++
}
}
for (let i = 0; i < limit; i++) {
callback()
}
}
let promiseArr = []
for (let i = 1; i < 8; i++) {
promiseArr.push({
fuc: (res) => {
setTimeout(() => {
res(i)
}, i * 1000)
},
})
}
together(promiseArr, 3)
Promise
实现请求中断
function abort(fuc, signal) {
return new Promise((res, rej) => {
fuc(res, rej)
if (signal) {
signal.addEventListener('abort', () => {
rej('abort')
})
}
})
}
const controller = new AbortController()
const signal = controller.signal
abort((res, rej) => { setTimeout(() => res(1), 2000) }, signal)
.then(res => console.log(res))
.catch(err => console.log(err))
setTimeout(() => {
controller.abort()
}, 1000)
async、await
async
、await
是es8
新增的处理异步的关键字,更好处理了回调地狱的问题,本质只是promise
和生成器函数的语法糖,以同步代码的形式去书写异步代码,async
将一个函数声明为异步函数,await
后接一个promise
对象,返回其处理成功后的结果,用try...catch
语法捕获错误,await
会把后面代码作为参数传入一个新promise
对象的resolve
,如果后面代码是promise
对象不处理,如果后面代码不是,则包装成promise
对象执行后再传入,这个新对象resolve
的最终结果则为await
返回值,所以await
后面的代码立即执行,下方代码异步阻塞
扩展运算符
...可以展开有迭代器接口的对象,无法直接展开对象,但可以用做对象的浅拷贝,拷贝数组时只能放在最后(对象无序,所以不要求位置)
数组地址
js
数组长度可变,且用原生数组方法操作数组时,长度变化时,地址不变
Object
assign
object.assign()
:可以将源对象所有可枚举属性,复制到目标对象上,本质为浅拷贝
freeze
Object.freeze()
:冻结对象
create
Object.create()
:将对象作为原型创造新对象
defineProperty
Object.defineProperty()
用于给对象设置属性,它有三个参数,第一个为对象,第二个为属性,第三个为特性,特性有六个可选值,分别为get
、set
、value
(值)、wirtable
(能否被重写)、enumerable
(能否被枚举)、configrable
(特性能否被更改)
数组API
forEach、map、reduce、filter
等API
都是对原数组浅拷贝一个新数组再对新数组进行操作,所以无法改变原数组里的基本类型和引用类型地址
sort
sort
的基本排序原理:
-
对本项和上一项进行传入函数的运算,返回值为正顺序不变,为负调换顺序
-
然后每增加对比一个元素,都会把新增元素通过二分法对已经排序好的部分元素对比,确定好新元素位置后,再重复这个步骤直至遍历完所有数组元素,这只是基本原理,实际原理会根据数组选择不同算法优化,更加复杂
forEach
forEach
的执行次数为数组里的元素个数,不为数组长度
reduce
reduce
如果没有初始值,会减少一次循环,把第一项作为初始值,直接从index=1
开始
简单实现
Array.prototype.myReduce = function (fallback, val) {
let start = val
for (let i = 0; i < this.length; i++) {
if (val === undefined && i === 0) {
start = this[i]
} else {
start = fallback(start, this[i], i, this)
}
}
return start
}
let res1 = [2, 3, 4].myReduce((pre, val, index, arr) => {
console.log(pre, val, index, arr);
return pre + val
}, 1)
// 1 2 0 [ 2, 3, 4 ]
// 3 3 1 [ 2, 3, 4 ]
// 6 4 2 [ 2, 3, 4 ]
let res2 = [2, 3, 4].myReduce((pre, val, index, arr) => {
console.log(pre, val, index, arr);
return pre + val
})
//2 3 1 [ 2, 3, 4 ]
//5 4 2 [ 2, 3, 4 ]
console.log(res1) //10
console.log(res2) //9
指数运算
math.pow()
**
指数运算符(es7)
数组拍平
flat
toString
json.stringify
- 递归
Web Components
基本使用
Web Components
是一个原生的组件化方案
通过类语法继承HTMLElement
生成组件
class ClassName extends HTMLElement {
constructor() {
super()
let el = document.createElement('p')
el.classList.add('name')
el.innerText = 'User Name'
this.append(el)
}
}
window.customElements.define('components-name', ClassName)//注册绑定组件
Web Components
提供了<template>
来实现组件复用和简化代码编写
<template id="Template">
<style>
:host {
width: 450px;
height: 180px;
}
</style>
<div class="container">
...
</div>
</template>
class ClassName extends HTMLElement {
constructor() {
super();
var templateElem = document.getElementById('Template')
var content = templateElem.content.cloneNode(true)
this.appendChild(content)
}
}
<template>
里可以插入<style>
来表示限定样式,里面的:host
伪类指向自定义元素本身
子元素传参:将属性绑定在父元素上,获取后传给子元素
class ClassName extends HTMLElement {
constructor() {
super()
var templateElem = document.getElementById('Template')
var content = templateElem.content.cloneNode(true)
content.querySelector('img').setAttribute('src', this.getAttribute('image'))
this.appendChild(content)
}
}
通过getAttribute()
获取注入属性
生命周期
-
connectedCallback
:当element
首次被插入文档DOM时被调用 -
disconnectedCallback
:当element
从文档DOM中删除时被调用 -
adoptedCallback
:当element
被移动到新的文档时被调用 -
attributeChangedCallback
: 当element
增加、删除、修改自身属性时被调用
Shadow DOM
Shadow DOM
是对DOM的一个简单封装,可以将标记结构、样式和行为隐藏起来,并与页面上的其他代码相隔离,保证不会混在一起,使代码更加干净、整洁
class ClassName extends HTMLElement {
constructor() {
super()
var shadow = this.attachShadow( { mode: 'closed' } )
var templateElem = document.getElementById('Template')
var content = templateElem.content.cloneNode(true)
shadow.appendChild(content)
}
}
对比vue
Web Components | vue |
---|---|
实例属性 | data |
attributes | props |
observedAttributes attributeChangedCallback | watch |
getters | computed |
class methods | methods |
connectedCallback | mounted |
disconnectedCallback | destroyed |
template中的style | style scoped |
template | template |
优缺点
优点
- 浏览器原生支持,不需要引入额外的第三方库
- 语义化
- 复用性,移植性高
- 不同团队不同项目可以共用组件
缺点
- 可能需要频繁操作DOM
- 目前浏览器兼容性、性能方面不够友好
- 和外部
css
交互比较难
Typescript
接口和type
- 接口只能声明对象类型,而
type
可以声明各种,基本类型,联合类型,数组类型等 - 接口可以通过继承或者实现,
type
不能 - 接口通过重复声明可以合并,
type
重复声明会报错,使用&合并 type
可以配合typeof
使用
对react支持
在类组件中,默认泛型格式为<props,state>
,函数组件为<props>
命名空间
命名空间用于支持在当前作用域重复声明相同变量,本质原理为函数闭包
类修饰符
private
:私有属性,仅在当前类使用protect
:受保护属性,可在当前类和子类使用public
:公开属性,可被实例化对象使用static
:静态属性,可不用实例化的静态方法abstract
:抽象类,抽象类的抽象属性必须在子类中实现readonly
:只读属性
和java
就一模一样
Git
git revert、git reset
git revert
是用一次新的commit
来回滚之前的commit
,git reset
是直接删除指定的commit
git reset
是把HEAD向后移动了一下,而git revert
是HEAD继续前进,只是新的commit
的内容和要revert
的内容正好相反,能够抵消要被revert
的内容- 如果回退分支的代码以后还需要的情况则使用
git revert
, 如果分支是提错了没用的并且不想让别人发现这些错误代码,则使用git reset
git merge、git rebase
git merge
是在本分支记录穿插拷贝的合并分支记录,并增加合并记录git rebase
会将两个分支彻底变为一个,不在当前分支记录里穿插合并分支记录,但会生成一个合并分支的记录副本,然后自己处理冲突后产生一个新记录
工程化
主要工作为四种:开发和调试(HMR
等)、转化(babel
等)、压缩(terser
等)、打包(webpack
等)
环境需求
开发环境
-
模块热更新 (本地开启服务,实时更新)
-
接口代理 (配置
proxy
解决开发环境中的跨域问题) -
sourceMap
(方便打包调试) -
代码规范检查 (代码规范检查工具)
生产环境
-
提取公共代码
-
压缩混淆(压缩混淆代码,清除代码空格,注释等信息使其变得难以阅读)
-
文件压缩/base64编码(压缩代码,减少线上环境文件包的大小)
-
去除无用的代码
srcipt
并行:&(windows
无效)、run-p
(基于npm-run-all
插件)
串行:&&、run-s
(基于npm-run-all
插件)
npm install过程
1.从dependencies
和devDependencies
配置开始递归获取依赖模块
2.获取依赖模块后可能包含大量重复模块,从npm3
开始加入了一个dedupe
的过程,它会遍历所有节点,发现有重复模块时,则将其丢弃,依赖模块版本兼容则保留先被查找的版本,不兼容则全部保留
3.在获取模块时会查询node_modules
下是否已存在该模块,存在则不再重新安装,不存在则会向registry
查询模块压缩包的网址
4.如果定义了preinstall
等钩子此时会被执行
5.根据模块地址下载压缩包,存放在根目录下的.npm
目录里
6.解压压缩包到当前项目的node_modules
目录
webpack
- 由
webpackCore
和一堆插件组成,webpackCore
本质只负责js
的打包,底层为js
,效率低,但插件和生态极其强大 - 对各种模块化语法(如
ESmodule、AMD
等)都做了兼容 - 一切资源都看作模块(如图片、
html、css
等) - 插件极其丰富(想要的功能差不多都能找到)
- 生态成熟活跃(坑都被别人踩了,问题都有人会解答)
- 支持回调(
SourceMap
) - 可实现代码分块优化,按需加载
基本配置
var path = require('path')
var node_modules = path.resolve(__dirname,'node_modules')
var pathToReact = path.resolve(node_modules,'react/dist/react.min.js')
module.exports = {
// 入口文件,是模块构建的起点,同时每一个入口文件对应最后生成的一个chunk
entry: './path/to/my/entry/file.js',
// 文件路径指向(可加快打包过程)
resolve: {
alias: {
'react': pathToReact
}
},
// 生成文件,模块构建的终点,包括输出文件与输出路径
output: {
path: path.resolve(__dirname,'build'),
filename: '[name].js'
},
// 处理各模块的loader,包括css预处理loader,es6编译loader,图片处理loader
module: {
rules: [
{
test: /\.js$/,
loader: 'babel',
query: {
presets: ['es2015', 'react']
}
}
],
noParse: [pathToReact]
},
// webpack各插件对象,在webpack的事件流中执行对应的方法
plugins: [
new webpack.HotModuleReplacementPlugin()
]
}
常用配置
entry
:
// string方式: 单入口,打包形成一个chunk,输出一个buldle文件,chunk的名称默认是main.js
entry: "./src/index.js",
// array方式:多入口,所有入口文件最终只会形成一个chunk,输出出去只有一个bundle文件
entry: ["./src/index.js", "./src/test.js"],
// object:多入口,有几个入口文件就形成几个chunk,输出几个bundle文件,此时chunk的名称就是对象key值
entry:{
index:"./src/index.js",
test:"./src/test.js",
}
output
:
output: {
// 输出文件目录(将来所有资源输出的公共目录,包括css和静态文件等等)
path: path.resolve(__dirname, "dist"), //默认
// 入口文件名称(指定名称+目录)
filename: "[name].js", // 默认
// 所有资源引入公共路径前缀,一般用于生产环境,小心使用
publicPath: "",
/*
非入口文件chunk的名称,所谓非入口即import动态导入形成的chunk或者optimization中的splitChunks提取的公共chunk
它支持和 filename 一致的内置变量
*/
chunkFilename: "[contenthash:10].chunk.js",
clean: true, // 打包前清空输出目录,相当于clean-webpack-plugin插件的作用,webpack5新增
/* 当用 Webpack 去构建一个可以被其他模块导入使用的库时需要用到library */
library: {
name: "[name]",//整个库向外暴露的变量名
type: "window"//库暴露的方式
}
},
loaders
:
rules: [
{
// 匹配哪些文件
test: /\.css$/,
// 使用哪些loader进行处理,执行顺序,从右至左,从下至上
use: [
// 创建style标签,将js中的样式资源(就是css-loader转化成的字符串)拿过来,添加到页面head标签生效
"style-loader",
// 将css文件变成commonjs一个模块加载到js中,里面的内容是样式字符串
"css-loader",
{
// css 兼容处理 postcss,注意需要在package.json配置browserslist
loader: "postcss-loader",
options: {
postcssOptions: {
ident: "postcss",
// postcss-preset-env插件:帮postcss找到package.json中的browserslist配置,根据配置加载指定的兼容性样式
plugins: [require("postcss-preset-env")()],
},
},
},
],
},
{
test: /\.js$/,
// 注意需要在package.json配置browserslist,否则babel-loader不生效
// js兼容处理 babel
loader: "babel-loader", // 规则只使用一个loader时推荐写法
options: {
presets: [
[
"@babel/preset-env",// 预设:指示babel做怎么样的兼容处理
{
useBuiltIns: "usage", //按需加载
corejs: {
version: "3",
},
targets: "defaults",
}
]
]
}
},
/*
Webpack5.0新增资源模块(asset module),它是一种模块类型,允许使用资源文件(字体,图标等)而无需配置额外 loader 支持以下四个配置:
asset/resource 发送一个单独的文件并导出 URL,之前通过使用 file-loader 实现
asset/inline 导出一个资源的 data URI,之前通过使用 url-loader 实现
asset/source 导出资源的源代码,之前通过使用 raw-loader 实现
asset 在导出一个 data URI 和发送一个单独的文件之间自动选择,之前通过使用 url-loader,并且配置资源体积限制实现
*/
// Webpack4使用file-loader实现
{
test: /\.(eot|svg|ttf|woff|)$/,
type: "asset/resource",
generator: {
// 输出文件位置以及文件名
filename: "fonts/[name][ext]"
},
},
// Webpack4使用url-loader实现
{
//处理图片资源
test: /\.(jpg|png|gif|)$/,
type: "asset",
generator: {
// 输出文件位置以及文件名
filename: "images/[name][ext]"
},
parser: {
dataUrlCondition: {
maxSize: 10 * 1024 //超过10kb不转base64
}
}
},
],
plugins
:
// CleanWebpackPlugin帮助你在打包时自动清除dist文件,学习时使用比较方便
// const { CleanWebpackPlugin } = require("clean-webpack-plugin");
// 从webpack5开始,webpack内置了该功能,只要在ouput中配置clear为true即可
// HtmlWebpackPlugin帮助你创建html文件,并自动引入打包输出的bundles文件,支持html压缩
const HtmlWebpackPlugin = require("html-webpack-plugin");
// 该插件将CSS提取到单独的文件中,这个它会为每个chunk创造一个css文件,需配合loader一起使用
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
// 该插件将在Webpack构建过程中搜索CSS资源,并优化\最小化CSS
const OptimizeCssAssetsWebpackPlugin = require("optimize-css-assets-webpack-plugin");
// vue-loaderV15版本以上,需要引入VueLoaderPlugin插件,它的作用是将你定义过的js、css等规则应用到vue文件中去
const { VueLoaderPlugin } = require('vue-loader')
module.exports = {
module: {
rules: [
{
test: /\.vue$/,
loader: "vue-loader"
},
{
test: /\.css$/,
use: [
// MiniCssExtractPlugin.loader的作用就是把css-loader处理好的样式资源(js文件内),单独提取出来成为css样式文件
MiniCssExtractPlugin.loader,//生产环境下使用,开发环境还是推荐使用style-loader
"css-loader",
],
},
],
},
plugins: [
new HtmlWebpackPlugin({
template:"index.html",
}),
new MiniCssExtractPlugin({
filename: "css/built.css",
}),
new OptimizeCssAssetsWebpackPlugin(),
new VueLoaderPlugin(),
]
}
mode
:
DefinePlugin
:定义全局变量process.env.NODE_ENV
,区分程序运行状态FlagDependencyUsagePlugin
:标记没有用到的依赖FlagIncludedChunksPlugin
:标记chunks
,防止chunks
多次加载ModuleConcatenationPlugin
:作用域提升(scope hosting
),预编译功能,提升或者预编译所有模块到一个闭包中,提升代码在浏览器中的执行速度NoEmitOnErrorsPlugin
:防止程序报错,就算有错误也继续编译TerserPlugin
:压缩js
代码
- 其他:
// 解析模块的规则:
resolve: {
// 配置解析模块路径别名:可简写路径
alias: {
"@": path.resolve(__dirname, "src")
},
// 配置省略文件路径的后缀名,默认省略js和json,也是webpack默认认识的两种文件类型
extensions: [".js", ".json", ".css"], // 新加css文件
// 告诉webpack解析模块是去找哪个目录
// 该配置明确告诉webpack,直接去上一层找node_modules
modules: [path.resolve(__dirname, "../node_modules")],
},
// devServer(开发环境下配置):
devServer: {
// 运行代码的目录
contentBase: path.resolve(__dirname, "build"),
// 为每个静态文件开启gzip压缩
compress: true,
host: "localhost",
port: 5000,
open: true, // 自动打开浏览器
hot: true, //开启HMR功能
// 设置代理
proxy: {
// 一旦devServer(5000端口)接收到/api/xxx的请求,就会用devServer起的服务把请求转发到另外一个服务器(3000)
// 以此来解决开发中的跨域问题
api: {
target: "htttp://localhost:3000",
// 发送请求时,请求路径重写:将/api/xxx --> /xxx (去掉/api)
pathRewrite: {
"^api": "",
},
},
},
},
// optimization(生产环境下配置)
optimization: {
// 提取公共代码
splitChunks: {
chunks: "all",
},
minimizer: [
// 配置生产环境的压缩方案:js和css
new TerserWebpackPlugin({
// 多进程打包
parallel: true,
terserOptions: {
// 启动source-map
sourceMap: true,
},
}),
],
},
构建过程
-
解析脚本命令,初始化插件和
Compilation
对象 -
从
entry
入口文件开始分析依赖,对每个依赖模块开始构建 -
加载
loader
并处理模块,对loader
加载完处理后的模块进行编译生成AST
,当遇到require
等导入其它模块语句时,便将其加入到依赖的模块列表,同时对新找出的依赖模块递归分析,最终搞清所有模块的依赖关系并生成AST
-
当模块全部构建完时将
AST
生成chunks
,对chunks
进行一系列的优化,生成代码 -
将代码输出到配置指定文件
loader
-
本质为函数,函数中的
this
作为上下文会被webpack
填充,因此最好不要将loader
设为一个箭头函数 -
函数接受一个参数,为
webpack
传递给loader
的文件源内容 -
函数中有异步操作或同步操作,同步用
return
,异步操作通过this.callback
返回
简单实现:
// 导出一个函数,source为webpack传递给loader的文件源内容
module.exports = function(source) {
const content = doSomeThing2JsString(source)
// 如果loader配置了options对象,那么this.query将指向options
const options = this.query
// 可以用作解析其他模块路径的上下文
console.log(this.context)
/*
* this.callback 参数:
* error:Error | null,当 loader 出错时向外抛出一个 error
* content:String | Buffer,经过 loader 编译后需要导出的内容
* sourceMap:为方便调试生成的编译后内容的 source map
* ast:本次编译生成的 AST 静态语法树,之后执行的 loader 可以直接使用这个 AST,进而省去重复生成 AST 的过程
*/
this.callback(null, content) // 异步
return content // 同步
}
plugin
-
由于
webpack
基于发布订阅模式,在运行的生命周期中会广播出许多事件,插件通过监听这些事件,就可以在特定的阶段执行自己的插件任务 -
插件必须是一个函数或者是一个包含
apply
方法的对象,这样才能访问compiler
实例 -
传给每个插件的
compiler
和compilation
对象都是同一个引用,因此不建议修改 -
异步的事件需要在插件处理完任务时调用回调函数通知
Webpack
进入下一个流程,不然会卡住
简单实现:
class MyPlugin {
// Webpack 会调用 MyPlugin 实例的 apply 方法给插件实例传入 compiler 对象
apply (compiler) {
// 找到合适的事件钩子,实现自己的插件功能
compiler.hooks.emit.tap('MyPlugin', compilation => {
// compilation: 当前打包构建流程的上下文
console.log(compilation);
// do something...
})
}
}
webpack
的生命周期函数
生命周期钩子 | |
---|---|
entry-option | 初始化 option 开始 |
run | 初始化运行中 |
compile | 真正开始的编译,在创建 compilation 对象之前 |
compilation | 生成好了 compilation 对象 |
make | 从 entry 开始递归分析依赖,准备对每个模块进行 build |
after-compile | 编译 build 过程结束 |
emit | 在将内存中 assets 内容写到磁盘文件夹之前 |
after-emit | 在将内存中 assets 内容写到磁盘文件夹之后 |
done | 完成所有的编译过程 |
failed | 编译失败的时候 |
loader对比plugin
-
loader
是帮助webpack
处理js
以外的文件加载器,plugin
让webpack
拥有除文件打包以外的其他新功能 -
loader
只作用于打包构建过程,而plugin
可作用于整个过程
文件压缩
-
js
代码压缩=>1.
TerserPlugin
:webpack
在生产模式下会默认使用,多进程处理(官方推荐)2.
uglifyJS
:单线程压缩代码,打包压缩代码速度慢,但兼容老项目 =>webpack-parallel-uglify-plugin
:多个文件压缩的工作分别给多个子进程去完成,再由主线程uglifyJS
处理,速度提升,但兼容性下降,uglify
不支持ES6以上的语法,需要和babel
配合使用3.
ESBuild
:快,爽,无敌 -
css
代码压缩=>css-minimizer-webpack-plugin
-
html
代码压缩=>使用HtmlWebpackPlugin
插件配置属性minify
进行html
优化,实际使用的是html-minifier-terser
-
文件代码压缩=>
compression-webpack-plugin
,例如gzip
压缩 -
图片压缩=>
image-webpack-loader
和file-loader
热更新
启动阶段
- 启动阶段为1 - 2 - A - B
在编写未经过webpack
打包的源代码后,Webpack Compile
将源代码和HMR Runtime
一起编译成bundle
文件,传输给Bundle Serve
静态资源服务器,再传给浏览器
- 更新阶段为1 - 2 - 3 - 4
1.文件改变后触发webpack
监听模式,重新编译处理文件有关全部依赖模块(tree-shaking
等处理在这之间)
2.webpack
通过之前sock.js
和浏览器建立websocket
长连接,将文件信息传递给浏览器,主要为文件的hash
值
(webpack
对静态资源进行监控,判断浏览器是否需要刷新)
3.浏览器端HMR runtime
,对接受消息进行分析判断是需要热更新还是浏览器刷新
4.浏览器通过jsonp
获取模块代码后分析进行局部更新
vite
vite
是一个前端工程化工具,组成如下:
-
一个开发服务器:基于
ESModule
静态语法分析,直接返回空页面,跳过整个编译打包,而是初始化和更新时完全根据浏览器请求按需导包编译 -
一套构建指令:默认使用
ESBuild
转化语法,rollup
打包,至于为什么不直接用ESbuild
,是因为对新技术稳定性的观望 -
其作用类似
webpack
+webpack-dev-server
+babel
,压缩优化等仍靠terser
等插件完成
简单对比webpack
工具 | webpack | vite |
---|---|---|
打包 | webpackCore | rollup |
压缩优化 | webpack插件(terser等) | rollup插件(terser等) |
开发调试 | webpack-dev-server | 基于ESModule的本地服务器 |
语法转化 | webpack插件(babel等) | ESBuild |
ESBuild
Js
语法转化和打包工具,相当于babel+terser+rollup
,底层使用go
,效率极高,但插件功能还不够完善,暂时无法作为构建工具
Rollup
rollup
为一个打包工具,基于ES
模块化自带tree-shaking
,虽然webpack
也实现了tree-shaking
和利用babel-plugin
兼容了Esmodule
,但rollup
比webpack
要轻,速度更快,打包结果更简洁,但只能处理js
文件,处理其他文件也需插件配合,且不具有热更新功能,所以一般只用于js
文件的打包,而不作为工程化构建工具
TreeShaking
暂时性去除掉未使用的包和代码,让打包体积变小
-
基于
ES Module
的静态语法分析(按需导入导出模块)实现tree-shaking
-
webpack
基于两种配置来实现tree-shaking
:
-
usedExports
:通过标记某些函数是否被使用,之后通过Terser
来进行优化 -
sideEffects
:跳过配置的模块/文件,直接查看该文件是否有副作用
Browserify
对commonjs
到ESmodule
的语法转化包
Babel
js
语法处理工具
功能包
核心包
@babel/core
:提供babel
基础运行功能,内部使用了@babel/parser
、@babel/traverse
、@babel/generator
三个工具来负责代码转译,分别对应解析、更新、生成
工具包
@babel/register
:改写require
命令,让导入的文件自动经过babel
处理@babel/node
、@babel/cli
:让命令行支持babel命令- 这三个包都依赖于
@babel/core
,且只适用于开发环境
插件包
@babel/preset-env
:据所需环境自动决定适合的babel
插件,集成了十几种插件的集合,可以根据useBuiltIns
参数导入@babel/polyfill
,'usage'
为按需,'entry'
为全部,'false'
不导入@babel/polyfill
:ES6+转化包,包括全局对象和其API,7.4后被core-js/stable
和regenerator-runtime/runtime
替代,全局引入会污染全局变量且体积很大@babel/plugin-transform-runtime
:ES6+转化包,包括全局对象和其API,按需引入体积更小更轻,但无法转化一些实例化方法,依赖于@babel-runtime
@babel-runtime
和@babel/polyfill
都集成并依赖于helpers
、regenerator
、core-js
,在版本7之后需要手动安装对应core-js
@babel/preset-react
:react
语法转化包@babel/preset-typescript
:ts
语法转化包
原理
parse
(@babel/parser
):将源代码转化为ast
transform
(@babel/traverse
):将ast
转化为目标代码的ast
generate
(@babel/generator
):将ast
生成目标代码
PostCss
postcss
是一种对css
编译的工具,本身不会对css
操作,它通过插件实现功能,常见的功能插件如:
-
postcss-cssnext
:支持下一代css
语法(css-next
) -
autoprefixer
:自动补全浏览器前缀 -
postcss-pxtorem
:自动把px
代为转换成rem
-
cssnano
:css
代码压缩 -
tailwindcss
:配合Tailwind-CSS
和css
预处理器处理时机区别:
postCss
工作流:
Tailwind CSS
提供了很多公用类样式,减少了去细化编写css
样式,增加了学习成本,但在一些非定制简单组件样式里提高了开发效率
网络
OSI七层模型和TCP/IP五层模型
OSI
七层模型是一种理论模型,TCP/IP
模型为实际使用中的模型,OSI
七层模型分别为物理层、数据链路层、网络层、传输层、会话层、表示层、应用层,而TCP/IP
模型将其中的物理层和数据链路层划分成了网络接口层,将会话层、表示层、应用层划分成了应用层
- 物理层主要指像网线、光纤、中继器等这种网络传输的物理设备,数据传输单位为
bit
- 数据链路层为网桥、以太网交换机等进行的传输,数据传输的单位为帧,代表协议有
HDLC
(高级链路控制协议)PPP
(点对点协议)SLIP
(串行线路接口协议)协议等 - 网络层任务为选择合适的网间路由和交换节点,数据传输单位为数据包,代表协议有
IP
(网际协议)ICMP
(网际控制消息协议)ARP
(地址解析协议)RARP
(反向地址解析协议)协议等 - 传输层提供了主机之间的交互,数据单元也为数据包,拥有代表性的
TCP
(控制传输协议)UDP
(用户数据报协议)协议等 - 应用层提供程序之间的交互,它的代表协议为
FTP
(文件传输协议)HTTP
(超文本传输协议)DNS
(域名服务器协议)SMTP
(简单邮件传输协议)NFS
(网络文件系统协议等
304缓存
缓存分为强缓存和协商缓存两种
强缓存指的是浏览器直接通过请求的头信息判断本地缓存是否过期,没有过期则直接使用本地缓存数据,与强缓存相关的字段有expires
、cache-control
-
expires
是一个绝对时间,代表资源超过某个时间节点后就需要重新向服务器发送请求,但如果客户端时间出现误差,就会导致数据缓存出现偏差 -
而
cache-control
则是相对时间,代表经过某个时间段后需要向服务器重新发送请求,如果cache-control
与expires
同时存在的话,cache-control
的优先级高于expires
-
Cache-Control
字段no-store
:禁用缓存no-cache
:强缓存失效,判断协商缓存
如果强缓存过期了,则使用协商缓存,客户端发送请求判断资源是否发生变化,如果未发生变化则使用本地缓存,发生变化则重新请求资源,与协商缓存相关的字段有Last-Modified/If-Modified-Since
、Etag/If-None-Match
-
Last-Modified/If-Modified-Since
中的Last-Modified
是服务端发送给客户端的资源最后发生修改的时间,下次客户端发送请求就会把这个时间作为If-Modified-Since
字段发送,来和服务器资源最后修改时间对比判断资源是否发生了修改,但时间粒度最小为秒,如果同一时间点多个请求一起操作了数据,就会导致数据缓存出现偏差 -
Etag/If-None-Match
中的Etag
则是一个代表资源的字符串,当资源发生改变时,它也会发生变化,客户端和服务端则通过对比字符串是否相同来判断资源是否发生了变化,Etag/If-None-Match
的优先级高于Last-Modified/If-Modified-Since
使用强缓存和重新请求状态码都是200,使用协商缓存状态码是304
TCP
可靠性连接
校验和:在数据传输的过程中,将发送的数据段都当做一个16位的整数,将这些整数加起来。并且前面的进位不能丢弃,补在后面,最后取反,得到校验和,如果接收方比对校验和与发送方不一致,那么数据一定传输有误,但是如果接收方比对校验和与发送方一致,数据不一定传输成功
序列号应答
超时重传
三握四挥
流量控制:
- 滑动窗口:在TCP协议的报头信息当中,有一个16位字段的窗口大小,窗口大小的内容实际上是接收端接收数据缓冲区的剩余大小这个数字越大,证明接收端接收缓冲区的剩余空间越大,接收端会在确认应答发送ACK报文时,将自己的即时窗口大小填入,并跟随ACK报文一起发送过去,而发送方根据ACK报文里的窗口大小的值的改变进而改变自己的发送速度,如果接收到窗口大小的值为0,那么发送方将停止发送数据,并定期的向接收端发送窗口探测数据段,让接收端把窗口大小告诉发送端
拥塞控制:
- 慢开始:刚开始建立连接的时候,发送窗口大小为1,然后逐步增加窗口的大小,如每次加倍
- 拥塞避免:当发送窗口达到一个门限值之后,窗口大小不再每次加倍,而是每次+1,减缓窗口增大速度
- 快重传: 快重传算法规定,发送方只要一连收到三个重复确认就应当立即重传对方尚未收到的报文段,而不必继续等待设置的重传计时器时间到期,这是因为此时网络状况良好,可以立即重传
- 快恢复:执行“乘法减小”算法将发送窗口门限值减半,以门限值为起点(而非0值),然后执行拥塞避免算法
三次握手、四次挥手
三次握手
TCP
三次握手的核心通俗点来说就是确认对方能否收到我发送的消息
- 客户端向服务端发送请求连接
- 服务端收到消息后对其作出回应给客户端
- 客户端收到回应后再给服务端发送确认消息
站在客户端的立场上,它在第一次中给服务端发送了消息,第二次中收到了回应,代表已经确认了服务端能收到客户端发送的消息,并在第三次对此作出了回应
站在服务端的立场上,它在第二次中给服务端发送了消息,第三次中收到了回应,代表已经确认了客户端能收到服务端发送的消息,如果缺少第三次握手,服务端就不能确认自己第二次发送的响应消息是否被客户端接收,假设没有被接收到,但服务端并不知道,它只会继续传输数据,而数据也无法被接收
四次挥手
-
客户端向服务端发送请求断开连接
-
服务端收到消息后向客户端作出响应:服务端已经收到消息,但需要判断资源是否都传输完成,传输完成后则会给客户端发送第二次回应
-
服务端已经处理完资源传输,向客户端发送可以断开连接了的消息
-
客户端对第三次挥手作出回应,并进入等待时间,服务端收到消息后断开连接
客户端进入等待原因
如果客户端的第四次挥手服务端迟迟没有接收到,服务端就会重新进行第三次挥手,而如果客户端已经断开连接了,就无法进行第四次挥手了,所以客户端会进入等待时间,如果重新收到了第三次挥手,就会重新进行第四次挥手,并重新计时等待
如果已经建立了连接,但是客户端突然出现故障了怎么办
客户端如果出现故障,服务器不会一直等下去,TCP
设有一个保活计时器,服务器每收到一次客户端的请求后都会重新复位这个计时器,时间通常是设置为2小时,若两小时还没有收到客户端的任何数据,服务器就会发送一个探测报文段,以后每隔75秒钟发送一次。若一连发送10个探测报文仍然没反应,服务器就认为客户端出了故障,接着就关闭连接
序列号
SYN
代表两端主动握手序列号,值为1
ACK
代表响应序列号,值为1
FIN
代表两端主动挥手序列号,值为1
seq
代表请求标识号,初始值为x
代表的未知数,会根据上次seq
请求+1
ack
代表响应对应seq
,值为seq
+1
对比UDP
-
TCP
是面向连接的,UDP
面向报文的 -
TCP
只能是1对1的,UDP
支持1对多 -
TCP
的首部较大为20字节,而UDP
只有8字节 -
TCP
可靠性强,而UDP
获取到数据后就开始传输,不保证顺序,也不管丢包,没有重传机制,也不会因为网络拥塞做出调整
数据加密
对称加密:双方采用相同的秘钥加密解密
非对称加密:双方加密后只有对方的私钥可以解密
混合加密:参考HTTPS
DNS缓存查询
浏览器缓存=>系统缓存=>host
文件=>(递归查询)本地域名服务器=>(迭代查询)根域名服务器
安全
XSS
XSS
为跨站脚本攻击,攻击者将恶意代码植入到js
脚本文件后,导致用户会在操作时会额外执行一些恶意代码
类型:
- 反射型:比如搜索栏,将问题代码通过
URL
直接传给后端,后端不处理拼接在html
中返回后,额外执行错误代码,较多出现在以前的mpa
渲染里 - 存储型:比如评论区,错误代码保存在数据库中,其他用户都能看到错误代码
dom
型:不与服务端交互,直接显示在页面上的,属于前端问题,比如输入什么就显示什么都气泡
防范:
- 对用户的输入进行检查过滤
- 设置
cookie
的httponly
来禁止js
脚本访问cookie
- 设置
cookie
的secure
属性仅在请求为https
的时候发送cookie
vue
、react
等框架渲染HTML
内容时都会将"'&<>
这几个字符进行转义处理,所以天然减少了XSS
问题
CSRF
CSRF
为跨站请求伪造,在缺陷网站攻击者利用用户在其他网站的登录凭证,以用户身份去其他网站提交操作,整个过程攻击者并不能获取到受害者的登录凭证,仅仅是冒用
- 防范
-
验证
HTTP
Referer
字段 -
使用
token
,token
不放在cookie
里就不会自动携带 -
在
HTTP
头中自定义属性并验证 -
设置
Cookie
的Samesite
-
(浏览器默认的同源策略限制)
HTTP
简单请求和非简单请求
同时满足以下两大条件,就属于简单请求
请求方法是HEAD
、GET
、POST
之一
HTTP的头信息不超出以下几种字段:
-
Accept
-
Accept-Language
-
Content-Language
-
Last-Event-ID
-
Content-Type
:只限于三个值application/x-www-form-urlencoded
、multipart/form-data
、text/plain
其余则为复杂请求
非简单请求的CORS
请求,会在正式通信之前,会增加一次option
预检请求,预检请求会包含浏览器通信将使用的信息,对应响应报文里会有预检请求的有效相对时间
请求方式
请求方式本质上并无区别,产生限制的原因不是HTTP
协议,而是浏览器
OPTION
用于试探连接HEAD
用于确认连接后对响应头参数进行确认,发送请求但不获取响应体GET
有请求大小限制,一般在2-8k,POST
没有限制
HTTP1.0
HTTP1.0
中规定浏览器和服务器只保持短连接,每次请求完成后都会断开TCP
连接,在下次请求时再重新与服务器建立TCP
连接
HTTP1.1
-
HTTP1.1
中支持了长连接,在一个TCP
连接上可以发送多个请求和响应 -
管道技术,不用等上次请求结果返回,就能发送下次请求,客户端可以同时开启多达6个
TCP
连接,在客户端并行传输,服务端串行按传输顺序接收并返回结果,如果有请求处理很慢就会造成队头堵塞 -
支持
host
域来区分同一IP的不同主机,且请求消息中如果没有host
域会报错(在HTTP1.0
中认为每台服务器都绑定一个唯一IP地址,但随着虚拟主机技术的发展,在一台物理服务器上可以存在多个虚拟主机,并且它们共享一个IP地址) -
支持断点续传
-
新增了一些请求方法(
PUT
、DELETE
等)、请求头和响应头(cache-control
、Etag/ If-None-Match
等)
HTTP2.0
-
将数据包拆分成了更小的信息或帧,对它们进行二进制编码后传输(之前为文本格式)
-
多路复用(所有的请求通过一个
TCP
连接传输,浏览器和服务器可以同时发送多个请求和响应,不用等下次响应再发送请求,减少了服务器压力,提升了访问速度,但每个数据包仍需一个一个处理,如果丢包导致数据不完整仍然会产生队头阻塞问题) -
服务器推送
-
可以指定数据的优先级
-
由
HTTPS
升级而来,保证了安全性 -
消息头压缩(消息头带有大量冗余信息)
HTTPS
-
是以安全为目标的
HTTP
通道,它基于HTTP
设置了ssl/tls
协议 -
HTTPS
的ssl
协议需要额外增加性能开销和金额开销,功能越强大,证书费用也越高 -
HTTP
协议的端口为80,HTTPS
的端口为443 -
ssl
协议过程:客户端向服务端发送协议版本号、可接受加密方式建立ssl
连接,服务端收到客户端请求后将公钥发送给第三方安全证书机构,第三方机构对申请者的信息用私钥做数字签名,然后将该签名和服务端公钥绑定成为公钥证书,服务端将证书发送给客户端,客户端用浏览器内置的第三方认证机构的公钥解密验证身份,选择加密等级并生成随机密钥,用服务端传递的公钥加密后发送给服务端,服务端用自己的私钥解密后获得传输密钥,双方使用传输密钥加密通信 -
中间人攻击是指中间人获取到了服务端的公钥,并用自己的密钥替换公钥发给客户端,客户端用中间人密钥加密后发送给客户端被中间人解密获取到之后通信的私钥,所以
HTTPS
使用了第三方认证对提供公钥的来源做出身份验证来保证安全 -
身份验证和密钥交换阶段通过非对称加密、数字签名、ca证书来保证身份安全性,通信阶段通过对称加密保证安全,摘要算法保证消息完整性
HTTP3.0
-
HTTP2.0
虽然已经极大改善了很多问题,但仍然存在部分问题,因为一直到HTTP2.0
,都是基于TCP
协议来实现的,HTTP2.0
解决了HTTP
因为需要顺序接收导致的队头阻塞问题,但并没有解决HTTP
的阻塞问题,在TCP
传输过程中会把数据拆分成一个个数据包按序传输接收,如果有丢包导致数据不完整,就会产生TCP
的队头阻塞,面对这个问题,如果对TCP
协议进行修改,或者使用新的协议都会因为需要对中间设备更新的巨大影响导致一个协议僵化的问题,所以UDP
就成了新的载体,基于UDP
使用QUIC
技术改造,就是HTTP3.0
的产生 -
实现了类似
TCP
的流量控制,可靠性传输 -
使用了
tls
协议 -
实现了基于
udp
的快速握手功能 -
支持多路复用,且在同一物理流上可以有多个独立的数据流,解决了
tcp
队头阻塞的问题 -
但目前支持度较低,设备优化性能较差
状态码
- 200(成功):请求已成功,请求所希望的响应头或数据体将随此响应返回
- 201(已创建):请求成功并且服务器创建了新的资源
- 202(未处理):服务器已经接收请求,但尚未处理
- 203(非授权信息):服务器已成功处理请求,但返回的信息可能来自另一来源
- 204(无内容):服务器成功处理请求,但没有返回任何内容
- 205(重置内容):服务器成功重置内容
- 206(部分内容):服务器成功处理了部分请求
- 301(永久重定向):请求的网页已永久移动到新位置,服务器返回此响应(对
GET
或HEAD
请求的响应)时,会自动将请求者转到新位置 - 302(临时重定向):服务器目前从不同位置的网页响应请求,但请求者应继续使用原有位置来进行以后的请求
- 303(查看其他位置):请求者应当对不同的位置使用单独的
GET
请求来检索响应时,服务器返回此代码 - 304(使用缓存):资源未修改,进行缓存访问
- 305 (使用代理):请求者只能使用代理访问请求的网页,表示请求者应使用代理
- 307 (临时重定向):和302相似,但用于
get
请求 - 400(错误请求): 服务器不理解请求的语法
- 401(未授权): 请求要求身份验证( 对于需要登录的网页,服务器可能返回此响应)
- 403(禁止): 服务器拒绝请求
- 404(未找到): 服务器找不到请求的网页
- 405(方法禁用): 禁用请求中指定的方法
- 407(代理身份): 请求要求代理的身份认证
- 408(请求超时): 服务器等候请求时发生超时
- 500(服务器内部错误):服务器遇到错误,无法完成请求
- 501(尚未实施):服务器不具备完成请求的功能,(服务器无法识别请求方法时可能会返回此代码)
- 502(错误网关): 服务器作为网关或代理,从上游服务器收到无效响应
- 503(服务不可用):服务器目前无法使用(由于超载或停机维护)
- 504(网关超时):服务器作为网关或代理,但是没有及时从上游服务器收到请求
- 505(
HTTP
版本不受支持):服务器不支持请求中所用的HTTP
协议版本
Socket、Websocket、Socket.io
Socket
socket
是两个应用程序实现全双工相互通信(双向同时发送信息)的方式,在传输层通过两端创建不同的socket
实例,一个建立连接,一个监听端口,建立TCP
连接实现双向通讯
webSocket
websocket
是HTML5
规范中的一个部分,为web
应用程序客户端和服务端之间提供了一种全双工通信机制,同时它是一种应用层协议,它虽然和HTTP
关系不大,但也不能完全脱离HTTP
,在创建websocket
实例绑定服务器地址后,会发送一个类似HTTP
的报文,里面有websocket
的核心:
GET /chat HTTP/1.1
Host: server.example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==
Sec-WebSocket-Protocol: chat, superchat
Sec-WebSocket-Version: 13
Origin: http://example.com
Socket.io
socket.io
是将WebSocket
、AJAX
和其它的通信方式全部封装成了统一的通信接口,兼容所有浏览器,解决浏览器不兼容问题,socket.io
包含websocket
数据交互
请求头
请求头一般分为三个部分,可接收的信息,用户信息,缓存信息
跨域
跨域是指在前后端分离架构中,浏览器请求后端服务器的资源受到了浏览器同源策略(协议、端口、域名(主域名和各子域名所对应的主机))的限制(请求仍然发出,但响应受到浏览器拦截),同源策略主要是为了防止CSRF
和对用户信息做出限制,在浏览器可能会保存着用户的登录信息(cookie
等),如果没有限制,那所有cookie
会被随意携带至各个网站,如果是恶意网站就会窃取用户信息,安全隐患很大
jsonp
这种方式原理是利用了script
标签可以不受同源策略影响,从不同域名下加载资源,具体实现为使用script
标签将数据和回调函数名作为参数发送给服务端,然后服务端根据数据处理结果,然后将结果传入回调函数,以字符串形式返回给浏览器,字符串会被自动解析为函数执行,但这种方法只能用于get请求
<script type="text/javascript" src="http://www.mian.com/jsonp.js?data=mian&callback=callback">
postMessage
和iframe
配合使用较多,通过获取需要接收数据的window
对象上的postMessage
API发送数据,在接收对象里通过监听message
事件接收数据
otherWindow.postMessage(message, targetOrigin, [transfer]);
- 跨域资源共享(
CORS
)
通过服务端给请求设置响应头来解决跨域,让浏览器接收响应
app.use(async (ctx, next)=> {
ctx.set('Access-Control-Allow-Origin', '*')//允许访问资源的浏览器地址源
ctx.set('Access-Control-Allow-Headers', 'Content-Type, Content-Length, Authorization, Accept, X-Requested-With , yourHeaderFeild');//允许访问资源的请求头
ctx.set('Access-Control-Allow-Methods', 'PUT, POST, GET, DELETE, OPTIONS');//允许访问资源的请求方法
ctx.set('Access-Control-Allow-Credentials',true)//CORS请求默认不发送Cookie和HTTP认证信息,设置后允许传递
})
- 代理跨域
同源策略是浏览器的安全策略,所以服务器向服务器请求数据不会受影响,常用的方式有
webpack
基于node
平台直接配置生成本地服务器转发- 使用
node
和web
框架搭建服务器转发 Nginx
反向代理
server {
listen 80;
location / {
root /var/www/html;
index index.html index.htm;
try_files $uri $uri/ /index.html;
}
location /api {
proxy_pass HTTP://127.0.0.1:3000;
proxy_redirect off;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
websocket
websocket
是HTML5
新增的协议,实现了浏览器和服务器的双向通信,同时也允许跨域
AJAX
ajax
意思为异步通信,用于前后端分离架构中交换数据
XMLHttpRequest
原生XMLHttpRequest
实现
-
创建
Ajax
的核心对象XMLHttpRequest
对象 -
通过
XMLHttpRequest
对象的open()
方法与服务端建立连接 -
构建请求所需的数据内容,并通过
XMLHttpRequest
对象的send()
方法发送给服务器端 -
通过
XMLHttpRequest
对象提供的onreadystatechange
事件监听服务器端你的通信状态 -
接受并处理服务端向客户端响应的数据结果
-
将处理结果更新到
HTML
页面中
xhr=new XMLHttpRequest()
xhr.onreadystatechange=function()
{
if (xhr.readyState==4 && xhr.status==200)
{
}
}
xhr.open("GET","http://www.mian,com",true)
xhr.send()
axios
是一个常用的基于promise
请求库,它本质上也是对XMLHttpRequest
的封装,它可以拦截请求响应,自动转换json
数据,支持async/await
语法,支持并发请求(all/spread>>>promise.all)
,axios
在浏览器封装XMLHttpRequest
实现,在node
里使用http
模块封装实现
fetch
fetch
是HTML5
增加的一种api
,它也是基于promise
语法的,但它是原生js
对象,并不是XMLHttpRequest
的封装,它更接近底层,fetch
默认不携带cookie
,且只能识别网络错误,400、500状态码都会当做正确请求
fetch(url, {
method: 'POST',
body: JSON.stringify(data),
headers: new Headers({
'Content-Type': 'application/json'
})
}).then(res => res.json())
.catch(error => console.error('Error:', error))
.then(response => console.log('Success:', response))
请求中断
XMLHttpRequest
有个abort
方法可以直接阻断请求fetch
自己没有中断方法,但可以通过AbortController
中断
const controller = new AbortController()
const signal = controller.signal
fetch('https://slowmo.glitch.me/5000', { signal })
controller.abort()
无论多优秀的请求库也需要根据实际使用进行二次封装
Cookie
字段
-
name
:cookie
的名称 -
value
:cookie
的值 -
domain
:可以访问此cookie
的域名 -
path
:可以访问此cookie
的页面路径 -
expires/Max-Age
:cookie
失效时间,不设置的话默认值是当浏览器关闭(不是浏览器标签页,而是整个浏览器) 后,此cookie
失效 -
Size
:cookie
大小 -
httponly
:若此属性为true
,则只有在HTTP
请求头中会带有此cookie
的信息,禁止document.cookie
来访问此cookie
-
secure
:设置是否只能通过HTTPS
来传递此条cookie
-
SameSite
:防止CSRF
攻击和用户追踪1.
Strict
:完全禁止第三方网站来源使用Cookie
请求数据,跨站点时,任何情况下都不会发送Cookie
2.
Lax
:规则稍稍放宽,大多数情况也是不发送,但是导航到目标网址的Get
请求除外3.
None
:关闭限制 -
Priority
:chrome
的提案,定义了三种优先级,Low/Medium/High
,当cookie
数量超出时,低优先级的cookie
会被优先清除
Cookie VS Token
-
cookie
、token
本质只是保存数据的字段名词,以下指基于它们的登录技术方案 -
用户登录成功后,会将登录信息在服务器保存为
session
并生成单独sessionID
,同时set-cookie
发送给客户端自动保存为cookie
,用户进行接口请求时,自动带上cookie
,在服务器进行验证,退出登录或超时清除session
和cookie
,但会受到分布式集群影响(因为负载均衡导致处理和保存的服务器并不一样) -
用户登录成功后,获取服务端发送的
token
,这个token
只需要存在客户端,用户进行任何操作时,都需要带上一个token
,服务器在收到数据后进行解析获取用户登录信息,token
只需要存在客户端(解决了分布式问题),token
传递可以放在Authorization
请求头或请求体或Webstorage
里(解决安全问题),当需要及时清除使用sessionStorage
,需要持久化存储使用localstorage
,并加入有效时间戳,下次验证失效后及时清理 -
cookie
受跨域影响,token
完全由应用管理,所以它可以避开同源策略,可以避免CSRF
攻击,可以在多个服务共享 -
cookie
需要服务端保存,是牺牲空间换时间,token
需要服务端解析,是牺牲时间换空间
跨域影响
在发送一个HTTP
请求的时候,携带的cookie
是这个HTTP
请求域的地址的cookie
,cookie
受跨域影响并不是指所有cookie
都无法发送,而是指每个不同源下只能发送该源下的cookie
,不同源之间不能共享,顶级域名只能获取到domain
设置为顶级域名的cookie
,domain
设置为如二级域名的无法获取,次级域名能获取到所有上级域名的cookie
,无法获取到同级或下级域名的cookie
,如果需要跨域发送cookie
,则需要设置XMLHttpRequest
的withCredentials
和响应头的ACAW
为true
webStorage
-
webStorage
只能存入字符串,无法直接存对象 -
sessionStorage
仅在当前浏览器窗口关闭前有效,localStorage
始终有效,窗口或浏览器关闭也一直保存(如果想让localStorage
信息过期失效,可在其内存入持续时间) -
sessionStorage
不会在不同的浏览器窗口共享,即使是同一个页面,而localStorage
和cookie
在所有同源窗口都是共享的 -
localstorage
可以用来在页面传递参数,sessionstorage
可以用来保存一些临时的数据 -
cookie
会始终在请求中携带,而webStorage
不会主动将数据发送给服务端
框架
虚拟DOM
虚拟DOM
本质上就是一个描述DOM
结构的js
对象,由多个虚拟节点js
对象组成
对比真实DOM
-
虚拟
DOM
不会进行排版与重绘操作,而真实DOM
会重排与重绘 -
对比直接渲染真实
DOM
在首次加载时需要多一次虚拟DOM
计算 -
虚拟
DOM
的快只是相对于直接使用innerHtml
这种方法全局大量无意义渲染导致频繁重绘重排的快 -
虚拟
DOM
优点在于能明确获取到哪个节点需要如何更新,避免使用innerHtml
这种无意义的更新,更新速度并比不上真实DOM
,如果能精确知道哪个节点需要如何改变,则虚拟DOM
刚好比真实DOM
多一次对比过程 -
在框架里使用虚拟
DOM
比起手动去操作真实DOM
操作来说更加简单方便 -
虚拟
DOM
为框架提供了跨平台能力,理论上我们可以生成虚拟DOM
后移植到任何平台上(node
,移动端)
vue对比react更新生成虚拟dom的粒度
-
vue
会精确根据数据变化通知订阅者的知道哪些组件需要重新render
生成虚拟dom
,粒度较细(基于template
的约束性) -
react
并不关心哪个数据发生了变化(因为jsx
的灵活,数据改变难以做到类vue
精确render
),setState
会导致该组件里的状态和全部子组件状态都重新渲染生成虚拟dom
,所以需要使用shouldComponentUpdate
等进行优化 -
生成虚拟
dom
后再进行diff
算法比较
渲染原理
vue
-
babel
通过compile
将模板生成一个渲染函数 -
渲染函数调用
render
生成虚拟dom
树 -
通过
patch
函数将虚拟dom
树渲染到真实的dom
上
react
-
编写
jsx
文件 -
Babel
转化为调用React.createElement
生成ReactElement
-
instantiaReactComponent
函数会将ReactElement
等转化生成虚拟dom
节点(ReactComponent
) -
调节器进行生成
Fiber
虚拟dom
树(有差异则Diff
后生成差异结果) -
渲染器将结果渲染成真实
dom
-
如果需要跨平台处理,则在生成虚拟
dom
树后用不同渲染器渲染成不同平台的结构
源码细节
-
React.createElement
接收type、config、children
三个参数,生成ReactElement
-
ReactElement
内部含type、key、context、props
四个关键属性,type
则用于标识组件的类型, 如果是字符串div
等,则表示组件对应的是一个DOM
对象,如果是函数组件,type
是函数名, 如果是类组件,type
是类名,ReactElement
相当于只是数据容器,它无法更改数据,因此我们需要将其传入工厂函数instantiaReactComponent
生成ReactComponent
,ReactElement
的props.children
也会依次递归传入该工厂函数 -
工厂函数
instantiaReactComponent
通过接收不同node
参数生成四种不同ReactComponent
:ReactDOMTextComponent(RTC)、ReactDOMComponent(RDC)、ReactCompositeComponent(RCC)、ReactDOMEmptyComponent(REC)
,如果node
是null
,则生成REC
,如果node
是数字或字符串,则生成RTC
,如果传入的node
是ReactElement
对象,则通过它的type
属性判断是普通DOM
元素还是自定义组件,由此分别生成RDC
和RCC
-
ReactComponent
里有四个重要方法:
construct
:用来接收虚拟dom
属性进行实例初始化mountComponent
:接收一个标志该元素的id
,并挂载到虚拟dom
树上unmountComponent
:是用来卸载内存中的虚拟dom
receiveComponent
:接收虚拟dom
的新属性状态,然后一般会调用updateComponent
方法来进行diff
更新
-
render
将ReactComponent
构成的虚拟dom
树渲染到真实dom
上
Template和JSX
-
vue
等采用了template
,让开发者具有约束性的去编写模板,对代码进行了静态和动态分类(静态标记和静态提升),数据依赖改变时,编译器能很好理解区分,则能以更小粒度去处理更新渲染,提高了渲染性能 -
jsx
具有JavaScript
的完整表现力,非常灵活,可以构建功能结果非常复杂的组件,但是灵活的语法,也意味着引擎更难以理解(template
里只有指令导致的渲染结构不同,但jsx
在复杂组件里因为js
的灵活性(如map、filter
等API
)可能出现极其复杂的结构,每个状态变化的可能导致渲染出的结构也大不相同),数据依赖改变时,难以做到类vue
精确render
和性能优化,所以每次更新render
全部数据依赖有关组件以及子组件,同时提供了React.memo
等一系列方法来让开发者来自己进行优化render
Diff
vue
-
同级对比,深度优先
-
由新
dom
和老dom
的双端的双向指针依次从两端向中间对比,相同节点在两端直接复用,在中间则移动到最前端后复用:
- 如果新老
dom
前端和前端相同或后端和后端相同则直接同时索引+1或-1 - 如果老
dom
前端和新dom
后端相同或新dom
前端和老dom
后端相同,两端索引变化后需要将节点移动到最前或最后 - 如果新
dom
对应key
在中间,则对比节点,如果相同,则将该节点移动到最前端,然后新老dom
索引同时+1
react
-
tree
级和component
级:同级比较,只有增加和删除操作,tree
级里如果构建tree
的组件结构发生了变化或component
级组件种类发生了变化,出现不同直接删除,创建新的 -
node
级:拥有插入、移动、删除三种操作,可以根据key
值作为唯一标识符,优先比较key
:
- 遍历新
dom
,依次用新dom
里的节点与原dom
里对应的节点对比,lastindex=0
代表已遍历的原dom
上最大索引号,老节点会拥有一个_mountIndex
属性代表节点在原dom
上的索引位置,nextIndex
代表当前遍历位置 - 如果有新节点就添加,并更新
_mountIndex = nextIndex
- 如果
_mountIndex > lastindex
,节点不移动,将lastindex = _mountIndex
,更新节点_mountIndex = nextIndex
- 如果
_mountIndex < lastindex
,进行移动操作enqueueMove(this, child._mountIndex, toIndex)
,根据_mountIndex
判断插入位置,将节点移动到新dom
里的位置(toindex
),更新_mountIndex = nextIndex
- 整体遍历老集合中节点,看有没有没用到的节点删除
Diff里的key
优点
- 方便对节点进行精确的渲染,提高diff效率,但更重要的是保证数据唯一性
缺点
- 使用随机值不会提高效率,使用
index
可能提高效率,也可能降低,还可能导致节点渲染错误 - 如果说只是简单文本节点内容改变了或者顺序改变了,节点结构并未改变,其实不写
key
反而性能和效率更高,主要是因为不写key
是将所有的文本内容替换一下,节点不会发生变化,而写key
则涉及到了节点的增,删,移动操作,发现旧key
不存在了,则将其删除,新key
在之前没有,则插入,由于dom
节点的开销是比较昂贵的,没有key
的情况下要比有key
的性能更好
<div key='1'>1</div> <div>1</div>
<div key='2'>2</div> <div>2</div>
<div key='3'>3</div> <div>3</div>
//节点内容改变
<div key='4'>4</div> <div>4</div>
<div key='5'>5</div> <div>5</div>
<div key='6'>6</div> <div>6</div>
<div key='1'>1</div> <div>1</div>
<div key='2'>2</div> <div>2</div>
<div key='3'>3</div> <div>3</div>
//顺序改变
<div key='2'>2</div> <div>4</div>
<div key='3'>3</div> <div>5</div>
<div key='1'>1</div> <div>6</div>
路由管理
前端路由描述了URL
与UI
之间的映射关系,这种映射是单向的,本质即监听URL
变化引起UI
更新,而不导致浏览器刷新,进行DOM
操作来模拟页面跳转
无论是vue
还是react
等框架里都有两种路由模式,hash
模式和history
模式,它们都保证了可以实现页面不刷新跳转,直面区别就是url
里的hash
模式有#,history
模式没有
实现原理
hash
模式:通过监听window
的hashchange
事件实现history
模式:通过HTML5
的history
的API
(pushState
、replaceState
),监听popstate
事件或订阅发布实现
组件通信
- 父子通信
- 共同父组件传值
- 跨级传值
- 订阅发布
- 状态管理插件库
Immutable
Immutable.js
是一个完全独立的库,无论基于什么框架都可以用它
对 Immutable
对象的任何修改或添加删除操作都会返回一个新的 Immutable
对象,Immutable
实现的原理是持久化数据结构:
- 用一种数据结构来保存数据
- 当数据被修改时,会返回一个对象,但是新的对象会尽可能的利用之前的数据结构而不会对内存造成浪费
也就是使用旧数据创建新数据时,要保证旧数据同时可用且不变,同时为了避免 deepCopy
把所有节点都复制一遍带来的性能损耗,Immutable
使用结构共享
如果对象树中一个节点发生变化,只修改这个节点和受它影响的父节点,其它节点则进行共享
插槽
-
插槽用于子组件里部分视图需要由父组件的数据状态控制,在子组件里展示,是一种减少了不必要传值的方式
-
vue
、svelte
里通过slot
实现,react
通过children
实现
ajax时机
ajax
时机一般放在拥有完整组件实例后vue
在created
后拥有完整数据实例(vue
数据实例可以与template
分离),react
在render
后拥有组件实例(react
的jsx
导致数据和视图结构不分离)- 所以
vue
里ajax
一般放在created
里,为了避免放在mounted
里会重复渲染出现闪动 - 而
react
里ajax
一般放在componentDidMounted
里,construct
里一般只用于数据初始化,这时还没有完整的组件实例,如果ajax
出现错误或超时,setState
和虚拟dom
可能会直接渲染失败,而render
后的生命周期就是componentDidMounted
React
事件机制
react
的事件机制:react
里的所有事件都会绑定在document
上进行代理,事件经过捕获冒泡再到document
阶段传入react
再派发到具体组件最后执行document
上绑定的事件react
事件执行顺序:原生dom
绑定的事件=>react
里事件绑定的事件=>document
上绑定的事件- 阻止合成事件间的冒泡,用
e.stopPropagation()
- 阻止合成事件与最外层
document
上的事件间的冒泡,用e.nativeEvent.stopImmediatePropagation()
懒加载
使用lazy
函数导入组件,Suspense
包裹组件,fallback
属性来实现loading
状态
受控和非受控组件
受控组件是将表单数据与react
状态绑定,通过onchange
事件获取用户数据,再由react
管理状态,也就是vue
里的双向绑定原理,非受控组件不受react
管理,直接获取dom
节点数据,进行操作
优化渲染
-
类组件里的生命周期
shouldComponentUpdate
和pureComponent
-
函数组件的
memo
,hooks
里的useCallback
和useMemo
错误边界
-
类组件用
componentDidCatch
捕获错误,获取错误状态信息进行日志上传、保存等,static getDerivedStateFromError()
改变state
状态来渲染替代页面 -
函数组件目前还没有对应
hooks
,官方正在开发
强制刷新视图
-
类组件用
this.forceUpdate()
-
函数组件没有对应
hooks
,可以使用一个标识符数据,每次需要刷新视图就改变这个标识符
类组件
生命周期
setState
-
在组件生命周期或
React
合成事件中,setState
导致的state
更新是异步;在setTimeout
或者原生dom
事件中,state
更新是同步 -
因为
react
对setState
的优化策略,可能将多次setState
导致的state
变化(根据参数纯函数变化)和render
延迟集中在一次,减少render
,这是基于react
内部有一个isBatchingUpdates
变量,通过这个变量来判断是否将setState
操作结果加入等待队列,默认为false
,不加入等待队列,直接render
同步更新,而通过react
合成事件和组件声明周期里的setState
会调用batchedUpdates
将这个变量改为true
,所以延迟异步render
后才能获取到更新后的数据 -
类组件调用
setstate
就会重新render
,而函数组件useState
函数set
与之前值相同的不会重新render
,但对比是浅比较,地址不变时也不会render
constructor
可以用来给组件注入props
,但无法注入context
PureComponent
React.PureComponent
会自动执行shouldComponentUpdate
,不过pureComponent
进行的是浅比较,如果是引用数据类型,只会比较是不是同一个地址
函数组件
官方部分hooks
-
useState
:保存需要立即刷新渲染的视图有关变量,负责组件状态保存和组件重新生成渲染 -
useEffect
:默认依赖为空数组时,当组件第一次生成时执行,之后根据依赖数组的数据变化执行,返回值函数在组件销毁时执行 -
useCallback
:缓存函数,监听数据来通知函数是否需要重新声明定义 -
useMemo
:缓存数据,监听数据,类似计算属性 -
useRef
:类似createref
,不同的是可以定义初始默认值,保存不需要立即刷新视图的基础变量 -
useContent
:使用createcontnt
创建传值对象后,使用对象的provide
属性标签value
提供数据,使用useContent
传入对象获取其提供的值对象(或将对象赋值给类组件的contentType
静态属性,使用this.content
),用于跨组件传值 -
useReducer
:创建一个迷你版的状态管理工具使用,类似vue
里的observer
和reactive
,为useState
底层实现 -
useLayoutEffect
:和componentDidMount
、componentDidUpdate
触发时机一致,useLayoutEffect
会在render
生成DOM后浏览器渲染前执行,阻塞浏览器真实dom
渲染,所以可能有些许卡顿,而useEffect
会在渲染真实dom
后执行,所以函数里如果操作dom
,useEffect
可能会有闪屏
hooks使用注意
-
hooks
返回值为数组是而不是对象,是因为解构赋值时数组可以重命名函数 -
使用函数组件时,该函数组件每次调用里面的
hooks
必须以同一顺序完整执行 -
不使用过时变量,如果函数依赖上次输出则使用纯函数参数
-
定时器里的变量记得及时清除或刷新
hooks原理
hooks
是函数组件用来保存状态或挂在组件状态变化时执行的函数,函数组件里初次渲染的hooks
状态会挂在fiber
节点上用单链表next
连接,每次setState
改变数据后更新视图都会对比hooks
状态是否发生了变化来判断数据状态是否更新,再根据数据状态变化调用useEffect
函数,同时用一个标识区别不同hooks
状态,标识是基于hooks
的定义顺序,所以要求每次执行hooks
顺序不变,也无法在条件语句中使用
并发组件
-
Suspense
组件实现组件渲染时,渲染fallback
替代模板组件 -
使用
useTransition
设置加载时间,包裹异步需要setState
的函数并行执行、延迟渲染,利用pending
准备状态渲染替代模板组件,当加载时间结束直接替换新模板
都是为了提高用户体验,减少白屏和卡顿
Fiber架构
type Fiber = {
// 用于标记fiber的WorkTag类型,主要表示当前fiber代表的组件类型如FunctionComponent、ClassComponent等
tag: WorkTag,
// ReactElement里面的key
key: null | string,
// ReactElement.type,调用`createElement`的第一个参数
elementType: any,
// The resolved function/class/ associated with this fiber.
// 表示当前代表的节点类型
type: any,
// 表示当前FiberNode对应的element组件实例
stateNode: any,
// 指向他在Fiber节点树中的`parent`,用来在处理完这个节点之后向上返回
return: Fiber | null,
// 指向自己的第一个子节点
child: Fiber | null,
// 指向自己的兄弟结构,兄弟节点的return指向同一个父节点
sibling: Fiber | null,
index: number,
ref: null | (((handle: mixed) => void) & { _stringRef: ?string }) | RefObject,
// 当前处理过程中的组件props对象
pendingProps: any,
// 上一次渲染完成之后的props
memoizedProps: any,
// 该Fiber对应的组件产生的Update会存放在这个队列里面
updateQueue: UpdateQueue<any> | null,
// 上一次渲染的时候的state
memoizedState: any,
// 一个列表,存放这个Fiber依赖的context
firstContextDependency: ContextDependency<mixed> | null,
mode: TypeOfMode,
// Effect
// 用来记录Side Effect:需要因为props更新的子树节点
effectTag: SideEffectTag,
// 单链表用来快速查找下一个side effect
nextEffect: Fiber | null,
// 子树中第一个side effect
firstEffect: Fiber | null,
// 子树中最后一个side effect
lastEffect: Fiber | null,
// 代表任务在未来的哪个时间点应该被完成,之后版本改名为 lanes
expirationTime: ExpirationTime,
// 快速确定子树中是否有不在等待的变化
childExpirationTime: ExpirationTime,
// fiber的版本池,即记录fiber更新过程,便于恢复
alternate: Fiber | null,
}
-
fiber
本质为新数据结构,虚拟dom
树变成了fiber
节点树,每个fiber
节点记录着元素节点的信息,fiber
节点上memoizedState
保存着hooks
状态单向链表的起始点,updateQueue
存放着需要更新的数据,全部更新完成后生成新的memoizedState
,当更新生成新的fiber
节点树时,这颗树每生成一个新的fiber
节点,都会将控制权交回给主线程,去检查有没有优先级更高的任务需要执行 -
生成虚拟
dom
阶段,生成Fiber
树,得出需要更新的节点信息,这一步是一个渐进的过程,可以被打断,而diff
阶段,将需要更新的节点一次过批量更新,这个过程不能被打断
基于这个新结构,react
也在功能上做出了调整:
-
浏览器渲染线程执行任务时会以帧形式划分,
Fiber
把渲染更新任务拆分成多个子任务,浏览器提供了requestIdleCallback
,支持在空闲期内回调执行任务,高优先级任务由requestAnimationFrame
执行,如动画等,避免了像之前一样如果递归dom
结构复杂导致每帧之间js
和渲染不均出现的卡顿问题 -
同时为每个增加了优先级,优先级高的任务可以中断低优先级的任务,然后再重新执行优先级低的任务,利用任务优先级高低,分别调度执行函数
-
React16
基于该原理实现了功能更完备的Scheduler(调度器)
synchronous
,与之前的Stack Reconciler
操作一样,同步执行task
,在next tick
之前执行animation
,下一帧之前执行high
,在不久的将来立即执行low
,稍微延迟执行也没关系offscreen
,下一次render
时或scroll
时才执行
VUE
生命周期
在Vue
生命周期钩子会自动绑定 this
上下文到实例中,因此你不能使用箭头函数来定义一个生命周期方法
- 创建实例
- 初始化数据
- 寻找挂载点
- 编译模板
- 模板挂载替换
- (数据修改更新和重新渲染)
- 文档的卸载和销毁
- 在这之间穿插了很多钩子函数
生命周期 | 描述 |
---|---|
beforeCreate |
执行时组件实例还未创建,通常用于插件开发中执行一些初始化任务 |
created |
组件初始化完毕,各种数据可以使用,常用于异步数据获取 |
beforeMount |
未执行渲染、更新,dom 未创建 |
mounted |
初始化结束,dom 已创建,可用于获取访问数据和dom 元素 |
beforeUpdate |
更新前,可用于获取更新前各种状态 |
updated |
更新后,所有状态已是最新 |
beforeDestroy |
销毁前,可用于一些定时器或订阅的取消 |
destroyed |
组件已销毁,作用同上 |
activated |
keep-alive 缓存的组件激活时 |
deactivated |
keep-alive 缓存的组件停用时调用 |
errorCaptured |
捕获一个来自子孙组件的错误时被调用 |
computed、watch、methods
-
methods
会在事件触发执行 -
watch
会在数据依赖改变时触发方法,异步函数中表现比计算属性更优秀,侧重点为监听数据依赖执行的函数 -
computed
会在数据依赖改变,触发get
方法后,才会重新计算,我们提供的是getter
方法,侧重点为数据依赖改变后生成新数据
mixin
-
当组件存在与
mixin
对象相同的选项的时候,进行递归合并的时候组件的选项会覆盖mixin
的选项 -
但是如果相同选项为生命周期钩子的时候,会合并成一个数组,先执行
mixin
的钩子,再执行组件的钩子
nextTick
-
下次
DOM
更新循环结束之后执行延迟,可以回调获取更新后的DOM
,使用回调函数或async/await
-
通过将回调任务传入
callbacks
,依次通过Promise.then
、MutationObserver
、setImmediate
、setTimeout
中的一种调用flushCallbacks
方法,将callbacks
里面的函数复制一份,同时callbacks
置空,依次执行callbacks
里面的函数
响应式原理和双向绑定
vue
实例会遍历其data
对象的所有属性,并在Observe
里使用Object.defineProperty
重写它们的getter/setter
(在vue3
中使用的为proxy
),组件实例的data
的每个key
都对应一个Dep
对象,由于data
的key
可能出现多次,所以每个Dep
需要管理多个Watcher
,之后当依赖项的setter
触发时,会先通知dep
,再通知其订阅的所有watcher
,从而使它关联的组件重新渲染
- 当
vue
数据发生变化,它更新dom
是异步的(代理方法为微任务),为了在数据变化之后等待Vue
完成更新DOM
,可以在数据变化之后立即使用Vue.nextTick(callback)
,这样回调函数将在DOM
更新完成后被调用
-
双向绑定在
vue2
中的数据劫持是通过Object.defineProperty
实现的,在vue3
里是通过proxy
实现的,原理是通过对对象属性的劫持代理,重写其get、set
方法,在数据改变时调用set
方法,改变视图层,监听视图层数据变化(onChange
事件),改变数据,在vue3
使用proxy
替换 -
Object.defineProperty
只能一次重写一个属性,而proxy
可以一次代理整个对象,这也就是vue2
中给数组和对象直接添加数据不生效的原因,因为新添加的数据没有被Object.defineProperty
劫持监听,使用Object.defineProperty
时需要额外创建一个对象,对源对象的操作会映射并实际操作在这个对象上(对源对象操作会产生死循环),使用proxy
则是对代理对象操作映射到源对象上
vue2.x
响应式原理对对象和数组的影响:
-
对象无法监听新增属性
-
无法监听数组通过赋值改变数组长度和或子元素的变化
-
但都可以通过拷贝对象替换键对应地址来修改
Eventbus
可以直接使用一个vue
组件实现,订阅发布不会改变原函数this
指向
图片引入
vue
在DOM
中直接引入的图片会默认被转为base64
格式的,但是使用变量引入的话,图片不会转为base64
格式的,所以不会正常显示,使用require
来引入图片
import imgUrl from "../assets/test.png"
data(){
return {
imgUrl:require(imgUrl)
}
}
vue3
v-for、v-if、v-show
v-if
和v-show
都会导致重排重绘,但v-if
会直接删除dom
节点,v-show
只是控制display
属性,vue3
版本中v-if
判断优先于v-for
,解决了vue2
中先渲染再判断的性能问题
组合式API
-
提高了代码逻辑复用性和开发效率
-
ref、reative
:用于响应化数据,ref
常用于基本数据类型,reative
常用于复杂数据类型 -
toRefs
:当使用扩展运算符对reactive
的对象进行展开时,是用变量拷贝了对象属性的值,拷贝的变量无法实现响应式,所以使用toRefs
转化整个对象再展开 -
watch、watchEffect
:watchEffect
会自动监听所有依赖,watch
需要手动设置,返回值为停止监听的函数
多根组件
基于fragment
传送组件
Teleport
:实现了组件状态在vue
组件里被其控制,但又根据to
改变渲染的位置,改良了在一些复杂项目里的组件通信难度
并发组件
Suspense
:实现组件异步渲染时,渲染替代模板组件,之前通过v-if
控制,Suspense
提供了template
的#default
和#fallback
控制
静态提升和静态标记
对改变过的组件添加flag
标记,下次发生变化时,直接首先对比该节点中,对不参与更新的元素,会做静态提升,只会被创建一次,在渲染时直接复用,减少了diff
递归对比过程,这样就免去了重复的创建节点,大型应用会受益于这个改动,免去了重复的创建操作,优化了运行时候的内存占用
Svelte
对比react、vue
react
,vue
都算是典型的runtime
框架,虽然它们也有少量的complier
,将jsx
和vue
文件的模板进行编译,但主要支持框架功能的包也会随之打包发送到浏览器端用于功能实现,但svelte
则把重心放在了编译层,在complier
就将框架和功能代码结合成js
,并把功能支持都转化成真实dom
的操作,它的简单包要比vue
小5倍,react
小20倍,但如果项目规模过大,整体代码变多,那么它这个优势也会变小,但不可否认,在小型项目中svelte
性能要比react
,vue
等框架性能更优秀,因为缺少svelte
处理大型复杂项目的案例,加上生态还不够特别完善,所以svelte
目前更适合简单小型项目
-
vue
和react
都是基于虚拟dom
的框架,但虚拟dom
并不代表一定高效,反而在react
里当组件变化时,可能会出现很多无意义的组件重新render
,虚拟dom
的优点在于其能准确知道哪个节点该如何渲染,保证了性能的下限,而当数据依赖变化时,如果能明确知道哪个节点该如何变化,直接使用真实dom
修改则比起vue
来说能刚好减少一次虚拟dom
的计算,更加高效,sevlte
就是基于真实dom
实现了响应式 -
它结合了类
jsx
语法和类vue
的单文件组件,语法简洁明了,开发效率高 -
框架自带全局状态管理
-
暂时
ts
支持性生态不算很好 -
原生支持
CSS scope
和CSS animation
,不需要像react
一样导入插件
对比vue基本语法
框架 | vue | Svelte |
---|---|---|
基本数据 | data | 直接声明 |
计算属性 | computed | $: |
数组、对象响应 | 既有包装过的方法,也可以直接整个替换 | 只能整个替换 |
数据传递 | props | export |
条件渲染 | v-if | {#if}({:else}){/if} |
多重渲染 | v-for=xx in xxs key=x | {# each xxs as xx (x)}{/ each} |
事件绑定 | @click | on:click |
事件修饰符 | .passive | |passive |
异步处理 | v-if+loading | {#await promise对象}{:then res结果}{:catch err错误}{/await} |
自定义事件 | @xxx+$emit | on:xxx+createEventDispatcher |
双向绑定 | v-model | bind:value |
延迟执行 | $nexttick | tick |
框架特色
-
如果自定义事件不给值,则自动向上转发
-
单个样式绑定简写:
class:类名={bolean}
-
<svelte:self>
代表自身组件 -
<svelte:component this={component}>
代表一个组件容器,this
来指定组件 -
<svelte:fragment>
空标签容器 -
<svelte:window>
代表window
对象
生命周期
onMount、onDestroy、beforeUpdate、afterUpdate
状态管理
-
writable
:保存可变数据 -
update
:用于更新值,参数为存储的数据 -
subscribe
:用于订阅数据变化,参数为新值,当数据依赖发生改变时调用,返回值为取消订阅的函数,防止内存泄漏 -
使用
$
符号简化为$data
代表订阅后的每次更新后的值,对$data
的操作会直接映射为updata
操作
响应式原理
- 在编译时就确定可能发生变化的数据,并对其数据进行处理,生成其数据依赖数组(采用32位的位掩码和数组存放)
- 将触发数据发生变化事件编译成调用
$$invalidate
方法,更新ctx
- 将变化依赖
make_dirty
标记为脏数据,将组件放入dirty_component
中 - 在下次
promise
微任务中遍历所有dirty_component
并调用p
方法将新ctx
有关的标签变量对象全部更新 - 重置
dirty
- 视图更新
Solid
- 采用类静态
jsx
语法 - 比起
sevlte
,使用innerhtml
代替了createElement
和appendchild
,提高了操作速度 - 响应式原理基于proxy订阅发布,当有数据依赖发生改变时,会依次修改依赖于该数据的
dom
,和react
不同,react
调用setState
改变数据依赖时会重新渲染全部生成虚拟dom
后diff
,而soild
调用set
方法只会把该数据依赖有关的dom
节点通过dom
操作进行修改
状态管理
flux模式
核心思想为数据和逻辑永远单向流动
组成:
dispatcher
负责分发事件store
负责保存数据,同时响应事件并更新数据view
负责订阅store
中的数据,并使用这些数据渲染相应的页面
Redux
- 单一数据源
- 状态只读性
- 分发不同种类的
action
对象通过纯函数来改变状态
源码组成
Redux
源码主要分为以下几个模块文件
compose.js
: 提供从右到左进行函数式编程createStore.js
:提供作为生成唯一store
的函数combineReducers.js
:提供合并多个reducer
的函数,保证store
的唯一性bindActionCreators.js
: 可以让开发者在不直接接触dispatch
的前提下直接通过actions
生成对应各个函数applyMiddleware.js
:这个方法通过中间件来增强dispatch
的功能
react里基本使用
1.createStore
传入reducer
(接收action
对象处理后生成新数据的纯函数)生成仓库对象
2.dispatch
不同类型的action
对象来改变仓库数据,getState
可以获取到仓库数据
3.导入redux-react
用provide
包裹根组件,绑定仓库数据
数据交互
- 类组件里使用
connect
关联UI组件和redux
仓库,第一次执行参数为映射仓库数据到组件数据的函数或对象,映射函数分别会接收state
和dispatch
返回一个映射对象,第二次执行参数为UI组件,所以可以直接使用装饰器语法关联,关联后就可以在组件props
里获取映射属性使用
connect(mapStateToProps, mapDispatchToProps)(AppUI)
- 函数组件使用
useSelector
、useDispatch
两个hooks
与store
交互,useSelector
参数函数直接将所需数据从state
上解构返回,useDispatch
调用直接返回dispatch
函数用于分发actions
中间件
-
createStore
的第二个参数可以通过applyMiddleware
使用中间件 -
middleware
原理为接收有Store
的getState
函数和dispatch
函数的对象,在中间件函数里对dispatch
函数进行处理加工或执行一些额外操作,然后再由reducer
处理 -
react-thunk
是把dispatch
里的普通action
对象换成了一个dispatch
为参数,异步分发action
对象的函数
Mobx
- 基于观察者模式和
object.defineproperty
- 生成
mobx
仓库类并创建实例 - 通过
observerable
包裹仓库数据 - 通过
action
包裹改变仓库数据的函数 - 通过
computed
包裹生成计算属性的函数 - 导出实例注入组件
- 通过
Provider
包裹根组件提供全部数据 - 通过
inject
给特定组件注入特定数据 - 通过
observer
包裹生成组件 - 用
useLocalStore
可以传入返回仓库对象实例的纯函数,其返回值可以在useObserver
或Observer
包裹中当作数据仓库使用,useObserver
或Observer
里通常是一个使用仓库数据的纯函数
对比Redux
-
redux
将数据保存在单一的store
中,mobx
将数据保存在分散的多个store
中 -
redux
使用不可变状态,这意味着状态是只读的,不能直接去修改它,而是应该返回一个新的状态,同时使用纯函数,mobx
中的状态是可变的,可以直接对其进行修改 -
redux
的编程范式是函数式的,而mobx
是面向对象的 -
因此数据上来说
Redux
理想的是immutable
的,每次都返回一个新的数据,而Mobx
从始至终都是一份引用,因此Redux
是支持数据回溯的 -
和
Redux
相比,使用Mobx
的组件可以做到精确更新,这一点得益于Mobx
的observable
Vuex
vue
官方推荐状态管理工具,组成部分:
Store
:Vuex
采用单一状态树,每个应用仅有一个Store
实例,在该实例下包含了state、actions、mutations、getters、modules
State
:Vuex
为单一数据源,可以将Store
注入全局之后使用this.$store.state
访问Getters
:Getters
作用类似计算属性,负责缓存仓库数据的派生状态Mutations
:Mutaions
是vuex
中改变State
的唯一途径(严格模式下),并且只能是同步操作,函数参数为State
,通过store.commit()
调用(如果使用其他方式修改仓库数据也能完成功能,但与vue
基于的flux
设计矛盾,且vue
只有通过Mutations
修改数据,才提供了方便调试的数据追踪)Actions
:一些对State
的异步操作可以放在Actions
中,并通过commit Mutaions
变更状态,函数参数为Store
,通过store.dispatch()
方法触发Modules
:当Store
对象过于庞大时,可根据业务需求分为多个Module
,每个Module
都具有自己的state、mutations、actions、getters
跨平台
移动端
发展
- 原生开发:使用
java
或koltin
语言开发安卓,oc
或swift
开发原生应用
- 混合开发:使用
HTML
和js
、css
通过容器层开发应用,通过webview
渲染视图层,js Bridge
调用原生方法
- 跨平台开发:它不使用
Web
技术,它的页面不是HTML5
页面,而是使用自己的dsl
编译生成native
组件,UI层通过虚拟dom
进行转化,逻辑层用自己的dsl
进行控制,中间使用桥接层通信,然后编译成各平台的原生App
混合开发和跨平台开发对比
- 以往最早的混合开发,主要依赖于
WebView
,但是WebView
是一个很重的控件,很容易产生内存问题,而且复杂的UI在WebView
上显示的性能不好 react-native
、week
等抛开了WebView
,利用JavaScriptCore
或v8来做桥接,将vnode
转为native
渲染,只牺牲了小部分性能
week
架构
Flutter
实现跨平台采用了更为彻底的方案,它既没有采用WebView
也没有采用JavaScriptCore
,而是自己实现了一台UI框架,然后直接系统更底层渲染系统上画UI,它采用的开发语言是Dart
,据称Dart
语言可以编译成原生代码,直接跟原生通信
PWA
PWA
全称Progressive Web App
,即渐进式WEB
应用,一个PWA
应用首先是一个网页, 可以通过Web
技术编写出一个网页应用
使用原理
- 生成
html
网页 - 配置移动端
manifest.json
文件,让PWA
应用被添加到主屏幕,使用manifest.json
定义应用的名称、图标等信息 - 添加
Service Worker
,Service Worker
在网页已经关闭的情况下还可以运行, 用来实现页面的缓存和离线,后台通知等等功能,配合cache Api
实现缓存刷新
优势
- 可以将
app
的快捷方式放置到桌面上,全屏运行,与原生app
无异 - 能够在各种网络环境下使用,包括网络差和断网条件下,不会显示
undefinded
- 推送消息的能力
- 其本质是一个网页,没有原生
app
的各种启动条件,快速响应用户指令
问题
- 支持率不高,现在
ios
手机端不支持PWA,IE也暂时不支持 Chrome
在中国桌面版占有率还是不错的,安卓移动端上的占有率却很低- 各大厂商还未明确支持
PWA
- 依赖的
GCM
服务在国内无法使用 - 小程序的竞争(因此其实国内
pwa
已经小烂了)
微信小程序
本质为迷你版网页
基本特点
-
使用双线程模型
-
webView
实现视图层,一个小程序可能存在多个界面,可能存在多个webView
线程 -
jsCore
运行js
代码实现逻辑层,通过native
层进行交互通信,通过js
桥调用原生API
-
在渲染层把
wxml
文件使用虚拟dom
js
对象表示,逻辑层数据改变时,通过native
转发数据变化生成新的虚拟dom
在视图层对比渲染
生命周期
总程序:
生命周期 | 说明 |
---|---|
onLaunch | 小程序初始化完成时触发,全局只触发一次 |
onShow | 小程序启动,或从后台进入前台显示时触发 |
onHide | 小程序从前台进入后台时触发 |
onError | 小程序发生脚本错误或 API 调用报错时触发 |
onPageNotFound | 小程序要打开的页面不存在时触发 |
onUnhandledRejection() | 小程序有未处理的 Promise 拒绝时触发 |
onThemeChange | 系统切换主题时触发 |
页面:
生命周期 | 说明 | 作用 |
---|---|---|
onLoad | 生命周期回调—监听页面加载 | 发送请求获取数据 |
onShow | 生命周期回调—监听页面显示 | 请求数据 |
onReady | 生命周期回调—监听页面初次渲染完成 | 获取页面元素(少用) |
onHide | 生命周期回调—监听页面隐藏 | 终止任务,如定时器或者播放音乐 |
onUnload | 生命周期回调—监听页面卸载 | 终止任务 |
组件:
生命周期 | 说明 |
---|---|
created | 生命周期回调—监听页面加载 |
attached | 生命周期回调—监听页面显示 |
ready | 生命周期回调—监听页面初次渲染完成 |
moved | 生命周期回调—监听页面隐藏 |
detached | 生命周期回调—监听页面卸载 |
error | 每当组件方法抛出错误时执行 |
类vue框架生成小程序原理
Electron
-
CSS
和JavaScript
来构建跨平台桌面应用程序的一个开源库,Electron
通过将Chromium
和Node.js
合并到同一个运行时环境中,并将其打包为Mac
,Windows
和Linux
系统下的应用 -
chromium
运行时有一个Browser Process
,以及一个或者多个Renderer Process
-
node.js
为应用提供了和window
平台交互的能力 -
性能比原生桌面应用要低,最终打包后的应用比原生应用大很多
后端数据服务器
请求代理
正向代理:代理客户端向服务端发送请求,可以突破客户端访问请求的某些限制,代理租客
反向代理:代理服务端,服务端通过负载均衡来分配到真实服务器,代理房东
负载均衡:由代理服务器判断哪个数据服务器空闲,将新请求转发给它,避免单个数据服务器压力过大
node
文件读取
-
readFile
异步读取文件内容,并存储在内存中,然后再传递给用户 -
createReadStream
使用一个可读的流,逐块读取文件,而不是全部存储在内存中 -
与
readFile
相比,createReadStream
使用更少的内存和更快的速度来优化文件读取操作,如果文件相当大,用户不必等待很长时间直到读取整个内容,因为读取时会先向用户发送小块内容,stream
流也可以直接通过pipe
传输
Path
path.resolve
如果是./或/直接拼接,如果是../向前拼接一位,如果是/则不拼接
Process
process.cwd()
:返回当前工作目录,如node
命令执行脚本时的目录
__dirname
:源代码所在的目录
Buffer
缓冲区,用于对数据进行缓冲,如果传输速度大于进程处理速度,则放入buffer
缓冲区等待
事件循环
-
定时器检测阶段(
timers
):执行setTimeout
、setInterval
里面的回调函数 -
I/O
事件回调阶段(I/O callbacks
):此阶段执行某些系统操作的回调,例如TCP
错误 -
闲置阶段(
idle
、prepare
):仅系统内部使用 -
轮询阶段(
poll
):获取新的I/O
事件, 例如操作读取文件等等,适当条件下node
将在这里阻塞 -
检查阶段(
check
):setImmediate()
回调函数在这里执行 -
关闭事件回调阶段(
close callback
):一些关闭的回调函数,如:socket.on('close', fallback)
stream流
-
Readable
:可读流 -
Writable
:可写流 -
Duplex
:继承可读流和可写流,为双工流 -
Transform
:继承双工流,作为可写流使用后会自动将写入内容保存在缓冲区并转化为可读流
多进程
Node.js
是以单线程的模式运行的,但它使用的是事件驱动来处理并发,这样有助于我们在多核 cpu
的系统上创建多个子进程,从而提高性能
Node
提供了child_process
模块来创建子进程,方法有三种:
exec
:直接在创建新进程时声明处理数据响应的回调函数spawn
:通过监听新进程的事件来获取响应数据并用回调函数进行处理fork
:与子进程之间拥有管道,可以直接将子进程的响应在主进程执行
多线程
worker_threads
模块允许使用并行地执行JavaScript
的线程- 与
child_process
或cluster
不同,worker_threads
可以共享内存,worker_threads
通过运行应用使用多个相互隔离的JavaScript workers
来实现并发,而workers
和父worker
之间的通信由Node
通过传输ArrayBuffer
实例或共享SharedArrayBuffer
实例来实现,它并未给JavaScript
本身带来多线程特性
const { Worker } = require('worker_threads')
const wk = new Worker(path)
go
数据库
MongoDB
1.连接数据库
2.创建新规则实例(schema
)
3.生成集合构造函数(model
)
4.创建集合实例(数据)保存或者文档插入(create
)
mysql
redis
算法思想
常见排序算法
-
冒泡:通过数组相邻比较,调换位置
-
插入:将未排序的数组数据依次在排序后数据里中从后向前找到合适位置并插入
-
选择:每次直接选择未排序数据的最大或最小值,插入排序后数据里
-
归并:分治思想,将原数组分为几个子数组先排序,排序结束后,每次对比所有子数组的最大值或最小值再选取极值插入新数组
-
快排:新建一个左数组和右数组,随机挑选原数组的一个值作为标准,其他值比较后分别放入左右数组,然后递归后拼接数组
function sort(arr) {
let lef = []
let rig = []
if (arr.length <= 1) {
return arr
}
let a = arr.slice(1)
let b = arr.shift()
a.forEach(item => {
if (item > b) {
lef.push(item)
}
if (item < b) {
rig.push(item)
}
}
)
return sort(lef).concat(b, sort(rig))
}
console.log(sort([1, 52, 5555, 111, 123]))
基本数据结构
-
数组:查找快,增删慢
-
栈:先进后出
-
队列:先进先出
-
优先级队列:进入队列后会根据优先级排序
-
链表:查找慢,增删快
链表反转:
function res(fir){
let cur=fir
let next=null
let pre=fir.next
while (pre){
cur.next=next
next=cur
cur=pre
pre=pre.next
}
cur.next=next
}
-
双向链表:双向查找,提高查找效率
-
哈希表:哈希表效率高原因,底层是数组,下标查找快,增删因为其他元素哈希值不变,不会影响下标,所以效率也高,普通数组会因为数组长度改变,下标改变受影响,所以效率慢,解决冲突常用两种方法:链地址法和开放地址法
-
树:二叉树
常见设计模式
-
单例模式:保证一个类仅有一个实例,并提供一个它的获取函数,获取时先判断实例存在与否,如果存在则直接返回已有实例,如果不存在就创建一个新实例返回(
vuex
的store
等) -
订阅者-发布者模式:订阅者订阅事件并设置回调,当事件产生时,发布者通知该事件所有订阅者,订阅者根据回调对事件做出响应(点击事件等)
-
代理模式:为对象提供一个代用品或占位符,以便控制对它的访问(
proxy
等) -
工厂模式:不关心内部的逻辑,通过传入不同的参数对象来返回不同的结果(
react
源码生成不同种类ReactComponent
等) -
装饰者模式:在不改变原对象基础上,给对象动态添加方法(
ES6
装饰器等)