前端学习笔记

个人前端学习笔记,不定时更新

计算机操作系统

进程、线程、协程

  • 进程是操作系统资源分配的基本单位,而线程是任务调度和执行的基本单位

  • 一个应用程序可以被设计为多进程,多进程的子进程会复用父进程的端口号

  • 多进程开销较大,通信较麻烦,但稳定性高,WindowsLinux系统多进程开销大

  • 多线程比多进程较快,开销较低,但任何一个线程挂掉都可能直接造成整个进程崩溃

  • 多核cpu支持多个线程同时执行

  • 多个线程是串行还是并行取决于该时间点是否有cpu处于空闲,单核cpu或该时间点无空闲cpu则生成多个时间切片串行,线程调度器切换线程不停执行

  • 多线程并行可能出现不同线程需要同一时间切片对同一资源进行操作,为了防止这种错误,会使用锁在资源被使用时加以限制

  • 多线程里串行却能提高效率的原因是,线程里的操作可能除了cpu计算还有其他IO等操作(任务可以分为计算密集型和IO密集型),切换线程让其他需要cpu操作的线程获得cpu资源

  • 线程里可以有多个协程,可以由函数实现,由开发者自己控制协程进行串行,在需要过多线程的情况下,增加线程里的协程数量,减少线程数量,这样可以减少锁的使用和过多线程自动切换开销,提高效率

不同语言通信

  • 协议统一数据格式(HTTP里的json
  • 共享内存指针
  • ABI(底层c通信)

编译原理

编译型语言和解释性语言

  • 从编译和执行时间来看,编译型语言会编译完成后再执行,所以运行效率很快,解释型语言会一边解析一边执行,所以速度较慢

  • 从编译结果来看,编译型语言会直接将源代码转化为机器码,而解释型语言需要将源代码编译转化为字节码后,再由虚拟机或解释器转为机械码执行

  • jspython等属于解释性语言,goc属于编译型语言

  • 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有性能瓶颈的场景中

alt

基本使用

  1. 编写源代码函数
  2. 使用编译器编译成wasm文件
  3. 利用如fetch等异步请求获取代码
  4. 返回结果解析成一个ArrayBuffer(缓冲区字节数组)
  5. WebAssembly.compile解析ArrayBuffer生成promise对象获取wasm模块
  6. new WebAssembly.Instance()接收wasm模块实例化成一个js模块对象,对象exports属性上保存着所有源代码方法可供js调用

对比js

  • wasm比起js来说文件代码更小
  • wasm已经是字节码,所以比起js进行编译和优化过程少
  • wasm是为编译器设计的
  • js有自动内存管理,但wasm一般是基于c++等手动管理内存语言编译而来,对开发者要求更高
  • 当前大多数WebAssembly开发者使用CC++Rust编写代码,对WebAssembly支持最多的编译器工具链是LLVM

AST(抽象语法树)

AST通过js代码解析获得,在如浏览器引擎(v8)、工具打包或js模块化等语法转化(webpack、rollupbabel)、代码混淆压缩(UglifyJS)、代码检查(Lint)等被广泛使用,这颗树定义了代码的结构,有更好更完善的抽象能力表示代码(如正则替换可能改变语义)

alt

浏览器基本渲染流程

浏览器基本组成

浏览器环境是多进程的:

  • Browser进程:浏览器的主进程(负责协调、主控),只有一个,作用为:
  1. 浏览器界面显示,与用户交互(前进、后退)
  2. 各个页面的管理,创建和销毁其他进程
  3. cookiewebstroage等数据的存储
  4. 网络资源的管理,下载等
  • 第三方插件进程:每种类型的插件对应一个进程,仅当使用该插件时才创建
  • GPU进程:用于绘制和渲染
  • Renderer进程(浏览器内核):默认每个标签页面一个进程,避免一个标签页错误影响全部标签页,内部是多线程的,作用为页面渲染,脚本执行,事件处理等

每个标签页面都是多线程的:

  • GUI渲染线程:负责生成渲染界面,解析HTMLCSS,构建DOM树和Render树,布局和描绘等(当元素大小、页面布局等不断改变,多次重新生成Render树时,这个过程称为重排),然后将信息等传给合成器线程和栅格线程生成合成器帧(当元素文字、颜色等不断改变,多次重新生成合成器帧,这个过程称为重绘),再将合成器帧传到GPU渲染到指定页面图层上
  • JS引擎线程:负责处理js脚本计算
  • 事件处理线程:用于控制事件循环、事件表,存放未满足执行条件的异步任务的回调函数,当对应的事件符合触发条件被触发时,该线程会把事件添加到待处理队列的队尾,等待js引擎的处理
  • 定时触发器线程:setIntervalsetTimeout所在线程,用于计时,如果放在js引擎线程处于阻塞线程状态就会影响记计时的准确
  • 异步http请求线程:XMLHttpRequestfetch在连接后开启进程,状态变化或接受到数据时,将对应回调传入事件表再由js引擎执行

GUI渲染线程与JS引擎线程是互斥的,所以相互阻塞

js引擎执行是单线程的,是为了防止多线程会出现同时对dom操作的问题,如果在不同线程js 改变了dom,那么就会造成渲染不同步,虽然HTML5中增加了web worker多线程操作,但其受主线程控制,一般用于计算,且不能操作dom,同时js为了防止执行阻塞,所以加入了异步操作

基本过程

  1. 浏览器进程的UI线程捕获输入

  2. 关键字则用搜索引擎搜索,域名则判断缓存

  3. 无缓存或缓存失效则通过浏览器进程的网络线程DNS解析,安全站点判断,TCP3握4挥,获取数据

  4. 创建渲染进程,浏览器进程的UI线程通过IPC通信将数据传给渲染进程

  5. 页面渲染展示

DOMContentLoaded和load

浏览器Browser进程拿到数据后传递给Renderer进程,Renderer进程里的GUI渲染线程对html数据便开始从上到下解析,同时会生成DOM,如果页面中有css,会根据css的内容形成CSSOM,然后DOMCSSOM会生成一个渲染树,浏览器会根据渲染树的内容计算出各个节点在页面中的确切大小和位置,并将其渲染在浏览器上,当遇到script 标签时,会阻断dom的渲染,转而去下载并执行js,浏览器会进入阻塞,这时浏览器会进行首次渲染,这是页面从白屏到首次渲染的时间节点,所以为了提高用户体验,且有一个相对完整的dom树结构,js通常放在最后(css放在最前是为了在生成dom的时候,同时生成CSSOM,提高性能),如果script便签拥有asyncdefer属性,则会并行下载js文件,async会在下载完成后立即执行,defer延迟到DOMContentLoaded事件前开始执行

DOM树构建完成后触发DOMContentLoaded事件,当html 文档中的图片资源,js 代码中所有异步加载的 cssjs 、图片资源都加载执行完毕之后,load 事件触发

alt

浏览器帧

alt 浏览器每帧里一般顺序包含以下任务:

1.处理用户的交互事件

2.JS解析执行(可能有)

3.窗口尺寸变更,页面滚去等的处理

4.requestAnimationFrame(rAF)

5.布局

6.绘制

7.rIC

js执行和渲染分配不均时,如果一帧里大部分只有js执行或渲染,就会出现掉帧卡顿现象

浏览器的渲染优化

由于每次重排都会造成计算消耗,因此现代大多数浏览器都会通过队列化批量执行来优化操作,浏览器会将操作放入到队列里,直到过了一段时间或者操作达到了一个阈值才清空队列(当受时间影响时和rAF机制类似,但同时也会受到次数影响,更新频率可能会高过每帧),但当你获取布局信息时会强制队列刷新,比如使用以下属性或方法:

  • offsetTop、offsetLeft、offsetWidth、offsetHeight

alt

  • scrollTop、scrollLeft、scrollWidth、scrollHeight
  • clientTop、clientLeft、clientWidth、clientHeight
  • getComputedStyle(返回元素的CSS样式对象)
  • getBoundingClientRect(返回元素大小和四边分别离左上轴的距离)

以上属性和方法都需返回最新的布局信息,因此浏览器不得不清空队列,触发回流重绘来获取正确的值,因此在我们在修改样式时,如果要多次使用它们,最好使用变量将值缓存起来

Serverless

Serverless为“无服务架构”,是一种软件系统设计架构思想和方法,它让开发者可以更加关注业务开发,而将繁杂的运维和部署交给云厂商提供,ServerlessFaasBaas组成,Faas为开发者提供业务运算环境,然后与Baas提供的数据和存储服务进行交互,从而提供与传统服务一致的体验

Serverless有两个核心特点:

  • 按需收费:根据使用者的项目大小和访问次数进行收费,减少项目冷门导致的巨大成本损失
  • 自动分配:普通项目部署在服务器上进程是常驻的,就算没有客户端请求,也会占用相应服务器资源,且为了考虑高流量场景,我们需要提供较高的服务器配置和多台服务进行负载均衡,这就导致服务处在低流量场景时,会多出很多额外的闲置资源浪费,Serverless会根据流量自动分配改变服务器资源大小

前端项目渲染类型

首先是目前最流行的spa单页面应用,用户打开在浏览器页面,向web服务器请求资源后,获取到一个空的html结构,这时候页面出现白屏,浏览器开始请求静态资源和js文件,获取到资源后在浏览器通过渲染内核和js引擎进行渲染,但因为js被设计成为单线程语言且js执行会阻断渲染,所以页面可能会出现长时间白屏

alt

基于spa首页白屏问题和空html结构问题,ssr模式出现,用户在打开浏览器页面发送请求后,web服务器在直接生成html结构,并引入对应资源渲染,生成完整html结构后再返回给浏览器,浏览器直接解析运行,这样既解决了spahtml结构不利于SEO的问题,也因为nodejs执行效率更高且不需要通过网络可以直接请求本服务器资源进行渲染减少了渲染时间,在首页返回后,后面ajax请求页面逻辑等与spa相同,也是在浏览器请求数据服务器获取数据后通过依赖框架实现页面渲染

alt

当然还有最初的那种mpa渲染模式,把所有页面都放在服务器里,路由和前端无关完全由服务器控制的方式,无论是请求数据还是页面跳转都需要重新向服务器发送请求获取新页面

渐进增强和优雅降级

渐进增强:主要针对低版本的浏览器进行开发,保证基本的功能情况下,再针对高级浏览器进行效果、交互等方面的改进和追加功能

优雅降级:主要针对高版本的浏览器进行开发,一开始就构建完整的功能,然后再针对低版本的浏览器进行兼容

编程范式

img

  • 命令式:编写具体的逻辑,关注程序执行的每个步骤
  • 声明式:告诉程序应该做什么或者说程序应该返回的结果应该是属于什么类型,而不关注过程
  • 函数式:只关注做什么而不是怎么做,函数式编程不仅仅局限于声明式编程,将程序通过函数一层层调用生成新结果,每个函数只处理属于自己的逻辑过程

前端性能优化

首屏优化

image.png

图片

base64

图片采用base64编码加载将图片从地址转化为字符串,可减少一次HTTP请求,让图片随着html下载,虽然可以被gzip压缩,但转化后仍然会使css文件大小增加,所以建议对在整个网站的复用性很高且基本不会被更新的小图片使用

雪碧图

通过将小图片拼接成大图片减少HTTP请求来实现优化,但也只适用于整个网站的复用性很高且基本不会被更新的小图片

懒加载

通过监听视图实现懒加载,基于scroll事件或IntersectionObserver实现,但后者更加开销更小,性能更优秀

JS

Fragment

使用document.createDocumentFragment()创建一个保存多个element的容器对象,减少额外嵌套标签,在reactvuesvelte等里实现了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接口上传

  1. 将需要上传的文件按照一定的分割规则,分割成相同大小的数据块
  2. 初始化一个分片上传任务,返回本次分片上传唯一标识
  3. 按照一定的策略(串行或并行)发送各个分片数据块
  4. 发送完成后,服务端根据判断数据上传是否完整,如果完整,则进行数据块合成得到原始文件

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)
})

