├── CSS ├── @media.md ├── CSS3 新特性 & 可不可以继承的 CSS 属性.md ├── JPEG & PNG & GIF & SVG & WebP.md ├── display & visibility & opacity.md ├── flex & z-index.md ├── line-heigh 1.5 & 150%.md ├── margin 重叠.md ├── textarea 根据输入文字长度自动调整大小而不出现滚动条.md ├── transition & transform & animation & @keyframes & 红绿灯.md ├── translateX(-100px) & left -100px.md ├── 三点骰子布局.md ├── 两个 div 元素设置为 inline-block,它们间有 4px 间距.md ├── 单行文本隐藏 & 多行文本隐藏.md ├── 定位 & 盒模型 & BFC.md ├── 实现三个子盒子上中下排列 + 中间的高度自适应.md ├── 实现两列三行.md ├── 实现九宫格.md ├── 实现偶数列有背景颜色.md ├── 实现吸顶导航栏.md ├── 实现图片自适应容器.md ├── 实现垂直居中.md ├── 实现头部导航栏 + 内容区域 + 页脚的布局.md ├── 实现文本单行居中 + 多行靠左.md ├── 实现某元素宽 20 过渡为 40.md ├── 实现毛玻璃.md ├── 实现水平垂直居中.md ├── 实现水平居中.md ├── 实现第 1 个屏幕上下 2 种颜色 + 后面 N 个屏幕 1 种颜色.md ├── 实现雪碧图 & 精灵图.md ├── 左右固定中间自适应.md ├── 引入 CSS 的方式 & 解决样式冲突 & CSS 模块化 & 样式穿透 & CSS 解析选择器 & 权重 & 伪类 & 伪元素.md ├── 查找 div 下最后一个 p 标签.md ├── 清理浮动 & 解决高度塌陷.md ├── 画 0.5px 的线.md ├── 画圆 & 三角形 & 平行四边形 & 六边形 & 扇形.md ├── 画立方体.md ├── 获取全部非空 table 项.md └── 颜色表示方式.md ├── Computer Network ├── Access Token & Refresh Token.md ├── CDN & DNS.md ├── CORS & 二次预检.md ├── CSRF.md ├── Chrome HTTP1.1 同一域名最大 6 个并发连接.md ├── Cookie & Session.md ├── DNS host 文件配置错误.md ├── DOMContentLoaded & load & 浏览器对象 & 浏览器渲染 & 浏览器进程线程 & 浏览器图层 & 重绘回流 & V8.md ├── GET & POST.md ├── HTTP & HTTPS & HTTP1.1 & HTTP2 & HTTP3 & SSL & TLS 握手 & 数字签名 & 判断是否为长连接.md ├── HTTP 数据传输方式.md ├── HTTP 请求头 & 响应头.md ├── IPv4 & IPv6.md ├── Iframe.md ├── JWT.md ├── Last-Midified & Etag 局限性.md ├── MAC.md ├── OSI 七层模型.md ├── PM2.md ├── Ping.md ├── TCP & UDP │ ├── HTTP Keep-Alive & TCP keepalive.md │ ├── MTU.md │ ├── TCP & UDP 与 IP 交互 & 发送数据.md │ ├── TCP & UDP 共用端口.md │ ├── TCP & UDP.md │ ├── TCP 上的协议.md │ ├── TCP 传送数据过程中断电 & 崩溃.md │ ├── TCP 保证可靠性.md │ ├── TCP 头的标志位.md │ ├── TCP 报文组成.md │ ├── TIME_WAIT.md │ ├── UDP 数据包大小.md │ ├── 三次握手的原因.md │ ├── 为什么有了 TCP 还要用 UDP 实现一个可靠性传输.md │ ├── 半连接 & 全连接.md │ ├── 四次挥手的原因.md │ ├── 数据从发送到接收的拷贝次数.md │ ├── 查看 TCP 占用内存情况.md │ ├── 第四次挥手若客户端 kill.md │ ├── 粘包.md │ └── 限制 TCP 最大连接数.md ├── WebSocket & Web Worker & Service Worker.md ├── XSS.md ├── localStorage & sessionStorage & 检测 localStorage 存储溢出.md ├── preload & prefetch.md ├── token 失效.md ├── 三次握手四次挥手.md ├── 前后端交互.md ├── 前端缓存 & 资源更新问题.md ├── 反向代理.md ├── 多端适配.md ├── 密码登录.md ├── 强缓存 & 协商缓存.md ├── 扫码登录.md ├── 本地 hosts 文件.md ├── 检测设备机型 & 检测桌面浏览器和移动浏览器.md ├── 浏览器兼容.md ├── 清除浏览器缓存并删除 token,如何保持登录状态?.md ├── 状态码.md ├── 离线包.md ├── 跨域方式.md ├── 跨域设置 Cookie.md ├── 输入 URL 到页面展示的全过程.md └── 鉴权.md ├── Data Structure ├── Array.prototype.sort.md ├── DFS & BFS.md ├── 二叉搜索树 & 平衡二叉树.md ├── 判断有向图是否存在环.md ├── 十大排序时空复杂度.md ├── 哈希冲突.md ├── 堆 & 栈.md ├── 拓扑排序.md ├── 时间复杂度 & 空间复杂度.md ├── 树 & 图.md ├── 稳定 & 不稳定.md ├── 红黑树 & B 树 & B+ 树.md ├── 链表 & 数组.md └── 队列.md ├── Front-End Engineering ├── JS 错误检测.md ├── Tree-Shaking & 代码分割.md ├── WebView & JSBridge.md ├── Webpack & Vite.md ├── git fetch & git pull & git merge & git rebase & git revert & git reset & git stash & 版本回滚 & 解决冲突.md ├── loader & plugin & import & require & esm & commonjs & pnpm & npm & module & chunk & bundle.md ├── require 原理 & 在浏览器中执行.md ├── 两个 JavaScript 文件循环引用.md ├── 内存泄漏检测.md ├── 前端工程化.md ├── 微信小程序.md ├── 执行 npm run dev 到页面展示的全过程.md ├── 把静态资源打包为同一 Bundle.md └── 白屏检测.md ├── HTML ├── Attribute & Property.md ├── Canvas.md ├── HTML5 新特性.md ├── alt & title.md ├── src & href.md ├── 自定义属性.md ├── 行内元素 & 块级元素.md └── 语义化.md ├── JS(practical aspects) ├── AJAX.md ├── Array.prototype.fill.md ├── Array.prototype.flat.md ├── Array.prototype.flatMap.md ├── Array.prototype.forEach & map & filter.md ├── Array.prototype.includes.md ├── Array.prototype.push & unshift.md ├── Array.prototype.reduce.md ├── Array.prototype.reverse.md ├── ArrayReader.md ├── DOM to JSON.md ├── DOM 节点查找.md ├── Event.Bus.md ├── JSON to DOM.md ├── JSON.parse.md ├── JSON.stringify.md ├── JSONP.md ├── Map & Set.md ├── O(n)时间查找数组排序后的最大差值.md ├── Object.assign.md ├── Object.create.md ├── Object.freeze.md ├── Object.is.md ├── Promise A+.md ├── Promise.all.md ├── Promise.allSettled.md ├── Promise.any.md ├── Promise.race.md ├── String.prototype.includes.md ├── String.prototype.repeat.md ├── String.prototype.trim.md ├── Virtual DOM && Render && Diff && Patch.md ├── Vue Router.md ├── Vuex.md ├── apply & call & bind.md ├── async & await.md ├── compose.md ├── curry.md ├── debounce & throttle.md ├── findDuplicate.md ├── instanceof.md ├── isString.md ├── lazyMan.md ├── localStorage 存储数据.md ├── lodash.get.md ├── mergePromise.md ├── new.md ├── requestAddition.md ├── setTimeout 实现 setInterval.md ├── sleep.md ├── tic-tac-toe.md ├── todoList & 拖拽.md ├── util.promisify.md ├── v-model.md ├── 一次 setState 一次更新.md ├── 三进制转十进制.md ├── 上传文件.md ├── 下拉刷新 & 单选 & 全选.md ├── 两数之和最大的所有组合数.md ├── 串行 pipeline.md ├── 串行网络请求.md ├── 事件冒泡和事件捕获阶段的点击事件弹窗.md ├── 事件委托.md ├── 二分查找.md ├── 倒计时.md ├── 函数缓存.md ├── 分红包.md ├── 列表单选 & 多选 & 取消选择.md ├── 判断对象是否为空.md ├── 判断数组是否为等差数列.md ├── 判断数组是否含重复值.md ├── 判断数组里各天的微信步数是否满足传入时间范围内的微信步数.md ├── 判断点是否在矩形内.md ├── 十大排序.md ├── 千分位转化.md ├── 单例模式.md ├── 原生路由.md ├── 合并对象.md ├── 合法 URL.md ├── 后端返回的对象数组属性名转换.md ├── 后端返回的对象数组根据 date 重排.md ├── 图片懒加载.md ├── 大数相加.md ├── 字符串转小写后按字典序并去重.md ├── 字符串转嵌套对象.md ├── 定时器.md ├── 寄生组合式继承.md ├── 对象拍平.md ├── 对象数组重叠区间.md ├── 对象转换为路径数组.md ├── 并发限制 Scheduler.md ├── 弹窗.md ├── 微信红包.md ├── 快速幂.md ├── 找出两个数组或对象数组中不共有的元素.md ├── 找到毒药的最少次数.md ├── 抖音移动端界面和刷视频功能.md ├── 数组乱序.md ├── 数组元素奇前偶后.md ├── 数组去重.md ├── 数组对象转换.md ├── 数组转换为对象.md ├── 数组转树.md ├── 日期格式化.md ├── 日期转化:一年第几天.md ├── 时间地区转换.md ├── 时间格式化输出.md ├── 有向连通图.md ├── 构造小于给定数的最大数.md ├── 查找 24 小时制时间列表中最小时间差.md ├── 栈.md ├── 树的 DFS & BFS.md ├── 树转数组.md ├── 根据给定字符串获取对象属性值.md ├── 检测数组是否有序.md ├── 模板引擎.md ├── 汉字数字转整数.md ├── 浅拷贝 & 深拷贝 & lodash.cloneDeep.md ├── 深度比较.md ├── 瀑布流.md ├── 牛顿法求根号 n.md ├── 生成区间 (left, rigth) 的 n 个不重复的数字.md ├── 登录页面.md ├── 短横线与驼峰转化.md ├── 系统内存占用.md ├── 红绿灯.md ├── 给定字符串, 找出所有长度 Num 的重复子串.md ├── 统计所有 url 中每天访问的 url.md ├── 翻转字符串中的每个单词.md ├── 自定义 React Hook.md ├── 荷兰国旗问题.md ├── 观察者模式.md ├── 解决回调地狱.md ├── 解析 URL 的查询字符串为对象.md ├── 解析依赖顺序.md ├── 计算器.md ├── 计算异步函数执行时间.md ├── 请求 5s 内无结果则提示超时.md ├── 请求失败重试.md ├── 调用一次函数获取下一质数.md ├── 路由导航主页 & 详情页.md ├── 轮播图.md ├── 递增数组格式化输出.md ├── 链式调用 add 函数.md ├── 链式调用实现加减乘除.md ├── 闭包实现计数器.md ├── 隔 1 秒输出 1,持续输出至隔 10 秒输出 10.md ├── 隔 s 秒执行一次,共执行 n 次.md └── 颜色字符串转换.md ├── JS(theoretical aspects) ├── Array & Object & Map & Set & WeakMap & WeakSet.md ├── BOM & DOM.md ├── ES6 & ES7.md ├── Generator & Async & Await.md ├── JS 单线程.md ├── JS 设计模式及原则.md ├── MutationObserver.md ├── NaN && === && ==.md ├── Promise.md ├── TS 与类型体操.md ├── TS 书写类型强制 & 非强制字段.md ├── TS 以属性值来为类型.md ├── TS 内置工具类型.md ├── TS 枚举.md ├── TS 装饰器.md ├── TS 设置只读.md ├── TypeScript.md ├── apply & call & bind.md ├── async & defer.md ├── for of & for in & Object.keys & forEach & map & 就地改变数组 & 返回新数组.md ├── null & undefined.md ├── requestAnimationFrame & requestIdleCallback.md ├── this & 箭头函数 & new & 类数组.md ├── var & let & const & 定义全局变量 & 变量提升 & 函数提升.md ├── 事件冒泡 & 事件捕获 & 事件委托 & window.addEventListener & event.target & event.currentTarget.md ├── 事件循环.md ├── 作用域.md ├── 判断两个 JavaScript 对象是否相等.md ├── 判断数据类型 & 判断数组.md ├── 原型 & 原型链 & 创建无原型的对象.md ├── 原生 JavaScript & 前端框架.md ├── 取消请求 & Axios 函数对象 & 拦截器 & AJAX.md ├── 可以继承的方法.md ├── 垃圾回收机制.md ├── 异步编程.md ├── 数据类型 & 值传递 & 引用传递 & 0.1 + 0.2 & 018 - 017 & 隐式类型转换.md ├── 数组增删改查的方法.md ├── 数组遍历的方法.md ├── 触发事件 & 添加事件.md ├── 闭包.md └── 面向过程 & 面向对象.md ├── LICENSE ├── My Interview ├── Aurora System 一面(OC).md ├── CSDN 一面(26min 过).md ├── Momenta 二面(67min 挂).md ├── Momenta 一面(61min 过).md ├── PatSnap 一面(62min OC).md ├── Pony.AI 一面(45min 挂).md ├── RightCaptial 一面(43min 挂).md ├── TCL 一面(20min 挂).md ├── VAST 一面(65 min 过).md ├── YY 一面(44min 挂).md ├── eBay 一面(44min 挂).md ├── 三维家(61min OC).md ├── 三维家(90min 挂).md ├── 亿迅科技(64min OC).md ├── 同程旅行一面(55min OC).md ├── 哈啰出行一面(40min 过).md ├── 哈啰出行二面(42min OC).md ├── 商汤科技一面(72min 过).md ├── 商汤科技二面(52min 过).md ├── 字节 Data AML 一面 & 二面 & 三面(挂).md ├── 字节 Tiktok 国际化广告一二面(HR 面挂).md ├── 字节抖音直播一面(挂).md ├── 字节飞书一面(72min 挂).md ├── 实在智能一面(20min 过).md ├── 实在智能二面(48min OC).md ├── 小米一面(67min 过).md ├── 小米二面(76min OC).md ├── 小红书一面(68min 挂).md ├── 小红书一面(72min).md ├── 小鹅通(53min 挂).md ├── 帆软一面(75min OC).md ├── 得物青训一面(27min OC).md ├── 微信公众号一面 & 微信小店一面.md ├── 快手商业化一面(61min 挂).md ├── 快手效率工程一面(69min 过).md ├── 快手效率工程二面(59min 挂).md ├── 恒生电子一面(25min 挂).md ├── 有乐今天一二面(95min OC).md ├── 海康威视一面(11min 挂).md ├── 海康威视一面(69min OC).md ├── 清华课题组(20min OC).md ├── 滴滴一面(49min 过).md ├── 滴滴网约车一面 & 二面(OC).md ├── 玄武云一面(88min OC).md ├── 百度二面(70min 挂).md ├── 科大讯飞一面(54min OC).md ├── 科大讯飞一面(58min 挂).md ├── 经纬恒润一面(68min OC).md ├── 网易云音乐一面(50min OC).md ├── 网易传媒一面(46min 过).md ├── 网易传媒二面(67min OC).md ├── 美团到店一面(挂).md ├── 美团商户保险一面(76min).md ├── 美团平台一面(44min 挂).md ├── 美的一面(61min OC).md ├── 英视睿达一面(30min OC).md ├── 蔚来一面(87min 挂).md ├── 蔚来供应链一面(69min 挂).md ├── 货拉拉一面(76min 过).md ├── 趣链科技(55min 过).md ├── 钛动科技一面(55min 挂).md └── 高顿一面(56min).md ├── Node.js ├── Express & Koa.md ├── Nest.js 生命周期 & 装饰器.md ├── Next.js 布局 & 路由 & 获取及更新数据 & CSR & SSR & SSG & ISR.md ├── 代理参数校验.md ├── 创建进程.md ├── 同步 & 异步方法.md ├── 处理高并发.md ├── 实现容灾.md ├── 封装接口.md ├── 执行类似 DOM 操作.md ├── 模块.md ├── 模块化.md ├── 流式读取 & 直接读取.md └── 错误处理.md ├── OS ├── 内存调度 & 磁盘调度算法.md ├── 死锁.md ├── 物理内存 & 虚拟内存.md ├── 状态机.md ├── 线程同步机制.md ├── 进程 & 线程.md ├── 进程切换.md ├── 进程管理.md ├── 进程调度算法 & CPU 调度.md ├── 进程间通信.md └── 银行家算法.md ├── README.md ├── React ├── Fiber & 调度过程.md ├── JSX & React.createElement & React.cloneElement.md ├── React Native.md ├── React router │ ├── history & hash.md │ ├── react-router-dom.md │ ├── 动态路由.md │ ├── 路由的原理.md │ └── 路由的配置.md ├── React 的性能优化.md ├── React-Redux.md ├── React17 & React18.md ├── Redux.md ├── State & Props.md ├── react-dom 包.md ├── 不可变数据.md ├── 事件机制.md ├── 单向数据流 & 双向数据绑定.md ├── 受控组件 & 非受控组件.md ├── 在函数组件中调用子组件中的方法 & 暴露子组件数据给父组件.md ├── 生命周期.md ├── 类组件 & 函数组件.md └── 组件通信.md ├── Vue ├── CSS scoped.md ├── MVC & MVVM.md ├── Vue & React.md ├── Vue Router.md ├── Vue2 & Vue3.md ├── Vuex.md ├── data 返回函数.md ├── keep-alive.md ├── key.md ├── method & computed & watch & watchEffect.md ├── ref & reactive.md ├── v-if & v-show & v-for & v-model.md ├── 全局组件 & defineComponent & mixin.md ├── 插槽全家桶.md ├── 模板编译.md ├── 父子组件通信 & 父组件调用子组件的方法.md ├── 生命周期钩子 & 其他钩子 & $nextTick.md ├── 自定义指令.md └── 访问数据的优先级.md └── type-challenge ├── Absolute.md ├── Append Argument.md ├── Append to object.md ├── Capitalize.md ├── Chainable Options.md ├── Deep Readonly.md ├── Diff.md ├── First of Array.md ├── Flatten.md ├── If.md ├── Includes.md ├── KebabCase.md ├── Last of Array.md ├── Length of String.md ├── Merge.md ├── Permutation.md ├── Pop.md ├── Promise.all.md ├── Push.md ├── Replace.md ├── ReplaceAll.md ├── String to Union.md ├── Trim Left.md ├── Trim.md ├── Tuple to Union.md ├── Type Lookup.md ├── Unshift.md ├── concat.md ├── 元组转换为对象.md ├── 获取元组长度.md └── 获取函数参数类型.md /CSS/@media.md: -------------------------------------------------------------------------------- 1 | 1. 屏幕宽高 `width` 和 `height`:`@media (min-width: 600px) { ... }` 2 | 2. 设备宽高 `device-width` 和 `device-height`:`@media (min-device-width: 800px) { ... }` 3 | 3. 方向 `orientation`:`@media (orientation: landscape) { ... }` 4 | 4. 纵横比 `aspect-ratio`:`@media (aspect-ratio: 16/9) { ... }` 5 | 5. 分辨率 `resolution`:`@media (min-resolution: 2dppx) { ... }` 6 | 6. 颜色 `color`:`@media (min-color: 2) { ... }` 7 | 7. 打印 `print`:`@media print { ... }` 8 | -------------------------------------------------------------------------------- /CSS/JPEG & PNG & GIF & SVG & WebP.md: -------------------------------------------------------------------------------- 1 | WebP 图片大小比 JPEG 或 PNG 小,但其压缩和解压缩过程更为复杂,浏览器解析 WebP 图片需更多的计算工作,因此在快网环境下其加载和渲染时间不如 JPEG 或 PNG 文件 2 | 3 | 1. Joint Photographic Experts Group:通过离散余弦变换和量化技术进行压缩,适用于需压缩以减少大小的彩色自然场景图片,传输大量图片,但有损压缩,不支持透明背景 4 | 2. Portable Network Graphics:通过 DEFLATE 压缩算法进行压缩,适用于 Logo 和高质量透明背景图片,无损压缩,支持透明背景,但不适合传输大量图片 5 | 3. Graphics Interchange Format:通过 Lempel-Ziv-Welch 压缩算法进行压缩,轻量级,无损压缩,支持动画,但支持 256 种颜色 6 | 4. Scalable Vector Graphics:基于 XML 的矢量图片格式,适用于 Logo 和动画,轻量级,无损压缩,支持无限缩放 7 | 5. WebP:通过 VP8 视频编码和 Riff 文件容器技术进行压缩,轻量级,无损压缩,支持动画和透明背景,压缩效率高于 JPEG 和 PNG,但兼容性较差,通过 HTML5 `picture` 指定多个源图片,浏览器选择其支持的首个图片格式加载 8 | 9 | -------------------------------------------------------------------------------- /CSS/display & visibility & opacity.md: -------------------------------------------------------------------------------- 1 | `display: none`:元素及其子元素从 Render Object 树中移除,不占据页面空间也不参与页面布局,隐藏元素时触发回流,同时由于其不存在于渲染树中,元素的 JavaScript 事件无法被触发 2 | `visibility: hidden`:元素及其子元素均不可见但仍存在于 Render Object 树中,占据页面空间并参与页面布局,隐藏元素时触发重绘,同时由于其仍存在于渲染树中,元素仍响应绑定的 JavaScript 事件程序 3 | `opacity: 0`:元素及其子元素均不可见但仍存在于 Render Object 树中,占据页面空间并参与页面布局,隐藏元素时触发重绘,同时由于其仍存在于渲染树中,元素仍响应绑定的 JavaScript 事件程序,此外 `opacity: 0` 还创建独立于默认文档流的复合图层 4 | -------------------------------------------------------------------------------- /CSS/line-heigh 1.5 & 150%.md: -------------------------------------------------------------------------------- 1 | `line-height: 1.5` 基于当前元素的 `font-size` 计算,随当前字体自动缩放,子元素继承比例系数,根据自身的 `font-size` 动态计算 2 | `line-height: 150%` 基于父元素的 `font-size` 计算,子元素父元素计算后的固定像素值 3 | 4 | ```css 5 | .parent { 6 | font-size: 16px; 7 | line-height: 1.5; 8 | /* 16px * 1.5 = 24px */ 9 | } 10 | 11 | .child { 12 | font-size: 14px; 13 | /* 14px * 1.5 = 21px */ 14 | } 15 | 16 | .parent { 17 | font-size: 16px; 18 | line-height: 150%; 19 | /* 16px * 150% = 24px */ 20 | } 21 | 22 | .child { 23 | font-size: 14px; 24 | /* 24px */ 25 | } 26 | ``` -------------------------------------------------------------------------------- /CSS/margin 重叠.md: -------------------------------------------------------------------------------- 1 | margin 重叠是指在某些情况下,两个垂直方向上相邻的块级元素之间的 `margin` 不会相加,而是选择其中的一个最大值作为实际的 `margin` 2 | 3 | 1. 相邻兄弟元素的 margin 重叠:如果两个块级元素相邻,且没有任何内容,也没有 padding、border 将它们分开,它们的垂直 `margin` 将重叠 4 | 5 | ```CSS 6 |
` 或 ` | `,该 ` | ` 或 ` | ` 包含非空白内容
2 |
3 | ```js
4 | const getNoEmptyTables = () => {
5 | const tables = document.querySelectorAll("table");
6 | const noEmptyTables = Array.from(tables).filter((table) => {
7 | const trs = table.querySelectorAll("tr");
8 | return Array.from(trs).some((tr) => {
9 | const cells = tr.querySelectorAll("td, th");
10 | return Array.from(cells).some((cell) => cell.textContent.trim() !== "");
11 | });
12 | });
13 | return noEmptyTables;
14 | };
15 |
16 | const noEmptyTables = getNoEmptyTables();
17 | console.log(noEmptyTables);
18 | ```
19 |
20 | ![[1712416976748.png]]
--------------------------------------------------------------------------------
/CSS/颜色表示方式.md:
--------------------------------------------------------------------------------
1 | ```css
2 | .color {
3 | color: red;
4 | color: #FF0000;
5 | color: rgb(255, 0, 0);
6 | color: rgba(255, 0, 0, 0.5);
7 | }
8 | ```
9 |
10 | RGBA:Red + Green + Blue + Alpha 接受四参数:红绿蓝值 0-255 和 Alpha 值 0-1,0 完全透明,1 完全不透明
11 | HEX:`#` + 6 位十六进制数,前两位代表红,中两位代表绿,后两位代表蓝,转换时,从 HEX 的十六进制数值中提取相应红绿蓝三色的十六进制值,将其转换为十进制数值,再结合给定 Alpha 值,通过 `rgba` 函数生成最终的颜色字符串
12 |
13 | ```JavaScript
14 | const hexToRgba = (hex, alpha = 1) => {
15 | let r = 0,
16 | g = 0,
17 | b = 0;
18 | if (hex.length === 4) {
19 | r = parseInt(hex[1] + hex[1], 16);
20 | g = parseInt(hex[2] + hex[2], 16);
21 | b = parseInt(hex[3] + hex[3], 16);
22 | } else if (hex.length === 7) {
23 | r = parseInt(hex[1] + hex[2], 16);
24 | g = parseInt(hex[3] + hex[4], 16);
25 | b = parseInt(hex[5] + hex[6], 16);
26 | }
27 | return `rgba(${r}, ${g}, ${b}, ${alpha})`;
28 | };
29 | ```
30 |
--------------------------------------------------------------------------------
/Computer Network/Access Token & Refresh Token.md:
--------------------------------------------------------------------------------
1 | Access Token:用于访问受保护的资源如 API,客户端在有效期内可用其认证状态和访问权限,其具有较短的有效期,风险相对较低,即便被泄露,攻击者也只能在短时间内使用它
2 | Refresh Token:用于在 Access Token 过期后获取一个新的 Access Token,无需用户再次登录,Refresh Token 有更长的有效期,其安全性比 Access Token 高,因为它只用于与认证服务端通信,而不是直接用来访问受保护的资源
3 |
4 | 1. 用户首次登录时,基于 OAuth 2.0 或 OpenID Connect 的认证服务端在认证用户后,同时发送 Access Token 和 Refresh Token 给客户端
5 | 2. 客户端使用 Access Token 访问受保护资源,若有效,则请求成功,若无效或过期,则使用 Refresh Token 来获取一个新的 Access Token,认证服务端验证 Refresh Token 的有效性,若验证成功,发送新的 Access Token
6 | 3. 客户端使用新的 Access Token 访问受保护资源,直到 Access Token 再次过期,若 Refresh Token 也过期,用户需重新登录
7 |
--------------------------------------------------------------------------------
/Computer Network/CDN & DNS.md:
--------------------------------------------------------------------------------
1 | Content Delivery Network 是基于 DNS 协议实现的一种分布式网络,它使用边缘计算原理,通过在各地部署多个节点服务端,将网站内容分发到距离用户最近的节点,从而更快更可靠地将内容发送给用户
2 |
3 | - CDN 提供商在全球范围内的多个地点部署节点服务端,这些节点缓存源服务端上的内容
4 | - 当用户请求一个通过 CDN 服务加速的网站或资源时,该请求首先到达 DNS 服务器,DNS 解析系统解析并返回最接近用户且负载较低的 CDN 节点的 IP 地址,即负载均衡
5 | - 用户请求被重定向到所选的 CDN 节点,用户从该节点获取内容,该节点看起来就像用户的本地服务器,若请求的内容已缓存在该节点,直接由此节点发送给用户,若内容未被缓存,该节点从源服务器或其他节点获取内容,进行缓存,并将其发送给用户
6 | - CDN 节点定期检查内容的有效性,并依据缓存策略更新,确保节点上的内容是最新的
7 |
8 | CDN 上的资源来自不同域名的原因:
9 |
10 | 1. 内容分发
11 | 2. 并行下载:浏览器对同一域名的并行下载数量有限制,大多数浏览器限制单个域名的并发连接数为 6 个左右,通过多个域名并行下载可突破这一限制
12 | 3. Cookie:主域名设置的 Cookie 被发送到所有子域名,为静态资源使用不同域名以避免发送无效的 Cookie,减少不必要的数据传输
13 | 4. 缓存:提高缓存利用率,不同静态资源被分配于不同子域名上,减少缓存冲突
14 |
15 | Domain Name System 作为域名和 IP 地址之间的映射系统,允许人们使用易于记忆的域名如 `example.com` 来访问特定服务端,而服务端实际上是通过 IP 地址如 `192.168.1.1` 来定位的
16 |
17 | DNS 查询流程:浏览器 DNS 缓存 -> OS 本地 `host` 文件 -> OS DNS 缓存 -> 网络请求 -> 本地服务器 -> 根域名服务器 -> 顶级域名服务器 -> 权威域名服务器
18 |
19 | DNSSEC 域名系统安全扩展验证 DNS 查询,返回响应并缓存相应信息,每个 DNS 记录均有 TTL,TTL 过期后删除缓存,后续请求重新触发完整的 DNS 查询流程
20 |
21 | 分层通过负载均衡分散压力,三级分层在实践中被证明既能满足全球互联网需求,又能保持稳定有效
22 |
--------------------------------------------------------------------------------
/Computer Network/Chrome HTTP1.1 同一域名最大 6 个并发连接.md:
--------------------------------------------------------------------------------
1 | HTTP/1.1 虽支持持久连接,即通过一个 TCP 连接依次处理多个 HTTP 请求,但各个连接在同一时间只能处理一个请求即无真正的并发性,为提升性能,Chrome 默认为同一域名(准确的说,包括协议域名端口号)维护一个最多包含 6 个 TCP 连接的连接池,由于每个 TCP 连接一次只能处理一个请求,因此 6 个连接最多允许同时处理 6 个请求,当超过 6 个请求时,后续请求被放入队列等待空闲连接,若某个请求处理时间过长,同一连接上的后续请求被阻塞,这是 HTTP/1.1 的固有缺陷,此外 Chrome 根据请求类型和页面加载阶段动态调整请求优先级,优先加载关键渲染路径资源
2 |
3 | 1. 域名分片:将资源分配给多个子域名,各个子域名独立使用 6 个 TCP 连接
4 | 2. 端口分片:通过后端配置并支持多个端口监听,前端随机选择不同端口的 URL 发送请求
5 | 3. HTTP2 升级:HTTP/2 支持多路复用,允许在单个 TCP 连接上并发传输多个 HTTP 请求
6 |
--------------------------------------------------------------------------------
/Computer Network/DNS host 文件配置错误.md:
--------------------------------------------------------------------------------
1 | 1. 定向错误:若 `hosts` 文件将域名错误指向不正确的 IP,用户在访问该域名时将被导航到错误的服务端
2 | 2. 服务中断:应用程序依赖于 `hosts` 文件配置来连接网络服务,若 `hosts` 文件将域名错误指向不正确的 IP,应用程序将无法连接到该网络服务
3 | 3. 安全风险:若 `hosts` 文件将域名错误指向不正确的 IP,用户在不知情的情况下输入敏感信息如用户名和密码等,有较大的安全风险
4 | 4. 调试困难
5 |
6 | ###### 排查错误
7 |
8 | - 验证文件路径是否正确,通常为 `C:\Windows\System32\drivers\etc\hosts`
9 | - 通过 `ping` 命令检查域名解析是否指向正确的 IP 如 `ping example.com` 应返回 `hosts` 文件中指定的 IP
10 | - 清除 DNS 缓存:在 Windows 中使用 `ipconfig /flushdns` 命令
11 |
--------------------------------------------------------------------------------
/Computer Network/HTTP 请求头 & 响应头.md:
--------------------------------------------------------------------------------
1 | 请求头:
2 |
3 | - `Content-Type` 报文类型
4 | - `Content-Length` 内容长度
5 | - `Accept` 允许接收的数据类型
6 | * `Accept-Encoding` 允许接收的压缩方式
7 | - `Cookie` 用户凭证,包含服务器通过 `Set-Cookie` 响应头发送的一个或多个 Cookie
8 | - `Connection` 完成传输是否关闭 TCP 连接,`keep-alive`(保持连接)和 `close`(关闭连接)
9 | - `Host` 访问的主机域名,指定请求的目标服务器
10 | - `User-Agent` 浏览器类型,标识发起请求的客户端类型
11 | - `Cache-Control` 缓存策略,如 `no-cache`、`max-age` 等
12 | - `Expires` 缓存策略,已被上面的替代
13 |
14 | 响应头:
15 |
16 | * `Content-Type` 报文类型
17 | * `Content-Length` 内容长度
18 | * `Content-Encoding` 内容编码,指定响应体使用的压缩方式
19 | * `Set-Cookie` 设置 Cookie
20 | * `Access-Control-Allow-Origin` CORS
21 | * `Connection` 完成传输是否关闭 TCP 连接,`keep-alive`(保持连接)和 `close`(关闭连接)
22 | * `Server` 服务器信息,如 `Apache`、`Nginx`
23 | * `Location` 重定向目标地址
24 | - `Cache-Control` 缓存策略,如 `no-cache`、`max-age` 等
25 | - `Expires` 缓存策略,已被上面的替代
26 | - `Etag` 协商缓存限制条件,提供资源的唯一标识符(通常是一个哈希值),用于确定资源在服务器上是否已被修改
27 | - `Last-Modified` 协商缓存限制条件,标识资源最后一次被修改的时间
28 |
--------------------------------------------------------------------------------
/Computer Network/IPv4 & IPv6.md:
--------------------------------------------------------------------------------
1 | IPv4 和 IPv6 用于网络设备间的通信
2 |
3 | 1. 地址长度
4 |
5 | - IPv4:32 位地址长度即 4 个十进制数,每个数字的范围从 0 到 255,以点号分隔如 192.168.0.1
6 | - IPv6:128 位地址长度即 8 组十六进制数,每组由 4 个字符组成,以冒号分隔如 2001:0db8:85a3:0000:0000:8a2e:0370:7334。这种格式允许压缩,省略内部的 0 以简化表示
7 |
8 | 2. 地址空间
9 |
10 | - IPv4:提供约 43 亿个独立地址,但不满足全球互联网设备日益增长的需求
11 | - IPv6:提供几乎无穷个独立地址即 3.4×10383.4×1038 个地址,可以满足全球互联网设备日益增长的需求
12 |
13 | 3. 地址分配和配置
14 |
15 | - IPv4:地址配置可手动完成或通过动态主机配置协议 DHCP 自动完成
16 | - IPv6:除支持类似 IPv4 的配置方式外,还支持无状态地址自动配置 SLAAC
17 |
18 | 4. 分组处理和路由
19 |
20 | - IPv4:头部结构相对复杂,包含许多选项,可在通信过程中修改,分组处理和路由较为复杂
21 | - IPv6:头部结构简单,不包含校验等不常用的选项,简化分组处理和路由
22 |
23 | 5. 广播和多播
24 |
25 | - IPv4:支持广播即发送数据包给网络上的所有设备
26 | - IPv6:不支持广播,支持多播即发送数据包给一组特定的目的地
27 |
28 | 6. 安全性
29 |
30 | - IPv4:未内置加密和认证,依赖应用层或第三方解决方案如 IPsec
31 | - IPv6:提供端到端的加密和数据完整性验证
32 |
33 |
--------------------------------------------------------------------------------
/Computer Network/Iframe.md:
--------------------------------------------------------------------------------
1 | Inline Frame 为 HTML 元素,其通过创建一个独立的 DOM 和 JavaScript 环境,允许在一个页面中显示其他页面内容,同时与原页面保持交互独立性,若 iframe 与外部页面同源,则两者可直接通过 JavaScript 访问彼此的全局变量、函数和 DOM 等,否则即 iframe 与外部页面跨域,通过 `postMessage` 和 `onmessage` 进行跨域通信
2 |
3 | ```html
4 |
6 | ```
7 |
8 | 1. 通过 `sandbox` 属性限制 Iframe 的行为
9 |
10 | - `allow-same-origin`:保持同源策略
11 | - `allow-scripts`:允许执行脚本
12 | - `allow-forms`:允许提交表单
13 | - `allow-popups`:允许打开新窗口
14 |
15 | 2. 模块化开发,独立加载子模块,适合微前端架构
16 | 3. 在 Chromium 中各个 Iframe 均创建自己独立的渲染进程,大量 Iframe 导致内存占用高,页面卡顿,同时搜索引擎可能忽略 Iframe 内容,对 SEO 不友好,响应式设计需动态调整宽高
17 | 4. XSS 攻击即嵌入恶意脚本的 Iframe,点击劫持即透明 Iframe 覆盖按钮,诱导用户点击,Phishing 钓鱼攻击即伪造登录页面的 Iframe
18 |
--------------------------------------------------------------------------------
/Computer Network/JWT.md:
--------------------------------------------------------------------------------
1 | JSON Web Token 定义一种自包含的方式,作为 JSON 对象用于在客户端和服务端间传递信息,它由三部分组成,用句点 `.` 分隔:
2 |
3 | ```JSON
4 | const token = base64urlEncoding(header) + '.' + base64urlEncoding(payload) + '.' + base64urlEncoding(signature)
5 | ```
6 |
7 | - Header 由两部分组成:令牌类型 `type` 和所使用的算法 `alg` 如 HMAC SHA256 或 RSA
8 |
9 | ```JSON
10 | {
11 | "alg": "HS256",
12 | "typ": "JWT"
13 | }
14 | ```
15 |
16 | - Payload 包含一个实体 (通常为用户) 和其他数据的声明集
17 |
18 | ```JSON
19 | {
20 | "loggedInAs": "admin",
21 | "iat": 1422779638
22 | }
23 | ```
24 |
25 | - Signature 由 header 和 payload 加密生成,确保它们在传输过程中未被篡改
26 |
27 | ```JSON
28 | HMAC_SHA256(
29 | secret,
30 | base64urlEncoding(header) + '.' +
31 | base64urlEncoding(payload)
32 | )
33 | ```
34 |
35 | ###### 工作流程
36 |
37 | - 当用户登录成功后,服务端返回一个 JWT 给客户端
38 | - 客户端在 localStorage 或 cookie 中储存该 JWT
39 | - 当客户端再次请求服务端时,在请求头中携带该 JWT
40 | - 服务端接收到请求后验证该 JWT,若验证成功,服务端认为该请求是合法的
41 |
42 | ###### 为什么使用 JWT ?
43 |
44 | - 实现无状态化,服务端无需存储 token,每次客户端发送请求,只需携带该 JWT
45 | - JWT 轻量、结构简单,可通过 URL、POST 参数或在 HTTP header 里发送
46 | - JWT 的信息集是自包含的,其中包含所有用户需要的信息,避免多次查询数据库
47 |
48 | ###### 局限性
49 |
50 | 虽然 JWT 提供一种简单的认证方法,但存在安全隐患,若其被拦截,它可被任何拥有该 JWT 的人使用,因此需用 HTTPS 进行传输
51 |
--------------------------------------------------------------------------------
/Computer Network/Last-Midified & Etag 局限性.md:
--------------------------------------------------------------------------------
1 | `Last-Modified`:协商缓存限制条件,标识资源最后一次被修改的时间
2 |
3 | 1. 时间精度限制:`Last-Modified` 以秒为单位表示时间,若在一秒内多次修改资源,`Last-Modified` 无法准确反映最新状态,导致浏览器无法获取到最新资源
4 | 2. 弱验证:`Last-Modified` 只能告诉浏览器资源最后修改的时间,不能有效识别内容实际上不变而只是修改时间的情况,导致资源重新下载
5 | 3. 依赖服务端:`Last-Modified` 的值依赖于服务端的时间设置,若服务端时间有误,或客户端和服务端间存在时差,则影响协商缓存验证的准确性
6 |
7 | `Etag`:协商缓存限制条件,提供资源的唯一标识符,通常为哈希值,用于确定资源在服务端上是否已被修改
8 |
9 | 强弱验证:强验证 -> 内容有任何变化,`ETag` 即变化,而弱验证在一定程度上缓解该问题,但其判断标准模糊
10 |
11 |
--------------------------------------------------------------------------------
/Computer Network/MAC.md:
--------------------------------------------------------------------------------
1 | MAC 媒体访问控制层为数据链路层的子层,由硬件和固件结合实现,硬件用于生成和校验 MAC 帧头部及 CRC 校验码,实现 CSMA/CD 算法以检测信号电平变化和碰撞检测,而在发生碰撞时,执行退避算法,等待随机退避时间后重传,固件则与操作系统网络协议栈对接,提供上层协议数据传递、配置接口和错误处理
2 |
3 | - 地址标识:各个网络设备均有唯一的 MAC 地址,用于在局域网内标识网络设备,MAC 层在帧中嵌入源和目的 MAC 地址以实现数据的定向传输
4 | - 帧封装与解封:MAC 层负责将上层数据封装成帧并在接收方解析它们以验证数据完整性
5 | - 访问控制:控制对共享传输介质的访问,保证同一时间只有一个设备在发送数据
6 |
7 |
--------------------------------------------------------------------------------
/Computer Network/OSI 七层模型.md:
--------------------------------------------------------------------------------
1 | OSI 七层模型:各层均接收由它下一层所提供的特定服务且负责为自己的上一层提供特定服务,上下层间进行交互时所遵循的约定即接口,同一层间的交互所遵循的约定即协议,协议分层就如同计算机软件中的模块化开发,OSI 七层模型的建议是比较理想化的,它希望实现从第一层到第七层的所有模块并将它们组合起来实现网络通信,分层可将各层独立使用,即使系统中某些分层变化,也不会影响整个系统。通过分层细分通信功能,易于实现各个分层的协议并界定各个分层的具体责任和义务。而分层的局限性即过分模块化,逻辑复杂化,每个分层不得不实现相似的业务逻辑
2 |
3 | 1. 应用层:用于为应用提供服务并规定应用通信相关细节如 HTTP、SSL/TLS、DNS 和 SSH
4 | 2. 表示层:用于数据格式编码和转换,将应用固有的数据格式转换为网络标准传输格式,或将来自下层的数据转换为上层能够处理的格式
5 | 3. 会话层:用于管理会话,建立、维护和终止进程间通信及数据分割
6 | 4. 传输层:用于端对端通信,确保数据传输的完整如 TCP 和 UDP
7 | 5. 网络层:用于路由选择和寻址,实现数据包从源到目的地的传输如 IP
8 | 6. 数据链路层:用于物理层面上节点间数据传输,将物理层传输的比特流转换为数据帧并检测和纠正错误如 PPP 点对点协议协议、以太网协议、无线局域网协议、HDLC 高级数据链路控制协议
9 | 7. 物理层:用于在物理媒介上传输比特流,进行 01 比特流与电压高低、灯光闪灭的转换
10 |
11 | ![[Pasted image 20240530172822.png]]
12 |
--------------------------------------------------------------------------------
/Computer Network/Ping.md:
--------------------------------------------------------------------------------
1 | Ping 基于 ICMP 协议(网络层),用于检测网络设备间的连通性、延迟和丢包情况
2 |
3 | 底层原理:
4 |
5 | 1. 源主机构造 ICMP Echo Request 报文(Type=8),包含序列号和时间戳,通过 IP 层发送至目标主机
6 |
7 | ```
8 | | IP Header | ICMP Header (Type=8) | Payload (Data) |
9 | ```
10 |
11 | 2. 目标主机接收到报文后,若网络正常且未被防火墙拦截,生成 ICMP Echo Reply 报文(Type=0),原样返回 Payload
12 | 3. 源主机接收到报文后,根据发送和接收时间差得出延迟,计算 RTT 往返时间,并通过发送次数与应答次数计算丢包率
13 |
--------------------------------------------------------------------------------
/Computer Network/TCP & UDP/HTTP Keep-Alive & TCP keepalive.md:
--------------------------------------------------------------------------------
1 | HTTP/1.1 默认开启 Keep-Alive,允许在一个 TCP 连接上发送和接收多个 HTTP 请求/响应,而无需每次交换数据时都重新建立连接,节约网络带宽和 CPU 资源,减少首屏加载时间,服务端为各个保持活动状态的连接分配资源,若有大量空闲连接,则消耗过多资源。客户端在 HTTP 请求头中包含 `Connection: keep-alive`,告诉服务端希望保持连接打开,以便发送后续请求,服务端同样在响应头中回复此字段,表示它同意保持连接打开
2 |
3 | TCP Keepalive:若在指定的时间内,连接上无任何数据传输,TCP 自动定期发送保活探测包即 Keepalive 探针以检测对方是否仍可响应,确认连接的有效性,若在指定时间内未接收响应,则认为连接已失效,自动关闭连接
4 |
5 | - 层次不同:HTTP Keep-Alive 工作在应用层,而 TCP Keepalive 工作在传输层
6 | - 配置:HTTP Keep-Alive 通过 HTTP 头部控制,依赖 HTTP 实现,TCP Keepalive 则由操作系统提供配置选项
7 | - 用途不同
8 |
9 |
--------------------------------------------------------------------------------
/Computer Network/TCP & UDP/MTU.md:
--------------------------------------------------------------------------------
1 | Maximum Transmission Unit:在一个网络传输层上可通过的最大数据包大小
2 |
3 | TCP 是一种面向连接的协议,它根据底层网络的 MTU 动态调整其段的大小即路径 MTU 发现,若一个 TCP 段大于网络的 MTU,IP 层需对其进行分片,使各个分片均小于 MTU,因此 TCP 通过 PMTUD 发现最大的数据包大小,避免在网络上的任一节点处进行分片,因为分片会增加重传的可能性
4 |
5 | UDP 是一种无连接的协议,不提供数据包的分片和重组功能,应用层需确保发送的数据包不大于网络的 MTU,否则数据包会在 IP 层分片,若不允许分片,可能丢失数据包
6 |
--------------------------------------------------------------------------------
/Computer Network/TCP & UDP/TCP & UDP 与 IP 交互 & 发送数据.md:
--------------------------------------------------------------------------------
1 | 1. 数据封装:数据首先被发送到传输层的 TCP,TCP 对数据分段,并为各段添加 TCP 头信息,这些头信息包含源端口、目标端口、序列号、确认应答号等。TCP 将各段传输给网络层的 IP,IP 为每个 TCP 段添加 IP 头信息,这些头信息包括源 IP 地址、目标 IP 地址、生存时间 TTL 等。数据接着传输给网络接口层,封装成帧,并通过物理媒介发送
2 | 2. 路由选择:IP 层使用头信息中的目标 IP 地址来确定如何将数据包通过网络从源地址路由到目标地址
3 | 3. 数据接收和解封装:在接收端,IP 层首先处理到达的数据包,检查 IP 头信息,确认数据包已到达正确的目标地址,接着 IP 层移除 IP 头信息,将剩余的 TCP 段数据传输给传输层的 TCP,TCP 根据 TCP 头信息处理各段,进行重组、流量控制、错误检测等操作,最后 TCP 将重组后的数据按照正确顺序传输给应用层
4 |
5 |
--------------------------------------------------------------------------------
/Computer Network/TCP & UDP/TCP & UDP 共用端口.md:
--------------------------------------------------------------------------------
1 | 1. TCP 和 UDP 是两种不同的协议,它们在操作系统的网络栈中分别独立实现,且端口号的概念在 TCP 和 UDP 中均存在,尽管 TCP 和 UDP 均使用端口号来标识连接和应用程序,但它们基于不同的协议,因此 TCP 和 UDP 端口空间是分开的
2 | 2. 在网络栈中,一个网络连接由源 IP 地址、目标 IP 地址、源端口号、目标端口号及协议这五元组来唯一确定,即使端口号相同但协议不同,则视为不同的应用
3 | 3. 从技术上讲,一个应用程序可使用 UDP 监听 80 端口,而与此同时,另一个应用程序使用 TCP 监听相同的 80 端口并不会产生冲突,这是因为端口号是协议特定的,所以 TCP 的 80 端口和 UDP 的 80 端口被视为两个不同端点
4 |
5 |
--------------------------------------------------------------------------------
/Computer Network/TCP & UDP/TCP & UDP.md:
--------------------------------------------------------------------------------
1 | TCP 是一种面向连接的协议,通信双方在数据传输前必须建立连接,它通过序列号、确认应答、重传机制等确保数据可靠传输,且以正确顺序到达接收方,它通过滑动窗口机制实现流量控制,通过慢启动、快重传等机制来避免网络拥塞,此外它面向字节流,发送方可按照任意大小发送数据,而接收方将接收到的数据流重新组装起来
2 |
3 | UDP 是一种无连接的协议,无需在数据传输前建立连接即无需握手,它不能保证数据的可靠到达,即数据包可能丢失或顺序错乱,且没有内建的错误纠正机制,它是面向报文的,发送方发送的每个报文在接收方都是独立接收的,它支持多播和广播发送,可同时发送数据给多个接收方
4 |
5 | - 连接方式:TCP 是面向连接的,UDP 是无连接的
6 | - 可靠性:TCP 通过序列号、确认应答、超时重传、流量控制、拥塞控制和 Keepalive 保活机制等确保数据可靠传输,且以正确顺序到达接收方,UDP 无法保证数据的可靠到达,数据包可能丢失或顺序错乱且没有内建的错误纠正机制
7 | - 传输速度:由于建立连接和保证数据可靠性,TCP 传输速度比 UDP 慢
8 | - 用途差异:TCP 用于可靠数据传输的应用如 Web 页面加载和文件传输等,而 UDP 用于实时应用如流媒体和在线游戏等
9 |
--------------------------------------------------------------------------------
/Computer Network/TCP & UDP/TCP 上的协议.md:
--------------------------------------------------------------------------------
1 | 1. HTTP & HTTPS
2 | 2. SSH:用于远程登录
3 | 3. DNS
4 | 4. Telnet:用于远程登录
5 | 5. FTP:用于文件传输
6 | 6. SMTP:用于发送邮件
7 |
8 |
--------------------------------------------------------------------------------
/Computer Network/TCP & UDP/TCP 传送数据过程中断电 & 崩溃.md:
--------------------------------------------------------------------------------
1 | ###### 断电
2 |
3 | 1. 立即中断:断电导致所有正在运行的程序和网络活动立即停止,TCP 连接被迫中断,没有机会正常关闭连接
4 | 2. 无关闭握手:由于断电导致的中断是非正常的,TCP 连接没有进行四次挥手来正常关闭连接。因此对方因为没有接收到关闭连接的信号而保持连接状态,直到超时
5 | 3. 数据丢失:正在传输但未被确认的数据丢失,TCP 确保数据的可靠传输是通过接收方发送 ACK 实现的
6 |
7 | ###### 系统崩溃
8 |
9 | 1. 操作系统响应:操作系统因为某些原因停止正常工作,根据崩溃的具体情况,它可能释放该进程所持有的所有资源,关闭崩溃进程的所有套接字,对于 TCP 连接,操作系统向对方发送一个 RST 重置包,表明连接异常终止。当对方接收到 RST 包时,任何试图读写该 TCP 连接的操作都立即返回错误。根据其错误处理逻辑,对方可能停止发送数据、尝试重建连接、记录错误日志或采取其他恢复措施
10 | 2. 数据完整性:和断电类似,正在传输的数据可能因为崩溃丢失,若操作系统或 TCP 在崩溃前发送 FIN 包,则对方 TCP 执行关闭连接的流程,否则也会遇到类似断电情况的问题
11 | 3. 部分状态保存:若系统崩溃不是由硬件故障引起的,某些状态可能被保存下来
12 |
--------------------------------------------------------------------------------
/Computer Network/TCP & UDP/TCP 头的标志位.md:
--------------------------------------------------------------------------------
1 | 1. URG:当置为 1 时表示紧急指针字段有效,接收方有紧急数据需优先处理
2 | 2. ACK:当置为 1 时表示确认序号字段有效
3 | 3. PSH:当置为 1 时表示推送字段有效,接收方立即从 TCP 接收缓冲区种提取数据,而不是等待缓冲区溢出
4 | 4. RST:当置为 1 时表示重置字段有效,TCP 连接出现严重问题,需强制断开连接
5 | 5. SYN:当置为 1 时表示同步字段有效,其为连接请求或接受段,用于同步序列号
6 | 6. FIN:当置为 1 时表示结束字段有效,发送方已达到数据传输末尾,可断开连接
7 |
--------------------------------------------------------------------------------
/Computer Network/TCP & UDP/TCP 报文组成.md:
--------------------------------------------------------------------------------
1 | - 源端口和目标端口:各占 16 位,用于标识发送方和接收方
2 | - 序列号:32 位,用于保证 TCP 的可靠性和数据包顺序
3 | - 确认序号:32 位,用于表示接收到的数据字节序号
4 | - 数据偏移:4 位,表示 TCP 头部长度,用于指示数据部分的开始位置
5 | - 保留:6 位,保留未来使用,目前为 0
6 | - 标志位:上述 6 个标志位各占 1 位
7 | - 窗口大小:16 位,用于流量控制
8 | - 校验和:16 位,用于错误检测
9 | - 紧急指针:16 位,表示紧急数据的结束位置
10 | - 选项:长度可变,用于支持更灵活的通信需求如 MSS、窗口扩大因子等
11 | - 数据:跟随 TCP 头部,其长度可从数据偏移字段推断
12 |
--------------------------------------------------------------------------------
/Computer Network/TCP & UDP/TIME_WAIT.md:
--------------------------------------------------------------------------------
1 | TIME_WAIT 状态:四次挥手的最后阶段,客户端接收到服务端的最后一个 FIN 报文后,向服务端返回 ACK 应答报文,客户端进入 TIME_WAIT 状态,在此状态下,客户端等待 2 MSL 后断开连接。Maximum Segment Lifetime 是任何报文段被丢弃前在网络内的最长时间,RFC 793 定义的标准 MSL 为 2 分钟,因此 TIME_WAIT 状态的持续时间是 4 分钟
2 |
3 | ###### TIME_WAIT 状态的原因
4 |
5 | 1. 确保最后一个 ACK 报文能够到达:若客户端的最后一个 ACK 报文丢失,服务端将重传最后的 FIN 报文,TIME_WAIT 状态确保客户端有机会重新发送 ACK 报文响应重传的 FIN 报文
6 | 2. 等待足够时间以确保网络中所有报文均已到期:防止延迟的报文段在相同的四元组上之新连接中被错误接受
7 |
--------------------------------------------------------------------------------
/Computer Network/TCP & UDP/UDP 数据包大小.md:
--------------------------------------------------------------------------------
1 | UDP 数据包的长度是由其 16 位长度字段定义的,理论上最大长度是 `65,535` 字节,其头部占用 `8` 字节,因此数据部分的最大长度为 `65,527` 字节。由于 IP 层也有其长度限制且考虑到 IP 头部至少 20 字节,UDP 数据包的实际最大长度通常为 `65,507` 字节,但在实际中,还需考虑网络 MTU,以太网的标准 MTU 是 `1500` 字节,若 UDP 数据包大于该值,它需在 IP 层分片,这增加了丢包风险。因此为避免分片,我们通常使用更小的 UDP 数据包
2 |
--------------------------------------------------------------------------------
/Computer Network/TCP & UDP/三次握手的原因.md:
--------------------------------------------------------------------------------
1 | 1. 确认双方的发送与接收能力:
2 |
3 | - 第一次握手:客户端发送一个 SYN 同步序列报文,以开始连接,证明客户端能发送数据
4 | - 第二次握手:服务端返回一个 SYN-ACK 同步应答报文,以确认接收到 SYN,证明服务端能发送与接收数据
5 | - 第三次握手:客户端发送一个 ACK 确认报文,以确认接收到 SYN-ACK,证明客户端能接收数据
6 |
7 | 2. 初始化序列号:每个方向的连接均有一个独立的序列号,三次握手允许双方协商初始化这些序列号,确保数据传输顺序和可靠性
8 | 3. 防止失效的连接请求建立连接:若客户端发送的初始连接请求 SYN 在网络中延迟,且客户端重新发送该连接请求并建立连接、完成通信,则该延迟的连接请求到达服务端后仍可被接收,导致服务端错误建立一个新的连接,三次握手通过确认双方的发送与接收能力可避免此种情况发生,因为客户端不会对一个已失效的 SYN-ACK 进行确认
9 | 4. 两次握手无法解决上述问题,若只有两次握手,则无法确认接收方知道发送方已准备好接收数据。四次握手是不必要的,一旦客户端和服务端交换 SYN 和 ACK 报文,它们有足够的信息来进行通信,同时确认各自的发送与接收能力,四次握手不会提供额外的保障,反而增加延迟
10 |
--------------------------------------------------------------------------------
/Computer Network/TCP & UDP/为什么有了 TCP 还要用 UDP 实现一个可靠性传输.md:
--------------------------------------------------------------------------------
1 | Quick UDP Internet Connections 是一个基于 UDP 实现的传输层协议,由 Google 首次提出,用于改进 HTTP/2 的传输,解决 TCP 的一些性能问题,同时优化类似 TCP 的可靠性特性
2 |
3 | 1. 减少连接和握手时间:QUIC 基于 UDP 实现,只需一个往返时间 RTT 即可完成握手并开始通信,而 TCP + TLS 需 2 到 3 个 RTT,对于支持 0-RTT 的 QUIC,握手时间进一步缩短
4 | 2. 连接迁移:QUIC 的连接标识独立于底层 IP 地址,即使客户端 IP 发生变化,QUIC 连接也能保持不中断
5 | 3. 多路复用:尽管 HTTP/2 支持多路复用,但 TCP 的有序传输导致队头阻塞问题,单个慢请求阻塞后续所有请求,QUIC 解决了这一问题,它在应用层实现多路复用,每个流均独立
6 | 4. 拥塞控制:QUIC 内置改进的拥塞控制算法如 Google 的 BBR,它们可以更快适应网络条件变化,提供更稳定的吞吐量
7 | 5. 安全性:QUIC 从一开始就设计为安全的协议,它将 TLS 加密整合到协议本身,而不是作为一个附加层
8 |
--------------------------------------------------------------------------------
/Computer Network/TCP & UDP/半连接 & 全连接.md:
--------------------------------------------------------------------------------
1 | 当 TCP 连接完全建立时,即经过三次握手,客户端和服务端间的连接即为全连接
2 |
3 | - 第一次握手:客户端发送一个 SYN 同步序列报文,以开始连接,证明客户端能发送数据
4 | - 第二次握手:服务端返回一个 SYN-ACK 同步应答报文,以确认接收到 SYN,证明服务端能发送与接收数据
5 | - 第三次握手:客户端发送一个 ACK 确认报文,作为对 SYN-ACK 的确认,证明客户端能接收数据
6 |
7 | 半连接即 SYN 队列:在三次握手过程中,服务端接收到客户端的 SYN 报文后,发送 SYN-ACK 前的状态,即只完成了三次握手的前两步,服务端维护一个半连接队列,用于存放处于该状态的连接。半连接的存在是为处理并发连接请求,防止服务端资源被未完成连接的客户端过度占用,由于 SYN 报文可能因为网络延迟而导致服务端等待时间过长,设置半连接队列可在服务器资源有限的情况下管理这些未完成的连接请求。利用 TCP 的半连接状态,攻击者可发送大量的 SYN 报文而不完成后续的握手过程,导致服务端的半连接队列迅速填满,正常的连接请求因此被拒绝,这种攻击即 SYN 洪泛攻击。为抵御这种攻击,操作系统和网络设备实现多种策略如 SYN cookies,用于在不占用服务端资源的情况下完成三次握手
8 |
--------------------------------------------------------------------------------
/Computer Network/TCP & UDP/四次挥手的原因.md:
--------------------------------------------------------------------------------
1 | - 为彻底关闭两个方向上的数据传输通道,每个方向均需一个 FIN 报文来断开该方向的连接和一个 ACK 报文来确认断开
2 | * 若只有三次挥手,则至少有一个方向的连接断开未被单独确认,无法保证双方了解连接的关闭状态,导致一方误认为连接仍建立
3 | * 四次挥手已足够在两个方向上分别确认连接关闭,第一和第二次挥手完成从客户端到服务端的关闭确认,第三和第四次挥手完成从服务端到客户端的关闭确认,五次挥手是不必要的,并不会提供额外的保障,反而会增加延迟
4 |
5 |
--------------------------------------------------------------------------------
/Computer Network/TCP & UDP/数据从发送到接收的拷贝次数.md:
--------------------------------------------------------------------------------
1 | 1. 用户空间到内核空间:当客户端发送数据时,数据首先从用户空间拷贝到内核空间的 TCP 发送缓冲区
2 | 2. 内核空间到网络接口:数据从内核空间的 TCP 发送缓冲区拷贝到网络接口的发送队列
3 | 3. 网络接口到内核空间:当服务端接收数据时,数据从网络接口拷贝到内核空间的 TCP 接收缓冲区
4 | 4. 内核空间到用户空间:数据从内核空间的 TCP 接收缓冲区拷贝到用户空间
5 |
6 | ###### 优化
7 |
8 | 零拷贝:减少或消除从用户空间到内核空间的数据拷贝,如Linux 的 `sendfile` 系统调用可直接在内核中传输数据
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/Computer Network/TCP & UDP/查看 TCP 占用内存情况.md:
--------------------------------------------------------------------------------
1 | - 内核管理:在 Linux 中,TCP 等网络协议栈均由内核管理,内核负责分配和管理每个 socket 使用的内存,包括发送缓冲区和接收缓冲区
2 | - 缓冲区:TCP 的性能和内存使用密切相关,因为 TCP 使用缓冲区来存储待发送及已接收但未被应用读取的数据,缓冲区大小可通过 socket 选项调整
3 | - 内存压力和释放:当系统内存紧张时,内核尝试减少为 socket 分配的内存,影响 TCP 连接的性能,而当 TCP 连接关闭时,相关的内存资源被释放回系统
4 |
5 | 在 Linux 系统中,`/proc/net/sockstat` 和 `/proc/net/sockstat6` 文件提供关于 socket 使用情况的统计信息,包括 TCP、UDP 等协议的 socket。通过查看这些文件,可获取到当前系统级别的 socket 使用情况,包括打开的 socket 数量、内存使用等
6 |
7 | ```bash
8 | cat /proc/net/sockstat
9 | cat /proc/net/sockstat6
10 | ```
11 |
12 | `ss` 和 `netstat` 用于查看系统中当前所有 TCP 连接的状态
13 |
14 | ```bash
15 | ss -tm
16 | ```
17 |
18 | 显示每个连接的内存使用情况 `-m` 和定时器信息 `-t`
19 |
20 | ```bash
21 | netstat -tan
22 | ```
23 |
24 | `/proc/[pid]/status` 文件提供特定进程的内存使用情况,其中 `[pid]` 是进程的 ID
25 |
26 | ```bash
27 | cat /proc/[pid]/status
28 | ```
29 |
30 |
--------------------------------------------------------------------------------
/Computer Network/TCP & UDP/第四次挥手若客户端 kill.md:
--------------------------------------------------------------------------------
1 | 1. 在此场景中,若客户端在发送最后一个 ACK 报文后立即被 kill 掉,TCP 连接仍将正常关闭,因为服务端在发送 FIN 报文并接收到客户端的 ACK 报文后,就认为连接已成功关闭
2 | 2. 若客户端在发送最后一个 ACK 报文前被 kill 掉,服务端无法接收到客户端的 ACK 报文,它的连接仍处于 LAST_ACK 状态,等待 ACK 报文的到来,正常情况下,客户端在发送最后一个 ACK 报文后进入 TIME_WAIT 状态,等待足够时间以确保服务端接收到 ACK报文,若客户端被 kill 掉,该阶段将被跳过。若服务端没有接收到最后的 ACK报文,它将重传 FIN 报文,等待客户端的 ACK报文,一旦超时,服务端将关闭连接并释放相关资源
--------------------------------------------------------------------------------
/Computer Network/TCP & UDP/粘包.md:
--------------------------------------------------------------------------------
1 | 粘包:发送方的若干数据包被 TCP 合并为一个包进行发送,或接收方一次性接收多个数据包
2 |
3 | - TCP 面向流,数据的发送和接收是连续的字节流,没有固定边界
4 | - 网络延迟或拥塞控制导致的数据包合并
5 |
6 | ###### 解决方案
7 |
8 | 1. 消息定长:发送固定长度的消息,接收方根据固定长度分割接收到的字节流
9 | 2. 设置边界标志:在各包末尾添加特殊字符作为边界,接收方通过这些边界字符来拆分消息
10 | 3. 长度前缀:各包开始部分先发送一个表示消息长度的字段,接收方先读取长度字段,再根据长度读取相应数据
11 | 4. 应用层协议:定义复杂的应用层协议如在消息中使用 JSON 或 XML 格式,这样即使发生粘包,也可通过解析 JSON 或 XML 来识别单独的消息
12 |
--------------------------------------------------------------------------------
/Computer Network/TCP & UDP/限制 TCP 最大连接数.md:
--------------------------------------------------------------------------------
1 | 1. 操作系统
2 |
3 | - 端口数:TCP 连接是通过端口号标识的,每个连接由源 IP 地址、目标 IP 地址、源端口号和目标端口号组成,由于端口号是 16 位的,理论上每个 IP 地址可拥有高达 65535 个端口,但实际可用端口数少于该理论值,因为某些端口号被系统保留
4 | - 最大文件描述符数:每个 TCP 连接在类 Unix 系统内核中均通过文件描述符来表示,操作系统对各个进程可打开的文件描述符数量有限制,这直接影响单个进程能打开的 TCP 连接数
5 | - 参数配置:如 Linux 系统中的 `/proc/sys/net/core/somaxconn` 参数可控制 socket 监听队列的最大长度,而`/proc/sys/net/ipv4/tcp_max_syn_backlog` 参数用于调整 SYN 接收队列的最大长度,这些间接限制 TCP 最大连接数
6 |
7 | 2. 硬件:每个 TCP 连接均消耗一定的内存和 CPU 资源,对于有大量并发连接的服务器,资源消耗限制 TCP 最大连接数
8 | 3. 网络:虽然网络带宽和延迟不直接限制 TCP 最大连接数,但它们影响数据传输效率,间接限制服务端有效处理的并发 TCP 连接数量
9 | 4. 程序本身
10 |
--------------------------------------------------------------------------------
/Computer Network/preload & prefetch.md:
--------------------------------------------------------------------------------
1 |
2 | | | preload | prefetch |
3 | | ----- | --------------------------------- | ----------------------------- |
4 | | 目标 | 当前页面关键渲染路径资源(首屏 CSS、动态加载的脚本和 SDK) | 预测页面可能用到的非关键资源(下一页和动态路由组件等) |
5 | | 加载时机 | 优先级高,立即加载,无需等待浏览器空闲时 | 优先级低,浏览器空闲时加载 |
6 | | 缓存储位置 | 存储于内存,随页面关闭释放 | 存储于硬盘,跨页面存储,缓存时间由 HTTP 头部字段控制 |
7 |
8 | ```html
9 |
10 |
11 |
12 |
13 |
14 | ```
15 |
--------------------------------------------------------------------------------
/Computer Network/token 失效.md:
--------------------------------------------------------------------------------
1 | 1. 过期时间
2 | 2. 黑名单:可将不安全的 token 添加到黑名单中,每次接收请求时,检查 token 是否在黑名单中,如果在则拒绝该请求
3 | 3. 数据库存储:存储与 token 相关的状态于数据库中,如用户退出或更改密码时,使当前的 token 失效
4 | 4. 双 token 机制:Access Token 用于访问受保护的资源如 API,客户端在有效期内可用其认证状态和访问权限,其具有较短的有效期,风险相对较低,即便被泄露,攻击者也只能在短时间内使用它,Refresh Token 用于在 Access Token 过期后获取一个新的 Access Token,无需用户再次登录,Refresh Token 有更长的有效期,其安全性比 Access Token 高,因为它只用于与认证服务端通信,而不是直接用来访问受保护资源
5 | 5. 绑定 token 于特定 IP 或设备:即使 token 被窃取,攻击者无法从其他设备或 IP 上使用它,token 自然失效
6 |
--------------------------------------------------------------------------------
/Computer Network/三次握手四次挥手.md:
--------------------------------------------------------------------------------
1 | 通过 TCP 三次握手与服务端建立连接,进行通信
2 |
3 | ![[Pasted image 20231030070246.png]]
4 |
5 | 客户端和服务端初始时为 CLOSED 状态,服务端主动监听某个端口,处于 LISTEN 状态
6 |
7 | ![[Pasted image 20231030070719.png]]
8 |
9 | 客户端随机初始化序号 client_isn ,将此序号置于 TCP 首部的|序号|字段中,同时将 SYN 置为 1,接着将第 1 个 SYN 报文发送给服务端,该报文不含应用层数据,此时客户端处于 SYN-SENT 状态
10 |
11 | ![[Pasted image 20231030070656.png]]
12 |
13 | 服务端接收到客户端的 SYN 报文后,它随机初始化序号 server_isn,将此序号填入
14 | TCP 首部的|序号|字段中,再将 client_isn + 1 填入|确认应答号|字段,将 SYN 和 ACK 置为 1,接着将该报文返回给客户端,该报文不含应用层数据,此时服务端处于 SYN-RCVD 状态
15 |
16 | ![[Pasted image 20231030070934.png]]
17 |
18 | 客户端接收到服务端报文后,向服务端发送最后一个应答报文,将 ACK 置为 1 ,再将 server_isn + 1 填入|确认应答号|字段,该报文可含应用层数据,此时客户端处于 ESTABLISHED 状态,服务端接收到客户端的应答报文后,进入 ESTABLISHED 状态
19 |
20 | 一旦完成三次握手,客户端和服务端即可进行通信,此后可通过四次挥手断开连接
21 |
22 | ![[Pasted image 20231030071543.png]]
23 |
24 | - 客户端打算关闭连接,发送一个 FIN 置为 1 的报文,客户端进入 FIN_WAIT_1 状态
25 | - 服务端接收到该报文后,向客户端返回 ACK 应答报文,服务端进入 CLOSED_WAIT 状态
26 | - 客户端接收到该报文后,进入 FIN_WAIT_2 状态
27 | - 服务端处理完数据后,向客户端发送 FIN 报文,服务端进入 LAST_ACK 状态
28 | - 客户端接收到该报文后,向服务端返回 ACK 应答报文,客户端进入 TIME_WAIT 状态
29 | - 服务器接收到该报文后,进入 CLOSED 状态
30 | - 客户端经过 2MSL 后,进入 CLOSED 状态
31 |
--------------------------------------------------------------------------------
/Computer Network/前后端交互.md:
--------------------------------------------------------------------------------
1 | 前端 -> 展示数据和用户界面
2 | 后端 -> 处理业务逻辑、数据库操作
3 |
4 | ###### 工作流程
5 |
6 | 1. 用户在前端界面上进行操作,如点击按钮、提交表单等
7 | 2. 前端通过 ajax & axios & fetch 发送 HTTP 请求与后端进行通信
8 | 3. 后端接收请求,根据请求的内容和传递的数据执行相应的业务逻辑,同时操作数据库
9 | 4. 后端处理完毕后,将结果以 HTTP 响应的形式返回给前端
10 | 5. 前端根据响应的内容更新用户界面
11 |
12 | ###### 注意事项
13 |
14 | 1. API 规范:如请求方式、路径、参数、返回数据格式和错误处理等
15 | 2. API 文档:定期维护迭代更新,并保持前后端间良好的沟通与协作
16 | 3. API 版本控制:特别是在生产环境中,以免更新导致前端应用崩溃
17 | 4. 错误处理:后端应返回明确的错误码和警告信息,前端根据不同错误进行相应处理
18 | 5. 安全性:如数据加密、防止 SQL 注入、XSS、CSRF 攻击等
19 | 6. 性能优化:如合理设计数据库查询、缓存策略,减少不必要的数据传输等
--------------------------------------------------------------------------------
/Computer Network/前端缓存 & 资源更新问题.md:
--------------------------------------------------------------------------------
1 | 1. 浏览器缓存:当用户首次访问页面时,浏览器缓存页面资源如 HTML、CSS、JavaScript 和图片,当用户再次访问该页面时,浏览器直接从缓存中加载这些资源而无需再向服务端发送请求
2 |
3 | - Memory Cache:体积较小或频繁访问的资源存储在内存中,浏览器关闭时即被清除
4 | - Disk Cache:体积较大或很少访问的资源存储在硬盘中,持续时间长,但读取速度慢
5 |
6 | 2. 强缓存 & 协商缓存
7 | 3. LocalStorage & SessionStorage & Cookie & Session
8 | 4. Service Worker
9 | 5. IndexedDB:在浏览器中存储大量结构化数据的 API
10 | 6. 网关缓存如 Nginx 和 CDN
11 | 7. Redis
12 |
13 | 线上资源已更新但浏览器页面尚未更新,可能为线上资源实际未更新成功或浏览器页面缓存资源
14 |
15 | 手动刷新页面 F5:浏览器在请求中携带 `Cache-Control:max-age=0` 询问服务端资源是否更新
16 | 强制刷新页面 CTRL + F5:浏览器在请求中携带 `Cache-Control:no-cache` 向服务端请求新资源
17 |
18 | 1. 通过 `[contenthash]` 命名文件,当文件内容修改时,`[contenthash]` 变化导致请求 URL 变化,浏览器直接请求新资源
19 | 2. 在服务端或 CDN 实现版本控制,根据请求的版本标识符重定向至对应资源
20 |
--------------------------------------------------------------------------------
/Computer Network/反向代理.md:
--------------------------------------------------------------------------------
1 | 出于安全原因,浏览器采用同源策略,禁止一个源的 HTML 或 JS 获取另一个源的资源以防止 XSS 攻击,但它也限制合法的跨域请求如 API 调用。反向代理是让客户端认为它仍在请求同源资源,但在后台,代理服务端将这些请求转发到目标服务端,由于浏览器的同源策略只针对浏览器,不针对服务端,因此无论是否同源,服务端间总能自由交流
2 |
3 | 1. 设置一个代理服务端,本地服务器或现有的云服务器均可
4 | 2. 配置代理规则,所有来自客户端的请求均转发到目标服务端,如当客户端请求 `https://your-domain.com/api/data` 时,代理服务端将其转发到 `https://target-domain.com/data`
5 | 3. 客户端请求代理服务端,由于两者同源,因此不会触发同源策略限制
6 | 4. 代理服务端接收到客户端的请求后,转发到目标服务端
7 | 5. 代理服务端从目标服务端接收响应后,将其返回客户端,客户端只知道代理服务端响应,如此一来,你成功绕过浏览器的同源策略限制
8 |
--------------------------------------------------------------------------------
/Computer Network/密码登录.md:
--------------------------------------------------------------------------------
1 | 1. 密码存储:不直接存储用户的明文密码,首先将明文密码和一个随机字符串即盐值混合,即便多用户使用相同明文密码,由于盐值不同,其哈希值不同,接着使用 SHA-256、bcrypt 等哈希算法计算明文密码的哈希值,将之存储,哈希函数将任意长度的输入转换成固定长度的输出,此过程为单向的,无法从哈希值反向推导出原始密码
2 | 2. 密码传输
3 | 3. 密码验证:从登录请求中接收用户密码,使用与存储哈希值时相同的盐值对用户密码进行加盐和哈希计算,将计算出的哈希值与数据库中存储的哈希值进行比较,若两者匹配,验证用户身份成功,允许登录,否则拒绝访问
4 |
--------------------------------------------------------------------------------
/Computer Network/强缓存 & 协商缓存.md:
--------------------------------------------------------------------------------
1 | ##### 强缓存
2 |
3 | 当客户端发起请求后,服务端在返回的响应头中携带相关的缓存策略,告诉客户端该资源的缓存期限。在此期间内,客户端不再向服务端请求该资源,而是直接从本地获取
4 |
5 | ###### 响应头
6 |
7 | 1. `Cache-Control`
8 |
9 | - `max-age= 5 | 6 | display: block/inline/inline-block 7 | ``` 8 | 9 | 元素根据显示方式分为块级元素和行内元素,通过 `display` 属性改变元素的默认行为 10 | 11 | 块级元素:用于创建页面结构,可包含块级/行内元素,在盒模型中设置 `width`、`height`、`padding` 和 `margin`,默认情况下占据其父容器的全部宽度,总是新开一行,其后元素被挤至下一行 12 | 行内元素:用于包裹文本,可包含行内元素,在盒模型中无法设置 `width` 和 `height`,不占一行,按行内顺序依次排列直至无法放置后另起一行 13 | -------------------------------------------------------------------------------- /JS(practical aspects)/Array.prototype.fill.md: -------------------------------------------------------------------------------- 1 | ```JavaScript 2 | Array.prototype._fill = function(value, start = 0, end = this.length) { 3 | if(this == null || typeof this[Symbol.iterator] !== 'function') throw new TypeError(`${this} is not a iterator`); 4 | start = Math.max(Math.min(start, this.length), 0); 5 | end = Math.max(Math.min(end, this.length), 0); 6 | // 原生方法直接修改调用它的数组或类数组 7 | for(let i = start;i < end;++i) this[i] = value; 8 | // 原生方法就地改变数组或类数组 9 | return this; 10 | }; 11 | ``` -------------------------------------------------------------------------------- /JS(practical aspects)/Array.prototype.flat.md: -------------------------------------------------------------------------------- 1 | ```JavaScript 2 | const flattenArray = (arr) => { 3 | const ans = []; 4 | const flatten = (arr) => { 5 | for (const item of arr) { 6 | if (Array.isArray(item)) flatten(item); 7 | else ans.push(item); 8 | } 9 | }; 10 | flatten(arr); 11 | return ans; 12 | }; 13 | 14 | console.log(flattenArray([1, 2, 3, [4, [5]], 1, 2, 6, 7])); 15 | ``` 16 | 17 | ```js 18 | const flattenArray = (arr) => { 19 | const stk = [...arr], 20 | ans = []; 21 | while (stk.length) { 22 | const next = stk.pop(); 23 | if (Array.isArray(next)) stk.push(...next); 24 | else ans.unshift(next); 25 | } 26 | return ans; 27 | }; 28 | 29 | console.log(flattenArray([1, 2, 3, [4, [5]], 1, 2, 6, 7])); 30 | ``` 31 | -------------------------------------------------------------------------------- /JS(practical aspects)/Array.prototype.flatMap.md: -------------------------------------------------------------------------------- 1 | ```js 2 | Array.prototype._flatMap = function (callback, thisArg) { 3 | const res = []; 4 | this.forEach((item, index, arr) => { 5 | const mappedValue = callback.call(thisArg, item, index, arr); 6 | if (Array.isArray(mappedValue)) res.push(...mappedValue); 7 | else res.push(mappedValue); 8 | }); 9 | return res; 10 | }; 11 | ``` 12 | 13 | 测试用例: 14 | 15 | ```js 16 | const arr = [1, 2, 3, 4]; 17 | const res = arr._flatMap((x) => [x, x * 2]); 18 | console.log(res); // [1, 2, 2, 4, 3, 6, 4, 8] 19 | ``` -------------------------------------------------------------------------------- /JS(practical aspects)/Array.prototype.includes.md: -------------------------------------------------------------------------------- 1 | ```JavaScript 2 | Array.prototype._myincludes = function(searchElement, fromIndex = 0) { 3 | if(this == null || typeof this[Symbol.iterator] !== 'function') throw new TypeError(`${this} is not an iterator`); 4 | fromIndex = Math.max(fromIndex, 0); 5 | for(let i = fromIndex;i < this.length;++i) { 6 | if(this[i] === searchElement || (isNaN(searchElement) && isNaN(this[i]))) return true; 7 | } 8 | return false; 9 | }; 10 | ``` 11 | 12 | 测试代码 13 | 14 | ```JavaScript 15 | let arr = [1, 2, 3, NaN]; 16 | console.log(arr._myincludes(2)); // true 17 | console.log(arr._myincludes(4)); // false 18 | console.log(arr._myincludes(NaN)); // true 19 | ``` 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /JS(practical aspects)/Array.prototype.reduce.md: -------------------------------------------------------------------------------- 1 | ```JavaScript 2 | Array.prototype._myreduce = function (callback, initialVal) { 3 | if (typeof callback !== "function") 4 | throw new TypeError(`${callback} is not a function`); 5 | if (this == null || typeof this[Symbol.iterator] !== "function") 6 | throw new TypeError(`${this} is not an iterator`); 7 | const arr = [...this]; 8 | let accumulator; 9 | if (initialVal !== undefined) accumulator = initialVal; 10 | else { 11 | if (arr.length === 0) 12 | throw new TypeError("Reduce of empty array with no initial value"); 13 | accumulator = arr.shift(); 14 | } 15 | for (let i = 0; i < arr.length; ++i) 16 | accumulator = callback(accumulator, arr[i], i, this); 17 | return accumulator; 18 | }; 19 | ``` 20 | 21 | 测试用例: 22 | 23 | ```JavaScript 24 | const numbers = [1, 2, 3, 4, 5]; 25 | const sum = numbers._myreduce((acc, num) => acc + num, 0); 26 | console.log(sum); // 15 27 | const product = numbers._myreduce((acc, num) => acc * num, 1); 28 | console.log(product); // 120 29 | ``` 30 | 31 | 32 | -------------------------------------------------------------------------------- /JS(practical aspects)/Array.prototype.reverse.md: -------------------------------------------------------------------------------- 1 | ```js 2 | Array.prototype._myReverse = function () { 3 | if (this == null || typeof this[Symbol.iterator] !== "function") 4 | throw new TypeError(`${this} is not an iterator`); 5 | const arr = [...this]; 6 | for (let left = 0, right = arr.length - 1; left < right; ++left, --right) 7 | [arr[left], arr[right]] = [arr[right], arr[left]]; 8 | for (let i = 0; i < arr.length; ++i) this[i] = arr[i]; 9 | return this; 10 | }; 11 | ``` 12 | 13 | 测试用例: 14 | 15 | ```js 16 | const numbers = [1, 2, 3, 4, 5]; 17 | console.log(numbers._myReverse()); // [ 5, 4, 3, 2, 1 ] 18 | ``` -------------------------------------------------------------------------------- /JS(practical aspects)/ArrayReader.md: -------------------------------------------------------------------------------- 1 | 为 Array 实现一个 Reader,通过接口 getReader 获取,Reader 有一个接口 read(n),每次调用按顺序读取数组的 n(默认为 1)个元素,调用不会改变数组本身的值,若数组已全部读取完则返回空数组,若传入的参数不为正整数则抛出异常 2 | 3 | ```js 4 | class ArrayReader { 5 | constructor(arr) { 6 | this.arr = arr; 7 | this.idx = idx; 8 | } 9 | getReader() { 10 | return this; 11 | } 12 | read(n = 1) { 13 | if (!Number.isInteger(n) || n <= 0) 14 | throw new Error("Parameter must be a positive integer"); 15 | if (this.idx >= this.arr.length) return []; 16 | const res = this.arr.slice(this.idx, this.idx + n); 17 | this.idx += n; 18 | return res; 19 | } 20 | } 21 | ``` 22 | -------------------------------------------------------------------------------- /JS(practical aspects)/DOM 节点查找.md: -------------------------------------------------------------------------------- 1 | ```js 2 | const parentNode = (node1, node2) => { 3 | while (true) { 4 | if (node1.contains(node2)) return node1; 5 | node1 = node1.parentNode; 6 | } 7 | }; 8 | ``` -------------------------------------------------------------------------------- /JS(practical aspects)/JSON to DOM.md: -------------------------------------------------------------------------------- 1 | ```js 2 | const render = (vnode) => { 3 | const el = document.createElement(vnode.tag.toLocaleLowerCase()); 4 | for (const item in vnode.attrs) el.setAttribute(item, vnode.attrs[item]); 5 | if (typeof vnode.children === "string") { 6 | const textNode = document.createTextNode(vnode.children); 7 | el.appendChild(textNode); 8 | } 9 | if (Array.isArray(vnode.children) && vnode.children.length) { 10 | vnode.children.forEach((child) => el.appendChild(render(child, el))); 11 | } 12 | return el; 13 | }; 14 | ``` -------------------------------------------------------------------------------- /JS(practical aspects)/JSON.parse.md: -------------------------------------------------------------------------------- 1 | `JSON.parse` 不能解析的数据: 2 | 3 | * undefined 4 | * Function 5 | * Date,RegExp, Map, Set,Error 6 | * 稀疏数组,缺失项解析为 `null` 7 | * 循环引用,原型链上的属性 8 | 9 | ```JavaScript 10 | let json = '{"name":"Clown", "age":21}'; 11 | let obj = eval("(" + json + ")"); 12 | ``` 13 | 14 | -------------------------------------------------------------------------------- /JS(practical aspects)/JSONP.md: -------------------------------------------------------------------------------- 1 | ```js 2 | const _jsonp = (url, params, callbackName) => { 3 | return new Promise((resolve, reject) => { 4 | const script = document.createElement("script"); 5 | window[callbackName] = (data) => { 6 | resolve(data); 7 | document.body.removeChild(script); 8 | }; 9 | params = { ...params, callback: callbackName }; 10 | const arr = Object.keys(params).map((key) => `${key}=${params[key]}`); 11 | script.src = `${url}?${arr.join("&")}`; 12 | document.body.appendChild(script); 13 | }); 14 | }; 15 | ``` -------------------------------------------------------------------------------- /JS(practical aspects)/O(n)时间查找数组排序后的最大差值.md: -------------------------------------------------------------------------------- 1 | ```js 2 | const maxGap = (nums) => { 3 | if (nums.length < 2) return 0; 4 | let min = Math.min(...nums), 5 | max = Math.max(...nums); 6 | if (min === max) return 0; 7 | const len = nums.length; 8 | const bucketSize = Math.ceil((max - min) / (len - 1)); 9 | const bucketCount = Math.floor((max - min) / bucketSize) + 1; 10 | const buckets = new Array(bucketCount) 11 | .fill(null) 12 | .map(() => ({ min: Infinity, max: -Infinity, used: false })); 13 | for (const num of nums) { 14 | const idx = Math.floor((num - min) / bucketSize); 15 | buckets[idx].used = true; 16 | buckets[idx].min = Math.min(buckets[idx].min, num); 17 | buckets[idx].max = Math.max(buckets[idx].max, num); 18 | } 19 | let maxGap = 0, 20 | preMax = min; 21 | for (const bucket of buckets) { 22 | if (!bucket.used) continue; 23 | maxGap = Math.max(maxGap, bucket.min - preMax); 24 | preMax = bucket.max; 25 | } 26 | return maxGap; 27 | }; 28 | 29 | const arr = [3, 6, 9, 1]; 30 | console.log(maxGap(arr)); // 3 31 | ``` 32 | 33 | -------------------------------------------------------------------------------- /JS(practical aspects)/Object.assign.md: -------------------------------------------------------------------------------- 1 | ```JavaScript 2 | function _myobjectAssign(target, ...sources) { 3 | if(target == null) { 4 | throw new TypeError(`cannot convert undefined or null to object`); 5 | } 6 | const to = Object(target); 7 | // 第一次 `forEach` 遍历所有提供的源对象 8 | sources.forEach((source) => { 9 | if(source !== null) { 10 | Object.keys(source).forEach(key => { 11 | to[key] = source[key]; 12 | }) 13 | } 14 | }) 15 | return to; 16 | }; 17 | ``` 18 | 19 | 将一个或多个源对象的自身可枚举属性复制到目标对象上并返回这个目标对象 20 | -------------------------------------------------------------------------------- /JS(practical aspects)/Object.create.md: -------------------------------------------------------------------------------- 1 | ```JavaScript 2 | function _objectCreate(proto, propertiesObject) { 3 | if (typeof proto !== "object" && typeof proto !== "function") 4 | throw new TypeError(`${proto} is not an object or null`); 5 | const fn = function () {}; 6 | fn.prototype = proto; 7 | const obj = new fn(); 8 | if (propertiesObject !== undefined) 9 | Object.defineProperties(obj, propertiesObject); 10 | return obj; 11 | } 12 | ``` 13 | 14 | 测试用例: 15 | 16 | 验证能创建具有正确原型的对象: 17 | 18 | ```JavaScript 19 | const prototype = { foo: 'bar' }; 20 | const obj = _objectCreate(prototype); 21 | console.assert(Object.getPrototypeOf(obj) === prototype, "Test 1 failed: Incorrect prototype."); 22 | ``` 23 | 24 | 验证可在新对象上定义属性: 25 | 26 | ```JavaScript 27 | const properties = { 28 | prop: { 29 | value: 42, 30 | writable: true 31 | } 32 | }; 33 | const objWithProps = _objectCreate(prototype, properties); 34 | console.assert(objWithProps.prop === 42, "Test 2 failed: Property value is incorrect."); 35 | ``` 36 | -------------------------------------------------------------------------------- /JS(practical aspects)/Object.freeze.md: -------------------------------------------------------------------------------- 1 | `Object.freeze`:对象属性的值不可更改,无法添加新的属性,且已有的属性不可删除或重新配置 2 | 3 | ```JavaScript 4 | const _myobjectFreeze = object => { 5 | if(object == null || (typeof object !== 'function' && typeof object !== 'object')) { 6 | return object; 7 | } 8 | const properties = Object.getOwnPropertyNames(object); 9 | for(let property of properties) { 10 | if(typeof object[property] === 'object' && object[property] != null) { 11 | _myobjectFreeze(object[property]); 12 | } 13 | let descriptor = Object.getOwnPropertyDescriptor(object, property); 14 | if(descriptor.writable) { 15 | descriptor.writable = false; 16 | } 17 | if(descriptor.configurable) { 18 | descriptor.configurable = false; 19 | } 20 | Object.defineProperty(object, property, descriptor); 21 | } 22 | Object.preventExtensions(object); 23 | return object; 24 | }; 25 | ``` 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /JS(practical aspects)/Object.is.md: -------------------------------------------------------------------------------- 1 | `Object.is` 判断两者是否为相同的值,其与 `==` 和 `===` 不同,它认为 NaN 与自身相等,且认为 +0 和 -0 不相等 2 | 3 | ```JavaScript 4 | function _myobjectIs(x, y) { 5 | if(x === y) return x !== 0 || 1 / x === 1 / y; 6 | else return x !== x && y !== y; 7 | }; 8 | ``` 9 | -------------------------------------------------------------------------------- /JS(practical aspects)/Promise.race.md: -------------------------------------------------------------------------------- 1 | ```js 2 | Promise._race = (promises) => { 3 | return new Promise((resolve, reject) => { 4 | if (promises == null || typeof promises[Symbol.iterator] !== "function") 5 | throw new TypeError(`${promises} is not an iterator`); 6 | promises = [...promises]; 7 | promises.forEach((promise) => 8 | Promise.resolve(promise).then(resolve).catch(reject) 9 | ); 10 | }); 11 | }; 12 | 13 | Promise._race([ 14 | new Promise((resolve, reject) => setTimeout(() => reject("A"), 1000)), 15 | new Promise((resolve, reject) => setTimeout(() => resolve("B"), 2000)), 16 | new Promise((resolve, reject) => setTimeout(() => resolve("C"), 3000)), 17 | ]) 18 | .then((res) => console.log(res)) 19 | .catch((err) => console.log(err)); // A 20 | ``` 21 | -------------------------------------------------------------------------------- /JS(practical aspects)/String.prototype.includes.md: -------------------------------------------------------------------------------- 1 | ```js 2 | String.prototype._includes = function (word) { 3 | if (word === "") return true; 4 | for (let i = 0; i < this.length - word.length + 1; ++i) { 5 | let subStr = ""; 6 | for (let j = i; j < i + word.length; ++j) subStr += this[j]; 7 | if (subStr === word) return true; 8 | } 9 | return false; 10 | }; 11 | console.log("Hello world"._includes("Hello ")); // true 12 | ``` -------------------------------------------------------------------------------- /JS(practical aspects)/String.prototype.repeat.md: -------------------------------------------------------------------------------- 1 | ```js 2 | String.prototype._repeat = function (n) { 3 | if (n < 0) throw new RangeError(`${n} must be non-negative`); 4 | let repeatStr = ""; 5 | for (let i = 0; i < n; ++i) repeatStr += this; 6 | return repeatStr; 7 | }; 8 | ``` -------------------------------------------------------------------------------- /JS(practical aspects)/String.prototype.trim.md: -------------------------------------------------------------------------------- 1 | ```js 2 | const myTrim = str => str.replace(/^\s+|\s+$/g, ''); 3 | ``` -------------------------------------------------------------------------------- /JS(practical aspects)/apply & call & bind.md: -------------------------------------------------------------------------------- 1 | ```JavaScript 2 | Function.prototype._apply = function (thisArg, args = []) { 3 | thisArg = 4 | thisArg !== null && thisArg !== undefined ? Object(thisArg) : window; 5 | const fn = Symbol(); 6 | thisArg[fn] = this; 7 | const res = thisArg[fn](args); 8 | delete thisArg[fn]; 9 | return res; 10 | }; 11 | ``` 12 | 13 | ```JavaScript 14 | Function.prototype._call = function (thisArg, ...args) { 15 | thisArg = 16 | thisArg !== null && thisArg !== undefined ? Object(thisArg) : window; 17 | const fn = Symbol(); 18 | thisArg[fn] = this; 19 | const res = thisArg[fn](args); 20 | delete thisArg[fn]; 21 | return res; 22 | }; 23 | ``` 24 | 25 | ```JavaScript 26 | Function.prototype._bind = function (thisArg, ...args) { 27 | const _this = this; 28 | thisArg = 29 | thisArg !== null && thisArg !== undefined ? Object(thisArg) : window; 30 | return (...newArgs) => { 31 | const fn = Symbol(); 32 | thisArg[fn] = _this; 33 | const res = thisArg[fn](...args, ...newArgs); 34 | delete thisArg[fn]; 35 | return res; 36 | }; 37 | }; 38 | ``` 39 | -------------------------------------------------------------------------------- /JS(practical aspects)/async & await.md: -------------------------------------------------------------------------------- 1 | ```JavaScript 2 | function generatorToAsync(generatorFn) { 3 | return function () { 4 | const gen = generatorFn.apply(this, arguments); 5 | return new Promise((resolve, reject) => { 6 | function go(key, arg) { 7 | let res; 8 | try { 9 | res = gen[key](arg); 10 | } catch (err) { 11 | return reject(err); 12 | } 13 | const { value, done } = res; 14 | return done 15 | ? resolve(value) 16 | : Promise.resolve(value).then( 17 | (val) => go("next", val), 18 | (err) => go("throw", err) 19 | ); 20 | } 21 | go("next"); 22 | }); 23 | }; 24 | } 25 | 26 | const fn = (num) => new Promise((resolve) => setTimeout(() => resolve(num * 2), 1000)); 27 | 28 | function* gen() { 29 | const num1 = yield fn(1); 30 | console.log(num1); 31 | const num2 = yield fn(num1); 32 | console.log(num2); 33 | const num3 = yield fn(num2); 34 | console.log(num3); 35 | return num3; 36 | } 37 | 38 | generatorToAsync(gen)().then((res) => console.log(res)); 39 | ``` 40 | -------------------------------------------------------------------------------- /JS(practical aspects)/compose.md: -------------------------------------------------------------------------------- 1 | ```js 2 | function compose(arr) { 3 | return function (ctx) { 4 | function next() { 5 | if (arr.length) { 6 | let func = arr.shift(); 7 | func(ctx, next); 8 | } 9 | } 10 | if (arr.length) { 11 | let func = arr.shift(); 12 | func(ctx, next); 13 | } 14 | }; 15 | } 16 | 17 | function func1(ctx, next) { 18 | ctx.index++; 19 | next(); 20 | } 21 | 22 | function func2(ctx, next) { 23 | setTimeout(function () { 24 | ctx.index++; 25 | next(); 26 | }); 27 | } 28 | 29 | function func3(ctx, next) { 30 | console.log(ctx.index); 31 | } 32 | 33 | compose([func1, func2, func3])({ index: 0 }); // 2 34 | ``` 35 | 36 | `compose` 函数接收一个函数数组 `arr`,其中每个函数均接收两个参数即 `ctx` 上下文和 `next` 指向下一个中间件的函数, 37 | `next` 函数递归调用自己从数组中取出并异步执行每个中间件,使用 `shift` 方法从数组中移除元素,使每个中间件只能执行一次 38 | -------------------------------------------------------------------------------- /JS(practical aspects)/curry.md: -------------------------------------------------------------------------------- 1 | 柯里化:将接收多个参数的函数转换为接收单一参数的函数,且返回接收剩余参数的新函数 2 | 3 | ```JavaScript 4 | function _curry(fn) { 5 | function curried(...args) { 6 | const context = this; 7 | if (fn.length >= args.length) { 8 | return fn.apply(context, args); 9 | } else { 10 | return function (...newArgs) { 11 | return curried.apply(context, args.concat(newArgs)); 12 | }; 13 | } 14 | } 15 | return curried; 16 | } 17 | ``` 18 | 19 | 测试用例: 20 | 21 | ```js 22 | function add(a, b, c) { 23 | return a + b + c; 24 | } 25 | const curriedAdd = _curry(add); 26 | console.log(curriedAdd(1)(2)(3)); // 6 27 | console.log(curriedAdd(1, 2)(3)); // 6 28 | console.log(curriedAdd(1)(2, 3)); // 6 29 | ``` 30 | 31 | -------------------------------------------------------------------------------- /JS(practical aspects)/findDuplicate.md: -------------------------------------------------------------------------------- 1 | ```js 2 | Array.prototype.findDuplicate = function (n) { 3 | const map = this.reduce((acc, item) => { 4 | acc[item] = (acc[item] || 0) + 1; 5 | return acc; 6 | }, {}); 7 | return Object.keys(map).filter((key) => map[key] >= n); 8 | }; 9 | 10 | const arr1 = [1, 2, 3, 1, 2, 1]; 11 | console.log(arr1.findDuplicate(2)); // [ '1', '2' ] 12 | 13 | const arr2 = ["apple", "banana", "apple", "apple", "banana", "cherry"]; 14 | console.log(arr2.findDuplicate(3)); // [ 'apple' ] 15 | 16 | const arr3 = [true, false, true, true]; 17 | console.log(arr3.findDuplicate(2)); // [ 'true' ] 18 | ``` -------------------------------------------------------------------------------- /JS(practical aspects)/instanceof.md: -------------------------------------------------------------------------------- 1 | `instanceof` 用于检测构造函数的 prototype 属性是否出现在某个实例对象的原型链上 2 | 3 | ```JavaScript 4 | const _instanceof = (target, fn) => { 5 | let proto = Object.getPrototypeOf(target); 6 | while (proto) { 7 | if (proto === fn.prototype) return true; 8 | proto = Object.getPrototypeOf(proto); 9 | } 10 | return false; 11 | }; 12 | ``` -------------------------------------------------------------------------------- /JS(practical aspects)/isString.md: -------------------------------------------------------------------------------- 1 | ```js 2 | const _isString = (value) => 3 | typeof value === "string" || value instanceof String; 4 | 5 | console.log(_isString("Hello World")); // true 6 | console.log(_isString(123)); // false 7 | console.log(_isString(new String("Hello World"))); // true 8 | ``` -------------------------------------------------------------------------------- /JS(practical aspects)/localStorage 存储数据.md: -------------------------------------------------------------------------------- 1 | ```JavaScript 2 | const setItemWithExpiry = (key, value, ttl) => { 3 | const item = { 4 | value: value, 5 | expiry: Date.now().getTime() + ttl, 6 | }; 7 | localStorage.setItem(key, JSON.stringify(item)); 8 | }; 9 | 10 | const getItemWithExpiry = (key) => { 11 | const itemStr = localStorage.getItem(key); 12 | if (!itemStr) return null; 13 | const item = JSON.parse(itemStr); 14 | if (item.expiry < Date.now().getItem()) { 15 | localStorage.removeItem(key); 16 | return null; 17 | } 18 | return item.value; 19 | }; 20 | 21 | setItemWithExpiry("myKey", "myValue", 3600000); 22 | console.log(getItemWithExpiry("myKey")); // myValue 23 | ``` 24 | 25 | ![[1699858192700.png]] 26 | -------------------------------------------------------------------------------- /JS(practical aspects)/mergePromise.md: -------------------------------------------------------------------------------- 1 | ```js 2 | const mergePromise = (promisesFn) => { 3 | let sequence = Promise.resolve(); 4 | const ans = []; 5 | promisesFn.forEach( 6 | (promiseFn) => 7 | (sequence = sequence.then(() => promiseFn()).then((res) => ans.push(res))) 8 | ); 9 | return sequence.then(() => ans); 10 | }; 11 | 12 | const timeout = (time) => 13 | new Promise((resolve) => setTimeout(() => resolve(), time)); 14 | const promise1 = () => timeout(2000).then(() => "promise1"); 15 | const promise2 = () => timeout(1000).then(() => "promise2"); 16 | const promise3 = () => timeout(3000).then(() => "promise3"); 17 | mergePromise([promise1, promise2, promise3]).then((res) => console.log(res)); 18 | ``` -------------------------------------------------------------------------------- /JS(practical aspects)/new.md: -------------------------------------------------------------------------------- 1 | ```js 2 | const _new = function (fn, ...args) { 3 | // 构造一个新的空对象 4 | // 将其原型设置为构造函数的 `prototype` 5 | const obj = Object.create(fn.prototype); 6 | // `apply` 确保 `this` 在构造函数内部指向新创建的对象 `obj` 7 | const res = fn.apply(obj, args); 8 | return typeof res === "object" && res !== null ? res : obj; 9 | }; 10 | ``` 11 | 12 | 为实现上述操作,`_new` 函数需知道两件事: 13 | 14 | 1. 哪个构造函数应被调用 -> `fn` 15 | 2. 构造函数的参数是什么 -> `...args` 16 | -------------------------------------------------------------------------------- /JS(practical aspects)/requestAddition.md: -------------------------------------------------------------------------------- 1 | ```js 2 | const requestAddition = (a, b) => 3 | new Promise((resolve) => setTimeout(() => resolve(a + b), 1000)); 4 | const calculate = (...nums) => { 5 | if (nums.length === 0) return Promise.resolve(0); 6 | if (nums.length === 1) return Promise.resolve(nums[0]); 7 | let promiseChain = requestAddition(nums[0], nums[1]); 8 | for (let i = 2; i < nums.length; ++i) 9 | promiseChain = promiseChain.then((res) => requestAddition(res, nums[i])); 10 | return promiseChain; 11 | }; 12 | 13 | calculate(1, 2, 3, 4) 14 | .then((res) => console.log(res)) 15 | .catch((err) => console.error(err)); 16 | ``` -------------------------------------------------------------------------------- /JS(practical aspects)/setTimeout 实现 setInterval.md: -------------------------------------------------------------------------------- 1 | ```JavaScript 2 | function _setInterval(fn, delay, ...args) { 3 | let cancel = false; 4 | const task = () => { 5 | setTimeout(() => { 6 | if (!cancel) { 7 | fn.apply(this, args); 8 | task(); 9 | } 10 | }, delay); 11 | }; 12 | task(); 13 | return () => (cancel = true); 14 | } 15 | ``` 16 | 17 | 测试用例: 18 | 19 | ```JavaScript 20 | const stopHello = _setInterval(() => console.log("Hello"), 1000); 21 | setTimeout(() => stopHello(), 5000); 22 | ``` 23 | 24 | ![[1697509242812.png]] 25 | 26 | -------------------------------------------------------------------------------- /JS(practical aspects)/sleep.md: -------------------------------------------------------------------------------- 1 | ```js 2 | const sleep = (delay) => 3 | new Promise((resolve) => setTimeout(() => resolve(), delay)); 4 | ``` -------------------------------------------------------------------------------- /JS(practical aspects)/util.promisify.md: -------------------------------------------------------------------------------- 1 | `util.promisify` 将基于回调的函数转换为返回 Promise 的函数 2 | 3 | ```js 4 | function promisify(fn) { 5 | if (typeof fn !== "function") throw new TypeError(`${fn} is not a function`); 6 | return function (...args) { 7 | return new Promise((resolve, reject) => { 8 | // Node.js 标准回调格式 err + result 9 | function callback(err, ...results) { 10 | if (err) return reject(err); 11 | if (results.length === 1) return resolve(results[0]); 12 | return resolve(results); 13 | } 14 | args.push(callback); 15 | fn.call(this, ...args); 16 | }); 17 | }; 18 | } 19 | 20 | const fs = require("fs"); 21 | const readFileAsync = promisify(fs.readFile); 22 | readFileAsync("./index.html", "utf8") 23 | .then((data) => console.log(data)) 24 | .catch((err) => console.error(err)); 25 | ``` 26 | 27 | -------------------------------------------------------------------------------- /JS(practical aspects)/一次 setState 一次更新.md: -------------------------------------------------------------------------------- 1 | ```jsx 2 | class MyComponent extends React.Component { 3 | state = { count: 0 }; 4 | 5 | updateStateMultipleTimes = async () => { 6 | await this.setStateAsync({ count: this.state.count + 1 }); 7 | console.log(this.state.count); 8 | await this.setStateAsync({ count: this.state.count + 1 }); 9 | console.log(this.state.count); 10 | }; 11 | 12 | setStateAsync(state) { 13 | return new Promise(resolve => this.setState(state, resolve)); 14 | } 15 | 16 | render() { 17 | return ( 18 |
19 |
22 | );
23 | }
24 | }
25 | ```
--------------------------------------------------------------------------------
/JS(practical aspects)/三进制转十进制.md:
--------------------------------------------------------------------------------
1 | ```js
2 | const three2Ten = (s) => {
3 | let ans = 0;
4 | for (const ch of s) ans = ans * 3 + Number(ch);
5 | return ans;
6 | };
7 |
8 | console.log(three2Ten("102")); // 11
9 | console.log(parseInt(102, 3)); // 11
10 | ```
--------------------------------------------------------------------------------
/JS(practical aspects)/两数之和最大的所有组合数.md:
--------------------------------------------------------------------------------
1 | ```js
2 | const findMaxSum = (arr) => {
3 | if (arr.length < 2) return [];
4 | const ans = [];
5 | let max1 = -Infinity,
6 | max2 = -Infinity;
7 | for (const num of arr) {
8 | if (num > max1) {
9 | max2 = max1;
10 | max1 = num;
11 | } else if (num > max2) {
12 | max2 = num;
13 | }
14 | }
15 | const maxSum = max1 + max2;
16 | for (let i = 0; i < arr.length; ++i) {
17 | for (let j = i + 1; i < arr.length; ++j) {
18 | if (arr[i] + arr[j] === maxSum) ans.push([arr[i], arr[j]]);
19 | }
20 | }
21 | return { maxSum, ans };
22 | };
23 | ```
--------------------------------------------------------------------------------
/JS(practical aspects)/串行 pipeline.md:
--------------------------------------------------------------------------------
1 | ```js
2 | function createPipeline(...tasks) {
3 | return function (initialValue) {
4 | return tasks.reduce(
5 | (pre, cur) => Promise.resolve(pre).then(cur),
6 | initialValue
7 | );
8 | };
9 | }
10 |
11 | const addOne = (x) => x + 1;
12 | const asyncMultiplyTwo = (x) =>
13 | new Promise((resolve) => setTimeout(() => resolve(x * 2), 500));
14 | const subtractThree = (x) => x - 3;
15 | const pipeline = createPipeline(addOne, asyncMultiplyTwo, subtractThree);
16 | pipeline(5)
17 | .then((res) => console.log(res)) // 9
18 | .catch((err) => console.error(err));
19 | ```
--------------------------------------------------------------------------------
/JS(practical aspects)/串行网络请求.md:
--------------------------------------------------------------------------------
1 | ```js
2 | const serialFetch = async (urls) => {
3 | const ans = [];
4 | for (const url of urls) {
5 | const response = await fetch(url);
6 | if (!response.ok) throw new Error(`${url}`, `${response.status}`);
7 | const data = await response.json();
8 | ans.push(data);
9 | }
10 | return ans;
11 | };
12 |
13 | const urls = [
14 | "https://jsonplaceholder.typicode.com/posts/1",
15 | "https://jsonplaceholder.typicode.com/posts/2",
16 | "https://jsonplaceholder.typicode.com/invalid-url",
17 | "https://jsonplaceholder.typicode.com/posts/3",
18 | ];
19 |
20 | serialFetch(urls)
21 | .then((res) => console.log(res))
22 | .catch((err) => console.error(err));
23 | ```
--------------------------------------------------------------------------------
/JS(practical aspects)/事件委托.md:
--------------------------------------------------------------------------------
1 | ```JavaScript
2 | function _addEventListener(element, type, fn, selector) {
3 | if(!selector) element.addEventListener(type, fn)
4 | else {
5 | element.addEventListener(type, function(event) {
6 | const target = event.target;
7 | if(target.matches(selector)) fn.call(target, event);
8 | })
9 | }
10 | };
11 | ```
12 |
13 | 请补全 JavaScript 代码,要求如下:
14 |
15 | 1. 给"ul"标签添加点击事件
16 | 2. 当点击某"li"标签时,该标签内容拼接"."符号。如:某"li"标签被点击时,该标签内容为".."
17 |
18 | 注意:
19 |
20 | 1. 必须使用 DOM0 级标准事件(onclick)
21 |
22 | ```js
23 | document.querySelector("ul").onclick = (event) => {
24 | const target = event.target;
25 | if (target.tagName.toLowerCase() === "li") target.innerHTML += ".";
26 | };
27 | ```
28 |
--------------------------------------------------------------------------------
/JS(practical aspects)/二分查找.md:
--------------------------------------------------------------------------------
1 | ```js
2 | const binarySearch = (arr, target) => {
3 | let left = 0,
4 | right = arr.length - 1;
5 | while (left <= right) {
6 | const mid = Math.floor((left + right) / 2);
7 | if (arr[mid] === target) return mid;
8 | else if (arr[mid] < target) left = mid + 1;
9 | else right = mid - 1;
10 | }
11 | return -1;
12 | };
13 | ```
14 |
15 |
--------------------------------------------------------------------------------
/JS(practical aspects)/倒计时.md:
--------------------------------------------------------------------------------
1 | ```js
2 | import { useState, useEffect } from "react";
3 |
4 | function CountdownTimer({ initialMinutes = 0, initialSeconds = 0 }) {
5 | const [minutes, setMinutes] = useState(initialMinutes);
6 | const [seconds, setSeconds] = useState(initialSeconds);
7 | useEffect(() => {
8 | const myInterval = setInterval(() => {
9 | if (seconds > 0) {
10 | setSeconds(seconds - 1);
11 | } else if (seconds === 0) {
12 | if (minutes === 0) {
13 | clearInterval(myInterval);
14 | } else {
15 | setMinutes(minutes - 1);
16 | setSeconds(59);
17 | }
18 | }
19 | }, 1000);
20 | return () => clearInterval(myInterval);
21 | });
22 | return (
23 | Count: {this.state.count} 20 | 21 |
24 | {minutes === 0 && seconds === 0 ? (
25 |
32 | );
33 | }
34 |
35 | export default CountdownTimer;
36 | ```
--------------------------------------------------------------------------------
/JS(practical aspects)/函数缓存.md:
--------------------------------------------------------------------------------
1 | ```js
2 | function memorize(fn) {
3 | const cache = {};
4 | return function (...args) {
5 | const key = JSON.stringify(args);
6 | if (cache[key]) return cache[key];
7 | const result = fn(...args);
8 | cache[key] = result;
9 | return result;
10 | };
11 | }
12 | ```
--------------------------------------------------------------------------------
/JS(practical aspects)/分红包.md:
--------------------------------------------------------------------------------
1 | 总金额不变 + 每个人都分到红包 + 红包金额随机且保留两位小数
2 |
3 | ```js
4 | const divideRedPocket = (total, num) => {
5 | let remain = total;
6 | const ans = [];
7 | for (let i = 0; i < num - 1; ++i) {
8 | let maxRed = remain - (num - 1 - i) * 0.01; // 每人至少分到 0.01 元
9 | let amount = (Math.random() * maxRed).toFixed(2);
10 | ans.push(amount);
11 | remain -= amount;
12 | }
13 | ans.push(remain.toFixed(2));
14 | return ans;
15 | };
16 |
17 | console.log(divideRedPocket(100, 10));
18 | ```
19 |
20 | 若不采取以下策略,采用上述所谓之完全随机,先前两个人拿到的红包已是大头,完全丧失公平性
21 |
22 | ```js
23 | const divideRedPocket = (totalAmount, num) => {
24 | let remain = totalAmount;
25 | const ans = [];
26 | for (let i = 0; i < num - 1; ++i) {
27 | let maxRed = (remain / (num - i)) * 2;
28 | let amount = Math.floor(Math.random() * maxRed).toFixed(2);
29 | ans.push(amount);
30 | remain -= amount;
31 | }
32 | ans.push(remain.toFixed(2));
33 | return ans;
34 | };
35 | console.log(divideRedPocket(100, 10));
36 | ```
--------------------------------------------------------------------------------
/JS(practical aspects)/判断对象是否为空.md:
--------------------------------------------------------------------------------
1 | ```js
2 | const isObjEmpty = (obj) => {
3 | if (typeof obj !== "object" || obj === null) return false;
4 | for (const key in obj) {
5 | if (obj.hasOwnProperty(key)) {
6 | // 发现一个非空对象
7 | if (
8 | typeof obj[key] === "object" &&
9 | obj[key] !== null &&
10 | !isObjEmpty(obj[key])
11 | )
12 | return false;
13 | // 发现一个非空值
14 | else if (obj[key] !== null && obj[key] !== undefined && obj[key] !== "")
15 | return false;
16 | }
17 | }
18 | return true; // 所有属性均为空
19 | };
20 | ```
--------------------------------------------------------------------------------
/JS(practical aspects)/判断数组是否为等差数列.md:
--------------------------------------------------------------------------------
1 | ```js
2 | const isArithmetic = (arr) => {
3 | if (arr.length < 2) return true;
4 | arr.sort((a, b) => a - b);
5 | const diff = arr[1] - arr[0];
6 | for (let i = 2; i < arr.length; ++i) {
7 | if (arr[i] - arr[i - 1] !== diff) return false;
8 | }
9 | return true;
10 | };
11 |
12 | console.log(isArithmetic([3, 5, 7, 9])); // true
13 | console.log(isArithmetic([1, 2, 4])); // false
14 | ```
15 |
16 |
--------------------------------------------------------------------------------
/JS(practical aspects)/判断数组是否含重复值.md:
--------------------------------------------------------------------------------
1 | 值位于 0 至 n-1 间,以数组本身作为哈希表,通过将对应索引处的值标记为负数来记录访问状态,若某个值的索引已访问过,再次访问则说明有重复值
2 |
3 | ```js
4 | const hasDuplicates = (arr) => {
5 | for (let i = 0; i < arr.length; ++i) {
6 | const val = Math.abs(arr[i]);
7 | if (arr[val] < 0) return true;
8 | arr[val] = -arr[val];
9 | }
10 | return false;
11 | };
12 | ```
13 |
14 | ```js
15 | const hasDuplicates = (arr) => {
16 | let i = 0;
17 | while (i < arr.length) {
18 | if (arr[i] === i || arr[i] < 0 || arr[i] >= arr.length) {
19 | ++i;
20 | continue;
21 | }
22 | if (arr[i] === arr[arr[i]]) return true;
23 | [arr[i], arr[arr[i]]] = [arr[arr[i]], arr[i]];
24 | }
25 | return false;
26 | };
27 | ```
--------------------------------------------------------------------------------
/JS(practical aspects)/判断数组里各天的微信步数是否满足传入时间范围内的微信步数.md:
--------------------------------------------------------------------------------
1 | ```js
2 | const checkDailyStep = (data, start, end, total) => {
3 | const startDate = new Date(start),
4 | endDate = new Date(end);
5 | const filtered = data.filter((item) => {
6 | const itemDate = new Date(item.date);
7 | return itemDate >= startDate && itemDate <= endDate;
8 | });
9 | return filtered.every((item) => item.step >= total);
10 | };
11 |
12 | const testData = [
13 | { date: "2025-03-20", step: 8000 },
14 | { date: "2025-03-21", step: 10000 },
15 | { date: "2025-03-22", step: 6000 },
16 | ];
17 |
18 | const result = checkDailyStep(testData, "2025-03-20", "2025-03-22", 7000);
19 | console.log(result); // false
20 | ```
--------------------------------------------------------------------------------
/JS(practical aspects)/判断点是否在矩形内.md:
--------------------------------------------------------------------------------
1 | 已知左下角坐标 x1、y1 和右上角的坐标 x2、y2
2 |
3 | ```js
4 | const isInside = (x1, y1, x2, y2, px, py) =>
5 | x1 <= px && px <= x2 && y1 <= py && py <= y2;
6 | ```
--------------------------------------------------------------------------------
/JS(practical aspects)/千分位转化.md:
--------------------------------------------------------------------------------
1 | 1. 正则
2 |
3 | ```js
4 | const formatToThousands = (num) => {
5 | const item = num.toString().split(".");
6 | item[0] = item[0].replace(/(\d)(?=(\d{3})+(?!\d))/g, "$1,");
7 | return `${item[0]}.${item[1]}`;
8 | };
9 |
10 | console.log(formatToThousands(123456789.123456789));
11 | ```
12 |
13 | 2. 数组 API
14 |
15 | ```js
16 | const formatToThousands = (num) => {
17 | const item = num.toString().split(".");
18 | const reversed = item[0].split("").reverse();
19 | const chunks = [];
20 | for (let i = 0; i < reversed.length; i += 3)
21 | chunks.push(reversed.slice(i, i + 3).join(""));
22 | const formatted = chunks.join(",").split("").reverse().join("");
23 | return `${formatted}.${item[1]}`;
24 | };
25 |
26 | console.log(formatToThousands(123456789.123456789));
27 | ```
28 |
29 | 3. `toLocaleString`
30 |
31 | ```js
32 | const formatToThousands = (num) =>
33 | num.toLocaleString("en-US", {
34 | minimumFractionDigits: 0,
35 | maximumFractionDigits: 20,
36 | });
37 |
38 | console.log(formatToThousands(123456789.123456789));
39 | ```
40 |
--------------------------------------------------------------------------------
/JS(practical aspects)/单例模式.md:
--------------------------------------------------------------------------------
1 | ```js
2 | class Singleton {
3 | constructor(name, age) {
4 | if (!Singleton.instance) {
5 | this.name = name;
6 | this.age = age;
7 | Singleton.instance = this;
8 | }
9 | return Singleton.instance;
10 | }
11 | }
12 |
13 | console.log(new Singleton("Clown", 21) === new Singleton("Rainyrou", 18)); // true
14 | ```
15 |
--------------------------------------------------------------------------------
/JS(practical aspects)/合并对象.md:
--------------------------------------------------------------------------------
1 | ```js
2 | const isObject = (obj) =>
3 | Object.prototype.toString.call(obj) === "[Object object]";
4 |
5 | const merge = (obj1, obj2) => {
6 | let result = { ...obj1 }; // 浅拷贝
7 | for (const key in obj2)
8 | result[key] =
9 | isObject(result[key]) && isObject(obj2[key])
10 | ? merge(result[key], obj2[key])
11 | : obj2[key];
12 | return result;
13 | };
14 |
15 | const a = { b: { c: { d: { e: 1 } } } };
16 | const x = { y: { z: 2, t: 521 } };
17 | console.log(JSON.stringify(merge(a, x))); // {"b":{"c":{"d":{"e":1}}},"y":{"z":2,"t":521}}
18 | ```
--------------------------------------------------------------------------------
/JS(practical aspects)/合法 URL.md:
--------------------------------------------------------------------------------
1 | [learn-regex/translations/README-cn.md at master · ziishaned/learn-regex · GitHub](https://github.com/ziishaned/learn-regex/blob/master/translations/README-cn.md)
2 |
3 | ```js
4 | const isUrl = (url) =>
5 | /^((https|http)?:\/\/)((([A-Za-z0-9]+-[A-Za-z0-9]+|[A-Za-z0-9]+)\.)+([A-Za-z]{2,6})|(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}))(:\d+)?(\/.*)?(\?.*)?(#.*)?$/.test(
6 | url
7 | );
8 |
9 | console.log(isUrl("http://114.132.167.29:5021/")); // true
10 | console.log(
11 | isUrl(
12 | "https://github.com/ziishaned/learn-regex/blob/master/translations/README-cn.md"
13 | ) // true
14 | );
15 | ```
--------------------------------------------------------------------------------
/JS(practical aspects)/后端返回的对象数组属性名转换.md:
--------------------------------------------------------------------------------
1 | ```js
2 | const data = [
3 | { name: "苹果", price: "1.0" },
4 | { name: "香蕉", price: null },
5 | { name: "", price: "2.0" },
6 | { name: "橘子", price: undefined },
7 | { name: "葡萄", price: "" },
8 | ];
9 |
10 | const transformData = data
11 | .filter((item) => item.name && item.name.trim() !== "")
12 | .map((item) => ({
13 | label: item.name,
14 | value: item.price ? parseFloat(item.price) : 0,
15 | }));
16 | console.log(transformData);
17 | ```
--------------------------------------------------------------------------------
/JS(practical aspects)/大数相加.md:
--------------------------------------------------------------------------------
1 | ```js
2 | const bigNumberAddition = (a, b) => {
3 | let i = a.length - 1,
4 | j = b.length - 1,
5 | carry = 0,
6 | ans = "";
7 | while (i >= 0 || j >= 0 || carry > 0) {
8 | let x = i >= 0 ? parseInt(a[i], 10) : 0,
9 | y = j >= 0 ? parseInt(b[j], 10) : 0;
10 | let sum = x + y + carry;
11 | carry = Math.floor(sum / 10);
12 | sum %= 10;
13 | ans = sum.toString() + ans;
14 | --i;
15 | --j;
16 | }
17 | return ans;
18 | };
19 | const a = "987654321987654321";
20 | const b = "123456789123456789";
21 | console.log(bigNumberAddition(a, b)); // 1111111111111111110
22 | ```
--------------------------------------------------------------------------------
/JS(practical aspects)/字符串转小写后按字典序并去重.md:
--------------------------------------------------------------------------------
1 | ```js
2 | const sortAndUnique = (arr) => {
3 | const lowerCaseArr = arr.map((item) => item.toLowerCase());
4 | return [...new Set(lowerCaseArr.sort())];
5 | };
6 |
7 | const inputArray = ["Apple", "banana", "apple", "Banana", "Cherry"];
8 | console.log(sortAndUnique(inputArray)); // ["apple", "banana", "cherry"]
9 | ```
--------------------------------------------------------------------------------
/JS(practical aspects)/字符串转嵌套对象.md:
--------------------------------------------------------------------------------
1 | ```js
2 | const convert = (path) => {
3 | const keys = path.split(".");
4 | let obj = {},
5 | cur = obj;
6 | keys.forEach((key, index) => {
7 | cur[key] = index === keys.length - 1 ? null : {};
8 | cur = cur[key];
9 | });
10 | return obj;
11 | };
12 |
13 | const path = "a.b.c.d";
14 | console.log(JSON.stringify(convert(path), null, 2));
15 | ```
16 |
17 | ```
18 | {
19 | "a": {
20 | "b": {
21 | "c": {
22 | "d": null
23 | }
24 | }
25 | }
26 | }
27 | ```
--------------------------------------------------------------------------------
/JS(practical aspects)/定时器.md:
--------------------------------------------------------------------------------
1 | ```js
2 | class Timer {
3 | constructor(callback, interval) {
4 | this.timerId = null;
5 | this.callback = callback;
6 | this.interval = interval;
7 | }
8 | start() {
9 | if (!this.timerId) this.timerId = setInterval(this.callback, this.interval);
10 | }
11 | stop() {
12 | if (this.timerId) {
13 | clearInterval(this.timerId);
14 | this.timerId = null;
15 | }
16 | }
17 | }
18 |
19 | const timer = new Timer(() => console.log("tick per 3s"), 3000);
20 | timer.start();
21 | setTimeout(() => {
22 | timer.stop();
23 | console.log("stop");
24 | }, 10000);
25 | ```
--------------------------------------------------------------------------------
/JS(practical aspects)/寄生组合式继承.md:
--------------------------------------------------------------------------------
1 | 请补全 JavaScript 代码,要求通过寄生组合式继承使"Chinese"构造函数继承于"Human"构造函数。要求如下:
2 |
3 | 1. 给"Human"构造函数的原型上添加"getName"函数,该函数返回调用该函数对象的"name"属性
4 | 2. 给"Chinese"构造函数的原型上添加"getAge"函数,该函数返回调用该函数对象的"age"属性
5 |
6 | ```JavaScript
7 | function Human(name) {
8 | this.name = name;
9 | this.kingdom = "animal";
10 | this.color = ["yellow", "white", "brown", "black"];
11 | }
12 |
13 | Human.prototype.getName = function () {
14 | return this.name;
15 | };
16 |
17 | function Chinese(name, age) {
18 | Human.call(this, name);
19 | this.age = age;
20 | this.color = "yellow";
21 | }
22 |
23 | Chinese.prototype = Object.create(Human.prototype);
24 | Chinese.prototype.constructor = Chinese;
25 | Chinese.prototype.getAge = function () {
26 | return this.age;
27 | };
28 | ```
29 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/JS(practical aspects)/对象拍平.md:
--------------------------------------------------------------------------------
1 | ```js
2 | const flattenObj = (target, prefix = "") => {
3 | const obj = {};
4 | for (const key in target) {
5 | if (target.hasOwnProperty(key)) {
6 | const newKey = prefix ? `${prefix}.${key}` : key;
7 | if (Array.isArray(target[key])) {
8 | target[key].forEach(
9 | (item, index) => (obj[`${newKey}[${index}]`] = item)
10 | );
11 | } else if (typeof target[key] === "object" && target[key] !== null) {
12 | Object.assign(obj, flattenObj(target[key], newKey));
13 | } else {
14 | obj[newKey] = target[key];
15 | }
16 | }
17 | }
18 | return obj;
19 | };
20 |
21 | const obj = {
22 | a: "hello",
23 | b: 111,
24 | c: {
25 | d: "world",
26 | e: [1, 2, 3],
27 | },
28 | };
29 |
30 | console.log(flattenObj(obj));
31 | ```
32 |
33 |
--------------------------------------------------------------------------------
/JS(practical aspects)/对象数组重叠区间.md:
--------------------------------------------------------------------------------
1 | ```JavaScript
2 | const isOverlap = (intervals) => {
3 | intervals.sort((a, b) => a.s - b.s);
4 | for (let i = 0; i < intervals.length - 1; ++i) {
5 | if (intervals[i].e > intervals[i + 1].s) return true;
6 | }
7 | return false;
8 | };
9 | const intervals = [
10 | { s: 1, e: 3 },
11 | { s: 2, e: 4 },
12 | { s: 3, e: 6 },
13 | ];
14 | console.log(isOverlap(intervals)); // true
15 | ```
--------------------------------------------------------------------------------
/JS(practical aspects)/对象转换为路径数组.md:
--------------------------------------------------------------------------------
1 | ```js
2 | const flattenObj = (obj, prePath = []) => {
3 | const ans = [];
4 | for (const key in obj) {
5 | if (obj.hasOwnProperty(key)) {
6 | const curPath = [...prePath, ...key];
7 | if (
8 | typeof obj[key] === "object" &&
9 | obj[key] !== null &&
10 | Object.keys(obj[key]).length > 0
11 | ) {
12 | ans.push(...flattenObj(obj[key], curPath));
13 | } else {
14 | ans.push(curPath);
15 | }
16 | }
17 | }
18 | return ans;
19 | };
20 |
21 | const obj = {
22 | a: { b: { c: {} } },
23 | d: { e: {} },
24 | };
25 |
26 | console.log(flattenObj(obj));
27 | ```
28 |
29 |
--------------------------------------------------------------------------------
/JS(practical aspects)/微信红包.md:
--------------------------------------------------------------------------------
1 | [微信红包](https://www.nowcoder.com/practice/fbcf95ed620f42a88be24eb2cd57ec54)
2 |
3 | 春节期间小明使用微信收到很多个红包,非常开心。在查看领取红包记录时发现,某个红包金额出现的次数超过红包总数的一半。请帮小明找到该红包金额。写出具体算法思路和代码实现,要求算法尽可能高效
4 |
5 | 给定一个红包的金额数组 gifts 及它的大小 n ,请返回所求红包的金额,若没有金额超过总数的一半,返回 0
6 |
7 | 数据范围: 1 ≤n≤ 1000,红包金额满足 1 ≤ gifti ≤100000
8 |
9 | 示例 1
10 |
11 | 输入:[1,2,3,2,2],5
12 |
13 | 返回值:2
14 |
15 | 示例 2
16 |
17 | 输入:[1,1,2,2,3,3],6
18 |
19 | 返回值:0
20 |
21 | ```js
22 | const getValue = (gifts, n) => {
23 | let candidate = null,
24 | count = 0;
25 | for (const gift of gifts) {
26 | if (count === 0) {
27 | candidate = gift;
28 | count = 1;
29 | } else if (gift === candidate) {
30 | ++count;
31 | } else {
32 | --count;
33 | }
34 | }
35 | let ans = 0;
36 | for (const gift of gifts) {
37 | if (gift === candidate) ++ans;
38 | }
39 | return ans > n / 2 ? candidate : 0;
40 | };
41 | ```
--------------------------------------------------------------------------------
/JS(practical aspects)/快速幂.md:
--------------------------------------------------------------------------------
1 | ```js
2 | const fastPow = (x, n) => {
3 | if (n < 0) {
4 | x = 1 / x;
5 | n = -n;
6 | }
7 | let ans = 1;
8 | while (n > 0) {
9 | if (n % 2 === 1) ans *= x;
10 | x *= x;
11 | n = Math.floor(n / 2);
12 | }
13 | return ans;
14 | };
15 |
16 | console.log(fastPow(2, 10)); // 1024
17 | console.log(fastPow(2, -3)); // 0.125
18 | ```
--------------------------------------------------------------------------------
/JS(practical aspects)/找出两个数组或对象数组中不共有的元素.md:
--------------------------------------------------------------------------------
1 | ###### 简单数组
2 |
3 | ```JavaScript
4 | const findUniqueElements = (arr1, arr2) =>
5 | arr1
6 | .filter((item) => !arr2.includes(item))
7 | .concat(arr2.filter((item) => !arr1.includes(item)));
8 |
9 | const arr1 = [1, 2, 3, 4];
10 | const arr2 = [3, 4, 5, 6];
11 | console.log(findUniqueElements(arr1, arr2));
12 | ```
13 |
14 | ###### 对象数组
15 |
16 | ```JavaScript
17 | const findUniqueElements = (arr1, arr2, key) => {
18 | const set2 = new Set(arr2.map((item) => item[key]));
19 | return arr1
20 | .filter((item) => !set2.has(item[key]))
21 | .concat(arr2.filter((item) => !arr1.some((i) => i[key] === item[key])));
22 | };
23 |
24 | const arr1 = [
25 | { id: 1, name: "Boss" },
26 | { id: 2, name: "Rainy" },
27 | ];
28 | const arr2 = [
29 | { id: 2, name: "Rainy" },
30 | { id: 3, name: "Clown" },
31 | ];
32 |
33 | console.log(findUniqueElements(arr1, arr2, "id"));
34 | ```
--------------------------------------------------------------------------------
/JS(practical aspects)/找到毒药的最少次数.md:
--------------------------------------------------------------------------------
1 | 16 瓶药中有 1 瓶毒药,有 4 只老鼠,1 次可喝 1 或多瓶,找到毒药的最少次数?
2 |
3 | 由于有四只老鼠,每只老鼠有生死两种结果,可获得16 种不同结果,恰与药瓶数量相匹配,即每瓶药均可分配一个唯一的四位二进制数,从 0000 到 1111,其中每一位代表一只老鼠是否应该喝这瓶药:0 表示该老鼠不喝,1 表示该老鼠喝
4 |
5 | 1. 为每瓶药分配一个编号,从 0 到 15,将这些编号转换为二进制形式,如编号 0 对应的二进制为 0000,编号 15 对应的二进制为 1111
6 | 2. 根据二进制编号决定老鼠的喝药方案,如一瓶药的二进制编号是 1010,则第一只和第三只老鼠需喝这瓶药,第二只和第四只老鼠不喝
7 | 3. 进行一次测试,等待足够时间观察老鼠反应
8 | 4. 根据老鼠的生死情况来确定毒药瓶,四个老鼠的生死情况共同组成一个四位二进制数,其对应的十进制数即为毒药瓶的编号
9 |
--------------------------------------------------------------------------------
/JS(practical aspects)/数组乱序.md:
--------------------------------------------------------------------------------
1 | 1. `Math.random` + `sort`
2 |
3 | ```JavaScript
4 | const shuffle = (arr) => arr.sort(() => Math.random() - 0.5);
5 | ```
6 |
7 | 非均匀分布 + 性能问题
8 |
9 | 2. Fisher-Yates/Knuth 洗牌算法:
10 |
11 | ```js
12 | const shuffle = (arr) => {
13 | for (let i = arr.length - 1; i > 0; --i) {
14 | const j = Math.floor(Math.random() * (i + 1));
15 | [arr[i], arr[j]] = [arr[j], arr[i]];
16 | }
17 | return arr;
18 | };
19 | ```
20 |
21 | 时间复杂度 O(n)
22 |
--------------------------------------------------------------------------------
/JS(practical aspects)/数组元素奇前偶后.md:
--------------------------------------------------------------------------------
1 | ```js
2 | const sortArray = (array) =>
3 | array.sort((a, b) => (a % 2 === 0) - (b % 2 === 0) || a - b);
4 | const array = [1, 2, 3, 4, 5];
5 | console.log(sortArray(array)); // [ 1, 3, 5, 2, 4 ]
6 | ```
7 |
8 | `a` 为偶 `b` 为奇 -> `a` 排 `b` 后
9 | `a` 为奇 `b` 为偶 -> `a` 排 `b` 前
10 | `a` 和 `b` 同为奇/偶 -> `a - b`
11 |
--------------------------------------------------------------------------------
/JS(practical aspects)/数组转换为对象.md:
--------------------------------------------------------------------------------
1 | ```js
2 | const arrayToObject = (arr) => {
3 | const obj = {};
4 | for (const item of arr) {
5 | const { name, value } = item;
6 | obj[name] = value;
7 | }
8 | return obj;
9 | };
10 |
11 | const list = [
12 | { name: "clown", value: 666 },
13 | { name: "Alice", value: 30 },
14 | { name: "Bob", value: 40 },
15 | { name: "Charlie", value: 50 },
16 | ];
17 |
18 | console.log(arrayToObject(list)); // { clown: 666, Alice: 30, Bob: 40, Charlie: 50 }
19 | ```
--------------------------------------------------------------------------------
/JS(practical aspects)/日期格式化.md:
--------------------------------------------------------------------------------
1 | ```JavaScript
2 | const formatDate = (date, format) => {
3 | const addZero = (n) => (Number(n) < 10 ? "0" + n : String(n));
4 | const convert = {
5 | YYYY: date.getFullYear(),
6 | MM: addZero(date.getMonth() + 1),
7 | DD: addZero(date.getDate()),
8 | HH: addZero(date.getHours()),
9 | mm: addZero(date.getMinutes()),
10 | ss: addZero(date.getSeconds()),
11 | };
12 | return format.replace(
13 | /(YYYY|MM|DD|HH|mm|ss)/g,
14 | (matched) => convert[matched]
15 | );
16 | };
17 | ```
18 |
19 | ```js
20 | const formatData = (input, separator) =>
21 | input.replace(/\[(\d{4})(\d{2})(\d{2})\]/g, `$1${separator}$2${separator}$3`);
22 |
23 | const date = "[20240517]";
24 | console.log(formatData(date, "."));
25 | console.log(formatData(date, "-"));
26 | console.log(formatData(date, "/"));
27 | ```
--------------------------------------------------------------------------------
/JS(practical aspects)/日期转化:一年第几天.md:
--------------------------------------------------------------------------------
1 | ```js
2 | function getDayOfYear(dateString) {
3 | const year = parseInt(dateString.substring(0, 4));
4 | const month = parseInt(dateString.substring(4, 6)) - 1;
5 | const day = parseInt(dateString.substring(6, 8));
6 | const date = new Date(year, month, day);
7 | const start = new Date(date.getFullYear(), 0, 0);
8 | const diff = date - start;
9 | const oneDay = 1000 * 60 * 60 * 24;
10 | const dayOfYear = Math.floor(diff / oneDay);
11 | return dayOfYear;
12 | }
13 | ```
--------------------------------------------------------------------------------
/JS(practical aspects)/时间地区转换.md:
--------------------------------------------------------------------------------
1 | ```js
2 | const formatTime = (dateStr, timeZone) => {
3 | const date = new Date(dateStr);
4 | const convert = new Intl.DateTimeFormat("en-US", {
5 | timeZone,
6 | year: "numeric",
7 | month: "2-digit",
8 | day: "2-digit",
9 | hour: "2-digit",
10 | minute: "2-digit",
11 | second: "2-digit",
12 | hour12: false,
13 | });
14 | const [month, day, year, hour, minute, second] = convert
15 | .format(date)
16 | .match(/\d+/g);
17 | return `${year}-${month}-${day} ${hour}-${minute}-${second}`;
18 | };
19 |
20 | const dataStr = "2024-05-17T12:00:00Z"; // UTC 时间
21 | const timeZone = "America/New_York"; // 目标时区
22 | console.log(formatTime(dataStr, timeZone)); // 2024-05-17 08-00-00
23 | ```
--------------------------------------------------------------------------------
/JS(practical aspects)/时间格式化输出.md:
--------------------------------------------------------------------------------
1 | ```JavaScript
2 | const formatDate = (date, format) => {
3 | const addZero = data => data < 10 ? '0' + data : data;
4 | const obj = {
5 | 'yyyy': date.getFullYear(),
6 | 'yy': date.getFullYear() % 100,
7 | 'MM': addZero(date.getMonth() + 1),
8 | 'M': date.getMonth() + 1,
9 | 'dd': addZero(date.getDate()),
10 | 'd': date.getDate(),
11 | 'HH': addZero(date.getHours()),
12 | 'H': date.getHours(),
13 | 'hh': addZero(date.getHours() % 12),
14 | 'h': date.getHours() % 12,
15 | 'mm': addZero(date.getMinutes()),
16 | 'm': date.getMinutes(),
17 | 'ss': addZero(date.getSeconds()),
18 | 's': date.getSeconds(),
19 | 'w': () {
20 | const arr = ['日', '一', '二', '三', '四', '五', '六'];
21 | return arr[date.getDay()];
22 | }
23 | }
24 | for(let i in obj) format = format.replace(i, obj[i]);
25 | return format;
26 | }
27 | ```
--------------------------------------------------------------------------------
/JS(practical aspects)/构造小于给定数的最大数.md:
--------------------------------------------------------------------------------
1 | 题意:给定一个整数如 `n = 1234` 和一个排序后的常数数组如 `digits = [1, 2, 3]`,构造一个小于 `n` 的最大数
2 |
3 | ```js
4 | const largestNumberLessThanN = (n, digits) => {
5 | let maxNum = -Infinity;
6 | const dfs = (curNum) => {
7 | if (curNum >= n) return;
8 | maxNum = Math.max(maxNum, curNum);
9 | for (const digit of digits) dfs(curNum + digit);
10 | };
11 | for (const digit of digits) {
12 | if (digit !== 0) dfs(digit.toString());
13 | }
14 | return maxNum;
15 | };
16 |
17 | console.log(largestNumberLessThanN(1234, [1, 2, 3])); // 1233
18 | console.log(largestNumberLessThanN(1000, [1, 2, 3])); // 333
19 | console.log(largestNumberLessThanN(5678, [1, 3, 5])); // 5555
20 | console.log(largestNumberLessThanN(4321, [1, 2, 3, 4])); // 4314
21 | console.log(largestNumberLessThanN(500, [1, 2, 3])); // 333
22 | ```
--------------------------------------------------------------------------------
/JS(practical aspects)/查找 24 小时制时间列表中最小时间差.md:
--------------------------------------------------------------------------------
1 | ```js
2 | const findMinDiff = (list) => {
3 | const mins = list
4 | .map((time) => {
5 | const [hour, min] = time.split(":").map(Number);
6 | return hour * 60 + min;
7 | })
8 | .sort((a, b) => a - b);
9 | let ans = Infinity;
10 | for (let i = 1; i < list.length; ++i)
11 | ans = Math.min(ans, mins[i] - mins[i - 1]);
12 | const circularDiff = 1440 + mins[0] - mins[list.length - 1];
13 | ans = Math.min(ans, circularDiff);
14 | return ans;
15 | };
16 |
17 | console.log(findMinDiff(["23:59", "00:00"])); // 1
18 | console.log(findMinDiff(["00:00", "23:59", "00:00"])); // 0
19 | ```
20 |
21 |
--------------------------------------------------------------------------------
/JS(practical aspects)/栈.md:
--------------------------------------------------------------------------------
1 | ```js
2 | function Stack() {
3 | let stk = [];
4 | this.push = function (item) {
5 | stk.push(item);
6 | };
7 | this.pop = function () {
8 | if (stk.length === 0) throw new Error("Stack is empty");
9 | return stk.pop();
10 | };
11 | this.peek = function () {
12 | if (stk.length === 0) throw new Error("Stack is empty");
13 | return stk[stk.length - 1];
14 | };
15 | Object.defineProperty(this, "length", {
16 | get: function () {
17 | return stk.length;
18 | },
19 | enumerable: true,
20 | configurable: false,
21 | });
22 | }
23 | ```
24 |
25 |
--------------------------------------------------------------------------------
/JS(practical aspects)/树的 DFS & BFS.md:
--------------------------------------------------------------------------------
1 | ```js
2 | class TreeNode {
3 | constructor(value) {
4 | this.value = value;
5 | this.children = [];
6 | }
7 | addChild(child) {
8 | this.children.add(child);
9 | }
10 | }
11 |
12 | const dfs = (node) => {
13 | if (!node) return;
14 | console.log(node);
15 | for (const child of node.children) dfs(child);
16 | };
17 |
18 | const bfs = (node) => {
19 | if (!node) return;
20 | const queue = [node];
21 | while (queue.length) {
22 | const cur = queue.shift();
23 | console.log(cur);
24 | for (const child of cur.children) queue.push(child);
25 | }
26 | };
27 | ```
--------------------------------------------------------------------------------
/JS(practical aspects)/根据给定字符串获取对象属性值.md:
--------------------------------------------------------------------------------
1 | ```js
2 | const getValueByStr = (obj, str) => {
3 | if (!obj || typeof obj !== "object") return undefined;
4 | if (typeof str !== "string") return undefined;
5 | const keys = str.split(".");
6 | let res = obj;
7 | for (const key of keys) {
8 | if (res && typeof res === "object" && key in res) res = res[key];
9 | else return undefined;
10 | }
11 | return res;
12 | };
13 |
14 | const data = {
15 | user: {
16 | name: "John Doe",
17 | address: {
18 | city: "New York",
19 | zip: "10001",
20 | },
21 | contact: {
22 | email: "john.doe@example.com",
23 | phone: "123-456-7890",
24 | },
25 | },
26 | };
27 |
28 | console.log(getValueByStr(data, "user.name")); // John Doe
29 | console.log(getValueByStr(data, "user.address.city")); // New York
30 | console.log(getValueByStr(data, "user.contact.email")); // john.doe@example.com
31 | console.log(getValueByStr(data, "user.age")); // undefined
32 | console.log(getValueByStr(data, "user.address.country")); // undefined
33 | ```
--------------------------------------------------------------------------------
/JS(practical aspects)/检测数组是否有序.md:
--------------------------------------------------------------------------------
1 | ```js
2 | const isSorted = (arr) => {
3 | if (arr.length <= 1) return true;
4 | let isAscending = true,
5 | isDescending = true;
6 | for (let i = 1; i < arr.length; ++i) {
7 | if (arr[i - 1] < arr[i]) isDescending = false;
8 | if (arr[i - 1] > arr[i]) isAscending = false;
9 | }
10 | return isAscending || isDescending;
11 | };
12 |
13 | console.log(isSorted([])); // true
14 | console.log(isSorted([1])); // true
15 | console.log(isSorted([1, 2, 3, 4, 5])); // true
16 | console.log(isSorted([5, 4, 3, 2, 1])); // true
17 | console.log(isSorted([1, 3, 2, 4, 5])); // false
18 | ```
--------------------------------------------------------------------------------
/JS(practical aspects)/深度比较.md:
--------------------------------------------------------------------------------
1 | ```js
2 | const deepEqual = (obj1, obj2) => {
3 | if (obj1 === obj2) return true;
4 | if (
5 | typeof obj1 !== "object" ||
6 | obj1 === null ||
7 | typeof obj2 !== "object" ||
8 | obj2 === null
9 | )
10 | return false;
11 | const key1 = Object.keys(obj1),
12 | key2 = Object.keys(obj2);
13 | if (key1.length !== key2.length) return false;
14 | for (const key of key1) {
15 | if (!key2.includes(key) || !deepEqual(obj1[key], obj2[key])) return false;
16 | }
17 | return true;
18 | };
19 |
20 | const obj1 = { a: 1, b: { c: 2 } };
21 | const obj2 = { a: 1, b: { c: 2 } };
22 | const obj3 = { a: 1, b: { c: 3 } };
23 | const obj4 = { a: 1, b: { c: 3, d: 4 } };
24 | const obj5 = 5;
25 |
26 | console.log(deepEqual(obj1, obj2)); // true
27 | console.log(deepEqual(obj1, obj3)); // false
28 | console.log(deepEqual(obj3, obj4)); // false
29 | console.log(deepEqual(obj1, obj5)); // false
30 | console.log(deepEqual(obj1, null)); // false
31 | console.log(deepEqual(null, null)); // true
32 | console.log(deepEqual(obj1, { a: 1, b: { c: 2 }, e: undefined })); // false
33 | ```
--------------------------------------------------------------------------------
/JS(practical aspects)/牛顿法求根号 n.md:
--------------------------------------------------------------------------------
1 | ```js
2 | const sqrtNewton = (n) => {
3 | if (n === 0) return 0;
4 | let x = n;
5 | while (true) {
6 | const nextX = 0.5 * (x + n / x);
7 | if (Math.abs(nextX - x) < 1e-7) return nextX;
8 | x = nextX;
9 | }
10 | };
11 |
12 | console.log(sqrtNewton(25)); // 5
13 | console.log(sqrtNewton(2)); // 1.414213562373095
14 | console.log(sqrtNewton(0)); // 0
15 | console.log(sqrtNewton(0.01)); // 0.1
16 | ```
--------------------------------------------------------------------------------
/JS(practical aspects)/生成区间 (left, rigth) 的 n 个不重复的数字.md:
--------------------------------------------------------------------------------
1 | 包括 left 和 right
2 |
3 | ```JavaScript
4 | const generateUnique = (n, left, right) => {
5 | if (n > right - left + 1)
6 | throw new Error("Not enough unique numbers can be generated in the range.");
7 | const set = new Set();
8 | while (set.size < n) {
9 | const num = Math.floor(Math.random() * (right - left + 1) + left);
10 | set.add(num);
11 | }
12 | return [...set];
13 | };
14 | ```
15 |
--------------------------------------------------------------------------------
/JS(practical aspects)/短横线与驼峰转化.md:
--------------------------------------------------------------------------------
1 | 短横线转驼峰:
2 |
3 | ```JavaScript
4 | const toCamelCase = (str) =>
5 | str.replace(/-([a-z])/g, (all, char) => char.toUpperCase());
6 | ```
7 |
8 | - `all` 即整个匹配到的字符串如输入为 `"background-color"`,则 `all` 为 `-c`
9 | - `char` 即匹配到的捕获组中之字符如输入为 `"background-color"`,则 `char` 为 `c`
10 |
11 | 驼峰转短横线:
12 |
13 | ```JavaScript
14 | const toKebabCase = (str) =>
15 | str.replace(/([a-z])([A-Z])/g, "$1-$2").toLowerCase();
16 | ```
17 |
--------------------------------------------------------------------------------
/JS(practical aspects)/给定字符串, 找出所有长度 Num 的重复子串.md:
--------------------------------------------------------------------------------
1 | ```JavaScript
2 | const findRepeat = (str, num) => {
3 | const seen = new Set(),
4 | ans = new Set();
5 | for (let i = 0; i <= str.length - num; ++i) {
6 | const sub = str.slice(i, i + num);
7 | if (seen.has(sub)) ans.add(sub);
8 | else seen.add(sub);
9 | }
10 | return [...ans];
11 | };
12 |
13 | const string = "ababbaba";
14 | const number = 2;
15 | console.log(findRepeat(string, number));
16 | ```
--------------------------------------------------------------------------------
/JS(practical aspects)/统计所有 url 中每天访问的 url.md:
--------------------------------------------------------------------------------
1 | ```js
2 | class urlTracker {
3 | constructor() {
4 | this.visited = {};
5 | }
6 | record(url, date) {
7 | if (!this.visited[date]) this.visited[date] = new Set();
8 | this.visited[date].add(url);
9 | }
10 | getVisitedUrls(date) {
11 | return this.visited[date] ? Array.from(this.visited[date]) : [];
12 | }
13 | }
14 |
15 | const tracker = new urlTracker();
16 | tracker.record("https://github.com/Rainyrou/FE-Clown", "2024-04-05");
17 | tracker.record("https://github.com/Rainyrou", "2024-04-05");
18 | tracker.record("https://github.com/Rainyrou/Pocket-React", "2024-04-06");
19 |
20 | console.log(tracker.getVisitedUrls("2024-04-05")); // [ 'https://github.com/Rainyrou/FE-Clown', https://github.com/Rainyrou' ]
21 | console.log(tracker.getVisitedUrls("2024-04-06")); // [ 'https://github.com/Rainyrou/Pocket-React' ]
22 | ```
--------------------------------------------------------------------------------
/JS(practical aspects)/翻转字符串中的每个单词.md:
--------------------------------------------------------------------------------
1 | ```js
2 | const reverseWords = (s) => {
3 | const words = s.split(" ");
4 | const reversedWords = words.map((word) => word.split("").reverse().join(""));
5 | return reversedWords.join(" ");
6 | };
7 |
8 | const input = "Let's take LeetCode contest";
9 | const output = reverseWords(input);
10 | console.log(output); // s'teL ekat edoCteeL tsetnoc
11 | ```
--------------------------------------------------------------------------------
/JS(practical aspects)/荷兰国旗问题.md:
--------------------------------------------------------------------------------
1 | ```js
2 | const sortColors = (nums) => {
3 | let left = 0,
4 | right = nums.length - 1,
5 | i = 0;
6 | while (i <= right) {
7 | if (nums[i] === 1) {
8 | [nums[i], nums[left]] = [nums[left], nums[i]];
9 | ++left;
10 | ++i;
11 | } else if (nums[i] === 2) {
12 | [nums[i], nums[right]] = [nums[right], nums[i]];
13 | --right;
14 | } else {
15 | ++i;
16 | }
17 | }
18 | return nums;
19 | };
20 |
21 | console.log(sortColors([2, 0, 2, 1, 1, 0])); // [ 1, 1, 0, 0, 2, 2 ]
22 | console.log(sortColors([1, 2, 0])); // [ 1, 0, 2 ]
23 | ```
--------------------------------------------------------------------------------
/JS(practical aspects)/观察者模式.md:
--------------------------------------------------------------------------------
1 | ```JavaScript
2 | class Subject {
3 | constructor() {
4 | this.observers = [];
5 | }
6 | add(observer) {
7 | this.observers.push(observer);
8 | }
9 | delete(observer) {
10 | const index = this.observers.indexOf(observer);
11 | if (index > -1) this.observers.splice(index, 1);
12 | }
13 | notify(data) {
14 | this.observers.forEach((observer) => observer.update(data));
15 | }
16 | }
17 |
18 | class Observer {
19 | update(data) {
20 | console.log(`receive data: ${data}`);
21 | }
22 | }
23 | ```
24 |
25 | 测试用例:
26 |
27 | ```JavaScript
28 | const subject = new Subject();
29 | const observer1 = new Observer();
30 | const observer2 = new Observer();
31 |
32 | subject.add(observer1);
33 | subject.add(observer2);
34 | subject.notify('Hello world!');
35 | subject.delete(observer1);
36 | subject.notify('Goodbye world!');
37 | ```
38 |
39 | 正确输出:
40 |
41 | ![[1697670839501.png]]
42 |
--------------------------------------------------------------------------------
/JS(practical aspects)/解决回调地狱.md:
--------------------------------------------------------------------------------
1 | ```js
2 | let t1 = setTimeout(() => {
3 | console.log(111);
4 | let t2 = setTimeout(() => {
5 | console.log(222);
6 | let t3 = setTimeout(() => {
7 | console.log(333);
8 | }, 3000);
9 | }, 2000);
10 | }, 1000);
11 | ```
12 |
13 | ```js
14 | const delay = (time) =>
15 | new Promise((resolve) => setTimeout(() => resolve(), time));
16 |
17 | const run = async () => {
18 | await delay(1000);
19 | console.log(111);
20 | await delay(2000);
21 | console.log(222);
22 | await delay(3000);
23 | console.log(333);
24 | };
25 | run();
26 | ```
27 |
28 | ```js
29 | const delay = (time) =>
30 | new Promise((resolve) => setTimeout(() => resolve(), time));
31 |
32 | delay(1000)
33 | .then(() => {
34 | console.log(111);
35 | return delay(2000);
36 | })
37 | .then(() => {
38 | console.log(222);
39 | return delay(3000);
40 | })
41 | .then(() => console.log(333));
42 | ```
--------------------------------------------------------------------------------
/JS(practical aspects)/解析 URL 的查询字符串为对象.md:
--------------------------------------------------------------------------------
1 | ```js
2 | const parseQuery = (url) => {
3 | const queryString = url.includes("?") ? url.split("?")[1].split("#")[0] : "";
4 | const obj = {};
5 | if (!queryString) return obj;
6 | queryString.split("&").forEach((part) => {
7 | const [key, val = ""] = part.split("=");
8 | const decodedKey = decodeURIComponent(key);
9 | const decodedVal = decodeURIComponent(val.replace(/\+/g, " "));
10 | obj[decodedKey] = obj[decodedKey]
11 | ? [].concat(obj[decodedKey], decodedVal)
12 | : decodedVal;
13 | });
14 | return obj;
15 | };
16 | ```
17 |
18 | 若 `obj[decodedKey]` 存在,`[].concat` 将 `obj[decodedKey]` 的值与 `decodedVal` 合并成一个新数组:
19 | * 如果 `obj[decodedKey]` 为一个值如字符串 `"Alice"`,则变成数组如 `[].concat("Alice", "Bob")` 会得到 `["Alice", "Bob"]`
20 | - 如果 `obj[decodedKey]` 已为数组,则直接添加 `decodedVal`
21 | 若 `obj[decodedKey]` 不存在,直接将 `decodedVal` 赋值给 `obj[decodedKey]`,无需合并数组
22 |
--------------------------------------------------------------------------------
/JS(practical aspects)/解析依赖顺序.md:
--------------------------------------------------------------------------------
1 | 打包工具解析文件间的依赖关系,在加载各个模块时确保其依赖已加载
2 |
3 | 输入:依赖树
4 |
5 | ```
6 | {
7 | name: "page.js",
8 | require: [
9 | {
10 | name: "A.js",
11 | require: [{ name: "C.js", require: [{ name: "F.js" }] }],
12 | },
13 | {
14 | name: "B.js",
15 | require: [
16 | {
17 | name: "D.js",
18 | require: [{ name: "F.js" }],
19 | },
20 | {
21 | name: "E.js",
22 | require: [],
23 | },
24 | ],
25 | },
26 | ],
27 | };
28 | ```
29 |
30 | 输出:`["F.js","E.js","D.js","C.js","B.js","A.js","page.js"]`
31 |
--------------------------------------------------------------------------------
/JS(practical aspects)/计算异步函数执行时间.md:
--------------------------------------------------------------------------------
1 | ```js
2 | function measureTime(fn) {
3 | return async function (...args) {
4 | const start = performance.now();
5 | const result = await fn(...args);
6 | const end = performance.now();
7 | const time = end - start;
8 | console.log(`execute ${time} ms`);
9 | return result;
10 | };
11 | }
12 |
13 | async function fn(x, y) {
14 | return new Promise((resolve) => setTimeout(() => resolve(x + y), 1000));
15 | }
16 |
17 | const asyncFn = measureTime(fn);
18 | asyncFn(5, 10).then((res) => console.log(`result: ${res}`));
19 | ```
--------------------------------------------------------------------------------
/JS(practical aspects)/请求 5s 内无结果则提示超时.md:
--------------------------------------------------------------------------------
1 | 同时执行 2 个 Promise:一个为实际的异步请求,另一个为在 5 秒后 resolved 的 Promise,接着使用 `Promise.race` 竞争此二者,以先 resolved 的 Promise 结果为准
2 |
3 | ```JavaScript
4 | const fetchData = () =>
5 | new Promise((resolve) => setTimeout(() => resolve("request success"), 4000));
6 | const timeOut = (delay) =>
7 | new Promise((resolve, reject) =>
8 | setTimeout(() => reject("request fail"), delay)
9 | );
10 | Promise.race([fetchData(), timeOut(5000)])
11 | .then((res) => console.log(res))
12 | .catch((err) => console.error(err));
13 | ```
14 |
--------------------------------------------------------------------------------
/JS(practical aspects)/请求失败重试.md:
--------------------------------------------------------------------------------
1 | ```js
2 | const requestWithRetry = async (fn, maxCount = 5, interval = 1000) => {
3 | let count = 0;
4 | const execute = async () => {
5 | try {
6 | ++count;
7 | const result = await fn();
8 | return result;
9 | } catch (err) {
10 | if (count < maxCount) {
11 | console.warn(`${count}`, `${err}`);
12 | await new Promise((resolve) => setTimeout(() => resolve(), interval));
13 | return execute();
14 | } else {
15 | throw new Error(`${err}`);
16 | }
17 | }
18 | };
19 | return execute();
20 | };
21 |
22 | const simulateRequest = () =>
23 | new Promise((resolve, reject) =>
24 | setTimeout(() => reject("request fail"), 1000)
25 | );
26 |
27 | requestWithRetry(simulateRequest)
28 | .then((res) => console.log(res))
29 | .catch((err) => console.warn(err));
30 | ```
31 |
32 | ```
33 | 1 request fail
34 | 2 request fail
35 | 3 request fail
36 | 4 request fail
37 | Error: request fail
38 | ```
--------------------------------------------------------------------------------
/JS(practical aspects)/调用一次函数获取下一质数.md:
--------------------------------------------------------------------------------
1 | ```js
2 | const isPrime = (n) => {
3 | if (n < 2) return false;
4 | for (let i = 2; i <= Math.sqrt(n); ++i) {
5 | if (n % i === 0) return false;
6 | }
7 | return true;
8 | };
9 |
10 | function createPrimeGenerator() {
11 | let cur = 1;
12 | return function getNext() {
13 | ++cur;
14 | while (!isPrime(cur)) ++cur;
15 | return cur;
16 | };
17 | }
18 |
19 | const nextPrime = createPrimeGenerator();
20 | console.log(nextPrime()); // 2
21 | console.log(nextPrime()); // 3
22 | console.log(nextPrime()); // 5
23 | console.log(nextPrime()); // 7
24 | ```
--------------------------------------------------------------------------------
/JS(practical aspects)/递增数组格式化输出.md:
--------------------------------------------------------------------------------
1 | ```js
2 | const getArray = (arr) => {
3 | const len = arr.length,
4 | ans = [];
5 | let i = 0;
6 | while (i < len) {
7 | let cur = i;
8 | while (i + 1 < len && arr[i] === arr[i + 1] - 1) ++i;
9 | if (cur !== i) ans.push(`${arr[cur]}->${arr[i]}`);
10 | else ans.push(`${arr[cur]}`);
11 | ++i;
12 | }
13 | return ans;
14 | };
15 |
16 | const arr = [0, 1, 2, 4, 5, 7, 11, 13, 14];
17 | console.log(getArray(arr));
18 | ```
--------------------------------------------------------------------------------
/JS(practical aspects)/链式调用 add 函数.md:
--------------------------------------------------------------------------------
1 | ```js
2 | function add(...args) {
3 | const sum = args.reduce((a, b) => a + b, 0);
4 | return function (...newArgs) {
5 | return newArgs.length ? add(sum, ...newArgs) : sum;
6 | };
7 | }
8 |
9 | console.log(add(1, 2, 3)(4, 5)(6)()); // 21
10 | ```
11 |
12 | ```js
13 | function sum(...args) {
14 | const allArgs = [...args];
15 | const fn = (...newArgs) => {
16 | allArgs.push(...newArgs);
17 | return fn;
18 | };
19 | fn.sumoff = () => allArgs.reduce((a, b) => a + b, 0);
20 | return fn;
21 | }
22 |
23 | console.log(sum(1, 2).sumoff()); // 3
24 | console.log(sum(1, 2)(3).sumoff()); // 6
25 | console.log(sum(1)(2)(3)(4).sumoff()); // 10
26 | ```
27 |
28 |
29 |
--------------------------------------------------------------------------------
/JS(practical aspects)/链式调用实现加减乘除.md:
--------------------------------------------------------------------------------
1 | ```js
2 | function myCalculator(num) {
3 | const calc = {
4 | value: num,
5 | add(x) {
6 | this.value += x;
7 | return this;
8 | },
9 | minus(x) {
10 | this.value -= x;
11 | return this;
12 | },
13 | multi(x) {
14 | this.value *= x;
15 | return this;
16 | },
17 | div(x) {
18 | this.value /= x;
19 | return this;
20 | },
21 | valueOf() {
22 | return this.value;
23 | },
24 | };
25 | return calc;
26 | }
27 |
28 | console.log(myCalculator(1).add(4).minus(2).multi(3).div(3).value); // 3
29 | ```
--------------------------------------------------------------------------------
/JS(practical aspects)/闭包实现计数器.md:
--------------------------------------------------------------------------------
1 | ```js
2 | function Counter() {
3 | let count = 0;
4 | return {
5 | increment: function () {
6 | ++count;
7 | return count;
8 | },
9 | decrement: function () {
10 | --count;
11 | return count;
12 | },
13 | reset: function () {
14 | count = 0;
15 | return count;
16 | },
17 | getCount: function () {
18 | return count;
19 | },
20 | };
21 | }
22 |
23 | const counter = Counter();
24 | console.log(counter.increment()); // 1
25 | console.log(counter.increment()); // 2
26 | console.log(counter.getCount()); // 2
27 | console.log(counter.decrement()); // 1
28 | console.log(counter.reset()); // 0
29 | ```
--------------------------------------------------------------------------------
/JS(practical aspects)/隔 1 秒输出 1,持续输出至隔 10 秒输出 10.md:
--------------------------------------------------------------------------------
1 | ```js
2 | const logWithDelay = (num, delay) =>
3 | new Promise((resolve) =>
4 | setTimeout(() => {
5 | console.log(num);
6 | resolve();
7 | }, delay * 1000)
8 | );
9 |
10 | const sequentialLog = async (num, max = 10) => {
11 | for (let i = num; i <= max; i++) await logWithDelay(i, 1);
12 | };
13 |
14 | sequentialLog(1);
15 | ```
--------------------------------------------------------------------------------
/JS(practical aspects)/隔 s 秒执行一次,共执行 n 次.md:
--------------------------------------------------------------------------------
1 | ```js
2 | function repeatFunction(fn, s, n) {
3 | let count = 0;
4 | const interval = setInterval(() => {
5 | if (count < n) {
6 | fn();
7 | ++count;
8 | } else {
9 | clearInterval(interval);
10 | }
11 | }, s * 1000);
12 | }
13 |
14 | repeatFunction(() => console.log("Hello"), 2, 5);
15 | ```
--------------------------------------------------------------------------------
/JS(theoretical aspects)/Array & Object & Map & Set & WeakMap & WeakSet.md:
--------------------------------------------------------------------------------
1 | Array:有序集合,JavaScript 引擎在 Array 底层结合动态数组和 Object 的特性,Array 可根据所需自动增长,数组元素按顺序存储且可通过索引访问,Array 允许稀疏存储即数组元素可为空如 `[1, , 3]`
2 | Object:键值对的集合,其键唯一,其值可为任何数据类型,JavaScript 引擎在 Object 底层使用哈希表来存储数据,每个 Object 的属性均有一个属性描述符(可通过 `Object.getOwnPropertyDescriptor` 获取),包含该属性的配置如可写性、可枚举性和可配置性,此外 Object 支持原型继承
3 | Map:键值对的集合,类似 Object,但其键可为任何数据类型,JavaScript 引擎在 Map 底层使用哈希表来存储数据,执行增删查找操作的时间复杂度为 O(1),若 Map 添加已存在的键,新值覆盖旧值,Map 依照元素的插入顺序进行遍历
4 | Set:允许存储任何数据类型唯一值的集合,JavaScript 引擎在 Set 底层使用哈希表来存储数据,确保存储值均唯一,执行增删查找操作的时间复杂度为 O(1),若 Set 添加已存在的值,直接忽略
5 | WeakMap:键值对的集合,其键为弱引用对象即引用数据类型,当无对 WeakMap 键的强引用时,这些键对应的对象可被垃圾回收,因为其键为弱引用对象,WeakMap 不支持枚举操作如 `forEach` 和迭代器等,因为键随时可能被垃圾回收,从而导致无法遍历所有键。JavaScript 引擎在 WeakMap 底层使用弱引用表来存储数据,弱引用表可跟踪对象的生命周期并在对象被垃圾回收时自动清理相应的键值对
6 | WeakSet:允许存储任何数据类型唯一值的集合,其对象为弱引用即引用数据类型,当无对 WeakSet 对象的强引用时,它们可被垃圾回收,因为其键为弱引用对象,WeakSet 不支持枚举操作如 `forEach` 和迭代器等,因为键随时可能被垃圾回收,从而导致无法遍历所有键。JavaScript 引擎在 WeakSet 底层使用弱引用表来存储数据,弱引用表可跟踪对象的生命周期并在对象被垃圾回收时自动清理相应的键值对
7 |
--------------------------------------------------------------------------------
/JS(theoretical aspects)/ES6 & ES7.md:
--------------------------------------------------------------------------------
1 | ###### ES6
2 |
3 | 1. `let` & `const`
4 | 2. Promise:用于处理异步操作,可替代回调
5 | 3. 箭头函数:不绑定自己的 `this`
6 | 4. 模板字符串:通过反引号实现字符串插值
7 | 5. 解构赋值:允许从数组或对象中提取值并赋值给新变量
8 | 6. 扩展操作符 `...`:用于函数调用和数组字面量中,用于扩展或收集元素
9 | 7. 类:提供基于类的面向对象编程的语法
10 | 8. 模块系统 `import/export`:允许模块化的代码组织和模块的导入/导出
11 | 9. `Generator`、`Proxy`、`Map`、`Set`、`WeakMap`、`WeakSet`、`Symbol` 等
12 |
13 | ###### ES7
14 |
15 | 1. async/await
16 | 2. `Array.prototype.includes`
17 | 3. 指数操作符 `**`:用于数学的幂运算
18 |
19 |
20 |
--------------------------------------------------------------------------------
/JS(theoretical aspects)/Generator & Async & Await.md:
--------------------------------------------------------------------------------
1 | Generator 即 ES6 关于协程的实现,其为一个封装的异步任务,以 `yield` 暂停执行函数,函数名前加星号。调用它不会立即执行函数体中的代码,而是返回指针对象。Generator 对象是一个迭代器对象,其 `next` 方法用于遍历函数内部的 `yield` 表达式,即分阶段执行 Generator 函数,每次调用 `next` 方法,均返回一个表示当前阶段信息的对象。`value` 是 `yield` 语句后表达式的值,表示当前阶段的值;`done` 为布尔值,表示函数是否执行完,`next` 方法返回的 `value` 是 Generator 函数的输出数据,`next` 方法接受的参数是外部向 Generator 函数的输入数据
2 |
3 | Async/Await 在实现上结合 Generator 与 Promise,简化使用多个 Promise 时的同步行为,无需关心某个操作是否为异步操作
4 |
--------------------------------------------------------------------------------
/JS(theoretical aspects)/JS 单线程.md:
--------------------------------------------------------------------------------
1 | JavaScript 在 1995 年发明,设计之初是为浏览器提供页面轻量交互功能,当时的 Web 页面无需复杂逻辑,单线程足以应付绝大多数情况,同时确保执行环境不必处理复杂的并发和数据一致性问题。在多线程环境中,共享资源如 DOM 元素的并发访问需要复杂的同步机制,这些机制涉及锁和阻塞,可能导致死锁和竞争条件,操作 UI DOM 可能出现冲突如两个线程同时操作同一 DOM,一个执行修改操作一个执行删除操作,可通过锁解决上述问题,但为避免引入锁而带来的复杂性,JS 在设计之初选择单线程的形式,简化前端开发者对于事件处理流程和执行流程的理解而无需考虑多线程的问题。此外 JavaScript 的执行环境通过事件循环支持异步编程,允许 JavaScript 进行非阻塞 I/O 操作如网络请求和文件操作等,即使是在单线程中也能执行高效的操作。尽管 JavaScript 是单线程的,但现代 JavaScript 已发展出多种方法来模拟多线程处理如 Web Workers,Web Workers 允许我们创建可以执行在背景线程中的 JavaScript 脚本,执行计算密集或时间长的任务而不会阻塞主线程
2 |
--------------------------------------------------------------------------------
/JS(theoretical aspects)/JS 设计模式及原则.md:
--------------------------------------------------------------------------------
1 | 1. 模块化模式:通过 IIFE 和闭包实现数据私有化和暴露公共接口,而 ES6 引入 ES Modules
2 | 2. 观察者模式 & 发布订阅模式:观察者模式定义对象间的依赖关系,观察者与主题直接通信,观察者知道主题的存在,当主题状态变化时,主题遍历所有观察者并逐一通知,观察者在接收到通知后执行相应操作。而发布者将消息发布于某主题上,订阅者通过事件通道订阅某主题,事件通道接收到消息后,查找该主题对应的订阅者列表并将消息传递给所有订阅者,事件通道解耦发布者和订阅者,发布者无需知道订阅者的存在及哪些订阅者接收消息,订阅者无需直接与发布者交互
3 | 3. 装饰器模式:为对象动态拓展属性和方法而不改变对象原有结构
4 | 4. 单例模式:通过静态变量或闭包保证类或模块只创建单一实例,后续获取实例时直接返回
5 | 5. 工厂模式:通过函数作用域和 `new` 动态分配内存,提供统一接口根据入参创建不同对象实例,无需关心对象创建的具体细节
6 | 6. 策略模式:通过闭包和高阶函数封装算法为独立的策略对象实现算法的动态切换
7 |
--------------------------------------------------------------------------------
/JS(theoretical aspects)/NaN && === && ==.md:
--------------------------------------------------------------------------------
1 | - `==`:宽松相等,若两者类型不同,JavaScript 尝试转换其数据类型接着比较
2 | - `===`:严格相等,若两者类型不同,直接返回 `false`
3 | - `null == undefined` 为 `true`,`null === undefined` 为 `false`,`NaN === NaN` 为 `false`,`{} === {} `为 `false`,`{} == {}` 为 `false`
4 | - `1 + '2'` 为 `'12'`,当数字和字符串相加时,数字被转换为字符串,然后进行字符串连接
5 | - `1 - '2'` 为 `-1`,当数字和字符串相减时,字符串被转换为数字,然后进行数学运算
6 | - `NaN` 非数字,其表示数学运算返回值无效,`typeof NaN` 返回 `"number"`,`Number.isNaN` 检测一个值是否为 `NaN`
--------------------------------------------------------------------------------
/JS(theoretical aspects)/TS 与类型体操.md:
--------------------------------------------------------------------------------
1 | TypeScript 的「类型体操」概念源于其图灵完备的类型系统,这是一种工程化的防御手段,随项目对类型安全要求越来越高,前端开始探索如何在类型层面"编程",即用类型系统来解决实际问题如实现 Deep Readonly、Omit 甚至 Promise.all 等。我们从演变、原理和场景三个维度作为切入点:
2 |
3 | 1. 类型能力的演变
4 |
5 | | 阶段 | 特征 | TS 版本 | 关键能力 |
6 | | ------ | -------- | ----- | ------------- |
7 | | 基础类型 | 简单类型标注 | 1.0+ | 接口、联合类型 |
8 | | 泛型编程 | 参数化类型 | 2.0+ | 泛型约束、条件类型 |
9 | | 类型元编程 | 类型层面逻辑运算 | 4.1+ | 模板字面量类型、递归类型 |
10 | | 类型图灵完备 | 模拟任意计算 | 4.8+ | 深度模式匹配、类型级控制流 |
11 | | | | | |
12 |
13 | 2. 图灵完备的证明(只是编译阶段)
14 |
15 | 通过实现类型层面的 SKI 组合子演算:
16 |
17 | ```ts
18 | type S = (x: A, y: B, z: C) => (x & z) & (y & z);
19 | type K = (x: A) => (_: B) => A;
20 | type I = (x: A) => A;
21 | type SKK = STime up26 | ) : ( 27 |28 | Time Remain: {minutes}:{seconds < 10 ? `0${seconds}` : seconds} 29 |30 | )} 31 | |
---|