梳理了一些知识图谱~
概览
详细描述
JavaScript
基础
原型与原型链
new 操作符
- 在内存中创建一个新对象
- 将对象内部的__proto__ 赋值为构造函数的 prototype 属性(原型链挂载)
- 将构造函数的this 指向新对象(this 绑定)
- 执行构造函数的代码
this 指向问题
核心: 谁调用就指向谁- 方法调用的时候, this 指向指向方法所在的对象。
- 当没有明确调用方的时候,this 就指向window,比如 setTimout 这种异步函数、匿名函数
- 箭头函数比较特殊,它在经过 babel 编译后,会用 _this 记录定义时候的this 对象,来实现一个固定this 的效果。
call/bind/apply
这三个都是改变this 指向的方法。- call、apply 几乎完全一样,只有第二个参数类型的区别。call 是多个参数传递,apply 是多个参数组成一个数组参数传递。
- bind 更类似于函数的延迟调用,传入一个作用域后返回一个新的函数,在调用新函数的时候传入对应参数。
闭包
闭包 =『函数』和『函数体内可访问的变量总和』
核心: 当一个变量被内部函数使用的时候,就产生了一个闭包。
一般用途:存储一些内部变量,如 ts 编译私有变量, 就是通过闭包实现的。
缺点:内存常驻,无法通过GC 回收。- 柯里化
可以理解为闭包的进阶用法,利用闭包和 apply/call 来达到延迟调用的目的。
一般用于函数式库中的链式调用 如 lodash fp 中 sum(2)(3) = 5
- 柯里化
事件循环
JavaScript 是单线程,非阻塞式的语言,为了实现非阻塞式的异步编程,就需要事件循环机制来处理任务。
在一次完整的JavaScript任务中,分为 同步任务和异步任务,异步任务又分为宏任务和微任务。- 微任务
优先级略微高点的异步任务,在同步任务执行完毕后,优先执行,代表如 promise.then 、mutationObserver 等 - 宏任务
在执行栈最底部调用,代表如 setTimeout、setInterval、IO 操作、UI 操作等。
Node 中的事件循环
Node 的事件循环和浏览器中的事件循环是两个不同的机制,它分为6个阶段,分别是- Timer, 执行到期的 setTimeout、setInterval 回调
- IO 阶段,主要处理上一次循环残留的 callback
- idle 阶段
- poll: 等待回调
- 执行回调
- 执行定时器
- 如果有到期的 setTimeout、setInterval,返回 Timer 阶段
- 如果有 setImmediate,则前往 check 阶段
- check 执行 setImmediate 的callback
- close callbacks 执行一些close 的回调, 比如 server.close、socket.close
- 微任务
关键帧回调
- requestAnimationFrame
多用于动画帧处理,在浏览器每次刷新的时候调用,以达到更流畅的动画效果 - requestIdleCallBack
在 React 的Fiber 架构中,有个任务调度的概念,这个方法就类似于 Fiber 任务调度
如果硬件配置比较高,浏览器在执行完一帧任务后,会有一定的空闲时间,这个时间就可以通过 requestIdleCallBack 去调用,从而来实现 空闲时执行任务的目的,它不会影响主线程执行。
- requestAnimationFrame
V8 的垃圾回收策略
分代式垃圾回收机制,根据对象的存活时间将内存的垃圾回收进行不同的分代,然后对不同分代采用不同的垃圾回收算法
新生代:
用于存放存活时间比较短的对象。再将新生代内存空间细分为两个空间 From 和 To,在程序执行过程中,会对From 空间的数据进行检查,如果发现有对象没有被使用(失活),就将此对象赋值到 To 空间中,如果有对象没有被其他地方引用,则对其进行垃圾回收(清除From),至此第一次线程任务执行完毕,From 和 To 完成身份互换。
在第二次任务执行的时候,重复上述操作,回收失活对象。
老生代:
当一个数据在 From、To 中完整的转移一次后,会认为是生命周期比较长的对象,在下一次垃圾回收的时候,会将其存储到老生代内存中(晋升)
老生代的算法有所变更,会采用标记清除
和标记整理
的方式来进行管理。垃圾回收器会基于 window 去遍历可访问的子节点,然后将这些节点标记为活动节点。如果发现某些属性不能访问或者无法遍历(失去作用域链),就对其进行垃圾回收。ES6、ES7
- let、const
let、const 和var 的区别就在于他们俩不会变量提升,只会有一个暂存死区(在预编译阶段实现) - Promise
简单点讲就是在构造函数执行的时候,对 .then 中的回调进行收集,存储到内部维护的_callback
数组中,在执行 resolve 的时候循环调用回调。
同时在内部维护三个状态Pending
rejected
fulfilled
, 用来控制任务状态。 - Async、Await
可以理解为 promise 的同步语法糖,可通过 async await 将 promise 转变成同步写法(实际还是异步)。
在通过 babel 编译后,具体会通过 while 循环来实现。 - Proxy
Vue3 的核心监听方法就是通过 Proxy 来实现的, 可以监听一个 target,并绑定一个handle 对象,handle 对象中有 set、get 属性。 - Symbol
- let、const
模块化
- AMD
requireJS 的默认加载规范,主要特征为依赖前置,在执行前就将依赖都加载完毕。 - CMD
seaJS 提出的加载规范,主要特征为随用随加载(依赖就近原则)。 - UMD
- CommonJS
Nodejs 的模块规范,一个单独的文件就是一个模块,加载的时候使用 require() 来加载,返回内部的 exports 对象 - ES6 Module
- AMD
跨页面通讯
- postMessage
- serviceWorker
Vue2
基本原理
基于发布订阅者模式以及观察者模式双向绑定原理
Vue2 中通过 defineProperty 来监听 options.data 中的数据,为数据添加 getter/setter,使其变成响应式数据。
当用户访问、修改响应式数据的时候,就会被拦截器拦截,从而进行后续的操作,如 watcher、dep 变更、diff 以及 patch。
但是同时也存在一些问题,defineProperty 无法拦截到数组的原型链操作,如push、slice,或者对象的 delete 操作符,vue2 通过原型方法重新的方式来解决这些问题。
当然好处就是兼容性好,支持到 IE8+。Observe
监听者,即内部通过 defineProperty来实现数据监听的一个函数。Dep
为发布订阅模式中的,订阅器,主要用于保存订阅者。
Vue 会为每个属性绑定上监视器,当属性值被修改的时候(触发 setter/set),就触发该值对应的 Dep.notify 来通知 subs(订阅者数组,由多个 Watcher 订阅者组成的数组),Watcher
观察者,也是发布订阅模式中的订阅者。
每个Vue 实例都会创建一个 Watcher 对象,Watcher负责接受 Dep 对象的变化,并且触发 re-render。依赖收集
Dep 收集Watcher 到subs,并存储到每个响应式数据中的这个过程就叫做依赖收集虚拟DOM
Vue2 采用了和 React 类似的虚拟Dom,一个虚拟DOM 就是包含了tag 信息、属性信息、依赖事件、子节点、父节点、兄弟节点的对象。
虚拟Dom 的优点是可以通过 Diff 计算差异,生成新的DOM 节点,随后进行一次性更新真实DOM,减少浏览器重排时间,进而提高性能。AST 语法树
Vue2 采用 .vue 模板形式开发,通过vue-template-loader
来编译文件的同时,就可以生成 AST 语法树并分析数据依赖。
所谓 AST 语法树则和 虚拟DOM比较类似,是一个树形的DOM 节点列表。For Key
为什么列表循环中需要Key?
Vue 的 Diff算法 & 就地复用策略决定。
在 Diff 过程中,Vue 会对比 tag名称、属性等来判断节点是否更新,如果Key 存在的话,Vue 会优先使用 Key 进行判断,性能会好很多。nextTick
在下次dom 更新结束后执行延迟回调,内部采用微任务 > 宏任务 来实现,优先通过 promise.then/requestAnimateRequest 来做,如果不支持,则通过 setTimeout来实现。
Diff 算法
Vue2 采用基于深度遍历的查找算法,然后同层对比,在对比同层的时候,采用单循环双指针同层对比(头头尾尾头尾)
对比时优先对比Key,如果Key 相同则认为是同元素,不执行 update 操作。
如果没有key(非列表元素)则对比 tagName/attrs/class 等具体属性,有差异则在 vNode中标记 update。keep-alive 原理
Vue3
- 基本原理
vue3 中使用了 Proxy 来代替 defineProperty,最直接的好处就是 proxy 可以直接监听一个对象,而非一个属性,并且可以监听到数组的原型方法。
proxy 中内置的拦截方法比较多, 基本可以满足所有拦截需求。 - watch
作用等同于 Vue2 的Watch,监听一个响应式数据,当此响应式数据发生变化的时候,触发对应的回调函数 - watchEffect
vue3 新增,区别于 watch,watch 是明确监听一个响应式数据,而 watchEffect 是隐式的监听内部数据
,并且在初始化的时候就立即执行回调函数。
更像 React 里面的 useEffect,一个副作用函数。 - watchPostEffect
在DOM 更新完成后执行。 - reactive
Vue3 的核心监听函数,内部会递归调用 new Proxy 将 data 数据转化为 proxy 对象 - proxy 拦截器
Vue3 在创建Proxy 时,对对象进行拦截,使用了 set/get/deleteProperty/has/ownKeys 拦截器
保证可以监听到对象的各种操作 - VueX
- Mobx
- 基本原理
React
基本原理
- 与Vue 的区别
- 基本原理不同:React 与 Vue 的设计思路不同,React 是Push 的方式去通知框架数据发生了变更,Vue 是通过依赖收集和双向绑定自动
监听数据变化,从而通知DOM 更新。 - DIFF 方式不同,React diff 采用的是fiber 加持下的单向链表依次对比,Vue 采用的是双向链表双指针头尾对比。
- React 存在 fiber 的time slice 调度,vue 没有
- 基本原理不同:React 与 Vue 的设计思路不同,React 是Push 的方式去通知框架数据发生了变更,Vue 是通过依赖收集和双向绑定自动
- 与Vue 的区别
Time Slice
- 时间切片,因为 JavaScript 单线程的局限性,为了尽可能提高用户的观感执行速度,
React 将比较耗时的任务拆分为微小地执行单元,通过内部实现的调度算法来决定先执行哪些比较重要的任务块。
- 时间切片,因为 JavaScript 单线程的局限性,为了尽可能提高用户的观感执行速度,
Fiber 架构
- 为了方便模拟JavaScript 调用栈,Fiber 提供了一种新的单向链表数据结构,用来替代原先的虚拟dom 节点 VNode,其中会包含一些节点信息、子节点信息、下一个节点信息,每个节点都是一个fiber 对象,模拟了调用栈帧后,方便在内存中记录执行状态,随时的中断或恢复
- 在以往的版本中,React 是一边Diff 一边提交更新,现在 Fiber 设计了两个阶段
- Reconciliation 协调阶段
此阶段可认为是 React 的Diff 阶段,此阶段会找出所有节点的变更,如节点的新增、删除、更新等,这些变更就被成为React 的副作用 (Effect),
这个阶段会调用 constructor/shouldComponentUpdate/render 生命周期。 - Commit 提交阶段
首先将上个阶段的Effect 一次性执行,此阶段不能被中断,必须同步执行,会触发 componentDidMount/componentDidUpdate/componentWillUnmount 生命周期。
- Reconciliation 协调阶段
- 协调阶段如果时间片用完,React 会选择让出控制权,因为协调阶段做的事情不会导致用户可见的变更。
- 而Commit 阶段中,用户的副作用(逻辑)操作被认为是优先级最高的,所以必须同步一次性按顺序执行。
如何优化 React
- 使用 React.memo 来缓存组件
- 使用 useMemo、userCallback 来缓存大量计算
- 如果有必要,使用 React.PureComponent 来优化函数组件
- PureComponent 会进行浅比较来判断是否更新组件,如果 Props 的值相同,就不会更新组件,节省一些开支。
- 避免使用内联对象,内联对象每次render 后都会创建一个新的引用地址,会被 React 认为是 props 更新。
- 尽可能少的使用匿名函数,原因同上。
- 对一些组件延迟加载,如使用 React.lazy()
- 使用 React.Fragment 来创建 Fiber 节点,而非Dom 元素
Angular
基本原理
- 不同于 Vue、React,Angular 继续使用 ng1 的数据脏检查来实现数据变动侦测,通过 zone.js 对一些用户关键操作,如setTimeout/form change/event 重写,在特定的时间
遍历数据,检测哪些数据发生变化,并更新dom。 - Angular 并没有像 Vue、React 一样采用虚拟Dom, 它使用了 WebComponent 中的 ShadowDOM 来做组件隔离和DOM 渲染
- 相比 React 的函数式设计思想、Vue 的组合式设计思想,Angular采用了传统的程序设计,采用了 数据模型、控制器、视图的结构划分,通过依赖注入来维护关系,内部深度依赖了 RX.js 来进行任务流编程。
- 不同于 Vue、React,Angular 继续使用 ng1 的数据脏检查来实现数据变动侦测,通过 zone.js 对一些用户关键操作,如setTimeout/form change/event 重写,在特定的时间
依赖注入
来自JAVA 的一种设计模式,在 Angular 中,通过 Typescript 的注解器来实现了 @Injection 方法,将模型传递给对应的被依赖的控制器。AOT & JIT
Angular 的编译模式,同样来自JAVA,分别为预先编译、运行时编译
Typescript
- 类型校验
- 装饰器
- 元编程
工具库
- RxJS
- Lodash
CSS
基础
选择器
盒模型
- 标准模型
- 怪异模型
BFC
自适应布局
Flex
Sass
Less
工程化
WebPack
构建流程
- 初始化参数
- 开始编译
- 从入口开始分析代码、加载文件
- 完成文件编译,执行插件
- 输出资源
Loader & Plugins
- Loader 是文件加载所用,Plugins 是各种各样的插件
Rollup
- 摇树优化
Vite
前端优化
http 优化
- 代码压缩,减少体积
- 服务器开启 Gzip
- splitChunks 拆包,按需加载
- 图片使用内联或者雪碧图
- 开启http2 首部压缩
- 合理使用 http 缓存
- 使用 nginx 中间件,将多个请求合并
- 利用 CDN 加速 vender
交互优化
- 减少页面的重排
- 优化动画效果,减少用户等待感知
- 骨架屏优化
- 图片懒加载
前端安全
- XSS 注入
- 即通过不安全的输入框、请求参数等,将攻击代码传输到服务器
- 一般情况下,后端框架会去做 xss 过滤校验,如果需要,前端可以在用户输入的地方进行一次前置性过滤
- 不过此方法依然不安全,前端代码是可见的,用户可任意篡改
- CSRF 请求伪造
- 即攻击者通过使用用户cookie 模拟用户请求
- 服务器可每次生成一个 token,存储在 session 中,跟随每次请求返回,客户端在下次请求的时候将这个token 传递给后端校验,用过一次后失效,生成新的token 。
浏览器
页面加载过程
JavaScript 的预编译
重绘、重排
浏览器在发现DOM 元素发生变化的时候会重新渲染。- 当发现DOM 的空间属性发生变化(如 zIndex、显示隐藏、位置变化、大小变化)的时候,会引起 DOM 重排。
- 当发现 DOM 的颜色等不影响布局的属性发生变化时,会引起 DOM 重绘。
- 重排一定会引起重绘,重绘不会引起重排
缓存
- 强缓存
如 Cache-Control/Expires ,浏览器判断缓存是否过期,如果没有过期就直接使用缓存信息,不经过服务器确认。
其中 Cache-Control 的 max-age 优先级高于 Expires。
当发现缓存已过期的时候,就检查协商缓存。 - 协商缓存
目前用的比较多的缓存策略,浏览器请求一次服务端,服务端返回对应的返回头,浏览器通过 header 信息来检查是否需要更新资源,常见的控制协商缓存的字段有:- etag 优先级较高
- if-none-match
- last-modified 最后修改时间,如果一致,服务端返回 304
- if-modified-since
- 强缓存
跨域
同源策略
浏览器的安全策略决定只能同源进行post、put 等高危操作,如果发现不同源,则拦截该请求,这个策略即同源策略。解决跨域
- CORS
在服务端设置请求头access-control-allow-origin
白名单,如果设置为 * 则代表允许所有域请求。
开启跨域后,需要设置 withCredentials 为 true ,cookie 才可以正常携带。 - JSONP
很古老的技术,原理是通过get 请求一个script 脚本,返回一个函数,前端接收到后执行,因为get 请求没有跨域问题,基本淘汰。 - 网关代理
比较优雅的解决方案,通过网关代理,将不同域的请求转化为同域,以解决跨域。比如通过 nginx 中的 rewrite 转发。
在webpack dev 中的proxy 也是基于代理做的。
- CORS
简单请求和复杂请求
请求方法为 GET、POST、HEAD,且请求头中只包含下列字段的请求,就叫做简单请求,- Accept
- Accept-Language
- Content-Language
- Content-Type
- DPR
- Downlink
- Save-Data
- Viewport-Width
- Width
Option 嗅探
在跨域发出复杂请求前, 浏览器会触发一个 Option 类型的请求,用来鉴定是否可以正常通讯。
后端
Node
Python
- flask
PHP
- CI
- lumen
通用
Cookie & Session
网络协议
TCP
- 三次握手
- 客户端发送 syn请求,进入 syn_send 状态,等待确认
- 服务端接受到 syn 请求,发送 syn + ack 包,进入 syn_recv 状态
- 客户端接受到 syn+ack 后,发送 ack 包,双方建立连接
- 四次挥手
- 客户端发送 fin 给服务端,服务端进入 fin-wait 状态
- 服务端发送 ack 给客户端,进入 close-wait 状态
- 服务端发送 ack+fin 给客户端,进入last-act 状态
- 客户端 发送 act 给服务端,进入 closed,关闭连接
- 三次握手
UDP
和 TCP 相比,UDP 是一种无连接协议,不需要建立链接的单向发送包,不需要维护状态,比如实时语音聊天HTTP
- 状态码
- 200 成功
- 301 永久重定向
- 302 临时重定向
- 304 资源未修改,使用缓存
- 400 请求错误
- 401 需要认证
- 403 权限问题,拒绝请求
- 404 不存在
- 500 服务器错误
- 502 网关错误
- 504 超时
- HTTP 2.0
新增了多路复用、首部压缩、服务端推送。 - HTTPS
网关设置端口为 443,并且配置 SSL 公钥证书,优点是会进行隧道加密。
- 状态码
Rest API
常见的的Rest 请求类型分为
- get
- post
- put
- patch
- delete
其中 get、put、delete 都应该是幂等操作
设计模式
- 工厂模式
- 代理模式
- 观察者模式
算法
DFS
深度优先算法,Vue、React 的Diff 查找中都使用此算法,在遍历的时候优先查找一个分支,当最终节点没有子节点的时候,重新返回父级查找兄弟节点。BFS
广度优先算法