硬件加速

transformopacity等不会导致重排重绘,因为其会开启一个新的复合图层,不会导致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的元素

  • positionfixedabsolute的元素

  • displayinline-blocktable-celltable-captionflexinline-flex的元素

  • overflow不为visible的元素

语义化标签

语义化标签是HTML5推出的概念,目的是让标签具有自己的结构含义,这样做有可以使代码更清晰整洁,便于团队开发、便于设备解析渲染、有利于SEO优化

伪元素

伪元素::after::before里必须设置content

flex

flex是一种弹性布局,当父盒子设置为display:flex时,里面的盒子都会变成它的子盒子,子盒子浮动会无效化:

容器

  • flex-direction(主轴方向)
  • flex-warp(换行)
  • flex-flowflex-directionflex-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模型,将所有元素表示为一个矩形盒子,从内到外依次为contentpaddingbordermargin

  • box-sizingcontent-box时(标准盒子模型,width对应content,设置paddingborder会撑大盒子)
  • box-sizingborder-box时(怪异盒模型(IE盒模型),width对应border

Canvas和SVG

CanvasCanvas通过JavaScript来绘制2D图形,逐像素进行渲染,一旦图形被绘制完成,它就不会继续得到浏览器的关注,如果其位置发生变化,那么整个场景也需要重新绘制,最适合图像密集型的游戏,其中的许多对象会被频繁重绘

SVG:使用XML描述2D图形的语言,SVG DOM中的每个元素都是可用的,可以为某个元素附加JavaScript事件处理器,每个被绘制的图形均被视为对象,如果SVG对象的属性发生变化,那么浏览器能够自动重现图形,最适合大型渲染区域,比如地图

JavaSrcipt

数据类型和类型判断

js基本数据类型:UndefinedNullBooleanNumberStringSymbol(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外的基本数据类型和functionnullArray等都会被识别成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:通过判断构造函数判断,几乎都能适用,但nullundefined没有constructor方法,无法判断,且因为constructor的指向可以改变,所以不安全
  • Object.prototype.toString.call:相对较全的判断方案,原理是toString方法在不同种类对象里的实现,objecttoString方法没有被重写

类型转化

  • +两边为numberboolean则相加

  • +两边如果有字符串或复杂数据类型,则两边toString后拼接

  • 其他运算符都转化成number类型运算,无法转化则NaN

window

浏览器里声明全局变量会成为window对象属性,但node不会,浏览器的全局对象为windownodeglobal

如果dom元素的id属性不与变量名重复,可以直接通过id值操作dom节点,因为会注册为window的属性

console

console.time监控时间

console.table列表展示属性

定时器

  • setTimeoutsetInterval的回调函数执行上下文默认指向window

  • setTimeoutsetInterval回调函数里的变量值为时间间隔结束后作用域里的值,如果想保存之前的值,可用函数包裹缓存或者传入第三个参数缓存(推荐)

  • setImmediate:该方法把一些需要长时间运行的操作放在一个回调函数里,在浏览器事件循环到宏任务时执行,在浏览器端和setTimeout(fallback,0)执行机制相同且支持度不高,在node端较常用

requestAnimationFrame

requestAnimationFrame会要求浏览器在下次重绘之前调用指定的回调函数更新动画,并且重绘或回流的时间间隔紧跟浏览器的刷新频率,一般这个频率为每秒60帧,可保证每个刷新间隔内,函数只被执行一次,这样既能保证流畅性,也能更好的节省函数执行开销,如果任务较多就会自动降低频率来防止卡顿掉帧

和定时器对比

  • 定时器任务被放入异步队列,只有当js线程任务执行完后才会执行队列中的任务,因此实际执行时间总是比设定时间要晚,时间间隔不一定与屏幕刷新间隔时间相同,会引起丢帧容易出现卡顿、抖动,requestAnimationFrame虽然属于宏任务,但回调函数渲染间隔完全由浏览器控制

  • 定时器任务如果时间间隔设置过短,一个刷新间隔内函数执行多次是没有意义的,因为多数显示器每16.7ms刷新一次,多次绘制并不会在屏幕上体现出来

  • 当页面处于不可见或不可用状态时,SetTinterval仍会在后台执行动画任务,而RequestAnimationFrame则完全不同,当页面处理未激活的状态下,跟着系统走的RequestAnimationFrame也会停止渲染

MutationObserver

用于对DOM节点实现监听,每次监听节点发生变化都会生成一个MutationRecord对象

使用

  • 通过传入一个回调函数来创建一个MutationObserver实例

  • MutationObserver对象通过observe 方法用来启动监听,接收两个参数,第一个参数为节点,第二个参数为节点MutationObserverInit配置对象,当调用observe()方法时,childListattributes 或者 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”后会变成crosfile协议默认不同源)

对比common.js

  • nodeECMAsrcipt
  • requireexport
  • 全部导入和按需导入
  • 值复制和值引用
  • 服务端和浏览器

预解析、事件循环、宏微任务

  1. es5前有预解析,把声明式函数和变量声明提前,但赋值不变,变量直接调用时,函数声明优先级更高,在iffor语句中预解析会受到影响,预解析时函数声明会默认生成一个为undefined的全局变量代替函数声明

  2. 接下来开始从上向下执行代码,同步任务依次执行,异步的进入事件处理线程Event Table并注册函数,当异步任务触发条件满足时,Event Table会将对应回调函数移入执行队列,等待js线程内同步任务执行结束为空时,会去执行队列读取对应函数执行,上述过程不断循环则为事件循环

  3. js任务又分为微任务和宏任务,js线程从执行队列中读取异步任务时(微任务和宏任务是不同的任务队列),会先读取微任务,再读取宏任务,且微任务是一次读取全部,宏任务为一次循环读取一个执行,多次循环读取

  4. js控制的任务就是微任务(ES6-promise),运行依赖宿主环境的就是宏任务(浏览器-setTimeout),根本原因是所属规范不同

常见的微任务有:

  • Promise.then
  • MutaionObserver
  • process.nextTickNode.js

常见的宏任务有:

  • script (可以理解为外层同步代码)
  • setTimeout/setInterval
  • UI rendering/UI事件
  • postMessageMessageChannel
  • setImmediateI/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,但值为强引用,不受外部影响,弱引用外部引用清除,则对应值和键值对也消失,便于垃圾回收

  • weakMapweakSet都没有生成可迭代对象的API,无法遍历

map对比object

  • object键只允许为字符串,map键值允许任何类型,因此可以使用地址不同但值相同的对象

  • map可迭代,object不行,因此map有序可扩展

  • map有长度属性

深浅拷贝

浅拷贝是拷贝一层,深层次的引用类型则共享内存地址

  • object.assigin

  • 扩展运算符

深拷贝开辟一个新的栈,两个对象属完成相同,但是对应两个不同的地址,修改一个对象的属性,不会改变另一个对象的属性

  • lodash_.cloneDeep()

  • JSON.stringify()无法复制函数、undefinedsymbol

  • 简单递归

     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指向执行上下文,一般为调用函数的对象,声明箭头函数会绑定外层非箭头函数的执行上下文作为自己的执行上下文,callsetTimeout等都无法改变

内存管理

js自动管理内存,闭包里的执行栈里的数据会自动被标记清除法回收,但引用对象不会,所以会导致内存泄露,js默认有个引用计数表,记录着对象的引用次数,在使用完对象后将其=null,就会是这个引用表对应次数置0js就会自动回收内存

内存泄漏可能产生途径:

1.ES5里的函数里声明全局变量

2.计时器

3.函数监听

4.闭包

5.在全局window上声明绑定属性

6.获取dom节点,节点被删除

原型链

alt

BOM

  • window:在浏览器中,window对象有双重角色,既是浏览器窗口的一个接口,又是全局对象

  • location:代表url地址,除了 hash之外,只要修改location的属性,就会导致页面重新加载新URL

  • navigator:主要用来获取浏览器的属性,区分浏览器类型

  • screen:代表显示器的有关信息

  • history:操作浏览器URL的历史记录

  • locationhrefwindowopenhistory.go都可以用于网页跳转

事件冒泡

focusblur这些事件没有事件冒泡机制,所以无法进行委托绑定事件

Drag

被拖拽的元素

  • dragstart:开始拖拽时触发

  • darg:拖拽时触发

  • dragend:拖拽操作结束时触发

受到拖拽事件影响的元素

  • dragenter:被拖拽元素进入元素时触发

  • dragover:被拖拽在元素内移动时触发

  • dragleave:被拖拽元素移出元素触发

  • drop:元素完全接受被拖拽元素时触发

var、let、const

  • var会生成全局变量

  • var有预解析、变量提升,letconst是暂时性死区,但函数声明仍存在变量提升

  • var为函数作用域,letconst为块级作用域

  • const声明常量值无法改变,声明引用对象地址无法改变

错误捕获

  • try...catch

  • window监听error事件

伪数组

伪数组有迭代接口,可以用于遍历,但没有继承Array原型,无法使用数组方法

Promise

  • promisees6中新增的处理异步任务的语法,它改善了回调地狱的问题,它是通过构造方法对传入异步函数封装成一个对象,封装成对象是因为对象可以保存状态,promise有三个状态分别是:pending(初始)--fulfilled(成功)--rejected (失败),执行异步任务成功后,通过resolve接收数据,then方法里传入成功的回调函数执行,失败后通过reject接收数据,将失败后的回调函数传入then里的第二个参数执行,或者传入catch方法执行,promise构造函数里调用resolvereject后仍会向后执行,promise构造函数会在声明后就立即执行

  • promise对象的状态从pending转化成fulfilledrejected后无法改变,promise对象每次调用thencatch都会生成新的promise对象用于链式调用,其中函数返回值会被生成的新对象resolveresolve如果接收一个fulfilled状态的promise对象会直接resolveresolve的值,如果接收一个rejectedpending状态的promise对象,则会resolve整个对象

  • Promise还有all方法和race方法,all方法是当所有的子Promise都完成,该Promise完成,返回值是全部值的数组,有任何一个失败,该Promise失败,返回值是第一个失败的子Promise结果,race区别是它会取第一个成功的子Promise结果为成功结果

return Promise.resolvenew 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

asyncawaites8新增的处理异步的关键字,更好处理了回调地狱的问题,本质只是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()用于给对象设置属性,它有三个参数,第一个为对象,第二个为属性,第三个为特性,特性有六个可选值,分别为getsetvalue(值)、wirtable(能否被重写)、enumerable(能否被枚举)、configrable(特性能否被更改)

数组API

forEach、map、reduce、filterAPI都是对原数组浅拷贝一个新数组再对新数组进行操作,所以无法改变原数组里的基本类型和引用类型地址

sort

sort的基本排序原理:

  1. 对本项和上一项进行传入函数的运算,返回值为正顺序不变,为负调换顺序

  2. 然后每增加对比一个元素,都会把新增元素通过二分法对已经排序好的部分元素对比,确定好新元素位置后,再重复这个步骤直至遍历完所有数组元素,这只是基本原理,实际原理会根据数组选择不同算法优化,更加复杂

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

优缺点

优点

  1. 浏览器原生支持,不需要引入额外的第三方库
  2. 语义化
  3. 复用性,移植性高
  4. 不同团队不同项目可以共用组件

缺点

  1. 可能需要频繁操作DOM
  2. 目前浏览器兼容性、性能方面不够友好
  3. 和外部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来回滚之前的commitgit 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.从dependenciesdevDependencies配置开始递归获取依赖模块

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

alt

  1. DefinePlugin:定义全局变量process.env.NODE_ENV,区分程序运行状态
  2. FlagDependencyUsagePlugin:标记没有用到的依赖
  3. FlagIncludedChunksPlugin:标记chunks,防止chunks多次加载
  4. ModuleConcatenationPlugin:作用域提升(scope hosting),预编译功能,提升或者预编译所有模块到一个闭包中,提升代码在浏览器中的执行速度
  5. NoEmitOnErrorsPlugin:防止程序报错,就算有错误也继续编译
  6. 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,
               },
           }),
       ],
   },

