网络
握手
三次握手
为什么不是两次?
防止第一次客户端发出建立连接请求延迟,当过了一段时间服务端才接受到时,如果没有再次确认就直接发送,造成资源浪费
四次挥手
http1.1
长连接:在一个TCP连接上可以传送多个http请求,避免了因为多次简历TCP连接的时间消耗和延时
缓存:新加了协商缓存,Etag、If-Modified-Since、If-Match、If-None-Match
带宽优化:请求头新加了range,支持断点续传
http2
多路复用
二进制传输
头部压缩
服务端push
缺点:丢包的时候会影响到连接上的流 效率低
hppt3
TCP协议出现丢包整个连接都要等待重传,在3里面改用UDP协议
udp tcp
udp
无连接
不可靠、高效
tcp
ARQ
等待ARQ
连续ARQ
滑动窗口
http、https
https使用443端口,http使用80端口
https需要申请证书
https经过加密传输更更安全
https比http慢
https加密方式
- 对称加密
客户端和服务端双方约定好一个加密秘钥,使用公用秘钥进行消息加密解密。秘钥泄漏内容就不安全了
- 非对称加密 RSA算法
客户端和服务端各自有自己的公钥和私钥,公钥用来加密,私钥用来解密。公钥给对方用来对消息加密,私钥自己用来解密。中间人可以用篡改公钥的方式获取或者修改传输内容,而且性能较差。
- 第三方认证
在非对称加密中不知道公钥是服务端返回的还是中间人返回的,所以加入一个第三方认证的环节。第三方使用私钥加密我们的公钥,浏览器使用第三方公钥解开加密过的公钥,如果能解开整正确。
中间人可以通过自己申请证书掉包原证书
- 数字签名
服务段本地生成一堆秘钥,拿着公钥、企业、网站信息到第三方中心(CA)申请数字证书,浏览器通过本地内置的CA公钥解开数字证书并验证。
安全
CSRF
跨站请求伪造:挟持用户在当前已登录的web应用程序上执行非本意的操作
假如黑客在自己的站点上放置了其他网站的外链,例如"www.weibo.com/api,默认情况下,浏…
防范
验证Token:浏览器请求服务器,返回token,每个请求都带上token才合法
设置SameSite: 设置cookie的SameSite,让cookie不随跨域请求发出
XSS
跨站脚本攻击:利用网页开发留下的漏洞,注入恶意指令代码到网页
存储型:攻击存储在服务器里
反射型:攻击者将脚本混在URL里面,服务端接收到URL奖恶意代码当做参数去除并拼接在HTML里返回。
DOM型:攻击脚本写在URL中,诱导用户点击,当URL被解析攻击脚本会被运行。不经过服务端
防范
输入检查:对输入内容的特定表情进行转义、过滤
设置httpOnly:防止攻击者获取用户cookie
开启CSP:开启白名单,可以阻止白名单外的资源加载和运行。
-
设置 HTTP Header 中的
Content-Security-Policy -
设置
meta标签的方式<meta http-equiv="Content-Security-Policy">
预检请求
它用于检查实际请求是否安全,以及服务器是否允许该请求。由浏览器自动发送的
当使用某些特定的 HTTP 方法(如PUT、DELETE等)或自定义的请求头时,浏览器会自动发送预检请求。预检请求使用 OPTIONS 方法发送到服务器,包含了实际请求的相关信息,例如请求方法、请求头等。
服务器在收到预检请求后,会检查请求头中的 Origin 字段,判断是否允许该请求。如果服务器允许该请求,会返回一个带有 CORS 相关头部信息的响应,浏览器在收到响应后,才会发送实际的请求。
预检请求的目的是确保跨域请求的安全性,防止恶意的跨域请求对服务器造成安全风险。它提供了一种机制,让服务器有选择地允许或拒绝跨域请求。
网络模型分层
同源策略
端口、协议、域名
同源策略是一种安全机制,它是浏览器对 JavaScript 实施的一种安全限制。所谓“同源”是指域名、协议、端口号均相同。同源策略限制了一个页面中的脚本只能与同源页面的脚本进行交互,而不能与不同源页面的脚本进行交互。这是为了防止恶意脚本窃取数据、进行 XSS 攻击等安全问题。
同源策略限制的资源包括:
Cookie、LocalStorage 和 IndexDB 等存储性资源
AJAX、WebSocket 等发送 HTTP 请求的方法
其他通过脚本或插件执行的跨域请求
这些资源只能与同源页面进行交互,不能与不同源的页面进行交互。
常见状态码
2XX:请求成功
200:客户端发送给服务器的请求被正确处理并返回
3XX:重定向
301:永久重定向
302:临时重定向
304:使用缓存
4XX:客户端错误
400:错误请求,一般是传参错误
401:未授权
403:服务器拒绝请求,一般是权限不足
404:未找到
5XX:服务器错误
500:服务器内部错误
501:服务器不具备完成请求的功能
502:无效响应
503:服务不可用
浏览器
缓存
优先级
1、Service Worker
2、meory cache(内存缓存)
3、disk cache(硬盘缓存)
4、push cache(推送缓存)
浏览器缓存策略
强缓存
Expires
Cache-Control
max-age:最大过期时间
private:响应只能被单个客户缓存,不能被代理服务器缓存
no-cache:缓存要经过服务器验证,浏览器使用缓存前对比 Etag
public:响应可以对任何对象缓存
no-store:禁止任何缓存
协商缓存
Last-Modified/If-Modified-Since
Etag/If-None-Match
垃圾回收
标记清除
优点:实现简单
缺点:造成内存碎片
引用计数
优点:清晰
缺点:无法解决循环引用问题
V8 优化
栈回收
调用栈上下文切换
堆回收
- 新生代内存
新生代内存分为 From、To 两部分,进行垃圾回收时先扫描 From,回收非存活对象,存活对象复制到 To 中
- 老生代
晋升:如果新生代变量经过多次回收依然存在,那么就会被放到老生代内存。
标记清除:回收不被使用的对象
引用计数、标记清除
引用计数:给一个变量赋值引用类型,则该对象引用次数 +1,如果这个变量变成其它值,该对象引用次数 -1。垃圾回收期会回收引用次数为 0 的对象。对象循环引用会造成内存无法释放。
标记清除:垃圾回收器先给内存中的对象加上标记,然后从根节点遍历,去掉被引用的对象和运行环境中对象的标记,剩下的清除。
整理内存碎片:把对象挪到一端惰性清理
内存泄漏
原因
意外的全局变量
未正确使用的闭包
游离的 dom 引用
事件监听器没有移除
循环引用
定时器没有清理
排查
使用浏览器自带的 performance 功能
使用浏览器的 mermory 进行分析
rail 模型
渲染过程
1、解析 HTML、生成 DOM 树。解析 CSS,生成 CSSOM 树。
2、将 DOM 树和 CSSOM 树结合,生成渲染树。
3、Layout(回流),根据渲染树进行回流,得到节点的几何信息(位置、大小)
4、Painting(重绘):根据渲染树以及回流得到的几何信息,得到节点的绝对像素
Dispaly:将像素发送给 GPU,展示在页面上。
重绘、回流
减少回流
使用 class 替换 style,减少 style 的使用。
使用 resize、scroll 进行防抖节流
使用 visibility 替换 display:none
批量修改元素时,先让元素脱离文档流,修改完毕再放回文档流。
复杂动画使用绝对定位使其脱离文档流
减少查询 offset、scroll、client。可以把值进行缓存起来。
回流:当元素尺寸或者位置发生变化需要重新计算渲染树
DOM 元素的几何属性发生变化:width、height、padding、margin、border
DOM 元素移动或者增加
读写 offset、scroll、client 等属性
调用 window.getComputedStyle
重绘:DOM 样式发生了变化,但没有营销 DOM 的几何属性
渲染合成层
合成方法
一般来说,拥有特定属性的渲染层会被浏览器自带提升为合成层,合成层拥有单独的图层。
1、transform: translateZ(0)
2、will-change 属性,可以启动 GPU 加速
3、video、canvas、iframe
4、可以通过 z-index 隐式合成
层压缩、层爆炸
当不符合预期的合成层达到一定量级时,会发生层爆炸,导致页面占用大量内存资源,可能导致页面白屏
浏览器会对合成层进行优化压缩
优缺点
优点:
1、开启硬件加速,合成层的位图会交由 GPU 合成,比 CPU 快
2、对于 transform 和 opacity 效果,不会触发 layout 和 paint
缺点:
1、过多的渲染工作交给 gpu,在现有的优化下,会导致渲染内存占用比大幅度提升,反而出现负面效果
2、隐式合成会残生大量意料之外的合成层,导致内存暂用页面卡顿
策略
使用 transform、opacity 来实现动画
谨慎使用 will-change
减少合成层绘制区域
v8 引擎
v8 引擎做了哪些优化
即时编译:如果一段代码被多次执行,那么 v8 会把这段代码转为机器码缓存下来,下次运行直接使用机器码
隐藏类:v8 借用了类和偏移位置的思想,将对象划分为不同的组,即隐藏类。
内嵌缓存:缓存对象查询的结果
垃圾回收管理
js 代码执行机制
预解析
生成 ast
生成字节码
生成机器码
垃圾回收机制
webpack
treeshaking
sideEffects
跳过整个模块/文件,直接查看该文件是否有副作用
usedExports
通过标记某些函数是否被使用,之后通过 Terser 来进行优化的
proxy
利用 http-proxy-middleware 这个 http 代理中间件,实现请求转发给其他服务器
服务器与服务器之间请求数据并不会存在跨域行为,跨域行为是浏览器安全策略限制
构建优化
- 缩短 loader 查找范围:include.exclude、
- 优化 resolve.alias 减少查找过程
- 使用 cache-loader
- 开启多进程
打包优化
- 文件压缩
- purgecss-webpack-plugin 清除无用 css
- splitChunks 分包配置
- 多入口按需加载(动态导入+代码分割)
- treeshaking(webpack4 自带)
- 使用 externals 优化 cdn 静态资源
- IgnorePlugin:忽略特定文件
工作流程
React
hooks
原理:基于函数组件的执行顺序和 hook 状态的管理,我们可以在函数组件添加状态、处理副作用操作、实现逻辑复用
优势
- 逻辑状态复用
- 函数组件的功能增强
- 更简洁的代码
- 方便跟踪理解组件的状态变化,进行精确更新渲染
缺点
依赖顺序:在使用多个 hooks 的组件中,hooks 的调用顺序如果发生错误,可能会导致组件状态不一致的副作用产生
fiber
是什么?
fiber 是 react 里面的一个性能优化方案。在 react 里面更新视图是通过虚拟 domdiff 更新的,在一些特殊情况下会有性能问题导致页面卡顿,用户操作事件无反馈等情况,为了解决这个问题 react 尝试对操作任务做时间切片,把调度权交还给浏览器,由浏览器优先进行页面帧率渲染和用户响应,减少卡顿。大致原理是 react 实现了一套 channelhandle 方法类似于谷歌 idelreconditon,对时间进行切片处理,然后 react 对任务进行优先级分类,大致分为 immidite user blocking normal low 根据时间切片当浏览器有空余时间的时候去处理队列中的事件,当低优先级时间长时间得不到执行的时候会根据超时时间进行优先级提升,这个设计有点像操作系统的任务调度系统。
做什么?
在数据更新时,react 生成了一棵更大的虚拟 dom 树,给第二步的 diff 带来了很大压力——我们想找到真正变化的部分,这需要花费更长的时间。js 占据主线程去做比较,渲染线程便无法做其他工作,用户的交互得不到响应,所以便出现了 react fiber。
React 通过 Fiber 架构,让这个执行过程变成可被中断。“适时”地让出 CPU 执行权,除了可以让浏览器及时地响应用户的交互,还有其他好处:
- 分批延时对 DOM 进行操作,避免一次性操作大量 DOM 节点,可以得到更好的用户体验;
- 给浏览器一点喘息的机会,它会对代码进行编译优化(JIT)及进行热代码优化,或者对 reflow 进行修正。
核心思想
它只是一种控制流程的让出机制。让出 CPU 的执行权,让 CPU 能在这段时间执行其他的操作。渲染的过程可以被中断,可以将控制权交回浏览器,让位给高优先级的任务,浏览器空闲后再恢复渲染。
18 的新特性
Concurrent Render 并发更新:根据用户的设备性能和网速对渲染过程进行适当的调整, 保证 React 应用在长时间的渲染过程中依旧保持可交互性,避免页面出现卡顿或无响应的情况,从而提升用户体验。
- 新的 root Api:通过 createRoot 手动创建 root 节点
- 自动批处理:所有更新都自动批处理
- useTransition:降低渲染优先级,给点是一个状态
- useDefferdValue:允许变量延时更新,给的是一个值
useEffect useLayoutEffect
useEffect 是异步,useLayoutEffect 是同步会阻塞浏览器渲染
forwardRef
ref 正常情况下没办法向子组件传递,但是可以通过 forwardRef 去实现
useImperativeHandle
把子组件的部分方法或值暴露给父组件
useMemo,useCallback
两者都用于缓存,useMemo 缓存计算结果,useCallback 缓存函数
hooks 为什么不能放条件语句
useState useEffect 每次调用都被添加到 Hook 链表中,动态的语句肯会导致调用 Hook 的顺序不一致,导致链表记录的数据失效
TS
interface 和 type 区别
1 写法不同。interface 后面直接跟定义 type 需要加=号
2、interface 可以多次重复定义,最终会合并为一个
3、扩展方式不同,interface 使用 extends,type 使用&
4、
infer
在 TypeScript 中,infer 是一种用于推断类型的关键字。它通常与条件类型(Conditional Types)一起使用,用于从给定的类型中提取或推断其他类型。
void 和 never 区别
void 表示没有返回值或空值。never 表示永远没有返回值。void 可以用于表达式或者函数,never 一般用于函数。never 一般代表终止程序执行
泛型
在定义函数、接口或类的时候,不预先指定具体的类型,而在使用的时候再指定类型的一种特性。
JS
都有哪些继承
原型链继承
- 通过将父类的实例作为子类的原型来实现继承。
- 优点:简单易用,能够继承父类的属性和方法。
- 缺点:所有子类实例共享父类的属性,无法传递参数给父类构造函数,无法实现多继承。
function Parent() {
this.name = 'Parent';
}
Parent.prototype.sayHello = function() {
console.log('Hello from Parent');
}
function Child() {}
Child.prototype = new Parent();
const child = new Child();
console.log(child.name); // 输出: Parent
child.sayHello(); // 输出: Hello from Parent
构造函数继承
- 在子类构造函数中调用父类构造函数,使用
call或apply方法将父类的属性和方法绑定到子类实例上。 - 优点:能够继承父类的属性和方法,每个子类实例都有自己的属性副本。
- 缺点:无法继承父类原型上的方法,每个子类实例都有自己的方法副本。
function Parent(name) {
this.name = name;
}
Parent.prototype.sayHello = function() {
console.log('Hello from Parent');
}
function Child(name) {
Parent.call(this, name);
}
const child = new Child('Child');
console.log(child.name); // 输出: Child
child.sayHello(); // 报错: child.sayHello is not a function
组合继承(class实现)
- 结合原型链继承和构造函数继承的方式。通过调用父类构造函数绑定属性,然后将父类的实例作为子类的原型。
- 优点:既能够继承父类的属性和方法,又能够继承父类原型上的方法。
- 缺点:调用了两次父类构造函数,子类实例上会有重复的属性。
function Parent(name) {
this.name = name;
}
Parent.prototype.sayHello = function() {
console.log('Hello from Parent');
}
function Child(name) {
Parent.call(this, name);
}
Child.prototype = new Parent();
Child.prototype.constructor = Child;
const child = new Child('Child');
console.log(child.name); // 输出: Child
child.sayHello(); // 输出: Hello from Parent
寄生继承
- 在原型式继承的基础上,增强对象,返回一个新对象。
- 优点:简单易用,能够继承父对象的属性和方法。
- 缺点:所有子对象共享父对象的属性,无法传递参数给父对象构造函数。
const parent = {
name: 'Parent',
sayHello: function() {
console.log('Hello from Parent');
}
};
function createChild(parent) {
const child = Object.create(parent);
child.sayHello = function() {
console.log('Hello from Child');
};
return child;
}
const child = createChild(parent);
console.log(child.name); // 输出: Parent
child.sayHello(); // 输出: Hello from Child
寄生组合继承
- 结合组合继承和寄生式继承的方式。通过调用父类构造函数绑定属性,然后将父类的原型克隆给子类。
- 优点:既能够继承父类的属性和方法,又能够继承父类原型上的方法,避免了重复调用父类构造函数。
- 缺点:相对复杂,需要额外的克隆操作。
function Parent(name) {
this.name = name;
}
Parent.prototype.sayHello = function() {
console.log('Hello from Parent');
}
function Child(name) {
Parent.call(this, name);
}
Child.prototype = Object.create(Parent.prototype);
Child.prototype.constructor = Child;
const child = new Child('Child');
console.log(child.name); // 输出: Child
child.sayHello(); // 输出: Hello from Parent
对象转基本数据类型
let a = {
valueOf() {
return 0
},
toString() {
return '1'
},
[Symbol.toPrimitive]() {
return 2
}
}
1 + a // => 3
== 和===
原型和原型链
在 JavaScript 中,原型(Prototype)是一个对象,它用来定义其他对象的共享属性和方法。每个对象都有一个原型对象,它是通过Object.prototype来访问的。
原型链是指一个对象的原型对象到另一个对象的原型对象之间的链接。当一个对象需要访问一个属性或方法时,如果在自身的属性或方法中找不到,它会沿着原型链依次查找,直到找到为止。
ESModule 和CommonJS
- CommonJS 一般动态导入, esmodule也可以通过import动态导入返回一个promise;
- CommonJS 是同步导入,因为用于服务端,文件都在本地,同步导入即使卡住主线程影响也不大。而后者是异步导入,因为用于浏览器,需要下载文件,如果也采用同步导入会对渲染有很大影响
- CommonJS 在导出时都是值拷贝,就算导出的值变了,导入的值也不会改变,所以如果想更新值,必须重新导入一次。但是 ES Module 采用实时绑定的方式,导入导出的值都指向同一个内存地址,所以导入值会跟随导出值变化
Node
Cluster
在 cluster 模块应用中,一个主进程只能管理一组工作进程。在 child_process 模块应用中,一个主进程能管理多组工作进程。cluster 是 child_process 和 net 模块的组合
Chikd-process
- spawn
- wxecFile
- Exec
- fork
并行&并发
并发的关键是有处理多个任务的能力,不一定要同时进行
并行的关键是有同时处理多个任务的能力
进程线程
区别
进程:独立空间
线程:空间、资源共享
多线程之间会共享同一块地址空间和所有可用数据的能力,这是进程所不具备的。
线程要比进程更轻量级,由于现场更轻,所以比进程更容易创建,也更容易撤销
通信
多线程通信:1、通过 work_threads 进行 postMessage 2、创建 MessageChannel 并发送给 worker
进程通信:命名管道、匿名管道、socket、信号量、共享内存、消息队列、Domain Socket
事件循环机制
1、Node.js 对宏任务做了优先级划分,从高到低分别是 Timers、Pending、Poll、Check、Close 这 5 种,也对微任务做了划分,也就是 nextTick 的微任务和其他微任务。执行流程是先执行完当前优先级的一定数量的宏任务(剩下的留到下次循环),然后执行 process.nextTick 的微任务,再执行普通微任务,之后再执行下个优先级的一定数量的宏任务
2、如果执行到 poll 阶段,发现 poll 队列为空并且 timers 队列、check 队列都没有任务要执行,那么就阻塞的等在这里等 IO 事件,而不是空转
- timer:执行定时器回调
- pending:io、网络的异常回调
- idel/prepare: 内部阶段
- poll:io 回调、网络 connection 回调
- check:setimmediate 回调
- close:关闭资源回调
进程调度策略
- 先来先服务
- 短作业优先
- 优先级调度
- 多级反馈队列调度
require 加载机制
-
计算模块路径
-
有缓存取缓存
-
如果是内置模块返回内置模块
-
生成模块实例存入缓存
-
调用 module.load 方法加载
设计模式
六大原则
- 开放封闭原则。
对扩展开放,对修改关闭,做到热插拔效果
- 里氏代换原则
有基类可以出现的地方,子类一定可以出现。当派生类可以替换掉基类并且软件单位的功能不受影响时,基类才能真正被复用。
- 依赖倒转原则
针对接口编程,依赖抽象而不是具体
- 接口隔离原则
降低类之间的耦合度
- 最少知道法则
一个实体应当尽量少的与其他实体进行相互作用,使得系统功能模块相对独立。
- 合成复用原则
进来使用合成、聚合的方式,而不是使用继承。
发布订阅/观察者
差异:
1、关注点不同,发布订阅模式中,发布中和观察者没有直接关联。观察者模式中两者直接关联。
2、耦合度:发布订阅是松耦合,观察者模式下紧密耦合相互关联。
3、事件处理方式不同:发布订阅模式下,事件不会立即执行,而是会按照顺序进行。而观察者模式下事件处理事同步的
优缺点:
1、发布订阅事件流式异步的,流转状态负责调试困难
2、发布订阅模式中发布者不知道订阅者存在,可能会出现每一正常取消订阅的情况导致内存泄漏
函数柯里化
是一种将接受多个参数的函数转换为一系列接受单个参数的函数的技术。通过函数柯里化,我们可以将一个函数的参数逐个传递,每次传递一个参数,返回一个新的函数,直到所有参数都被传递完毕并执行最终的操作。
优势
参数复用:通过柯里化,我们可以将一个函数的部分参数固定下来,生成一个新的函数,方便在后续调用中复用这些参数。
延迟执行:柯里化可以延迟函数的执行。每次传递一个参数后返回一个新的函数,我们可以在需要的时候再执行最终的操作。
函数组合:柯里化可以方便地进行函数组合。通过将多个柯里化函数组合在一起,可以构建出更复杂的函数。
CSS
文本溢出···
overflow: hidden; // 溢出隐藏
text-overflow: ellipsis; // 溢出用省略号显示
display:-webkit-box; // 作为弹性伸缩盒子模型显示。
-webkit-box-orient:vertical; // 设置伸缩盒子的子元素排列方式:从上到下垂直排列
-webkit-line-clamp:3; // 显示的行数