构建过程

  1. 解析脚本命令,初始化插件和Compilation对象

  2. entry入口文件开始分析依赖,对每个依赖模块开始构建

  3. 加载loader并处理模块,对loader加载完处理后的模块进行编译生成AST,当遇到require等导入其它模块语句时,便将其加入到依赖的模块列表,同时对新找出的依赖模块递归分析,最终搞清所有模块的依赖关系并生成AST

  4. 当模块全部构建完时将AST生成chunks,对chunks进行一系列的优化,生成代码

  5. 将代码输出到配置指定文件

alt

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实例

  • 传给每个插件的compilercompilation对象都是同一个引用,因此不建议修改

  • 异步的事件需要在插件处理完任务时调用回调函数通知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以外的文件加载器,pluginwebpack拥有除文件打包以外的其他新功能

  • loader只作用于打包构建过程,而plugin可作用于整个过程

文件压缩

  • js代码压缩=>

    1.TerserPluginwebpack在生产模式下会默认使用,多进程处理(官方推荐)

    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-loaderfile-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获取模块代码后分析进行局部更新

img

vite

vite是一个前端工程化工具,组成如下:

  • 一个开发服务器:基于ESModule静态语法分析,直接返回空页面,跳过整个编译打包,而是初始化和更新时完全根据浏览器请求按需导包编译

  • 一套构建指令:默认使用ESBuild转化语法,rollup打包,至于为什么不直接用ESbuild,是因为对新技术稳定性的观望

  • 其作用类似webpack+webpack-dev-server+babel,压缩优化等仍靠terser等插件完成

    alt

简单对比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,但rollupwebpack要轻,速度更快,打包结果更简洁,但只能处理js文件,处理其他文件也需插件配合,且不具有热更新功能,所以一般只用于js文件的打包,而不作为工程化构建工具

TreeShaking

暂时性去除掉未使用的包和代码,让打包体积变小

  • 基于ES Module的静态语法分析(按需导入导出模块)实现tree-shaking

  • webpack基于两种配置来实现tree-shaking

  1. usedExports:通过标记某些函数是否被使用,之后通过Terser来进行优化

  2. sideEffects:跳过配置的模块/文件,直接查看该文件是否有副作用

Browserify

commonjsESmodule的语法转化包

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/stableregenerator-runtime/runtime替代,全局引入会污染全局变量且体积很大
  • @babel/plugin-transform-runtime:ES6+转化包,包括全局对象和其API,按需引入体积更小更轻,但无法转化一些实例化方法,依赖于@babel-runtime
  • @babel-runtime@babel/polyfill都集成并依赖于helpersregeneratorcore-js,在版本7之后需要手动安装对应core-js
  • @babel/preset-reactreact语法转化包
  • @babel/preset-typescriptts语法转化包

原理

  • 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

  • cssnanocss代码压缩

  • tailwindcss:配合Tailwind-CSS

css预处理器处理时机区别:

alt

postCss工作流:

alt

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缓存

缓存分为强缓存和协商缓存两种

强缓存指的是浏览器直接通过请求的头信息判断本地缓存是否过期,没有过期则直接使用本地缓存数据,与强缓存相关的字段有expirescache-control

  • expires是一个绝对时间,代表资源超过某个时间节点后就需要重新向服务器发送请求,但如果客户端时间出现误差,就会导致数据缓存出现偏差

  • cache-control则是相对时间,代表经过某个时间段后需要向服务器重新发送请求,如果cache-controlexpires同时存在的话,cache-control的优先级高于expires

  • Cache-Control字段

    no-store:禁用缓存

    no-cache:强缓存失效,判断协商缓存

如果强缓存过期了,则使用协商缓存,客户端发送请求判断资源是否发生变化,如果未发生变化则使用本地缓存,发生变化则重新请求资源,与协商缓存相关的字段有Last-Modified/If-Modified-SinceEtag/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

img

img

对比UDP

  • TCP是面向连接的,UDP面向报文的

  • TCP只能是1对1的,UDP支持1对多

  • TCP的首部较大为20字节,而UDP只有8字节

  • TCP可靠性强,而UDP获取到数据后就开始传输,不保证顺序,也不管丢包,没有重传机制,也不会因为网络拥塞做出调整

数据加密

对称加密:双方采用相同的秘钥加密解密

非对称加密:双方加密后只有对方的私钥可以解密

混合加密:参考HTTPS

DNS缓存查询

浏览器缓存=>系统缓存=>host文件=>(递归查询)本地域名服务器=>(迭代查询)根域名服务器

安全

XSS

XSS为跨站脚本攻击,攻击者将恶意代码植入到js脚本文件后,导致用户会在操作时会额外执行一些恶意代码

类型:

  1. 反射型:比如搜索栏,将问题代码通过URL直接传给后端,后端不处理拼接在html中返回后,额外执行错误代码,较多出现在以前的mpa渲染里
  2. 存储型:比如评论区,错误代码保存在数据库中,其他用户都能看到错误代码
  3. dom型:不与服务端交互,直接显示在页面上的,属于前端问题,比如输入什么就显示什么都气泡

防范:

  1. 对用户的输入进行检查过滤
  2. 设置cookiehttponly来禁止js脚本访问cookie
  3. 设置cookiesecure属性仅在请求为https的时候发送cookie
  4. vuereact等框架渲染HTML内容时都会将 "'&<> 这几个字符进行转义处理,所以天然减少了XSS问题

CSRF

CSRF为跨站请求伪造,在缺陷网站攻击者利用用户在其他网站的登录凭证,以用户身份去其他网站提交操作,整个过程攻击者并不能获取到受害者的登录凭证,仅仅是冒用

  • 防范
  1. 验证 HTTP Referer 字段

  2. 使用tokentoken不放在cookie里就不会自动携带

  3. HTTP头中自定义属性并验证

  4. 设置CookieSamesite

  5. (浏览器默认的同源策略限制)

HTTP

简单请求和非简单请求

同时满足以下两大条件,就属于简单请求

请求方法是HEADGETPOST之一

HTTP的头信息不超出以下几种字段:

  • Accept

  • Accept-Language

  • Content-Language

  • Last-Event-ID

  • Content-Type:只限于三个值application/x-www-form-urlencodedmultipart/form-datatext/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地址)

  • 支持断点续传

  • 新增了一些请求方法(PUTDELETE等)、请求头和响应头(cache-controlEtag/ If-None-Match等)

HTTP2.0

  • 将数据包拆分成了更小的信息或帧,对它们进行二进制编码后传输(之前为文本格式)

  • 多路复用(所有的请求通过一个TCP连接传输,浏览器和服务器可以同时发送多个请求和响应,不用等下次响应再发送请求,减少了服务器压力,提升了访问速度,但每个数据包仍需一个一个处理,如果丢包导致数据不完整仍然会产生队头阻塞问题)

  • 服务器推送

  • 可以指定数据的优先级

  • HTTPS升级而来,保证了安全性

  • 消息头压缩(消息头带有大量冗余信息)

HTTPS

  • 是以安全为目标的HTTP通道,它基于HTTP设置了ssl/tls协议

  • HTTPSssl协议需要额外增加性能开销和金额开销,功能越强大,证书费用也越高

  • 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(永久重定向):请求的网页已永久移动到新位置,服务器返回此响应(对GETHEAD请求的响应)时,会自动将请求者转到新位置
  • 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

websocketHTML5规范中的一个部分,为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是将WebSocketAJAX和其它的通信方式全部封装成了统一的通信接口,兼容所有浏览器,解决浏览器不兼容问题,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对象上的postMessageAPI发送数据,在接收对象里通过监听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认证信息,设置后允许传递
})
  • 代理跨域

同源策略是浏览器的安全策略,所以服务器向服务器请求数据不会受影响,常用的方式有

  1. webpack基于node平台直接配置生成本地服务器转发
  2. 使用nodeweb框架搭建服务器转发
  3. 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

websocketHTML5新增的协议,实现了浏览器和服务器的双向通信,同时也允许跨域

AJAX

ajax意思为异步通信,用于前后端分离架构中交换数据

XMLHttpRequest

原生XMLHttpRequest实现

  1. 创建Ajax的核心对象XMLHttpRequest对象

  2. 通过XMLHttpRequest对象的 open() 方法与服务端建立连接

  3. 构建请求所需的数据内容,并通过XMLHttpRequest对象的send()方法发送给服务器端

  4. 通过XMLHttpRequest对象提供的 onreadystatechange 事件监听服务器端你的通信状态

  5. 接受并处理服务端向客户端响应的数据结果

  6. 将处理结果更新到 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

fetchHTML5增加的一种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

字段

  • namecookie的名称

  • valuecookie的值

  • domain:可以访问此cookie的域名

  • path:可以访问此cookie的页面路径

  • expires/Max-Agecookie失效时间,不设置的话默认值是当浏览器关闭(不是浏览器标签页,而是整个浏览器) 后,此cookie失效

  • Sizecookie大小

  • httponly:若此属性为true,则只有在HTTP请求头中会带有此cookie的信息,禁止document.cookie来访问此cookie

  • secure:设置是否只能通过HTTPS来传递此条cookie

  • SameSite:防止CSRF攻击和用户追踪

    1.Strict:完全禁止第三方网站来源使用Cookie请求数据,跨站点时,任何情况下都不会发送 Cookie

    2.Lax:规则稍稍放宽,大多数情况也是不发送,但是导航到目标网址的Get请求除外

    3.None:关闭限制

  • Prioritychrome的提案,定义了三种优先级,Low/Medium/High,当cookie数量超出时,低优先级的cookie会被优先清除

  • cookietoken本质只是保存数据的字段名词,以下指基于它们的登录技术方案

  • 用户登录成功后,会将登录信息在服务器保存为session并生成单独sessionID,同时set-cookie发送给客户端自动保存为cookie,用户进行接口请求时,自动带上cookie,在服务器进行验证,退出登录或超时清除sessioncookie,但会受到分布式集群影响(因为负载均衡导致处理和保存的服务器并不一样)

  • 用户登录成功后,获取服务端发送的token,这个token只需要存在客户端,用户进行任何操作时,都需要带上一个token,服务器在收到数据后进行解析获取用户登录信息,token只需要存在客户端(解决了分布式问题),token传递可以放在Authorization请求头或请求体或Webstorage里(解决安全问题),当需要及时清除使用sessionStorage,需要持久化存储使用localstorage,并加入有效时间戳,下次验证失效后及时清理

  • cookie受跨域影响,token完全由应用管理,所以它可以避开同源策略,可以避免CSRF 攻击,可以在多个服务共享

  • cookie需要服务端保存,是牺牲空间换时间,token需要服务端解析,是牺牲时间换空间

跨域影响

在发送一个HTTP请求的时候,携带的cookie是这个HTTP请求域的地址的cookiecookie受跨域影响并不是指所有cookie都无法发送,而是指每个不同源下只能发送该源下的cookie,不同源之间不能共享,顶级域名只能获取到domain设置为顶级域名的cookiedomain设置为如二级域名的无法获取,次级域名能获取到所有上级域名的cookie,无法获取到同级或下级域名的cookie,如果需要跨域发送cookie,则需要设置XMLHttpRequestwithCredentials和响应头的ACAWtrue

webStorage

  • webStorage只能存入字符串,无法直接存对象

  • sessionStorage仅在当前浏览器窗口关闭前有效,localStorage始终有效,窗口或浏览器关闭也一直保存(如果想让localStorage信息过期失效,可在其内存入持续时间)

  • sessionStorage不会在不同的浏览器窗口共享,即使是同一个页面,而localStoragecookie在所有同源窗口都是共享的

  • localstorage可以用来在页面传递参数,sessionstorage可以用来保存一些临时的数据

  • cookie会始终在请求中携带,而webStorage不会主动将数据发送给服务端

框架

虚拟DOM

虚拟DOM本质上就是一个描述DOM结构的js对象,由多个虚拟节点js对象组成

CgqCHl_qypGAZPuGAADYrK9nkJY878_mh1609406162857.jpg

对比真实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

alt

react

  • 编写jsx文件

  • Babel转化为调用React.createElement生成ReactElement

  • instantiaReactComponent函数会将ReactElement等转化生成虚拟dom节点(ReactComponent)

  • 调节器进行生成Fiber虚拟dom树(有差异则Diff后生成差异结果)

  • 渲染器将结果渲染成真实dom

  • 如果需要跨平台处理,则在生成虚拟dom树后用不同渲染器渲染成不同平台的结构

源码细节
  1. React.createElement接收type、config、children三个参数,生成ReactElement

  2. ReactElement内部含type、key、context、props四个关键属性,type则用于标识组件的类型, 如果是字符串div等,则表示组件对应的是一个DOM对象,如果是函数组件,type是函数名, 如果是类组件,type是类名,ReactElement相当于只是数据容器,它无法更改数据,因此我们需要将其传入工厂函数instantiaReactComponent生成ReactComponentReactElementprops.children也会依次递归传入该工厂函数

  3. 工厂函数instantiaReactComponent通过接收不同node参数生成四种不同ReactComponentReactDOMTextComponent(RTC)、ReactDOMComponent(RDC)、ReactCompositeComponent(RCC)、ReactDOMEmptyComponent(REC),如果nodenull,则生成REC,如果node是数字或字符串,则生成RTC,如果传入的nodeReactElement对象,则通过它的type属性判断是普通DOM元素还是自定义组件,由此分别生成RDCRCC

  4. ReactComponent里有四个重要方法:

  • construct:用来接收虚拟dom属性进行实例初始化
  • mountComponent:接收一个标志该元素的id,并挂载到虚拟dom树上
  • unmountComponent:是用来卸载内存中的虚拟dom
  • receiveComponent:接收虚拟dom的新属性状态,然后一般会调用updateComponent方法来进行diff更新
  1. renderReactComponent构成的虚拟dom树渲染到真实dom

alt

Template和JSX

alt

  • vue等采用了template,让开发者具有约束性的去编写模板,对代码进行了静态和动态分类(静态标记和静态提升),数据依赖改变时,编译器能很好理解区分,则能以更小粒度去处理更新渲染,提高了渲染性能

  • jsx具有JavaScript的完整表现力,非常灵活,可以构建功能结果非常复杂的组件,但是灵活的语法,也意味着引擎更难以理解(template里只有指令导致的渲染结构不同,但jsx在复杂组件里因为js的灵活性(如map、filterAPI)可能出现极其复杂的结构,每个状态变化的可能导致渲染出的结构也大不相同),数据依赖改变时,难以做到类vue精确render和性能优化,所以每次更新render全部数据依赖有关组件以及子组件,同时提供了React.memo等一系列方法来让开发者来自己进行优化render

Diff

vue

  • 同级对比,深度优先

  • 由新dom和老dom的双端的双向指针依次从两端向中间对比,相同节点在两端直接复用,在中间则移动到最前端后复用:

  1. 如果新老dom前端和前端相同或后端和后端相同则直接同时索引+1或-1
  2. 如果老dom前端和新dom后端相同或新dom前端和老dom后端相同,两端索引变化后需要将节点移动到最前或最后
  3. 如果新dom对应key在中间,则对比节点,如果相同,则将该节点移动到最前端,然后新老dom索引同时+1

react

  • tree级和component级:同级比较,只有增加和删除操作,tree级里如果构建tree的组件结构发生了变化或component级组件种类发生了变化,出现不同直接删除,创建新的

  • node级:拥有插入、移动、删除三种操作,可以根据key值作为唯一标识符,优先比较key

  1. 遍历新dom,依次用新dom里的节点与原dom里对应的节点对比,lastindex=0代表已遍历的原dom上最大索引号,老节点会拥有一个_mountIndex属性代表节点在原dom上的索引位置,nextIndex代表当前遍历位置
  2. 如果有新节点就添加,并更新_mountIndex = nextIndex
  3. 如果_mountIndex > lastindex,节点不移动,将lastindex = _mountIndex,更新节点_mountIndex = nextIndex
  4. 如果_mountIndex < lastindex,进行移动操作enqueueMove(this, child._mountIndex, toIndex),根据_mountIndex判断插入位置,将节点移动到新dom里的位置(toindex),更新_mountIndex = nextIndex
  5. 整体遍历老集合中节点,看有没有没用到的节点删除

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>

路由管理

前端路由描述了URLUI之间的映射关系,这种映射是单向的,本质即监听URL变化引起UI更新,而不导致浏览器刷新,进行DOM操作来模拟页面跳转

无论是vue还是react等框架里都有两种路由模式,hash模式和history模式,它们都保证了可以实现页面不刷新跳转,直面区别就是url里的hash模式有#,history模式没有

实现原理

  • hash模式:通过监听windowhashchange事件实现
  • history模式:通过HTML5historyAPIpushStatereplaceState),监听popstate事件或订阅发布实现

组件通信

  • 父子通信
  • 共同父组件传值
  • 跨级传值
  • 订阅发布
  • 状态管理插件库

Immutable

Immutable.js 是一个完全独立的库,无论基于什么框架都可以用它

Immutable对象的任何修改或添加删除操作都会返回一个新的 Immutable对象,Immutable 实现的原理是持久化数据结构:

  • 用一种数据结构来保存数据
  • 当数据被修改时,会返回一个对象,但是新的对象会尽可能的利用之前的数据结构而不会对内存造成浪费

也就是使用旧数据创建新数据时,要保证旧数据同时可用且不变,同时为了避免 deepCopy把所有节点都复制一遍带来的性能损耗,Immutable 使用结构共享

如果对象树中一个节点发生变化,只修改这个节点和受它影响的父节点,其它节点则进行共享

插槽

  • 插槽用于子组件里部分视图需要由父组件的数据状态控制,在子组件里展示,是一种减少了不必要传值的方式

  • vuesvelte里通过slot实现,react通过children实现

ajax时机

  1. ajax时机一般放在拥有完整组件实例后
  2. vuecreated后拥有完整数据实例(vue数据实例可以与template分离),reactrender后拥有组件实例(reactjsx导致数据和视图结构不分离)
  3. 所以vueajax一般放在created里,为了避免放在mounted里会重复渲染出现闪动
  4. reactajax一般放在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节点数据,进行操作

优化渲染

  • 类组件里的生命周期 shouldComponentUpdatepureComponent

  • 函数组件的memohooks里的useCallbackuseMemo

错误边界

  • 类组件用componentDidCatch捕获错误,获取错误状态信息进行日志上传、保存等,static getDerivedStateFromError()改变state状态来渲染替代页面

  • 函数组件目前还没有对应hooks,官方正在开发

强制刷新视图

  • 类组件用this.forceUpdate()

  • 函数组件没有对应hooks,可以使用一个标识符数据,每次需要刷新视图就改变这个标识符

类组件

生命周期

img

setState
  • 在组件生命周期或React合成事件中,setState导致的state更新是异步;在setTimeout或者原生dom事件中,state更新是同步

  • 因为reactsetState的优化策略,可能将多次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里的observerreactive,为useState底层实现

  • useLayoutEffect:和componentDidMountcomponentDidUpdate触发时机一致,useLayoutEffect会在render生成DOM后浏览器渲染前执行,阻塞浏览器真实dom渲染,所以可能有些许卡顿,而useEffect会在渲染真实dom后执行,所以函数里如果操作domuseEffect可能会有闪屏

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(调度器)

  1. synchronous,与之前的Stack Reconciler操作一样,同步执行
  2. task,在next tick之前执行
  3. animation,下一帧之前执行
  4. high,在不久的将来立即执行
  5. low,稍微延迟执行也没关系
  6. offscreen,下一次render时或scroll时才执行

VUE

生命周期

Vue生命周期钩子会自动绑定 this 上下文到实例中,因此你不能使用箭头函数来定义一个生命周期方法

  • 创建实例
  • 初始化数据
  • 寻找挂载点
  • 编译模板
  • 模板挂载替换
  • (数据修改更新和重新渲染)
  • 文档的卸载和销毁
  • 在这之间穿插了很多钩子函数

alt

生命周期 描述
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.thenMutationObserversetImmediatesetTimeout中的一种调用flushCallbacks方法,将callbacks里面的函数复制一份,同时callbacks置空,依次执行callbacks里面的函数

响应式原理和双向绑定

  • vue实例会遍历其data对象的所有属性,并在Observe里使用 Object.defineProperty重写它们的getter/setter(在vue3中使用的为proxy),组件实例的data的每个key都对应一个Dep对象,由于datakey可能出现多次,所以每个Dep需要管理多个Watcher,之后当依赖项的 setter 触发时,会先通知dep,再通知其订阅的所有 watcher,从而使它关联的组件重新渲染

img

  • vue数据发生变化,它更新dom是异步的(代理方法为微任务),为了在数据变化之后等待 Vue完成更新DOM,可以在数据变化之后立即使用 Vue.nextTick(callback),这样回调函数将在DOM更新完成后被调用

alt

  • 双向绑定在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指向

图片引入

vueDOM中直接引入的图片会默认被转为base64 格式的,但是使用变量引入的话,图片不会转为base64格式的,所以不会正常显示,使用require来引入图片

import imgUrl from "../assets/test.png"
data(){
     return {
         imgUrl:require(imgUrl)
     }
}

vue3

v-for、v-if、v-show

v-ifv-show都会导致重排重绘,但v-if会直接删除dom节点,v-show只是控制display属性,vue3 版本中v-if 判断优先于v-for,解决了vue2中先渲染再判断的性能问题

组合式API
  • 提高了代码逻辑复用性和开发效率

  • ref、reative:用于响应化数据,ref常用于基本数据类型,reative常用于复杂数据类型

  • toRefs:当使用扩展运算符对reactive的对象进行展开时,是用变量拷贝了对象属性的值,拷贝的变量无法实现响应式,所以使用toRefs转化整个对象再展开

  • watch、watchEffectwatchEffect会自动监听所有依赖,watch需要手动设置,返回值为停止监听的函数

多根组件

基于fragment

传送组件

Teleport:实现了组件状态在vue组件里被其控制,但又根据to改变渲染的位置,改良了在一些复杂项目里的组件通信难度

并发组件

Suspense:实现组件异步渲染时,渲染替代模板组件,之前通过v-if控制,Suspense提供了template#default#fallback控制

静态提升和静态标记

对改变过的组件添加flag标记,下次发生变化时,直接首先对比该节点中,对不参与更新的元素,会做静态提升,只会被创建一次,在渲染时直接复用,减少了diff递归对比过程,这样就免去了重复的创建节点,大型应用会受益于这个改动,免去了重复的创建操作,优化了运行时候的内存占用

Svelte

对比react、vue

  • reactvue都算是典型的runtime框架,虽然它们也有少量的complier,将jsxvue文件的模板进行编译,但主要支持框架功能的包也会随之打包发送到浏览器端用于功能实现,但svelte则把重心放在了编译层,在complier就将框架和功能代码结合成js,并把功能支持都转化成真实dom的操作,它的简单包要比vue小5倍,react小20倍,但如果项目规模过大,整体代码变多,那么它这个优势也会变小,但不可否认,在小型项目中svelte性能要比reactvue等框架性能更优秀,因为缺少svelte处理大型复杂项目的案例,加上生态还不够特别完善,所以svelte目前更适合简单小型项目

alt

  • vuereact都是基于虚拟dom的框架,但虚拟dom并不代表一定高效,反而在react里当组件变化时,可能会出现很多无意义的组件重新render,虚拟dom的优点在于其能准确知道哪个节点该如何渲染,保证了性能的下限,而当数据依赖变化时,如果能明确知道哪个节点该如何变化,直接使用真实dom修改则比起vue来说能刚好减少一次虚拟dom的计算,更加高效,sevlte就是基于真实dom实现了响应式

  • 它结合了类jsx语法和类vue的单文件组件,语法简洁明了,开发效率高

  • 框架自带全局状态管理

  • 暂时ts支持性生态不算很好

  • 原生支持CSS scopeCSS 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操作

响应式原理

  1. 在编译时就确定可能发生变化的数据,并对其数据进行处理,生成其数据依赖数组(采用32位的位掩码和数组存放)
  2. 将触发数据发生变化事件编译成调用$$invalidate方法,更新ctx
  3. 将变化依赖make_dirty标记为脏数据,将组件放入dirty_component
  4. 在下次promise微任务中遍历所有dirty_component并调用p方法将新ctx有关的标签变量对象全部更新
  5. 重置dirty
  6. 视图更新

alt

Solid

  • 采用类静态jsx语法
  • 比起sevlte,使用innerhtml代替了createElementappendchild,提高了操作速度
  • 响应式原理基于proxy订阅发布,当有数据依赖发生改变时,会依次修改依赖于该数据的dom,和react不同,react调用setState改变数据依赖时会重新渲染全部生成虚拟domdiff,而soild调用set方法只会把该数据依赖有关的dom节点通过dom操作进行修改

状态管理

flux模式

核心思想为数据和逻辑永远单向流动

组成:

  • dispatcher负责分发事件
  • store负责保存数据,同时响应事件并更新数据
  • view负责订阅store中的数据,并使用这些数据渲染相应的页面

alt

Redux

  • 单一数据源
  • 状态只读性
  • 分发不同种类的action对象通过纯函数来改变状态

alt

源码组成

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-reactprovide包裹根组件,绑定仓库数据

数据交互

  • 类组件里使用connect关联UI组件和redux仓库,第一次执行参数为映射仓库数据到组件数据的函数或对象,映射函数分别会接收statedispatch返回一个映射对象,第二次执行参数为UI组件,所以可以直接使用装饰器语法关联,关联后就可以在组件props里获取映射属性使用
 connect(mapStateToProps, mapDispatchToProps)(AppUI)
  • 函数组件使用useSelectoruseDispatch两个hooksstore交互,useSelector参数函数直接将所需数据从state上解构返回,useDispatch调用直接返回dispatch函数用于分发actions

中间件

  • createStore的第二个参数可以通过applyMiddleware使用中间件

  • middleware原理为接收有StoregetState函数和dispatch函数的对象,在中间件函数里对dispatch函数进行处理加工或执行一些额外操作,然后再由reducer处理

  • react-thunk是把dispatch里的普通action对象换成了一个dispatch为参数,异步分发action对象的函数

Mobx

  • 基于观察者模式和object.defineproperty
  • 生成mobx仓库类并创建实例
  • 通过observerable包裹仓库数据
  • 通过action包裹改变仓库数据的函数
  • 通过computed包裹生成计算属性的函数
  • 导出实例注入组件
  • 通过Provider包裹根组件提供全部数据
  • 通过inject给特定组件注入特定数据
  • 通过observer包裹生成组件
  • useLocalStore可以传入返回仓库对象实例的纯函数,其返回值可以在useObserverObserver包裹中当作数据仓库使用,useObserverObserver里通常是一个使用仓库数据的纯函数

对比Redux

  • redux将数据保存在单一的store中,mobx将数据保存在分散的多个store

  • redux使用不可变状态,这意味着状态是只读的,不能直接去修改它,而是应该返回一个新的状态,同时使用纯函数,mobx中的状态是可变的,可以直接对其进行修改

  • redux的编程范式是函数式的,而mobx是面向对象的

  • 因此数据上来说Redux理想的是immutable的,每次都返回一个新的数据,而Mobx从始至终都是一份引用,因此Redux是支持数据回溯的

  • Redux相比,使用Mobx的组件可以做到精确更新,这一点得益于Mobxobservable

Vuex

vue官方推荐状态管理工具,组成部分:

  • StoreVuex采用单一状态树,每个应用仅有一个Store实例,在该实例下包含了state、actions、mutations、getters、modules
  • StateVuex为单一数据源,可以将Store注入全局之后使用this.$store.state访问
  • GettersGetters作用类似计算属性,负责缓存仓库数据的派生状态
  • MutationsMutaionsvuex中改变State的唯一途径(严格模式下),并且只能是同步操作,函数参数为State,通过store.commit()调用(如果使用其他方式修改仓库数据也能完成功能,但与vue基于的flux设计矛盾,且vue只有通过Mutations修改数据,才提供了方便调试的数据追踪)
  • Actions:一些对State的异步操作可以放在Actions中,并通过commit Mutaions变更状态,函数参数为Store,通过store.dispatch()方法触发
  • Modules:当Store对象过于庞大时,可根据业务需求分为多个Module,每个Module都具有自己的state、mutations、actions、getters

alt

跨平台

移动端

发展

  • 原生开发:使用javakoltin语言开发安卓,ocswift开发原生应用

alt

  • 混合开发:使用HTMLjscss通过容器层开发应用,通过webview渲染视图层,js Bridge调用原生方法

alt

  • 跨平台开发:它不使用Web技术,它的页面不是HTML5页面,而是使用自己的dsl编译生成native组件,UI层通过虚拟dom进行转化,逻辑层用自己的dsl进行控制,中间使用桥接层通信,然后编译成各平台的原生App

alt

混合开发和跨平台开发对比

  • 以往最早的混合开发,主要依赖于WebView,但是WebView是一个很重的控件,很容易产生内存问题,而且复杂的UI在WebView上显示的性能不好
  • react-nativeweek等抛开了WebView,利用JavaScriptCore或v8来做桥接,将vnode转为native渲染,只牺牲了小部分性能

week架构

alt

  • Flutter实现跨平台采用了更为彻底的方案,它既没有采用WebView也没有采用JavaScriptCore,而是自己实现了一台UI框架,然后直接系统更底层渲染系统上画UI,它采用的开发语言是Dart,据称Dart语言可以编译成原生代码,直接跟原生通信 alt

PWA

PWA全称Progressive Web App,即渐进式WEB应用,一个PWA应用首先是一个网页, 可以通过Web技术编写出一个网页应用

使用原理

  • 生成html网页
  • 配置移动端manifest.json文件,让 PWA 应用被添加到主屏幕,使用manifest.json 定义应用的名称、图标等信息
  • 添加Service WorkerService 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在视图层对比渲染

alt

生命周期

总程序:

生命周期 说明
onLaunch 小程序初始化完成时触发,全局只触发一次
onShow 小程序启动,或从后台进入前台显示时触发
onHide 小程序从前台进入后台时触发
onError 小程序发生脚本错误或 API 调用报错时触发
onPageNotFound 小程序要打开的页面不存在时触发
onUnhandledRejection() 小程序有未处理的 Promise 拒绝时触发
onThemeChange 系统切换主题时触发

页面:

生命周期 说明 作用
onLoad 生命周期回调—监听页面加载 发送请求获取数据
onShow 生命周期回调—监听页面显示 请求数据
onReady 生命周期回调—监听页面初次渲染完成 获取页面元素(少用)
onHide 生命周期回调—监听页面隐藏 终止任务,如定时器或者播放音乐
onUnload 生命周期回调—监听页面卸载 终止任务

组件:

生命周期 说明
created 生命周期回调—监听页面加载
attached 生命周期回调—监听页面显示
ready 生命周期回调—监听页面初次渲染完成
moved 生命周期回调—监听页面隐藏
detached 生命周期回调—监听页面卸载
error 每当组件方法抛出错误时执行

类vue框架生成小程序原理

alt

alt

Electron

  • CSSJavaScript来构建跨平台桌面应用程序的一个开源库, Electron通过将ChromiumNode.js合并到同一个运行时环境中,并将其打包为MacWindowsLinux系统下的应用

  • 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缓冲区等待

事件循环

alt

  • 定时器检测阶段(timers):执行setTimeoutsetInterval里面的回调函数

  • I/O事件回调阶段(I/O callbacks):此阶段执行某些系统操作的回调,例如TCP错误

  • 闲置阶段(idleprepare):仅系统内部使用

  • 轮询阶段(poll):获取新的I/O事件, 例如操作读取文件等等,适当条件下node将在这里阻塞

  • 检查阶段(check):setImmediate()回调函数在这里执行

  • 关闭事件回调阶段(close callback):一些关闭的回调函数,如:socket.on('close', fallback)

alt

stream流

  • Readable:可读流

  • Writable:可写流

  • Duplex:继承可读流和可写流,为双工流

  • Transform:继承双工流,作为可写流使用后会自动将写入内容保存在缓冲区并转化为可读流

多进程

Node.js是以单线程的模式运行的,但它使用的是事件驱动来处理并发,这样有助于我们在多核 cpu的系统上创建多个子进程,从而提高性能

Node提供了child_process模块来创建子进程,方法有三种:

  • exec:直接在创建新进程时声明处理数据响应的回调函数
  • spawn:通过监听新进程的事件来获取响应数据并用回调函数进行处理
  • fork:与子进程之间拥有管道,可以直接将子进程的响应在主进程执行

多线程

  • worker_threads模块允许使用并行地执行JavaScript的线程
  • child_processcluster不同,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]))

alt

基本数据结构

  • 数组:查找快,增删慢

  • 栈:先进后出

  • 队列:先进先出

  • 优先级队列:进入队列后会根据优先级排序

  • 链表:查找慢,增删快

    链表反转:


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
}

  • 双向链表:双向查找,提高查找效率

  • 哈希表:哈希表效率高原因,底层是数组,下标查找快,增删因为其他元素哈希值不变,不会影响下标,所以效率也高,普通数组会因为数组长度改变,下标改变受影响,所以效率慢,解决冲突常用两种方法:链地址法和开放地址法

  • 树:二叉树

常见设计模式

  • 单例模式:保证一个类仅有一个实例,并提供一个它的获取函数,获取时先判断实例存在与否,如果存在则直接返回已有实例,如果不存在就创建一个新实例返回(vuexstore等)

  • 订阅者-发布者模式:订阅者订阅事件并设置回调,当事件产生时,发布者通知该事件所有订阅者,订阅者根据回调对事件做出响应(点击事件等)

  • 代理模式:为对象提供一个代用品或占位符,以便控制对它的访问(proxy等)

  • 工厂模式:不关心内部的逻辑,通过传入不同的参数对象来返回不同的结果(react源码生成不同种类ReactComponent等)

  • 装饰者模式:在不改变原对象基础上,给对象动态添加方法(ES6装饰器等)

全部评论

相关推荐

03-29 14:19
门头沟学院 Java
你背过凌晨4点的八股文么:加油同学,人生的容错率很高,只是一个暑期罢了,后面还有很多机会!
点赞 评论 收藏
分享
评论
1
5
分享

创作者周榜

更多
牛客网
牛客企业服务