├── .gitignore ├── 2022年03月17日.md ├── 2022年03月18日.md ├── Interview.md ├── README.md ├── algorithm ├── 0.tree-traverse.js ├── 1.hash-map │ ├── 1.lru-cache.js │ ├── 2.design-hashset.js │ ├── 3.design-hashmap.js │ ├── 4.hashmap+crash.js │ └── README.md ├── 2.sort │ ├── 1.bubble-sort.js │ ├── 2.insert-sort.js │ ├── 3.select-sort.js │ ├── 4.merge-sort.js │ └── 5.quick-sort.js ├── 20.sliding-window-maximum.js ├── 21.dfs-bfs.js ├── 22.permutations.js ├── 23.subsets.js ├── 24.combinations.js ├── 25.binary-tree-preorder-traversal.js ├── 26.binary-tree-postorder-traversal.js ├── 27.binary-tree-inorder-traversal.js ├── 28.binary-tree-level-order-traversal.js ├── 29.invert-binary-tree.js ├── 3.dynamic-planning │ ├── 1.climbing-stairs.js │ ├── 2.coin.js │ ├── 3.jump-game.js │ ├── 4.knapsack.js │ ├── 5.longest-increasing-subsequence.js │ ├── 6.longest-common-subsequence.js │ ├── 7.longest-palindromic-substring.js │ └── 8.maximum-product-subarray.js ├── 30.bst.js ├── 31.delete-node-in-a-bst.js ├── 32.sorted-array-to-bst.js ├── 33.avl.js ├── 34.heap.js ├── 4.array-string │ ├── 1.two-sum.js │ ├── 10.range-list.js │ ├── 2.merge-sorted-array.js │ ├── 3.3sum.js │ ├── 4.valid-palindrome-ii.js │ ├── 5.merge-intervals.js │ ├── 6.design-add-and-search-words-data-structure.js │ ├── 7.string-to-integer-atoi.js │ ├── 8.best-time-to-buy-and-sell-stock.js │ └── 9.best-time-to-buy-and-sell-stock-ii.js ├── 5.list │ ├── 1.merge-two-sorted-lists.js │ ├── 10.delete-list-node.js │ ├── 2.remove-duplicates-from-sorted-list.js │ ├── 3.remove-duplicates-from-sorted-list-ii.js │ ├── 4.remove-nth-node-from-end-of-list.js │ ├── 5.reverse-linked-list.js │ ├── 6.reverse-linked-list-ii.js │ ├── 7.linked-list-cycle.js │ ├── 8.linked-list-cycle-ii.js │ └── 9.delete-node-in-a-linked-list.js ├── 5.palindrome-number.js ├── 6.stack-queue │ ├── 1.valid-parentheses.js │ ├── 2.daily-temperatures.js │ ├── 3.min-stack.js │ ├── 4.implement-queue-using-stacks.js │ └── 5.sliding-window-maximum.js └── README.md ├── article.md ├── coding ├── README.md └── doc │ ├── 0.reg.js │ ├── 1.settimeout-setinterval.js │ ├── 10.promise-application.js │ ├── 10.promise-doc.js │ ├── 10.promise-practice.js │ ├── 10.promise-question.js │ ├── 10.promise-resource.js │ ├── 11.concurrent-request.js │ ├── 12.curry.js │ ├── 13.remove-duplicates.js │ ├── 14.event-emit.js │ ├── 15.array-tree.js │ ├── 16.xhr.html │ ├── 17.lazyman.js │ ├── 18.repeat.js │ ├── 19.width:height.html │ ├── 2.deepClone.js │ ├── 20.jsonp.js │ ├── 21.array-sort.js │ ├── 22.inherit.js │ ├── 3.debounce-throttle.html │ ├── 4.flatten.js │ ├── 5.big-number-sum.js │ ├── 6.checkQQDomian.js │ ├── 7.compose.js │ ├── 8.new.js │ ├── 9.call-apply-bind.js │ ├── package.json │ ├── server.js │ └── yarn.lock ├── css └── css.md ├── hand-webpack ├── loaders │ ├── logger1-loader.js │ ├── logger2-loader.js │ └── runner.js ├── package.json ├── plugins │ ├── 1.plugin.js │ ├── 2.babel.plugin.js │ ├── done-plugin.js │ └── run-plugin.js ├── src │ ├── entry1.js │ ├── entry2.js │ └── title.js ├── webpack.config.js ├── webpack │ ├── Compiler.js │ ├── Complication.js │ ├── tapable.js │ └── webpack.js └── yarn.lock ├── html └── html.md ├── js ├── closure.md ├── es6.md └── js.md ├── network ├── README.md ├── assets │ └── httpwebsocket.png ├── cdn.md ├── http2_http3.md ├── https.md ├── ip.md ├── questions.md ├── tcp_udp.md └── websocket │ ├── 1.server.js │ ├── doc │ ├── 1.js │ ├── 2.js │ └── 3.js │ ├── index.html │ ├── package-lock.json │ ├── package.json │ ├── utils.js │ ├── websocket.md │ └── ws.js ├── old.md ├── optimize └── virtual-list.html ├── service-worker ├── 1.js ├── index.html ├── index.js └── worker.js ├── vue ├── README.md └── doc │ ├── 1.reactive.js │ └── 2.diff.js ├── websocket ├── README.md ├── index.html ├── package.json ├── reconnect.js ├── server.js ├── upgrade.js └── yarn.lock └── write ├── 01.big-add.js ├── 02.small-add.js ├── 03.width-height.html ├── 04.deep-clone.js ├── 05.native-ajax.js ├── 06.debounce-throttle.js ├── 07.bind-call-apply.js ├── 08.new.js ├── 09.currying.js ├── 10.reduce.js ├── 11.flatten.js ├── 12.jsonp.js ├── 13.stringify.js ├── 14.heart-check.js ├── 15.private-variable.js ├── 16.fill.js ├── 17.removing-duplicate.js ├── 18.sort.js ├── 19.promise-application.js ├── 19.promise-es6.js ├── 19.promise-resource.js ├── 20.defineProperty-proxy.html ├── 21.vue3.html ├── 22.vue-ssr.html ├── 23.36hexadecimal.js ├── 24.base64.js ├── 25.virtual-list.html ├── package.json └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /dist 4 | 5 | # local env files 6 | .env.local 7 | .env.*.local 8 | 9 | # Log files 10 | npm-debug.log* 11 | yarn-debug.log* 12 | yarn-error.log* 13 | pnpm-debug.log* 14 | 15 | # Editor directories and files 16 | .idea 17 | .vscode 18 | *.suo 19 | *.ntvs* 20 | *.njsproj 21 | *.sln 22 | *.sw? -------------------------------------------------------------------------------- /2022年03月17日.md: -------------------------------------------------------------------------------- 1 | ### 面试者技能栈&项目 2 | 3 | * 熟悉vue全家桶、源码 4 | * webpack、gulp源码 5 | * babel编译源码 6 | * cordova混合app开发 7 | * 前端工程化、脚手架、搭建CICD 8 | * 计算机网络、web安全、浏览器原理 9 | * 低代码 10 | * java 11 | * 微信小程序 12 | 13 | ### vue 14 | 15 | * 介绍 Vue template 到 render 的过程? 16 | * vue怎么进行依赖收集的? 17 | * 对虚拟DOM的理解?虚拟DOM主要做了什么? 18 | * vue3了解嘛,做了哪些优化,新功能? 19 | * vue3 hook有哪些? 20 | * 说一下vue3的compistion API? 21 | * vite了解嘛?原理?会存在哪些问题? 22 | * 做过ssr嘛?遇到过哪些问题?怎么解决? 23 | 24 | ### babel 25 | 26 | * 开发过babel插件嘛 27 | * 插件是怎么实现的 28 | * 为什么会想到用babel方案去开发这个插件 29 | * 开发这个babel插件遇到了哪些问题,怎么解决的 30 | 31 | ### webpack 32 | 33 | * webpack 和 gulp 的优缺点 34 | * webpack运行的流水线 35 | * esModule 和 commonJS 在webpack打包过程中有什么不同 36 | * webpack优化 37 | * webpack有哪些缺点 38 | * sourcemap,生产环境怎么调试? 39 | * tapable是干嘛的?异步串行钩子实现思路? 40 | 41 | ### cordova混合app开发 42 | 43 | * cordova架构 44 | * 和webview h5对比 45 | 46 | ### 前端工程化、脚手架、cicd 47 | 48 | * 你理解的前端工程化,要做哪些 49 | * 脚手架做了什么 50 | * cicd搭建流程 51 | * 有哪些改进点 52 | 53 | ### 计算机网络 54 | 55 | * https握手过程 56 | * tcp、udp区别 57 | * tcp靠什么保证传输文稳定性,滑动窗口、慢启动、拥塞控制 58 | * websocket了解嘛?心跳检测? 59 | 60 | ### web安全 61 | 62 | * 项目中做过哪些web安全方面的处理 63 | * 中间人攻击 64 | * csp 65 | 66 | ### 浏览器原理 67 | 68 | * 浏览器渲染流程 69 | * 渲染和事件循环的关系 70 | * 浏览器分层+合成 71 | * transform:translate3d为什么性能较好 72 | 73 | ### 低代码 74 | 75 | * 对低代码平台的理解 76 | * 你们这个项目能够实现哪些功能 77 | * 低代码项目团队需要多少人 78 | 79 | ### 数据结构&算法 80 | 81 | * 项目中有遇到过数据结构&算法的问题嘛 82 | 83 | ### 微信小程序 84 | 85 | * 微信小程序底层架构?为什么性能比h5要强? 86 | 87 | ### 其他 88 | 89 | * 浏览器垃圾回收机制 90 | * 闭包和内存泄漏 91 | * 长列表怎么优化 92 | * 作用域和执行上下文区别 93 | * 项目中做过监控嘛?性能监控?错误监控? 94 | 95 | ### 开放题 96 | 97 | * 侧边栏组件设计思路 98 | * java和js差异,分别适合什么类型的项目 99 | * 做一个视频会议技术选型、要考虑的点 100 | * 哔哩哔哩要考虑的点 101 | -------------------------------------------------------------------------------- /2022年03月18日.md: -------------------------------------------------------------------------------- 1 | ### 面试者技能栈 2 | 3 | * js基础 4 | * http协议 5 | * vue框架 6 | * webpack 掌握程度? 7 | * node 掌握程度 8 | * 移动端h5开发 9 | * 小程序 10 | * 公众号 11 | * 前端音视频 12 | 13 | ### 项目 14 | 15 | * 股票行情,怎么实时刷新数据 16 | * 音视频直播(连接过程,遇到过哪些问题) 17 | 18 | ### js基础 19 | 20 | * 作用域和执行上下文的区别 21 | * es5的继承,es6继承 22 | * js的垃圾回收机制 23 | 24 | ### http协议 25 | 26 | * https握手过程 27 | * http2、http3 28 | 29 | ### vue框架 30 | 31 | * vue2 diff算法的过程 32 | * nextTick是干嘛的 33 | * vue3优化点 34 | 35 | ### 性能优化专题 36 | 37 | * 项目中做过哪些性能优化 38 | 39 | ### 移动端开发 40 | 41 | * 过程中遇到过哪些坑?怎么解决 42 | * 做够哪些性能优化 43 | * 性能优化指标 44 | 45 | 46 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | --- 2 | theme: cyanosis 3 | --- 4 | # 螺丝拧得好,火箭少不了 5 | 6 | 芜湖,起飞!🚀🚀🚀 7 | 8 | > ### 目录 9 | > 10 | > 1. 应用专题 11 | > 1. 手写题 12 | > 1. html/css 13 | > 1. js 14 | > 1. ts 15 | > 1. 移动端 16 | > 1. 微信小程序 17 | > 1. node 18 | > 1. 数据结构/算法 19 | > 1. webpack 20 | > 1. 网络 21 | > 1. 浏览器 22 | > 1. web安全 23 | > 1. vue 24 | > 1. react 25 | > 1. 性能优化 26 | > 1. 跨端 27 | > 1. 数据库 28 | > 1. nginx 29 | > 1. 设计模式 30 | > 1. 前端监控 31 | > 1. 浏览器插件 32 | > 1. 脚手架 33 | > 1. 团队、项目、架构、管理 34 | > 1. 开放题目 35 | 36 | # 应用专题 37 | 38 | 1. [大文件的分片上传、断点续传及其相关拓展](https://juejin.cn/post/7071877982574346277) 39 | 1. [后端一次给你XX万条数据,除了干一架之外就没别的办法了吗?](https://juejin.cn/post/7071979844115890206) 40 | 1. [虚拟列表是什么?说一下它的实现原理?](https://juejin.cn/post/7071993460261126152) 41 | 1. Promise并发控制 42 | 1. 前端导出Excel文件过于庞大怎么处理 43 | 1. oauth2.0轻量与完整实现原理 44 | 1. mock方案,能够设计出满足各种场景需要的mock数据方案,同时能说出对前后端分离的理解。考虑mock方案的通用性、场景覆盖度,以及代码或工程侵入程度 45 | 1. 埋点方案,能够说明白前端埋点方案技术底层实现,以及技术选型原理。能够设计出基于埋点的数据采集和分析方案,关键字包括:分桶策略,采样率,时序性,数据仓库,数据清洗等。 46 | 1. 说下你对前端工程化的理解 47 | 1. 说一下实现骨架屏的方案?具体思路? 48 | 1. 有没有搭建过 CI/CD?说下 CI/CD 搭建的流程? 49 | 1. 如果一个页面突然出现一段广告,可能是什么原因,怎么解决? 50 | 1. 前端怎样做单元检测? 51 | 1. 单元测试如何测试?代码覆盖率如何? 52 | 1. 页面埋点怎么实现? 53 | 1. 请说明JS进行压缩、合并、打包实现的原理是什么?为什么需要压缩、合并、打包? 54 | 1. 组件库设计有什么原则呢? 55 | 1. 说一下对 URL 进行编码/解码的实现方式? 56 | 1. utf-8 和 asc 码有什么区别? 57 | 1. 说一下Taro的编译原理 58 | 1. 设计搜索组件(具有自动补全功能组件),你需要考虑的问题是什么? 59 | 1. 说一下对 ESLint 的了解? 如何使用? 它的工作原理? 60 | 1. PWA 是什么,对 PWA 有什么了解? 61 | 1. 对 to B 和 to C 的业务的理解? 62 | 1. 是否了解 glob 库,glob 是如何处理文件的?是否有别的方法? 63 | 1. 如何做工程上的优化? 64 | 1. 说一下进程和线程的区别? 65 | 1. 简单描述静态链接和动态链接的区别,并举例说明 66 | 1. 什么是死锁? 67 | 1. 微服务和单体应用的区别是什么? 68 | 1. 什么是微服务? 69 | 1. 用微服务有什么好处? 70 | 1. 实现单点登录的原理 71 | 1. 扫描二维码登录网页是什么原理,前后两个事件是如何联系的? 72 | 1. 项目如何管理模块? 73 | 1. 说一下错误监控的实现,错误监控的正确使用方式,日志如何分等级? 74 | 1. 封装公共组件需要注意什么? 75 | 1. 说出前端框架设计模式(MVVM 或 MVP 或 MVC)的含义以及原理 76 | 1. 说一下事件循环机制(node 浏览器) 77 | 1. 前端灰度发布 78 | 79 | # 手写题 80 | 81 | 1. LazyMan 82 | 1. 防抖节流 83 | 1. 实现一个 compose 函数 84 | 1. 大数相加 85 | 86 | # 数据结构/算法 87 | 88 | 1. 二分查找 89 | 1. 写一个 LRU 缓存函数 90 | 1. 排序 91 | 1. 两个栈实现一个队列 92 | 1. 输入两个数组 [1,2,3,2,1], [3,2,1,4,7] 返回公共的并且长度最长子数组的长度 93 | 94 | # HTML/CSS 95 | 96 | 97 | # JavaScript 98 | 99 | 1. requestAnimationFram 与 requestIdleCallback 的区别 100 | 1. 语义化标签,history api,storage,ajax2.0等。 101 | 1. 文档流,重绘重排,flex,BFC,IFC,before/after,动画,keyframe,画三角,优先级矩阵等 102 | 1. 数组常用api,reduce、map。。。 103 | 1. event loop原理?宏微任务?为什么要区分?node和浏览器在实现loop时候的差别? 104 | 1. 继承、作用域、闭包、模块。。。概念融汇贯通 105 | 1. axios或同级别网络请求库,知道axios的核心功能。 106 | 1. xhr用法 107 | 1. 知道网络请求相关技术和技术底层,包括但不限于: 108 | 1. content-type,不同type的作用 109 | 1. restful设计理念; 110 | 1. cors处理方案,以及浏览器和服务端执行流程;口喷 111 | 1. 如何完成登陆模块,包括但不限于:登陆表单如何实现;cookie登录态维护方案;token base登录态方案;session概念; 112 | 113 | # ts 114 | 115 | # 移动端 116 | 117 | 1. [移动端适配方案具体实现以及对比]() 118 | 119 | # node 120 | 121 | 1. pm2 122 | 123 | # webpack 124 | 125 | 1. webpack、rollup、gulp、vite的区别,以及它们的适用场景 126 | 1. webpack5新特性 127 | 1. webpack基础配置 128 | 1. webpack打包结果的代码结构和 129 | 1. webpack的执行流程 130 | 1. index.js,runtime.js是干嘛的 131 | 1. webpack打包链路 132 | 1. plugin生命周期 133 | 1. 怎么写一个plugin和loader 134 | 1. 常见loader做了什么事情,能几句话说明白,比如babel-loader,vue-loader 135 | 1. 能结合性能优化聊webpack配置怎么做,能清楚说明白核心要点有哪些,并说明解决什么问题,需要哪些外部依赖,比如cdn,接入层等 136 | 1. 了解异步模块加载的实现原理,核心逻辑 137 | 138 | # babel 139 | 140 | 1. 这个babel插件的适用场景有哪些 141 | 1. 开发这个babel插件遇到了哪些问题,怎么解决的 142 | 1. 为什么会想到用babel方案去开发这个插件,调研流程是怎么样的 143 | 1. 这个插件还有哪些可以优化的地方,怎么优化 144 | 1. babel的preset和pollyfill可以互相替代吗,为什么可以/不可以 145 | 1. babel7相较于之前做了哪些优化 146 | 147 | # 网络 148 | 149 | 1. websocket用法,包括但不限于:鉴权,房间分配,心跳机制,重连方案等。 150 | 1. http 151 | 1. https 152 | 1. udp/tcp 153 | 1. http2 154 | 1. http3 155 | 1. cdn 156 | 1. dns 157 | 1. websocket、socket 158 | 159 | # 浏览器 160 | 161 | * 浏览器缓存 162 | 163 | # web安全 164 | 165 | 1. 对称加密 166 | 1. xss 167 | 1. csrf 168 | 169 | # vue 170 | 171 | 1. Vue 的双向绑定机制 172 | 173 | # react 174 | 175 | 1. react常用生命周期 176 | 1. react常见优化方案 177 | 1. react大致实现思路,实现一个简化版的react 178 | 1. 对比react和js控制原生dom的差异 179 | 1. diff算法大致实现思路 180 | 1. 对state和props有自己的使用心得,结合受控组件、hoc等特性描述,需要说明各种方案的适用场景。 181 | 1. 能说明白为什么要实现fiber,以及可能带来的坑。 182 | 1. 能说明白为什么要实现hook。 183 | 1. 能说明白为什么要用immutable,以及用或者不用的考虑。 184 | 1. 知道react不常用的特性,比如context,portal。 185 | 1. 能用自己的理解说明白react like框架的本质,能说明白如何让这些框架共存。 186 | 187 | # 性能优化 188 | 189 | 1. 说一下项目性能优化过程中,是怎样充分利用 chrome 调试工具的? 190 | 1. 请问如何进行首页加载优化? 191 | 1. 如何加快页面渲染速度,都有哪些方式? 192 | 1. 写出常用的页面优化实现方案? 193 | 194 | # 跨端 195 | 196 | 1. 移动端api请求与socket如何通过native发送,知道如何与native进行数据交互,知道ios与安卓jsbridge实现原理。 197 | 1. 移动端webview和基础能力 198 | 1. iOS端uiwebview与wkwebview差异 199 | 1. webview资源加载优化方案 200 | 1. webview池管理方案 201 | 1. native路由 202 | 203 | # nginx 204 | 205 | 1. [前端到底用nginx来做啥](#https://juejin.cn/post/7064378702779891749) 206 | 207 | # 前端监控 208 | 209 | # 脚手架 210 | 211 | # 团队、项目、架构、管理 212 | 213 | 1. 曾经做过的最有挑战的两个项目 214 | 1. 项目脚手架搭建,及如何以工具形态共享。 215 | 1. 团队eslint规范如何设计,及如何统一更新。 216 | 1. 工具化打包发布流程,包括本地调试、云构建、线上发布体系、一键部署能力。同时,方案不仅限于前端工程部分,包含相关服务端基础设施,比如cdn服务搭建,接入层缓存方案设计,域名管控等。 217 | 1. 客户端缓存及预加载方案。 218 | 219 | # 开放题目 220 | 221 | 1. 你是怎么看待做后台管理系统的?很多人觉得它没有难点,你觉得呢? 222 | 1. 未来的职业规划是什么样的? 223 | 1. 对当前新的技术有了解吗? 224 | 1. 对客户端知识有了解吗? 225 | 1. 为什么要离职? 226 | 1. 上家公司做了哪些事情以及做事的流程 227 | 1. 未来职业规划 228 | 1. 感兴趣的工作方向 229 | 230 | -------------------------------------------------------------------------------- /algorithm/0.tree-traverse.js: -------------------------------------------------------------------------------- 1 | function tree(val) { 2 | this.left = this.right = null; 3 | this.val = val; 4 | } 5 | const root = { 6 | val: "A", 7 | left: { 8 | val: "B", 9 | left: { 10 | val: "D", 11 | }, 12 | right: { 13 | val: "E", 14 | }, 15 | }, 16 | right: { 17 | val: "C", 18 | right: { 19 | val: "F", 20 | }, 21 | }, 22 | }; 23 | function pre(root) { 24 | if (!root) return null; 25 | console.log(root.val); 26 | pre(root.left); 27 | pre(root.right); 28 | } 29 | pre(root); 30 | console.log("----------------"); 31 | function mid(root) { 32 | if (!root) return null; 33 | mid(root.left); 34 | console.log(root.val); 35 | mid(root.right); 36 | } 37 | mid(root); 38 | console.log("----------------"); 39 | function post(root) { 40 | if (!root) return null; 41 | post(root.left); 42 | post(root.right); 43 | console.log(root.val); 44 | } 45 | post(root); 46 | console.log("----------------"); 47 | -------------------------------------------------------------------------------- /algorithm/1.hash-map/2.design-hashset.js: -------------------------------------------------------------------------------- 1 | // 不使用任何内建的哈希表库设计一个哈希集合(HashSet)。 2 | // 实现 MyHashSet 类: 3 | // void add(key) 向哈希集合中插入值 key 。 4 | // bool contains(key) 返回哈希集合中是否存在这个值 key 。 5 | // void remove(key) 将给定值 key 从哈希集合中删除。如果哈希集合中没有这个值,什么也不做。 6 | // 示例: 7 | // 输入: 8 | // ["MyHashSet", "add", "add", "contains", "contains", "add", "contains", "remove", "contains"] 9 | // [[], [1], [2], [1], [3], [2], [2], [2], [2]] 10 | // 输出: 11 | // [null, null, null, true, false, null, true, null, false] 12 | // 解释: 13 | // MyHashSet myHashSet = new MyHashSet(); 14 | // myHashSet.add(1); // set = [1] 15 | // myHashSet.add(2); // set = [1, 2] 16 | // myHashSet.contains(1); // 返回 True 17 | // myHashSet.contains(3); // 返回 False ,(未找到) 18 | // myHashSet.add(2); // set = [1, 2] 19 | // myHashSet.contains(2); // 返回 True 20 | // myHashSet.remove(2); // set = [1] 21 | // myHashSet.contains(2); // 返回 False ,(已移除) 22 | var MyHashSet = function () { 23 | this.BASE = 769; //因为数据源可能并非完全随机的,如果用素数的话可以保证不受规律数据源的影响。 24 | this.data = new Array(this.BASE).fill(0).map(() => new Array()); 25 | }; 26 | MyHashSet.prototype.add = function (key) { 27 | const h = this.hash(key); 28 | for (const element of this.data[h]) { 29 | if (element === key) { 30 | return; 31 | } 32 | } 33 | this.data[h].push(key); 34 | }; 35 | MyHashSet.prototype.remove = function (key) { 36 | const h = this.hash(key); 37 | const it = this.data[h]; 38 | for (let i = 0; i < it.length; ++i) { 39 | if (it[i] === key) { 40 | it.splice(i, 1); 41 | return; 42 | } 43 | } 44 | }; 45 | MyHashSet.prototype.contains = function (key) { 46 | const h = this.hash(key); 47 | for (const element of this.data[h]) { 48 | if (element === key) { 49 | return true; 50 | } 51 | } 52 | return false; 53 | }; 54 | MyHashSet.prototype.hash = function (key) { 55 | return key % this.BASE; 56 | }; 57 | const myHashSet = new MyHashSet(); 58 | myHashSet.add(1); // set = [1] 59 | myHashSet.add(2); // set = [1, 2] 60 | console.log(myHashSet.contains(1)); // 返回 True 61 | console.log(myHashSet.contains(3)); // 返回 False ,(未找到) 62 | myHashSet.add(2); // set = [1, 2] 63 | console.log(myHashSet.contains(2)); // 返回 True 64 | myHashSet.remove(2); // set = [1] 65 | console.log(myHashSet.contains(2)); // 返回 False ,(已移除) 66 | -------------------------------------------------------------------------------- /algorithm/1.hash-map/3.design-hashmap.js: -------------------------------------------------------------------------------- 1 | // 不使用任何内建的哈希表库设计一个哈希映射(HashMap)。 2 | // 实现 MyHashMap 类: 3 | // MyHashMap() 用空映射初始化对象 4 | // void put(int key, int value) 向 HashMap 插入一个键值对 (key, value) 。如果 key 已经存在于映射中,则更新其对应的值 value 。 5 | // int get(int key) 返回特定的 key 所映射的 value ;如果映射中不包含 key 的映射,返回 -1 。 6 | // void remove(key) 如果映射中存在 key 的映射,则移除 key 和它所对应的 value 。 7 | // 示例: 8 | // 输入: 9 | // ["MyHashMap", "put", "put", "get", "get", "put", "get", "remove", "get"] 10 | // [[], [1, 1], [2, 2], [1], [3], [2, 1], [2], [2], [2]] 11 | // 输出: 12 | // [null, null, null, 1, -1, null, 1, null, -1] 13 | // 解释: 14 | // MyHashMap myHashMap = new MyHashMap(); 15 | // myHashMap.put(1, 1); // myHashMap 现在为 [[1,1]] 16 | // myHashMap.put(2, 2); // myHashMap 现在为 [[1,1], [2,2]] 17 | // myHashMap.get(1); // 返回 1 ,myHashMap 现在为 [[1,1], [2,2]] 18 | // myHashMap.get(3); // 返回 -1(未找到),myHashMap 现在为 [[1,1], [2,2]] 19 | // myHashMap.put(2, 1); // myHashMap 现在为 [[1,1], [2,1]](更新已有的值) 20 | // myHashMap.get(2); // 返回 1 ,myHashMap 现在为 [[1,1], [2,1]] 21 | // myHashMap.remove(2); // 删除键为 2 的数据,myHashMap 现在为 [[1,1]] 22 | // myHashMap.get(2); // 返回 -1(未找到),myHashMap 现在为 [[1,1]] 23 | var MyHashMap = function () { 24 | this.BASE = 769; 25 | this.data = new Array(this.BASE).fill(0).map(() => new Array()); 26 | }; 27 | MyHashMap.prototype.put = function (key, value) { 28 | const h = this.hash(key); 29 | for (const it of this.data[h]) { 30 | if (it[0] === key) { 31 | it[1] = value; 32 | return; 33 | } 34 | } 35 | this.data[h].push([key, value]); 36 | }; 37 | MyHashMap.prototype.get = function (key) { 38 | const h = this.hash(key); 39 | for (const it of this.data[h]) { 40 | if (it[0] === key) { 41 | return it[1]; 42 | } 43 | } 44 | return -1; 45 | }; 46 | MyHashMap.prototype.remove = function (key) { 47 | const h = this.hash(key); 48 | for (const it of this.data[h]) { 49 | if (it[0] === key) { 50 | const idx = this.data[h].indexOf(it); 51 | this.data[h].splice(idx, 1); 52 | return; 53 | } 54 | } 55 | }; 56 | MyHashMap.prototype.hash = function (key) { 57 | return key % this.BASE; 58 | }; 59 | 60 | const myHashMap = new MyHashMap(); 61 | myHashMap.put(1, 1); // myHashMap 现在为 [[1,1]] 62 | myHashMap.put(1, 1); // myHashMap 现在为 [[1,1]] 63 | myHashMap.put(2, 2); // myHashMap 现在为 [[1,1], [2,2]] 64 | console.log(myHashMap.get(1)); // 返回 1 ,myHashMap 现在为 [[1,1], [2,2]] 65 | console.log(myHashMap.get(3)); // 返回 -1(未找到),myHashMap 现在为 [[1,1], [2,2]] 66 | myHashMap.put(2, 1); // myHashMap 现在为 [[1,1], [2,1]](更新已有的值) 67 | console.log(myHashMap.get(2)); // 返回 1 ,myHashMap 现在为 [[1,1], [2,1]] 68 | myHashMap.remove(2); // 删除键为 2 的数据,myHashMap 现在为 [[1,1]] 69 | console.log(myHashMap.get(2)); // 返回 -1(未找到),myHashMap 现在为 [[1,1]] 70 | -------------------------------------------------------------------------------- /algorithm/1.hash-map/4.hashmap+crash.js: -------------------------------------------------------------------------------- 1 | class HashMap { 2 | constructor(size) { 3 | this.table = new Array(size); 4 | this.size = 0; 5 | } 6 | // 哈希函数,将value转化,计算出存储的key 7 | hashConversion(value) { 8 | let keyCode = 0; 9 | for (let item of value) { 10 | keyCode += item.charCodeAt(0); 11 | } 12 | console.log(keyCode); 13 | let key = keyCode % this.table.length; 14 | return key; 15 | } 16 | set(value) { 17 | let key = this.hashConversion(value); 18 | // this.size++; 19 | // this.table[key] = value; 20 | while (this.table[key] !== undefined && this.table[key] !== value) { 21 | key++; 22 | if (key >= this.table.length) { 23 | throw new Error("已经没有可用空间"); 24 | } 25 | } 26 | if (this.table[key] !== value) { 27 | this.size++; 28 | this.table[key] = value; 29 | } 30 | } 31 | get(value) { 32 | let key = this.hashConversion(value); 33 | while (this.table[key] !== undefined && this.table[key] !== value) { 34 | key++; 35 | if (key >= this.table.length) { 36 | return undefined; 37 | } 38 | } 39 | return this.table[key]; 40 | } 41 | delete(value) { 42 | let key = this.hashConversion(value); 43 | while (this.table[key] !== undefined && this.table[key] !== value) { 44 | key++; 45 | if (key >= this.table.length) { 46 | return false; 47 | } 48 | } 49 | this.table[key] = undefined; 50 | this.size--; 51 | return true; 52 | } 53 | has(value) { 54 | let key = this.hashConversion(value); 55 | while (this.table[key] !== undefined && this.table[key] !== value) { 56 | key++; 57 | if (key >= this.table.length) { 58 | return false; 59 | } 60 | } 61 | if (this.table[key] !== undefined) { 62 | return true; 63 | } else { 64 | return false; 65 | } 66 | } 67 | showAllData() { 68 | let result = []; 69 | for (let item of this.table) { 70 | if (item !== undefined) { 71 | result.push(item); 72 | } 73 | } 74 | return result; 75 | } 76 | } 77 | // 1、很明显没有做冲突的处理,当输入的值发生冲突时,我们就没有办法得到想要的结果,在这里扩展上方代码,用线性探测法进行冲突处理。 78 | let hashTable = new HashMap(10); 79 | hashTable.set("1"); 80 | hashTable.set("aa"); 81 | hashTable.set("6a"); 82 | hashTable.set("75"); 83 | console.log("size:" + hashTable.size); 84 | console.log(hashTable.showAllData()); 85 | -------------------------------------------------------------------------------- /algorithm/1.hash-map/README.md: -------------------------------------------------------------------------------- 1 | ## Map 2 | 3 | 1. LRU缓存 --- [leetcode-146](https://leetcode-cn.com/problems/lru-cache/) 4 | 2. 设计哈希集合 --- [leetcode-705](https://leetcode-cn.com/problems/design-hashset/) 5 | 3. 设计哈希映射 --- [leetcode-706](https://leetcode-cn.com/problems/design-hashmap/) 6 | 4. 哈希表原理,哈希碰撞时怎么处理 -------------------------------------------------------------------------------- /algorithm/2.sort/1.bubble-sort.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 冒泡排序 3 | * @param {*} arr 4 | * @returns 5 | */ 6 | function bubbleSort(arr) { 7 | // 缓存数组长度 8 | const len = arr.length; 9 | // 外层循环用于控制从头到尾的比较+交换到底有多少轮 10 | for (let i = 0; i < len; i++) { 11 | // 内层循环用于完成每一轮遍历过程中的重复比较+交换 12 | for (let j = 0; j < len - 1; j++) { 13 | // 若相邻元素前面的数比后面的大 14 | if (arr[j] > arr[j + 1]) { 15 | // 交换两者 16 | [arr[j], arr[j + 1]] = [arr[j + 1], arr[j]]; 17 | } 18 | } 19 | } 20 | // 返回数组 21 | return arr; 22 | } 23 | console.log(bubbleSort([2, 5, 1, 3, 4, 0])) 24 | -------------------------------------------------------------------------------- /algorithm/2.sort/2.insert-sort.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 插入排序 3 | * @param {*} arr 4 | * @returns 5 | */ 6 | function insertSort(arr) { 7 | // 缓存数组长度 8 | const len = arr.length; 9 | // temp 用来保存当前需要插入的元素 10 | let temp; 11 | // i用于标识每次被插入的元素的索引 12 | for (let i = 1; i < len; i++) { 13 | // j用于帮助 temp 寻找自己应该有的定位 14 | let j = i; 15 | temp = arr[i]; 16 | // 判断 j 前面一个元素是否比 temp 大 17 | while (j > 0 && arr[j - 1] > temp) { 18 | // 如果是,则将 j 前面的一个元素后移一位,为 temp 让出位置 19 | arr[j] = arr[j - 1]; 20 | j--; 21 | } 22 | // 循环让位,最后得到的 j 就是 temp 的正确索引 23 | arr[j] = temp; 24 | } 25 | return arr; 26 | } 27 | console.log(insertSort([2, 5, 1, 3, 4, 0])) 28 | 29 | -------------------------------------------------------------------------------- /algorithm/2.sort/3.select-sort.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 选择排序 3 | * @param {*} arr 4 | * @returns 5 | */ 6 | function selectSort(arr) { 7 | // 缓存数组长度 8 | const len = arr.length; 9 | // 定义 minIndex,缓存当前区间最小值的索引,注意是索引 10 | let minIndex; 11 | // i 是当前排序区间的起点 12 | for (let i = 0; i < len - 1; i++) { 13 | // 初始化 minIndex 为当前区间第一个元素 14 | minIndex = i; 15 | // i、j分别定义当前区间的上下界,i是左边界,j是右边界 16 | for (let j = i; j < len; j++) { 17 | // 若 j 处的数据项比当前最小值还要小,则更新最小值索引为 j 18 | if (arr[j] < arr[minIndex]) { 19 | minIndex = j; 20 | } 21 | } 22 | // 如果 minIndex 对应元素不是目前的头部元素,则交换两者 23 | if (minIndex !== i) { 24 | [arr[i], arr[minIndex]] = [arr[minIndex], arr[i]]; 25 | } 26 | } 27 | return arr; 28 | } 29 | console.log(selectSort([2, 5, 1, 3, 4, 0])); 30 | -------------------------------------------------------------------------------- /algorithm/2.sort/4.merge-sort.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 归并排序 3 | * @param {*} arr 4 | * @returns 5 | */ 6 | function mergeSort(arr) { 7 | const len = arr.length; 8 | // 处理边界情况 9 | if (len <= 1) { 10 | return arr; 11 | } 12 | // 计算分割点 13 | const mid = Math.floor(len / 2); 14 | // 递归分割左子数组,然后合并为有序数组 15 | const leftArr = mergeSort(arr.slice(0, mid)); 16 | // 递归分割右子数组,然后合并为有序数组 17 | const rightArr = mergeSort(arr.slice(mid, len)); 18 | // 合并左右两个有序数组 19 | arr = mergeArr(leftArr, rightArr); 20 | // 返回合并后的结果 21 | return arr; 22 | } 23 | function mergeArr(arr1, arr2) { 24 | // 初始化两个指针,分别指向 arr1 和 arr2 25 | let i = 0, 26 | j = 0; 27 | // 初始化结果数组 28 | const res = []; 29 | // 缓存arr1的长度 30 | const len1 = arr1.length; 31 | // 缓存arr2的长度 32 | const len2 = arr2.length; 33 | // 合并两个子数组 34 | while (i < len1 && j < len2) { 35 | if (arr1[i] < arr2[j]) { 36 | res.push(arr1[i]); 37 | i++; 38 | } else { 39 | res.push(arr2[j]); 40 | j++; 41 | } 42 | } 43 | // 若其中一个子数组首先被合并完全,则直接拼接另一个子数组的剩余部分 44 | if (i < len1) { 45 | return res.concat(arr1.slice(i)); 46 | } else { 47 | return res.concat(arr2.slice(j)); 48 | } 49 | } 50 | console.log(mergeSort([2, 5, 1, 3, 4, 0])); 51 | -------------------------------------------------------------------------------- /algorithm/2.sort/5.quick-sort.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 快速排序入口 3 | * @param {*} arr 4 | * @param {*} left 5 | * @param {*} right 6 | * @returns 7 | */ 8 | function quickSort(arr, left = 0, right = arr.length - 1) { 9 | // 定义递归边界,若数组只有一个元素,则没有排序必要 10 | if (arr.length > 1) { 11 | // lineIndex表示下一次划分左右子数组的索引位 12 | const lineIndex = partition(arr, left, right); 13 | // 如果左边子数组的长度不小于1,则递归快排这个子数组 14 | if (left < lineIndex - 1) { 15 | // 左子数组以 lineIndex-1 为右边界 16 | quickSort(arr, left, lineIndex - 1); 17 | } 18 | // 如果右边子数组的长度不小于1,则递归快排这个子数组 19 | if (lineIndex < right) { 20 | // 右子数组以 lineIndex 为左边界 21 | quickSort(arr, lineIndex, right); 22 | } 23 | } 24 | return arr; 25 | } 26 | // 以基准值为轴心,划分左右子数组的过程 27 | function partition(arr, left, right) { 28 | // 基准值默认取中间位置的元素 29 | let pivotValue = arr[Math.floor(left + (right - left) / 2)]; 30 | // 初始化左右指针 31 | let i = left; 32 | let j = right; 33 | // 当左右指针不越界时,循环执行以下逻辑 34 | while (i <= j) { 35 | // 左指针所指元素若小于基准值,则右移左指针 36 | while (arr[i] < pivotValue) { 37 | i++; 38 | } 39 | // 右指针所指元素大于基准值,则左移右指针 40 | while (arr[j] > pivotValue) { 41 | j--; 42 | } 43 | // 若i<=j,则意味着基准值左边存在较大元素或右边存在较小元素,交换两个元素确保左右两侧有序 44 | if (i <= j) { 45 | // swap(arr, i, j); 46 | [arr[i], arr[j]] = [arr[j], arr[i]]; 47 | i++; 48 | j--; 49 | } 50 | } 51 | // 返回左指针索引作为下一次划分左右子数组的依据 52 | return i; 53 | } 54 | console.log(quickSort([2, 5, 1, 3, 4, 0])); 55 | -------------------------------------------------------------------------------- /algorithm/20.sliding-window-maximum.js: -------------------------------------------------------------------------------- 1 | // 给你一个整数数组 nums,有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k 个数字。滑动窗口每次只向右移动一位。 2 | // 返回 滑动窗口中的最大值 。 3 | // 示例 1: 4 | // 输入:nums = [1,3,-1,-3,5,3,6,7], k = 3 5 | // 输出:[3,3,5,5,6,7] 6 | // 解释: 7 | // 滑动窗口的位置 最大值 8 | // --------------- ----- 9 | // [1 3 -1] -3 5 3 6 7 3 10 | // 1 [3 -1 -3] 5 3 6 7 3 11 | // 1 3 [-1 -3 5] 3 6 7 5 12 | // 1 3 -1 [-3 5 3] 6 7 5 13 | // 1 3 -1 -3 [5 3 6] 7 6 14 | // 1 3 -1 -3 5 [3 6 7] 7 15 | // 16 | // 示例 2: 17 | // 输入:nums = [1], k = 1 18 | // 输出:[1] 19 | 20 | /** 21 | * @param {number[]} nums 22 | * @param {number} k 23 | * @return {number[]} 24 | */ 25 | // 维持队列的递减性:确保队头元素是当前滑动窗口的最大值。这样我们每次取最大值时,直接取队头元素即可。 26 | // 这一步没啥好说的,就是在维持队列递减性的基础上、更新队列的内容。 27 | // 维持队列的有效性:确保队列里所有的元素都在滑动窗口圈定的范围以内。 28 | // 排除掉滑动窗口还没有初始化完成、第一个最大值还没有出现的特殊情况。 29 | var maxSlidingWindow = function (nums, k) { 30 | const len = nums.length; 31 | const res = []; 32 | const queue = []; 33 | for (let i = 0; i < len; i++) { 34 | // 当队尾元素小于当前元素,移除,保证队列头部的值为最大 35 | while (queue.length && nums[queue[queue.length - 1]] < nums[i]) { 36 | queue.pop(); 37 | } 38 | // 入队当前元素索引(注意是索引) 39 | queue.push(i); 40 | // 窗口滑动,把左侧的老数据删掉 41 | while (queue.length && i >= k + queue[0]) { 42 | queue.shift(); 43 | } 44 | // 当凑够一个窗口长度时,后面每一次循环都把队头索引所在值push进结果数组 45 | if (i >= k - 1) { 46 | res.push(nums[queue[0]]); 47 | } 48 | } 49 | return res; 50 | }; 51 | var nums1 = [1, 3, -1, -3, 5, 3, 6, 7], 52 | k1 = 3; 53 | var nums2 = [1], 54 | k2 = 1; 55 | var nums3 = [11, 9, 8, 7, 5, 3, 2], 56 | k3 = 3; 57 | console.log(maxSlidingWindow(nums1, k1)); 58 | console.log(maxSlidingWindow(nums2, k2)); 59 | console.log(maxSlidingWindow(nums3, k3)); 60 | -------------------------------------------------------------------------------- /algorithm/21.dfs-bfs.js: -------------------------------------------------------------------------------- 1 | // 所有遍历函数的入参都是树的根结点对象 2 | function DFS(root) { 3 | // 递归边界,root 为空 4 | if (!root) { 5 | return; 6 | } 7 | // 输出当前遍历的结点值 8 | console.log("当前遍历的结点值是:", root.val); 9 | // 递归遍历左子树 10 | DFS(root.left); 11 | // 递归遍历右子树 12 | DFS(root.right); 13 | } 14 | function BFS(root) { 15 | const queue = []; 16 | queue.push(root); 17 | while (queue.length) { 18 | const top = queue[0]; 19 | console.log("当前遍历的结点值是:", top.val); 20 | // 操作top 21 | if (top.left) { 22 | queue.push(top.left); 23 | } 24 | if (top.right) { 25 | queue.push(top.right); 26 | } 27 | queue.shift(); 28 | } 29 | } 30 | const root = { 31 | val: "A", 32 | left: { 33 | val: "B", 34 | left: { 35 | val: "D", 36 | }, 37 | right: { 38 | val: "E", 39 | }, 40 | }, 41 | right: { 42 | val: "C", 43 | right: { 44 | val: "F", 45 | }, 46 | }, 47 | }; 48 | DFS(root); 49 | console.log("-------------------"); 50 | BFS(root); 51 | -------------------------------------------------------------------------------- /algorithm/22.permutations.js: -------------------------------------------------------------------------------- 1 | // 给定一个不含重复数字的数组 nums ,返回其 所有可能的全排列 。你可以 按任意顺序 返回答案。 2 | // 3 | // 示例 1: 4 | // 输入:nums = [1,2,3] 5 | // 输出:[[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]] 6 | // 示例 2: 7 | // 输入:nums = [0,1] 8 | // 输出:[[0,1],[1,0]] 9 | // 示例 3: 10 | // 输入:nums = [1] 11 | // 输出:[[1]] 12 | 13 | /** 14 | * @param {number[]} nums 数组 15 | * @return {number[][]} 16 | */ 17 | const permute = function (nums) { 18 | // 缓存数组的长度 19 | const len = nums.length; 20 | // curr 变量用来记录当前的排列内容 21 | const curr = []; 22 | // res 用来记录所有的排列顺序 23 | const res = []; 24 | // visited 用来避免重复使用同一个数字 25 | const visited = {}; 26 | // 定义 dfs 函数,入参是坑位的索引(从 0 计数) 27 | function dfs(nth) { 28 | // 若遍历到了不存在的坑位(第 len+1 个),则触碰递归边界返回 29 | if (nth === len) { 30 | // 此时前 len 个坑位已经填满,将对应的排列记录下来 31 | res.push(curr.slice()); 32 | return; 33 | } 34 | // 检查手里剩下的数字有哪些 35 | for (let i = 0; i < len; i++) { 36 | // 若 nums[i] 之前没被其它坑位用过,则可以理解为“这个数字剩下了” 37 | if (!visited[nums[i]]) { 38 | // 给 nums[i] 打个“已用过”的标 39 | visited[nums[i]] = 1; 40 | // 将nums[i]推入当前排列 41 | curr.push(nums[i]); 42 | // 基于这个排列继续往下一个坑走去 43 | dfs(nth + 1); 44 | // nums[i]让出当前坑位 45 | curr.pop(); 46 | // 下掉“已用过”标识 47 | visited[nums[i]] = 0; 48 | } 49 | } 50 | } 51 | // 从索引为 0 的坑位(也就是第一个坑位)开始 dfs 52 | dfs(0); 53 | return res; 54 | }; 55 | 56 | var n1 = [1, 2, 3]; 57 | var n2 = [0, 1]; 58 | var n3 = [1]; 59 | console.log(permute(n1)); 60 | console.log(permute(n2)); 61 | console.log(permute(n3)); 62 | -------------------------------------------------------------------------------- /algorithm/23.subsets.js: -------------------------------------------------------------------------------- 1 | // 给你一个整数数组 nums ,数组中的元素 互不相同 。返回该数组所有可能的子集(幂集)。 2 | // 解集 不能 包含重复的子集。你可以按 任意顺序 返回解集。 3 | // 示例 1: 4 | // 输入:nums = [1,2,3] 5 | // 输出:[[],[1],[2],[1,2],[3],[1,3],[2,3],[1,2,3]] 6 | // 示例 2: 7 | // 输入:nums = [0] 8 | // 输出:[[],[0]] 9 | /** 10 | * @param {number[]} nums 11 | * @return {number[][]} 12 | */ 13 | var subsets = function (nums) { 14 | // 初始化结果数组 15 | const res = []; 16 | // 缓存数组长度 17 | const len = nums.length; 18 | // 初始化组合数组 19 | const subset = []; 20 | // 进入 dfs 21 | dfs(0); 22 | // 定义 dfs 函数,入参是 nums 中的数字索引 23 | function dfs(index) { 24 | // 每次进入,都意味着组合内容更新了一次,故直接推入结果数组 25 | res.push(subset.slice()); 26 | // 从当前数字的索引开始,遍历 nums 27 | for (let i = index; i < len; i++) { 28 | // 这是当前数字存在于组合中的情况 29 | subset.push(nums[i]); 30 | // 基于当前数字存在于组合中的情况,进一步 dfs 31 | dfs(i + 1); 32 | // 这是当前数字不存在与组合中的情况 33 | subset.pop(); 34 | } 35 | } 36 | // 返回结果数组 37 | return res; 38 | }; 39 | 40 | const r = subsets([1, 2, 3]); 41 | console.log(r); 42 | -------------------------------------------------------------------------------- /algorithm/24.combinations.js: -------------------------------------------------------------------------------- 1 | // 给定两个整数 n 和 k,返回范围 [1, n] 中所有可能的 k 个数的组合。 2 | // 你可以按 任何顺序 返回答案。 3 | // 示例 1: 4 | // 输入:n = 4, k = 2 5 | // 输出: 6 | // [ 7 | // [2,4], 8 | // [3,4], 9 | // [2,3], 10 | // [1,2], 11 | // [1,3], 12 | // [1,4], 13 | // ] 14 | // 示例 2: 15 | // 输入:n = 1, k = 1 16 | // 输出:[[1]] 17 | /** 18 | * @param {number} n 19 | * @param {number} k 20 | * @return {number[][]} 21 | */ 22 | var combine = function (n, k) { 23 | // 初始化结果数组 24 | const res = []; 25 | // 初始化组合数组 26 | const subset = []; 27 | // 进入 dfs,起始数字是1 28 | dfs(1); 29 | // 定义 dfs 函数,入参是当前遍历到的数字 30 | function dfs(index) { 31 | if (subset.length === k) { 32 | res.push(subset.slice()); 33 | return; 34 | } 35 | // 从当前数字的值开始,遍历 index-n 之间的所有数字 36 | for (let i = index; i <= n; i++) { 37 | // 这是当前数字存在于组合中的情况 38 | subset.push(i); 39 | // 基于当前数字存在于组合中的情况,进一步 dfs 40 | dfs(i + 1); 41 | // 这是当前数字不存在与组合中的情况 42 | subset.pop(); 43 | } 44 | } 45 | // 返回结果数组 46 | return res; 47 | }; 48 | // function xxx(入参) { 49 | // 前期的变量定义、缓存等准备工作 50 | 51 | // // 定义路径栈 52 | // const path = [] 53 | 54 | // // 进入 dfs 55 | // dfs(起点) 56 | 57 | // // 定义 dfs 58 | // dfs(递归参数) { 59 | // if(到达了递归边界) { 60 | // 结合题意处理边界逻辑,往往和 path 内容有关 61 | // return 62 | // } 63 | 64 | // // 注意这里也可能不是 for,视题意决定 65 | // for(遍历坑位的可选值) { 66 | // path.push(当前选中值) 67 | // 处理坑位本身的相关逻辑 68 | // path.pop() 69 | // } 70 | // } 71 | // } 72 | -------------------------------------------------------------------------------- /algorithm/25.binary-tree-preorder-traversal.js: -------------------------------------------------------------------------------- 1 | // 给你二叉树的根节点 root ,返回它节点值的 前序 遍历。 2 | // 示例 1: 3 | // 输入:root = [1,null,2,3] 4 | // 输出:[1,2,3] 5 | // 示例 2: 6 | // 输入:root = [] 7 | // 输出:[] 8 | // 示例 3: 9 | // 输入:root = [1] 10 | // 输出:[1] 11 | // 示例 4: 12 | // 输入:root = [1,2] 13 | // 输出:[1,2] 14 | // 示例 5: 15 | // 输入:root = [1,null,2] 16 | // 输出:[1,2] 17 | // 18 | // 进阶:递归算法很简单,你可以通过迭代算法完成吗? 19 | /** 20 | * Definition for a binary tree node. 21 | * function TreeNode(val, left, right) { 22 | * this.val = (val===undefined ? 0 : val) 23 | * this.left = (left===undefined ? null : left) 24 | * this.right = (right===undefined ? null : right) 25 | * } 26 | */ 27 | /** 28 | * @param {TreeNode} root 29 | * @return {number[]} 30 | */ 31 | var preorderTraversal = function (root) { 32 | const res = []; 33 | if (!root) { 34 | return res; 35 | } 36 | const stack = []; 37 | stack.push(root); 38 | while (stack.length) { 39 | const cur = stack.pop(); 40 | res.push(cur.val); 41 | if (cur.right) { 42 | stack.push(cur.right); 43 | } 44 | // 后进先出,先序遍历,left放后面 45 | if (cur.left) { 46 | stack.push(cur.left); 47 | } 48 | } 49 | return res; 50 | }; 51 | 52 | const root = { 53 | val: "A", 54 | left: { 55 | val: "B", 56 | left: { 57 | val: "D", 58 | }, 59 | right: { 60 | val: "E", 61 | }, 62 | }, 63 | right: { 64 | val: "C", 65 | right: { 66 | val: "F", 67 | }, 68 | }, 69 | }; 70 | 71 | console.log(preorderTraversal(root)); 72 | -------------------------------------------------------------------------------- /algorithm/26.binary-tree-postorder-traversal.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 后序遍历 3 | * @param {TreeNode} root 4 | * @return {number[]} 5 | */ 6 | var postorderTraversal = function (root) { 7 | const res = []; 8 | if (!root) { 9 | return res; 10 | } 11 | const stack = []; 12 | stack.push(root); 13 | while (stack.length) { 14 | const cur = stack.pop(); 15 | // 后序遍历,使用unshift到结果数组 16 | res.unshift(cur.val); 17 | // 后进先出,后序遍历,left放前面 18 | if (cur.left) { 19 | stack.push(cur.left); 20 | } 21 | if (cur.right) { 22 | stack.push(cur.right); 23 | } 24 | } 25 | return res 26 | }; 27 | const root = { 28 | val: "A", 29 | left: { 30 | val: "B", 31 | left: { 32 | val: "D", 33 | }, 34 | right: { 35 | val: "E", 36 | }, 37 | }, 38 | right: { 39 | val: "C", 40 | right: { 41 | val: "F", 42 | }, 43 | }, 44 | }; 45 | console.log(postorderTraversal(root)); 46 | -------------------------------------------------------------------------------- /algorithm/27.binary-tree-inorder-traversal.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 中序遍历 3 | * @param {TreeNode} root 4 | * @return {number[]} 5 | */ 6 | var inorderTraversal = function (root) { 7 | const res = []; 8 | const stack = []; 9 | let cur = root; 10 | while (cur || stack.length) { 11 | while (cur) { 12 | // 往left方向遍历,逐个push进stack 13 | stack.push(cur); 14 | cur = cur.left; 15 | } 16 | cur = stack.pop(); 17 | // 实现左 中 右的顺序 18 | res.push(cur.val); 19 | cur = cur.right; 20 | } 21 | return res; 22 | }; 23 | const root = { 24 | val: "A", 25 | left: { 26 | val: "B", 27 | left: { 28 | val: "D", 29 | }, 30 | right: { 31 | val: "E", 32 | }, 33 | }, 34 | right: { 35 | val: "C", 36 | right: { 37 | val: "F", 38 | }, 39 | }, 40 | }; 41 | console.log(inorderTraversal(root)); 42 | -------------------------------------------------------------------------------- /algorithm/28.binary-tree-level-order-traversal.js: -------------------------------------------------------------------------------- 1 | // 给你二叉树的根节点 root ,返回其节点值的 层序遍历 。 (即逐层地,从左到右访问所有节点)。 2 | // 示例 1: 3 | // 输入:root = [3,9,20,null,null,15,7] 4 | // 输出:[[3],[9,20],[15,7]] 5 | // 示例 2: 6 | // 输入:root = [1] 7 | // 输出:[[1]] 8 | // 示例 3: 9 | // 输入:root = [] 10 | // 输出:[] 11 | /** 12 | * 层序遍历 13 | * @param {TreeNode} root 14 | * @return {number[][]} 15 | */ 16 | var levelOrder = function (root) { 17 | const res = []; 18 | if (!root) { 19 | return res; 20 | } 21 | const queue = []; 22 | queue.push(root); 23 | while (queue.length) { 24 | // while每循环一次,就是遍历了一层 25 | const len = queue.length; 26 | const level = []; 27 | for (let i = 0; i < len; i++) { 28 | // 取队头的元素 29 | const top = queue.shift(); 30 | level.push(top.val); 31 | if (top.left) { 32 | queue.push(top.left); 33 | } 34 | if (top.right) { 35 | queue.push(top.right); 36 | } 37 | } 38 | res.push(level); 39 | } 40 | return res; 41 | }; 42 | const root = { 43 | val: "A", 44 | left: { 45 | val: "B", 46 | left: { 47 | val: "D", 48 | }, 49 | right: { 50 | val: "E", 51 | left: { 52 | val: 9, 53 | }, 54 | }, 55 | }, 56 | right: { 57 | val: "C", 58 | right: { 59 | val: "F", 60 | }, 61 | }, 62 | }; 63 | console.log(levelOrder(root)); 64 | -------------------------------------------------------------------------------- /algorithm/29.invert-binary-tree.js: -------------------------------------------------------------------------------- 1 | // 给你一棵二叉树的根节点 root ,翻转这棵二叉树,并返回其根节点。 2 | // 示例 1: 3 | // 输入:root = [4,2,7,1,3,6,9] 4 | // 输出:[4,7,2,9,6,3,1] 5 | // 示例 2: 6 | // 输入:root = [2,1,3] 7 | // 输出:[2,3,1] 8 | // 示例 3: 9 | // 输入:root = [] 10 | // 输出:[] 11 | /** 12 | * @param {TreeNode} root 13 | * @return {TreeNode} 14 | */ 15 | const invertTree = function (root) { 16 | if (!root) { 17 | return null; 18 | } 19 | // 递归 20 | const left = invertTree(root.left); 21 | const right = invertTree(root.right); 22 | // 交换 23 | root.left = right; 24 | root.right = left; 25 | return root; 26 | }; 27 | const r = { 28 | val: "A", 29 | left: { 30 | val: "B", 31 | left: { 32 | val: "D", 33 | }, 34 | right: { 35 | val: "E", 36 | left: { 37 | val: 9, 38 | }, 39 | }, 40 | }, 41 | right: { 42 | val: "C", 43 | right: { 44 | val: "F", 45 | }, 46 | }, 47 | }; 48 | console.log(invertTree(r)); 49 | -------------------------------------------------------------------------------- /algorithm/3.dynamic-planning/1.climbing-stairs.js: -------------------------------------------------------------------------------- 1 | // 假设你正在爬楼梯。需要 n 阶你才能到达楼顶。 2 | 3 | // 每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢? 4 | 5 | // 示例 1: 6 | 7 | // 输入:n = 2 8 | // 输出:2 9 | // 解释:有两种方法可以爬到楼顶。 10 | // 1. 1 阶 + 1 阶 11 | // 2. 2 阶 12 | 13 | // 示例 2: 14 | 15 | // 输入:n = 3 16 | // 输出:3 17 | // 解释:有三种方法可以爬到楼顶。 18 | // 1. 1 阶 + 1 阶 + 1 阶 19 | // 2. 1 阶 + 2 阶 20 | // 3. 2 阶 + 1 阶 21 | 22 | /** 23 | * @param {number} n 24 | * @return {number} 25 | */ 26 | const cache = []; 27 | var climbStairs = function (n) { 28 | if (n === 1) { 29 | return 1; 30 | } 31 | if (n === 2) { 32 | return 2; 33 | } 34 | if (!cache[n]) cache[n] = climbStairs(n - 1) + climbStairs(n - 2); 35 | return cache[n]; 36 | }; 37 | // */ 38 | // var climbStairs = function (n) { 39 | // const dp = []; 40 | // dp[1] = 1; 41 | // dp[2] = 2; 42 | // for (let i = 3; i <= n; i++) { 43 | // dp[i] = dp[i - 1] + dp[i - 2]; 44 | // } 45 | // return dp[n] 46 | // }; 47 | console.log(climbStairs(5)); 48 | -------------------------------------------------------------------------------- /algorithm/3.dynamic-planning/2.coin.js: -------------------------------------------------------------------------------- 1 | // 给定不同面额的硬币 coins 和一个总金额 amount。编写一个函数来计算可以凑成总金额所需的最少的硬币个数。如果没有任何一种硬币组合能组成总金额,返回 -1。 2 | // 你可以认为每种硬币的数量是无限的。 3 | // 示例 1: 4 | // 输入:coins = [1, 2, 5], amount = 11 5 | // 输出:3 6 | // 解释:11 = 5 + 5 + 1 7 | // 示例 2: 8 | // 输入:coins = [2], amount = 3 9 | // 输出:-1 10 | // 示例 3: 11 | // 输入:coins = [1], amount = 0 12 | // 输出:0 13 | // 示例 4: 14 | // 输入:coins = [1], amount = 1 15 | // 输出:1 16 | // 示例 5: 17 | // 输入:coins = [1], amount = 2 18 | // 输出:2 19 | /** 20 | * @param {number[]} coins 21 | * @param {number} amount 22 | * @return {number} 23 | */ 24 | var coinChange = function (coins, amount) { 25 | const dp = []; 26 | dp[0] = 0; 27 | for (let i = 1; i <= amount; i++) { 28 | dp[i] = Infinity; 29 | for (let j = 0; j < coins.length; j++) { 30 | if (coins[j] <= i) { 31 | dp[i] = Math.min(dp[i], dp[i - coins[j]] + 1); 32 | } 33 | } 34 | } 35 | return dp[amount] === Infinity ? -1 : dp[amount]; 36 | }; 37 | console.log(coinChange([1, 2, 5], 11)); 38 | console.log(coinChange([1, 2, 5], 21)); 39 | console.log(coinChange([5], 3)); 40 | console.log(coinChange([1], 1)); 41 | -------------------------------------------------------------------------------- /algorithm/3.dynamic-planning/3.jump-game.js: -------------------------------------------------------------------------------- 1 | // 给定一个非负整数数组 nums ,你最初位于数组的 第一个下标 。 2 | // 数组中的每个元素代表你在该位置可以跳跃的最大长度。 3 | // 判断你是否能够到达最后一个下标。 4 | // 示例 1: 5 | // 输入:nums = [2,3,1,1,4] 6 | // 输出:true 7 | // 解释:可以先跳 1 步,从下标 0 到达下标 1, 然后再从下标 1 跳 3 步到达最后一个下标。 8 | // 示例 2: 9 | // 输入:nums = [3,2,1,0,4] 10 | // 输出:false 11 | // 解释:无论怎样,总会到达下标为 3 的位置。但该下标的最大跳跃长度是 0 , 所以永远不可能到达最后一个下标。 12 | /** 13 | * @param {number[]} nums 14 | * @return {boolean} 15 | */ 16 | var canJump = function (nums) { 17 | let distance = 0; 18 | for (let i = 0; i < nums.length; i++) { 19 | if (distance >= i) { 20 | distance = Math.max(distance, i + nums[i]); 21 | if (distance >= nums.length - 1) { 22 | return true; 23 | } 24 | } 25 | } 26 | return false; 27 | }; 28 | const nums1 = [2, 3, 1, 1, 4]; 29 | const nums2 = [3, 2, 1, 0, 4]; 30 | const nums3 = [0]; 31 | console.log(canJump(nums1)); 32 | console.log(canJump(nums2)); 33 | console.log(canJump(nums3)); 34 | -------------------------------------------------------------------------------- /algorithm/3.dynamic-planning/4.knapsack.js: -------------------------------------------------------------------------------- 1 | // 有 n 件物品,物品体积用一个名为 w 的数组存起来,物品的价值用一个名为 value 的数组存起来;每件物品的体积用 w[i] 来表示,每件物品的价值用 value[i] 来表示。现在有一个容量为 c 的背包,问你如何选取物品放入背包,才能使得背包内的物品总价值最大? 2 | 3 | // 注意:每种物品都只有1件 4 | 5 | // 入参是物品的个数和背包的容量上限,以及物品的重量和价值数组 6 | /** 7 | * @param {*} n 有 n 件物品 8 | * @param {*} c 一个容量为 c 的背包 9 | * @param {*} w 物品体积用一个名为 w 的数组存起来 10 | * @param {*} value 物品的价值用一个名为 value 的数组存起来 11 | * @returns 12 | */ 13 | // 入参是物品的个数和背包的容量上限,以及物品的重量和价值数组 14 | function knapsack(n, c, w, value) { 15 | // 把背包的空间化成数组 16 | const dp = new Array(c + 1).fill(0); 17 | let result = -Infinity; 18 | for (let i = 0; i < n; i++) { 19 | for (let j = c; j >= w[i]; j--) { 20 | dp[j] = Math.max(dp[j], dp[j - w[i]] + value[i]); 21 | if (dp[j] > result) { 22 | result = dp[j]; 23 | } 24 | } 25 | } 26 | return result; 27 | } 28 | console.log(knapsack(3, 6, [1, 3, 2], [1, 3, 10])); 29 | -------------------------------------------------------------------------------- /algorithm/3.dynamic-planning/5.longest-increasing-subsequence.js: -------------------------------------------------------------------------------- 1 | // 给你一个整数数组 nums ,找到其中最长严格递增子序列的长度。 2 | // 子序列 是由数组派生而来的序列,删除(或不删除)数组中的元素而不改变其余元素的顺序。例如,[3,6,2,7] 是数组 [0,3,1,6,2,2,7] 的子序列。 3 | // 4 | // 示例 1: 5 | // 输入:nums = [10,9,2,5,3,7,101,18] 6 | // 输出:4 7 | // 解释:最长递增子序列是 [2,3,7,101],因此长度为 4 。 8 | // 9 | // 示例 2: 10 | // 输入:nums = [0,1,0,3,2,3] 11 | // 输出:4 12 | // 13 | // 示例 3: 14 | // 输入:nums = [7,7,7,7,7,7,7] 15 | // 输出:1 16 | // 进阶: 17 | // 你能将算法的时间复杂度降低到 O(n log(n)) 吗? 18 | /** 19 | * @param {number[]} nums 20 | * @return {number} 21 | */ 22 | var lengthOfLIS = function (nums) { 23 | const len = nums.length; 24 | if (len === 0) return 0; 25 | const dp = new Array(len).fill(1); 26 | let maxLength = 1; 27 | for (let i = 1; i < len; i++) { 28 | // 每遍历一个新元素,都要“回头看”,看看能不能延长原有的上升子序列 29 | for (let j = 0; j < i; j++) { 30 | if (nums[i] > nums[j]) { 31 | // 若遇到了一个比当前元素小的值,则意味着遇到了一个可以延长的上升子序列,故更新当前元素索引位对应的状态 32 | dp[i] = Math.max(dp[i], dp[j] + 1); 33 | } 34 | } 35 | if (dp[i] > maxLength) { 36 | maxLength = dp[i]; 37 | } 38 | } 39 | return maxLength; 40 | }; 41 | console.log(lengthOfLIS([10, 9, 2, 5, 3, 7, 101, 18])); 42 | console.log(lengthOfLIS([0, 1, 0, 3, 2, 3])); 43 | console.log(lengthOfLIS([7, 7, 7, 7, 7, 7, 7])); 44 | -------------------------------------------------------------------------------- /algorithm/3.dynamic-planning/6.longest-common-subsequence.js: -------------------------------------------------------------------------------- 1 | // 给定两个字符串 text1 和 text2,返回这两个字符串的最长 公共子序列 的长度。如果不存在 公共子序列 ,返回 0 。 2 | // 一个字符串的 子序列 是指这样一个新的字符串:它是由原字符串在不改变字符的相对顺序的情况下删除某些字符(也可以不删除任何字符)后组成的新字符串。 3 | // 例如,"ace" 是 "abcde" 的子序列,但 "aec" 不是 "abcde" 的子序列。 4 | // 两个字符串的 公共子序列 是这两个字符串所共同拥有的子序列。 5 | // 示例 1: 6 | // 输入:text1 = "abcde", text2 = "ace" 7 | // 输出:3 8 | // 解释:最长公共子序列是 "ace" ,它的长度为 3 。 9 | // 示例 2: 10 | // 输入:text1 = "abc", text2 = "abc" 11 | // 输出:3 12 | // 解释:最长公共子序列是 "abc" ,它的长度为 3 。 13 | // 示例 3: 14 | // 输入:text1 = "abc", text2 = "def" 15 | // 输出:0 16 | // 解释:两个字符串没有公共子序列,返回 0 。 17 | 18 | // var longestCommonSubsequence = function (text1, text2) { 19 | // const m = text1.length, 20 | // n = text2.length; 21 | // const dp = new Array(m + 1).fill(0).map(() => new Array(n + 1).fill(0)); 22 | // console.log(dp); 23 | // for (let i = 1; i <= m; i++) { 24 | // const c1 = text1[i - 1]; 25 | // for (let j = 1; j <= n; j++) { 26 | // const c2 = text2[j - 1]; 27 | // if (c1 === c2) { 28 | // dp[i][j] = dp[i - 1][j - 1] + 1; 29 | // } else { 30 | // dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]); 31 | // } 32 | // } 33 | // } 34 | // return dp[m][n]; 35 | // }; 36 | function longestCommonSubsequence(text1, text2) { 37 | let len1 = text1.length; 38 | let len2 = text2.length; 39 | let dp = new Array(len1 + 1).fill(0).map(() => new Array(len2 + 1).fill(0)); 40 | for (let i = 1; i <= len1; i++) { 41 | const c1 = text1[i - 1]; 42 | for (let j = 1; j <= len2; j++) { 43 | const c2 = text2[j - 1]; 44 | if (c1 === c2) { 45 | dp[i][j] = dp[i - 1][j - 1] + 1; 46 | } else { 47 | dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]); 48 | } 49 | } 50 | } 51 | return dp[len1][len2]; 52 | } 53 | // console.log(longestCommonSubsequence("abcde", "ace")); 54 | console.log(longestCommonSubsequence([1, 2, 3, 2, 1, 0], [3, 2, 1, 4, 7, 0])); 55 | -------------------------------------------------------------------------------- /algorithm/3.dynamic-planning/7.longest-palindromic-substring.js: -------------------------------------------------------------------------------- 1 | // 给你一个字符串 s,找到 s 中最长的回文子串。 2 | // 示例 1: 3 | // 输入:s = "babad" 4 | // 输出:"bab" 5 | // 解释:"aba" 同样是符合题意的答案。 6 | // 示例 2: 7 | // 输入:s = "cbbd" 8 | // 输出:"bb" 9 | /** 10 | * @param {string} s 11 | * @return {string} 12 | */ 13 | var longestPalindrome = function (s) { 14 | const dp = []; 15 | const len = s.length; 16 | // 初始化二维数组 17 | for (let i = 0; i < len; i++) { 18 | dp[i] = new Array(len).fill(0) 19 | } 20 | // 初始化两个端点值 21 | let start = (end = 0); 22 | for (let i = 0; i < len; i++) { 23 | dp[i][i] = 1; 24 | } 25 | // 为了降低复杂度,对 s[i][i+1] 做处理 26 | for (let i = 0; i < len; i++) { 27 | if (s[i] === s[i + 1]) { 28 | dp[i][i + 1] = 1; 29 | start = i; 30 | end = i + 1; 31 | } 32 | } 33 | console.log(dp) 34 | // l代表子串的长度,从3开始递增 35 | for (let l = 3; l <= len; l++) { 36 | for (let i = 0; i <= len - l; i++) { 37 | let j = i + l - 1; 38 | if (dp[i + 1][j - 1]) { 39 | if (s[i] === s[j]) { 40 | dp[i][j] = 1; 41 | start = i; 42 | end = j; 43 | } 44 | } 45 | } 46 | } 47 | return s.substring(start, end + 1); 48 | }; 49 | console.log(longestPalindrome("sssass")); 50 | -------------------------------------------------------------------------------- /algorithm/3.dynamic-planning/8.maximum-product-subarray.js: -------------------------------------------------------------------------------- 1 | // 给你一个整数数组 nums ,请你找出数组中乘积最大的非空连续子数组(该子数组中至少包含一个数字),并返回该子数组所对应的乘积。 2 | // 测试用例的答案是一个 32-位 整数。 3 | // 子数组 是数组的连续子序列。 4 | // 示例 1: 5 | // 输入: nums = [2,3,-2,4] 6 | // 输出: 6 7 | // 解释: 子数组 [2,3] 有最大乘积 6。 8 | // 示例 2: 9 | // 输入: nums = [-2,0,-1] 10 | // 输出: 0 11 | // 解释: 结果不能为 2, 因为 [-2,-1] 不是子数组。 12 | 13 | /** 14 | * @param {number[]} nums 15 | * @return {number} 16 | */ 17 | var maxProduct = function (nums) { 18 | let curMax = nums[0], 19 | curMin = nums[0], 20 | max = nums[0], 21 | length = nums.length; 22 | for (let i = 1; i < length; i++) { 23 | // 之前的最大值和最小值 24 | let prevMax = curMax, 25 | prevMin = curMin; 26 | // 存在负负得正、正负得负的情况,所以把之前的最大最小值和nums[i]做乘积和nums[i]比较 27 | curMax = Math.max(nums[i], prevMax * nums[i], prevMin * nums[i]); 28 | curMin = Math.min(nums[i], prevMax * nums[i], prevMin * nums[i]); 29 | // 每次得出的最大值和历史最大值比较 30 | max = Math.max(curMax, max); 31 | } 32 | return max; 33 | }; 34 | console.log(maxProduct([2, 3, -2, 4])); 35 | // console.log(maxProduct([-3, 3, 2, 3, -2, 4])); 36 | -------------------------------------------------------------------------------- /algorithm/30.bst.js: -------------------------------------------------------------------------------- 1 | // 树的定义总是以递归的形式出现,二叉搜索树也不例外,它的递归定义如下: 2 | // 1、是一棵空树 3 | // 2、是一棵由根结点、左子树、右子树组成的树,同时左子树和右子树都是二叉搜索树,且左子树上所有结点的数据域都小于等于根结点的数据域,右子树上所有结点的数据域都大于等于根结点的数据域 4 | // 满足以上两个条件之一的二叉树,就是二叉搜索树。 5 | 6 | function search(root, n) { 7 | // 若 root 为空,查找失败,直接返回 8 | if (!root) { 9 | return; 10 | } 11 | // 找到目标结点,输出结点对象 12 | if (root.val === n) { 13 | console.log("目标结点是:", root); 14 | } else if (root.val > n) { 15 | // 当前结点数据域大于n,向左查找 16 | search(root.left, n); 17 | } else { 18 | // 当前结点数据域小于n,向右查找 19 | search(root.right, n); 20 | } 21 | } 22 | 23 | function insertIntoBST(root, n) { 24 | // 若 root 为空,说明当前是一个可以插入的空位 25 | if (!root) { 26 | // 用一个值为n的结点占据这个空位 27 | root = new TreeNode(n); 28 | return root; 29 | } 30 | 31 | if (root.val > n) { 32 | // 当前结点数据域大于n,向左查找 33 | root.left = insertIntoBST(root.left, n); 34 | } else { 35 | // 当前结点数据域小于n,向右查找 36 | root.right = insertIntoBST(root.right, n); 37 | } 38 | 39 | // 返回插入后二叉搜索树的根结点 40 | return root; 41 | } 42 | 43 | /** 44 | * 给定一个二叉树,判断其是否是一个有效的二叉搜索树。 45 | * @param {TreeNode} root 46 | * @return {boolean} 47 | */ 48 | const isValidBST = function (root) { 49 | // 定义递归函数 50 | function dfs(root, minValue, maxValue) { 51 | // 若是空树,则合法 52 | if (!root) { 53 | return true; 54 | } 55 | // 若右孩子不大于根结点值,或者左孩子不小于根结点值,则不合法 56 | if (root.val <= minValue || root.val >= maxValue) return false; 57 | // 左右子树必须都符合二叉搜索树的数据域大小关系 58 | return ( 59 | dfs(root.left, minValue, root.val) && dfs(root.right, root.val, maxValue) 60 | ); 61 | } 62 | // 初始化最小值和最大值为极小或极大 63 | return dfs(root, -Infinity, Infinity); 64 | }; 65 | -------------------------------------------------------------------------------- /algorithm/31.delete-node-in-a-bst.js: -------------------------------------------------------------------------------- 1 | function deleteNode(root, n) { 2 | // 如果没找到目标结点,则直接返回 3 | if (!root) { 4 | return root; 5 | } 6 | // 定位到目标结点,开始分情况处理删除动作 7 | if (root.val === n) { 8 | // 若是叶子结点,则不需要想太多,直接删除 9 | if (!root.left && !root.right) { 10 | root = null; 11 | } else if (root.left) { 12 | // 寻找左子树里值最大的结点 13 | const maxLeft = findMax(root.left); 14 | // 用这个 maxLeft 覆盖掉需要删除的当前结点 15 | root.val = maxLeft.val; 16 | // 覆盖动作会消耗掉原有的 maxLeft 结点 17 | root.left = deleteNode(root.left, maxLeft.val); 18 | } else { 19 | // 寻找右子树里值最小的结点 20 | const minRight = findMin(root.right); 21 | // 用这个 minRight 覆盖掉需要删除的当前结点 22 | root.val = minRight.val; 23 | // 覆盖动作会消耗掉原有的 minRight 结点 24 | root.right = deleteNode(root.right, minRight.val); 25 | } 26 | } else if (root.val > n) { 27 | // 若当前结点的值比 n 大,则在左子树中继续寻找目标结点 28 | root.left = deleteNode(root.left, n); 29 | } else { 30 | // 若当前结点的值比 n 小,则在右子树中继续寻找目标结点 31 | root.right = deleteNode(root.right, n); 32 | } 33 | return root; 34 | } 35 | // 寻找左子树最大值 36 | function findMax(root) { 37 | while (root.right) { 38 | root = root.right; 39 | } 40 | return root; 41 | } 42 | // 寻找右子树的最小值 43 | function findMin(root) { 44 | while (root.left) { 45 | root = root.left; 46 | } 47 | return root; 48 | } 49 | -------------------------------------------------------------------------------- /algorithm/32.sorted-array-to-bst.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 将一个按照升序排列的有序数组,转换为一棵高度平衡二叉搜索树。 3 | * @param {number[]} nums 4 | * @return {TreeNode} 5 | */ 6 | const sortedArrayToBST = function (nums) { 7 | // 处理边界条件 8 | if (!nums.length) { 9 | return null; 10 | } 11 | 12 | // root 结点是递归“提”起数组的结果 13 | const root = buildBST(0, nums.length - 1); 14 | 15 | // 定义二叉树构建函数,入参是子序列的索引范围 16 | function buildBST(low, high) { 17 | // 当 low > high 时,意味着当前范围的数字已经被递归处理完全了 18 | if (low > high) { 19 | return null; 20 | } 21 | // 二分一下,取出当前子序列的中间元素 22 | const mid = Math.floor(low + (high - low) / 2); 23 | // 将中间元素的值作为当前子树的根结点值 24 | const cur = new TreeNode(nums[mid]); 25 | // 递归构建左子树,范围二分为[low,mid) 26 | cur.left = buildBST(low, mid - 1); 27 | // 递归构建左子树,范围二分为为(mid,high] 28 | cur.right = buildBST(mid + 1, high); 29 | // 返回当前结点 30 | return cur; 31 | } 32 | // 返回根结点 33 | return root; 34 | }; 35 | -------------------------------------------------------------------------------- /algorithm/33.avl.js: -------------------------------------------------------------------------------- 1 | // 平衡二叉树(又称 AVL Tree)指的是任意结点的左右子树高度差绝对值都不大于1的二叉搜索树。 2 | // 平衡二叉树的出现,是为了降低二叉搜索树的查找时间复杂度。 3 | 4 | // 给定一个二叉树,判断它是否是高度平衡的二叉树。 5 | const isBalanced = function (root) { 6 | // 立一个flag,只要有一个高度差绝对值大于1,这个flag就会被置为false 7 | let flag = true; 8 | // 定义递归逻辑 9 | function dfs(root) { 10 | // 如果是空树,高度记为0;如果flag已经false了,那么就没必要往下走了,直接return 11 | if (!root || !flag) { 12 | return 0; 13 | } 14 | // 计算左子树的高度 15 | const left = dfs(root.left); 16 | // 计算右子树的高度 17 | const right = dfs(root.right); 18 | // 如果左右子树的高度差绝对值大于1,flag就破功了 19 | if (Math.abs(left - right) > 1) { 20 | flag = false; 21 | // 后面再发生什么已经不重要了,返回一个不影响回溯计算的值 22 | return 0; 23 | } 24 | // 返回当前子树的高度 25 | return Math.max(left, right) + 1; 26 | } 27 | 28 | // 递归入口 29 | dfs(root); 30 | // 返回flag的值 31 | return flag; 32 | }; 33 | 34 | /** 35 | * 给你一棵二叉搜索树,请你返回一棵平衡后的二叉搜索树,新生成的树应该与原来的树有着相同的节点值。 36 | * @param {TreeNode} root 37 | * @return {TreeNode} 38 | */ 39 | const balanceBST = function (root) { 40 | // 初始化中序遍历序列数组 41 | const nums = []; 42 | // 定义中序遍历二叉树,得到有序数组 43 | function inorder(root) { 44 | if (!root) { 45 | return; 46 | } 47 | inorder(root.left); 48 | nums.push(root.val); 49 | inorder(root.right); 50 | } 51 | 52 | function buildAVL(low, high) { 53 | // 若 low > high,则越界,说明当前索引范围对应的子树已经构建完毕 54 | if (low > high) { 55 | return null; 56 | } 57 | // 取数组的中间值作为根结点值 58 | const mid = Math.floor(low + (high - low) / 2); 59 | // 创造当前树的根结点 60 | const cur = new TreeNode(nums[mid]); 61 | // 构建左子树 62 | cur.left = buildAVL(low, mid - 1); 63 | // 构建右子树 64 | cur.right = buildAVL(mid + 1, high); 65 | // 返回当前树的根结点 66 | return cur; 67 | } 68 | // 调用中序遍历方法,求出 nums 69 | inorder(root); 70 | // 基于 nums,构造平衡二叉树 71 | return buildAVL(0, nums.length - 1); 72 | }; 73 | -------------------------------------------------------------------------------- /algorithm/34.heap.js: -------------------------------------------------------------------------------- 1 | // 完全二叉树是指同时满足下面两个条件的二叉树: 2 | 3 | // 从第一层到倒数第二层,每一层都是满的,也就是说每一层的结点数都达到了当前层所能达到的最大值 4 | // 最后一层的结点是从左到右连续排列的,不存在跳跃排列的情况(也就是说这一层的所有结点都集中排列在最左边)。 5 | 6 | // 堆是完全二叉树的一种特例。根据约束规则的不同,堆又分为两种: 7 | 8 | // 大顶堆 9 | // 小顶堆 10 | 11 | // 如果对一棵完全二叉树来说,它每个结点的结点值都不小于其左右孩子的结点值,这样的完全二叉树就叫做“大顶堆”: 12 | 13 | // 若树中每个结点值都不大于其左右孩子的结点值,这样的完全二叉树就叫做“小顶堆” 14 | 15 | // 入参是堆元素在数组里的索引范围,low表示下界,high表示上界 16 | /** 17 | * @param {*} low 18 | * @param {*} high 19 | */ 20 | function downHeap(low, high) { 21 | // 初始化 i 为当前结点,j 为当前结点的左孩子 22 | let i = low, 23 | j = i * 2 + 1; 24 | // 当 j 不超过上界时,重复向下对比+交换的操作 25 | while (j <= high) { 26 | // 如果右孩子比左孩子更大,则用右孩子和根结点比较 27 | if (j + 1 <= high && heap[j + 1] > heap[j]) { 28 | j = j + 1; 29 | } 30 | 31 | // 若当前结点比孩子结点小,则交换两者的位置,把较大的结点“拱上去” 32 | if (heap[i] < heap[j]) { 33 | // 交换位置 34 | const temp = heap[j]; 35 | heap[j] = heap[i]; 36 | heap[i] = temp; 37 | 38 | // i 更新为被交换的孩子结点的索引 39 | i = j; 40 | // j 更新为孩子结点的左孩子的索引 41 | j = j * 2 + 1; 42 | } else { 43 | break; 44 | } 45 | } 46 | } 47 | 48 | // 入参是堆元素在数组里的索引范围,low表示下界,high表示上界 49 | function upHeap(low, high) { 50 | // 初始化 i(当前结点索引)为上界 51 | let i = high; 52 | // 初始化 j 为 i 的父结点 53 | let j = Math.floor((i - 1) / 2); 54 | // 当 j 不逾越下界时,重复向上对比+交换的过程 55 | while (j >= low) { 56 | // 若当前结点比父结点大 57 | if (heap[j] < heap[i]) { 58 | // 交换当前结点与父结点,保持父结点是较大的一个 59 | const temp = heap[j]; 60 | heap[j] = heap[i]; 61 | heap[i] = temp; 62 | 63 | // i更新为被交换父结点的位置 64 | i = j; 65 | // j更新为父结点的父结点 66 | j = Math.floor((i - 1) / 2); 67 | } else { 68 | break; 69 | } 70 | } 71 | } 72 | 73 | // 在未排序的数组中找到第 k 个最大的元素。请注意,你需要找的是数组排序后的第 k 个最大的元素,而不是第 k 个不同的元素。 74 | /** 75 | * @param {number[]} nums 76 | * @param {number} k 77 | * @return {number} 78 | */ 79 | const findKthLargest = function (nums, k) { 80 | // 初始化一个堆数组 81 | const heap = []; 82 | // n表示堆数组里当前最后一个元素的索引 83 | let n = 0; 84 | // 缓存 nums 的长度 85 | const len = nums.length; 86 | // 初始化大小为 k 的堆 87 | function createHeap() { 88 | for (let i = 0; i < k; i++) { 89 | // 逐个往堆里插入数组中的数字 90 | insert(nums[i]); 91 | } 92 | } 93 | 94 | // 尝试用 [k, n-1] 区间的元素更新堆 95 | function updateHeap() { 96 | for (let i = k; i < len; i++) { 97 | // 只有比堆顶元素大的才有资格进堆 98 | if (nums[i] > heap[0]) { 99 | // 用较大数字替换堆顶数字 100 | heap[0] = nums[i]; 101 | // 重复向下对比+交换的逻辑 102 | downHeap(0, k); 103 | } 104 | } 105 | } 106 | 107 | // 向下对比函数 108 | function downHeap(low, high) { 109 | // 入参是堆元素在数组里的索引范围,low表示下界,high表示上界 110 | let i = low, 111 | j = i * 2 + 1; 112 | // 当 j 不超过上界时,重复向下对比+交换的操作 113 | while (j <= high) { 114 | // // 如果右孩子比左孩子更小,则用右孩子和根结点比较 115 | if (j + 1 <= high && heap[j + 1] < heap[j]) { 116 | j = j + 1; 117 | } 118 | 119 | // 若当前结点比孩子结点大,则交换两者的位置,把较小的结点“拱上去” 120 | if (heap[i] > heap[j]) { 121 | // 交换位置 122 | const temp = heap[j]; 123 | heap[j] = heap[i]; 124 | heap[i] = temp; 125 | 126 | // i 更新为被交换的孩子结点的索引 127 | i = j; 128 | // j 更新为孩子结点的左孩子的索引 129 | j = j * 2 + 1; 130 | } else { 131 | break; 132 | } 133 | } 134 | } 135 | 136 | // 入参是堆元素在数组里的索引范围,low表示下界,high表示上界 137 | function upHeap(low, high) { 138 | // 初始化 i(当前结点索引)为上界 139 | let i = high; 140 | // 初始化 j 为 i 的父结点 141 | let j = Math.floor((i - 1) / 2); 142 | // 当 j 不逾越下界时,重复向上对比+交换的过程 143 | while (j >= low) { 144 | // 若当前结点比父结点小 145 | if (heap[j] > heap[i]) { 146 | // 交换当前结点与父结点,保持父结点是较小的一个 147 | const temp = heap[j]; 148 | heap[j] = heap[i]; 149 | heap[i] = temp; 150 | 151 | // i更新为被交换父结点的位置 152 | i = j; 153 | // j更新为父结点的父结点 154 | j = Math.floor((i - 1) / 2); 155 | } else { 156 | break; 157 | } 158 | } 159 | } 160 | 161 | // 插入操作=将元素添加到堆尾部+向上调整元素的位置 162 | function insert(x) { 163 | heap[n] = x; 164 | upHeap(0, n); 165 | n++; 166 | } 167 | 168 | // 调用createHeap初始化元素个数为k的队 169 | createHeap(); 170 | // 调用updateHeap更新堆的内容,确保最后堆里保留的是最大的k个元素 171 | updateHeap(); 172 | // 最后堆顶留下的就是最大的k个元素中最小的那个,也就是第k大的元素 173 | return heap[0]; 174 | }; 175 | -------------------------------------------------------------------------------- /algorithm/4.array-string/1.two-sum.js: -------------------------------------------------------------------------------- 1 | // 1. 两数之和 2 | // 给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target 的那 两个 整数,并返回它们的数组下标。 3 | // 你可以假设每种输入只会对应一个答案。但是,数组中同一个元素在答案里不能重复出现。 4 | // 你可以按任意顺序返回答案。 5 | 6 | // 示例 1: 7 | // 输入:nums = [2,7,11,15], target = 9 8 | // 输出:[0,1] 9 | // 解释:因为 nums[0] + nums[1] == 9 ,返回 [0, 1] 。 10 | 11 | // 示例 2: 12 | // 输入:nums = [3,2,4], target = 6 13 | // 输出:[1,2] 14 | 15 | // 示例 3: 16 | // 输入:nums = [3,3], target = 6 17 | // 输出:[0,1] 18 | 19 | /** 20 | * @param {number[]} nums 21 | * @param {number} target 22 | * @return {number[]} 23 | */ 24 | var twoSum = function (nums, target) { 25 | const map = new Map(); 26 | for (let i = 0; i < nums.length; i++) { 27 | if (map.has(target - nums[i])) { 28 | return [map.get(target - nums[i]), i]; 29 | } else { 30 | map.set(nums[i], i); 31 | } 32 | } 33 | return -1; 34 | }; 35 | console.log(twoSum([2, 7, 11, 15], 9)); 36 | console.log(twoSum([3, 2, 4], 6)); 37 | console.log(twoSum([3, 3], 6)); 38 | -------------------------------------------------------------------------------- /algorithm/4.array-string/10.range-list.js: -------------------------------------------------------------------------------- 1 | class RangeList { 2 | constructor() { 3 | this.list = []; 4 | } 5 | add(range) { 6 | if (!this.list.length) { 7 | this.list = [range]; 8 | return; 9 | } 10 | this.list.push(range); 11 | this.list = this.list.sort((a, b) => a[0] - b[0]); 12 | for (let i = 0; i < this.list.length; i++) { 13 | if (this.list[i + 1] && this.list[i][1] >= this.list[i + 1][0]) { 14 | this.list[i][1] = Math.max( 15 | this.list[i][1], 16 | this.list[i + 1][0], 17 | this.list[i + 1][1] 18 | ); 19 | this.list.splice(i + 1, 1); 20 | i--; 21 | } 22 | } 23 | } 24 | remove(range) { 25 | for (let i = 0; i < this.list.length; i++) { 26 | // 有交集 27 | if (range[0] < this.list[i][1] && range[1] > this.list[i][0]) { 28 | if (range[1] >= this.list[i][1]) { 29 | if (range[0] > this.list[i][0]) { 30 | this.list[i][1] = range[0]; 31 | } else { 32 | this.list.splice(i, 1); 33 | i--; 34 | } 35 | } else { 36 | if (range[0] <= this.list[i][0]) { 37 | this.list[i][0] = range[1]; 38 | } else { 39 | this.list.splice(i + 1, 0, [range[1], this.list[i][1]]); 40 | this.list[i][1] = range[0]; 41 | i++; 42 | } 43 | } 44 | } 45 | } 46 | } 47 | print() { 48 | console.log(this.list); 49 | } 50 | } 51 | const r1 = new RangeList(); 52 | r1.add([1, 5]); 53 | r1.print(); // [[1,5]] 54 | r1.add([10, 20]); 55 | r1.print(); // [[1,5], [10, 20]] 56 | r1.add([20, 20]); 57 | r1.print(); // [[1,5], [10, 20]] 58 | r1.add([20, 21]); 59 | r1.print(); // [[1,5], [10, 21]] 60 | r1.add([2, 4]); 61 | r1.print(); // [[1,5], [10, 21]] 62 | r1.add([3, 8]); 63 | r1.print(); // [[1,8], [10, 21]] 64 | console.log("+++++++++++++++++++++"); 65 | r1.remove([10, 10]); 66 | r1.print(); // [[1,8], [10, 21]] 67 | r1.remove([10, 11]); 68 | r1.print(); // [[1,8], [11, 21]] 69 | r1.remove([15, 17]); 70 | r1.print(); // [[1,8], [11, 15], [17, 21]] 71 | r1.remove([3, 19]); 72 | r1.print(); // [[1,3],[19, 21]] 73 | -------------------------------------------------------------------------------- /algorithm/4.array-string/2.merge-sorted-array.js: -------------------------------------------------------------------------------- 1 | // 给你两个按 非递减顺序 排列的整数数组 nums1 和 nums2,另有两个整数 m 和 n ,分别表示 nums1 和 nums2 中的元素数目。 2 | // 请你 合并 nums2 到 nums1 中,使合并后的数组同样按 非递减顺序 排列。 3 | // 注意:最终,合并后数组不应由函数返回,而是存储在数组 nums1 中。为了应对这种情况,nums1 的初始长度为 m + n,其中前 m 个元素表示应合并的元素,后 n 个元素为 0 ,应忽略。nums2 的长度为 n 。 4 | // 示例 1: 5 | // 输入:nums1 = [1,2,3,0,0,0], m = 3, nums2 = [2,5,6], n = 3 6 | // 输出:[1,2,2,3,5,6] 7 | // 解释:需要合并 [1,2,3] 和 [2,5,6] 。 8 | // 合并结果是 [1,2,2,3,5,6] ,其中斜体加粗标注的为 nums1 中的元素。 9 | // 示例 2: 10 | // 输入:nums1 = [1], m = 1, nums2 = [], n = 0 11 | // 输出:[1] 12 | // 解释:需要合并 [1] 和 [] 。 13 | // 合并结果是 [1] 。 14 | // 示例 3: 15 | // 输入:nums1 = [0], m = 0, nums2 = [1], n = 1 16 | // 输出:[1] 17 | // 解释:需要合并的数组是 [] 和 [1] 。 18 | // 合并结果是 [1] 。 19 | // 注意,因为 m = 0 ,所以 nums1 中没有元素。nums1 中仅存的 0 仅仅是为了确保合并结果可以顺利存放到 nums1 中。 20 | // 21 | 22 | var merge = function (nums1, m, nums2, n) { 23 | let i = m - 1; 24 | let j = n - 1; 25 | while (j >= 0) { 26 | console.log(i, j) 27 | if (nums1[i] >= nums2[j]) { 28 | nums1[i + j + 1] = nums1[i]; 29 | i--; 30 | } else { 31 | nums1[i + j + 1] = nums2[j]; 32 | j--; 33 | } 34 | } 35 | return nums1; 36 | }; 37 | console.log(merge([12, 22, 32, 42, 71, 0, 0, 0], 5, [9, 9, 9], 3)); 38 | console.log(merge([1, 2, 0, 0, 0], 2, [2, 5, 6], 3)); 39 | console.log(merge([1], 1, [], 0)); 40 | console.log(merge([0], 0, [1], 1)); 41 | -------------------------------------------------------------------------------- /algorithm/4.array-string/3.3sum.js: -------------------------------------------------------------------------------- 1 | // 给你一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?请你找出所有和为 0 且不重复的三元组。 2 | // 注意:答案中不可以包含重复的三元组。 3 | // 示例 1: 4 | // 输入:nums = [-1,0,1,2,-1,-4] 5 | // 输出:[[-1,-1,2],[-1,0,1]] 6 | // 示例 2: 7 | // 输入:nums = [] 8 | // 输出:[] 9 | // 示例 3: 10 | // 输入:nums = [0] 11 | // 输出:[] 12 | // 13 | /** 14 | * @param {number[]} nums 15 | * @return {number[][]} 16 | */ 17 | var threeSum = function (nums) { 18 | nums = nums.sort((a, b) => a - b); 19 | const result = []; 20 | for (let i = 0; i < nums.length - 2; i++) { 21 | if (nums[i] === nums[i - 1]) { 22 | continue; 23 | } 24 | let j = i + 1; 25 | let k = nums.length - 1; 26 | while (j < k) { 27 | if (nums[i] + nums[j] + nums[k] > 0) { 28 | k--; 29 | } else if (nums[i] + nums[j] + nums[k] < 0) { 30 | j++; 31 | } else { 32 | result.push([nums[i], nums[j], nums[k]]); 33 | j++; 34 | k--; 35 | } 36 | while (nums[j] === nums[j - 1] && j > i + 1) { 37 | j++; 38 | } 39 | while (nums[k] === nums[k + 1]) { 40 | k--; 41 | } 42 | } 43 | } 44 | return result; 45 | }; 46 | console.log(threeSum([-1, 0, 1, 2, -1, -4])); 47 | console.log(threeSum([])); 48 | console.log(threeSum([0])); 49 | console.log(threeSum([-2, 0, 0, 2, 2])); 50 | -------------------------------------------------------------------------------- /algorithm/4.array-string/4.valid-palindrome-ii.js: -------------------------------------------------------------------------------- 1 | // 给定一个非空字符串 s,最多删除一个字符。判断是否能成为回文字符串。 2 | // 示例 1: 3 | // 输入: s = "aba" 4 | // 输出: true 5 | // 示例 2: 6 | // 输入: s = "abca" 7 | // 输出: true 8 | // 解释: 你可以删除c字符。 9 | // 示例 3: 10 | // 输入: s = "abc" 11 | // 输出: false 12 | function validPalindrome(s) { 13 | let i = 0, 14 | j = s.length - 1; 15 | while (i < j && s[i] === s[j]) { 16 | i++; 17 | j--; 18 | } 19 | function valid(start, end) { 20 | while (start < end) { 21 | if (s[start] !== s[end]) { 22 | return false; 23 | } 24 | start++; 25 | end--; 26 | } 27 | return true; 28 | } 29 | return valid(i, j - 1) || valid(i + 1, j); 30 | } 31 | console.log(validPalindrome("aba")); 32 | console.log(validPalindrome("abca")); 33 | console.log(validPalindrome("abc")); 34 | console.log(validPalindrome("eeccccbebaeeabebccceea")); 35 | console.log(validPalindrome("eedede")); 36 | console.log(validPalindrome("deeee")); 37 | -------------------------------------------------------------------------------- /algorithm/4.array-string/5.merge-intervals.js: -------------------------------------------------------------------------------- 1 | // 以数组 intervals 表示若干个区间的集合,其中单个区间为 intervals[i] = [starti, endi] 。请你合并所有重叠的区间,并返回 一个不重叠的区间数组,该数组需恰好覆盖输入中的所有区间 。 2 | 3 | // 示例 1: 4 | // 输入:intervals = [[1,3],[2,6],[8,10],[15,18]] 5 | // 输出:[[1,6],[8,10],[15,18]] 6 | // 解释:区间 [1,3] 和 [2,6] 重叠, 将它们合并为 [1,6]. 7 | 8 | // 示例 2: 9 | // 输入:intervals = [[1,4],[4,5]] 10 | // 输出:[[1,5]] 11 | // 解释:区间 [1,4] 和 [4,5] 可被视为重叠区间。 12 | 13 | /** 14 | * @param {number[][]} intervals 15 | * @return {number[][]} 16 | */ 17 | var merge = function (intervals) { 18 | let len = intervals.length; 19 | if (len < 2) { 20 | return intervals; 21 | } 22 | const result = []; 23 | intervals = intervals.sort((a, b) => a[0] - b[0]); 24 | result.push(intervals[0]); 25 | for (let i = 1; i < len; i++) { 26 | if (result[result.length - 1][1] >= intervals[i][0]) { 27 | result[result.length - 1][1] = Math.max( 28 | result[result.length - 1][1], 29 | intervals[i][1] 30 | ); 31 | } else { 32 | result.push(intervals[i]); 33 | } 34 | } 35 | return result; 36 | }; 37 | console.log( 38 | merge([ 39 | [1, 3], 40 | [2, 6], 41 | [8, 10], 42 | [15, 18], 43 | ]) 44 | ); 45 | console.log( 46 | merge([ 47 | [1, 4], 48 | [4, 5], 49 | ]) 50 | ); 51 | console.log( 52 | merge([ 53 | [1, 4], 54 | [0, 4], 55 | ]) 56 | ); 57 | -------------------------------------------------------------------------------- /algorithm/4.array-string/6.design-add-and-search-words-data-structure.js: -------------------------------------------------------------------------------- 1 | // 请你设计一个数据结构,支持 添加新单词 和 查找字符串是否与任何先前添加的字符串匹配 。 2 | // 实现词典类 WordDictionary : 3 | // WordDictionary() 初始化词典对象 4 | // void addWord(word) 将 word 添加到数据结构中,之后可以对它进行匹配 5 | // bool search(word) 如果数据结构中存在字符串与 word 匹配,则返回 true ;否则,返回  false 。word 中可能包含一些 '.' ,每个 . 都可以表示任何一个字母。 6 | // 示例: 7 | // 输入: 8 | // ["WordDictionary","addWord","addWord","addWord","search","search","search","search"] 9 | // [[],["bad"],["dad"],["mad"],["pad"],["bad"],[".ad"],["b.."]] 10 | // 输出: 11 | // [null,null,null,null,false,true,true,true] 12 | // 解释: 13 | // WordDictionary wordDictionary = new WordDictionary(); 14 | // wordDictionary.addWord("bad"); 15 | // wordDictionary.addWord("dad"); 16 | // wordDictionary.addWord("mad"); 17 | // wordDictionary.search("pad"); // 返回 False 18 | // wordDictionary.search("bad"); // 返回 True 19 | // wordDictionary.search(".ad"); // 返回 True 20 | // wordDictionary.search("b.."); // 返回 True 21 | 22 | var WordDictionary = function () { 23 | this.words = {}; 24 | }; 25 | WordDictionary.prototype.addWord = function (word) { 26 | if (this.words[word.length]) { 27 | this.words[word.length].push(word); 28 | } else { 29 | this.words[word.length] = [word]; 30 | } 31 | }; 32 | WordDictionary.prototype.search = function (word) { 33 | const len = word.length; 34 | if (!this.words[len]) { 35 | return false; 36 | } 37 | if (!word.includes(".")) { 38 | return this.words[len].includes(word); 39 | } 40 | const reg = new RegExp(word) 41 | return this.words[len].some(item => reg.test(item)) 42 | }; 43 | -------------------------------------------------------------------------------- /algorithm/4.array-string/7.string-to-integer-atoi.js: -------------------------------------------------------------------------------- 1 | // 请你来实现一个 myAtoi(string s) 函数,使其能将字符串转换成一个 32 位有符号整数(类似 C/C++ 中的 atoi 函数)。 2 | // 函数 myAtoi(string s) 的算法如下: 3 | // 读入字符串并丢弃无用的前导空格 4 | // 检查下一个字符(假设还未到字符末尾)为正还是负号,读取该字符(如果有)。 确定最终结果是负数还是正数。 如果两者都不存在,则假定结果为正。 5 | // 读入下一个字符,直到到达下一个非数字字符或到达输入的结尾。字符串的其余部分将被忽略。 6 | // 将前面步骤读入的这些数字转换为整数(即,"123" -> 123, "0032" -> 32)。如果没有读入数字,则整数为 0 。必要时更改符号(从步骤 2 开始)。 7 | // 如果整数数超过 32 位有符号整数范围 [−231,  231 − 1] ,需要截断这个整数,使其保持在这个范围内。具体来说,小于 −231 的整数应该被固定为 −231 ,大于 231 − 1 的整数应该被固定为 231 − 1 。 8 | // 返回整数作为最终结果。 9 | // 注意: 10 | // 本题中的空白字符只包括空格字符 ' ' 。 11 | // 除前导空格或数字后的其余字符串外,请勿忽略 任何其他字符。 12 | // 示例 1: 13 | // 输入:s = "42" 14 | // 输出:42 15 | // 解释:加粗的字符串为已经读入的字符,插入符号是当前读取的字符。 16 | // 第 1 步:"42"(当前没有读入字符,因为没有前导空格) 17 | // 第 2 步:"42"(当前没有读入字符,因为这里不存在 '-' 或者 '+') 18 | // 第 3 步:"42"(读入 "42") 19 | // 解析得到整数 42 。 20 | // 由于 "42" 在范围 [-231, 231 - 1] 内,最终结果为 42 。 21 | // 示例 2: 22 | // 输入:s = " -42" 23 | // 输出:-42 24 | // 解释: 25 | // 第 1 步:" -42"(读入前导空格,但忽视掉) 26 | // 第 2 步:" -42"(读入 '-' 字符,所以结果应该是负数) 27 | // 第 3 步:" -42"(读入 "42") 28 | // 解析得到整数 -42 。 29 | // 由于 "-42" 在范围 [-231, 231 - 1] 内,最终结果为 -42 。 30 | // 示例 3: 31 | // 输入:s = "4193 with words" 32 | // 输出:4193 33 | // 解释: 34 | // 第 1 步:"4193 with words"(当前没有读入字符,因为没有前导空格) 35 | // 第 2 步:"4193 with words"(当前没有读入字符,因为这里不存在 '-' 或者 '+') 36 | // 第 3 步:"4193 with words"(读入 "4193";由于下一个字符不是一个数字,所以读入停止) 37 | // 解析得到整数 4193 。 38 | // 由于 "4193" 在范围 [-231, 231 - 1] 内,最终结果为 4193 。 39 | var myAtoi = function (s) { 40 | // ? 等价于{0,1},表示出现或者不出现。记忆方式:问号的意思表示,有吗 41 | // + 等价于{1,},表示出现至少一次。记忆方式:加号是追加的意思,得先有一个,然后才考虑追加。 42 | // * 等价于{0,},表示出现任意次,有可能不出现。记忆方式:看看天上的星星,可能一颗没有,可能零散有几颗,可能数也数不过来。 43 | const reg = /^\s*([-\+]?[\d]+).*/; 44 | const MAX = Math.pow(2, 31) - 1 45 | const MIN = -MAX - 1 46 | const result = s.match(reg); 47 | let num = 0 48 | if (result) { 49 | num = +result[1] 50 | num = Math.max(Math.min(MAX, num), MIN) 51 | } 52 | return num 53 | }; 54 | console.log(myAtoi("+42")); 55 | console.log(myAtoi(" -42")); 56 | console.log(myAtoi("4193 with words")); 57 | -------------------------------------------------------------------------------- /algorithm/4.array-string/8.best-time-to-buy-and-sell-stock.js: -------------------------------------------------------------------------------- 1 | // 给定一个数组 prices ,它的第 i 个元素 prices[i] 表示一支给定股票第 i 天的价格。 2 | // 你只能选择 某一天 买入这只股票,并选择在 未来的某一个不同的日子 卖出该股票。设计一个算法来计算你所能获取的最大利润。 3 | // 返回你可以从这笔交易中获取的最大利润。如果你不能获取任何利润,返回 0 。 4 | // 示例 1: 5 | // 输入:[7,1,5,3,6,4] 6 | // 输出:5 7 | // 解释:在第 2 天(股票价格 = 1)的时候买入,在第 5 天(股票价格 = 6)的时候卖出,最大利润 = 6-1 = 5 。 8 | // 注意利润不能是 7-1 = 6, 因为卖出价格需要大于买入价格;同时,你不能在买入前卖出股票。 9 | // 示例 2: 10 | // 输入:prices = [7,6,4,3,1] 11 | // 输出:0 12 | // 解释:在这种情况下, 没有交易完成, 所以最大利润为 0。 13 | var maxProfit = function (prices) { 14 | // 记录最低价格 15 | let minPrice = Number.MAX_SAFE_INTEGER; 16 | // 记录最大利润 17 | let maxProfit = 0; 18 | for (let i = 0; i < prices.length; i++) { 19 | if (prices[i] < minPrice) { 20 | minPrice = prices[i]; 21 | } else if (prices[i] - minPrice > maxProfit) { 22 | maxProfit = prices[i] - minPrice; 23 | } 24 | } 25 | return maxProfit; 26 | }; 27 | console.log(maxProfit([7, 1, 5, 3, 6, 4])); 28 | console.log(maxProfit([7, 6, 4, 3, 1])); 29 | -------------------------------------------------------------------------------- /algorithm/4.array-string/9.best-time-to-buy-and-sell-stock-ii.js: -------------------------------------------------------------------------------- 1 | // 给定一个数组 prices ,其中 prices[i] 表示股票第 i 天的价格。 2 | 3 | // 在每一天,你可能会决定购买和/或出售股票。你在任何时候 最多 只能持有 一股 股票。你也可以购买它,然后在 同一天 出售。 4 | // 返回 你能获得的 最大 利润 。 5 | 6 | // 示例 1: 7 | 8 | // 输入: prices = [7,1,5,3,6,4] 9 | // 输出: 7 10 | // 解释: 在第 2 天(股票价格 = 1)的时候买入,在第 3 天(股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5-1 = 4 。 11 | //   随后,在第 4 天(股票价格 = 3)的时候买入,在第 5 天(股票价格 = 6)的时候卖出, 这笔交易所能获得利润 = 6-3 = 3 。 12 | // 示例 2: 13 | 14 | // 输入: prices = [1,2,3,4,5] 15 | // 输出: 4 16 | // 解释: 在第 1 天(股票价格 = 1)的时候买入,在第 5 天 (股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5-1 = 4 。 17 | //   注意你不能在第 1 天和第 2 天接连购买股票,之后再将它们卖出。因为这样属于同时参与了多笔交易,你必须在再次购买前出售掉之前的股票。 18 | // 示例 3: 19 | 20 | // 输入: prices = [7,6,4,3,1] 21 | // 输出: 0 22 | // 解释: 在这种情况下, 没有交易完成, 所以最大利润为 0。 23 | /** 24 | * 贪心算法 25 | * @param {*} prices 26 | * @returns 27 | */ 28 | var maxProfit = function (prices) { 29 | let profit = 0; 30 | for (let i = 1; i < prices.length; i++) { 31 | profit += Math.max(0, prices[i] - prices[i - 1]); 32 | } 33 | return profit; 34 | }; 35 | console.log(maxProfit([7, 1, 5, 3, 6, 4])); 36 | console.log(maxProfit([1, 2, 3, 4, 5])); 37 | console.log(maxProfit([7, 6, 4, 3, 1])); 38 | -------------------------------------------------------------------------------- /algorithm/5.list/1.merge-two-sorted-lists.js: -------------------------------------------------------------------------------- 1 | // 将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。 2 | 3 | // 示例 1 4 | // 输入:l1 = [1,2,4], l2 = [1,3,4] 5 | // 输出:[1,1,2,3,4,4] 6 | // 示例 2 7 | // 输入:l1 = [], l2 = [] 8 | // 输出:[] 9 | // 示例 3 10 | // 输入:l1 = [], l2 = [0] 11 | // 输出:[0] 12 | 13 | function ListNode(val, next) { 14 | this.val = val === undefined ? 0 : val; 15 | this.next = next === undefined ? null : next; 16 | } 17 | var mergeTwoLists = function (list1, list2) { 18 | const head = new ListNode(); 19 | let cur = head; 20 | while (list1 && list2) { 21 | if (list1.val <= list2.val) { 22 | cur.next = list1; 23 | list1 = list1.next; 24 | } else { 25 | cur.next = list2; 26 | list2 = list2.next; 27 | } 28 | cur = cur.next; 29 | } 30 | cur.next = list1 || list2 31 | return head.next; 32 | }; 33 | let h2 = { 34 | val: 1, 35 | next: { 36 | val: 2, 37 | next: { 38 | val: 3, 39 | next: { 40 | val: 4, 41 | next: null, 42 | }, 43 | }, 44 | }, 45 | }; 46 | let h3 = { 47 | val: 1, 48 | next: { 49 | val: 3, 50 | next: null, 51 | }, 52 | }; 53 | console.log(JSON.stringify(mergeTwoLists(h2, h3))); 54 | -------------------------------------------------------------------------------- /algorithm/5.list/10.delete-list-node.js: -------------------------------------------------------------------------------- 1 | // 给定单向链表的头指针和一个要删除的节点的值,定义一个函数删除该节点。 2 | // 返回删除后的链表的头节点。 3 | // 注意:此题对比原题有改动 4 | // 示例 1: 5 | // 输入: head = [4,5,1,9], val = 5 6 | // 输出: [4,1,9] 7 | // 解释: 给定你链表中值为 5 的第二个节点,那么在调用了你的函数之后,该链表应变为 4 -> 1 -> 9. 8 | // 示例 2: 9 | // 输入: head = [4,5,1,9], val = 1 10 | // 输出: [4,5,9] 11 | // 解释: 给定你链表中值为 1 的第三个节点,那么在调用了你的函数之后,该链表应变为 4 -> 5 -> 9. 12 | let list = { 13 | val: 1, 14 | next: { 15 | val: 2, 16 | next: { 17 | val: 3, 18 | next: { 19 | val: 4, 20 | next: { 21 | val: 5, 22 | next: null, 23 | }, 24 | }, 25 | }, 26 | }, 27 | }; 28 | function ListNode(val, next) { 29 | this.val = val === undefined ? 0 : val; 30 | this.next = next === undefined ? null : next; 31 | } 32 | var deleteNode = function (head, val) { 33 | const dummy = new ListNode(); 34 | dummy.next = head; 35 | let cur = dummy; 36 | while (cur && cur.next) { 37 | if (cur.next.val === val) { 38 | cur.next = cur.next.next; 39 | } 40 | cur = cur.next; 41 | } 42 | return dummy.next; 43 | }; 44 | console.dir(deleteNode(list, 3), { depth: null }); 45 | -------------------------------------------------------------------------------- /algorithm/5.list/2.remove-duplicates-from-sorted-list.js: -------------------------------------------------------------------------------- 1 | // 给定一个已排序的链表的头 head , 删除所有重复的元素,使每个元素只出现一次 。返回 已排序的链表 。 2 | // 示例 1: 3 | // 输入:head = [1,1,2] 4 | // 输出:[1,2] 5 | // 示例 2: 6 | // 输入:head = [1,1,2,3,3] 7 | // 输出:[1,2,3] 8 | /** 9 | * @param {ListNode} head 10 | * @return {ListNode} 11 | */ 12 | var deleteDuplicates = function (head) { 13 | let cur = head; 14 | while (cur && cur.next) { 15 | if (cur.val === cur.next.val) { 16 | cur.next = cur.next.next; 17 | } else { 18 | cur = cur.next; 19 | } 20 | } 21 | return head; 22 | }; 23 | let h1 = { 24 | val: 1, 25 | next: { 26 | val: 1, 27 | next: { 28 | val: 2, 29 | next: null, 30 | }, 31 | }, 32 | }; 33 | let h2 = { 34 | val: 1, 35 | next: { 36 | val: 1, 37 | next: { 38 | val: 2, 39 | next: { 40 | val: 3, 41 | next: { 42 | val: 3, 43 | next: null, 44 | }, 45 | }, 46 | }, 47 | }, 48 | }; 49 | let h3 = { 50 | val: 1, 51 | next: { 52 | val: 1, 53 | next: null, 54 | }, 55 | }; 56 | console.log(deleteDuplicates(h1)); 57 | console.log(deleteDuplicates(h2)); 58 | console.log(deleteDuplicates(h3)); 59 | -------------------------------------------------------------------------------- /algorithm/5.list/3.remove-duplicates-from-sorted-list-ii.js: -------------------------------------------------------------------------------- 1 | // 给定一个已排序的链表的头 head , 删除原始链表中所有重复数字的节点,只留下不同的数字 。返回 已排序的链表 。 2 | 3 | // 示例 1: 4 | // 输入:head = [1,2,3,3,4,4,5] 5 | // 输出:[1,2,5] 6 | // 示例 2: 7 | // 输入:head = [1,1,1,2,3] 8 | // 输出:[2,3] 9 | 10 | function ListNode(val, next) { 11 | this.val = val === undefined ? 0 : val; 12 | this.next = next === undefined ? null : next; 13 | } 14 | /** 15 | * @param {ListNode} head 16 | * @return {ListNode} 17 | */ 18 | var deleteDuplicates = function (head) { 19 | if (!head || !head.next) { 20 | return head; 21 | } 22 | const dummy = new ListNode(); 23 | dummy.next = head; 24 | let cur = dummy; 25 | while (cur.next && cur.next.next) { 26 | if (cur.next.val === cur.next.next.val) { 27 | const val = cur.next.val; 28 | while (cur.next && cur.next.val === val) { 29 | cur.next = cur.next.next; 30 | } 31 | } else { 32 | cur = cur.next; 33 | } 34 | } 35 | return dummy.next; 36 | }; 37 | let h1 = { 38 | val: 1, 39 | next: { 40 | val: 1, 41 | next: { 42 | val: 2, 43 | next: null, 44 | }, 45 | }, 46 | }; 47 | let h2 = { 48 | val: 1, 49 | next: { 50 | val: 1, 51 | next: { 52 | val: 2, 53 | next: { 54 | val: 3, 55 | next: { 56 | val: 3, 57 | next: null, 58 | }, 59 | }, 60 | }, 61 | }, 62 | }; 63 | let h3 = { 64 | val: 1, 65 | next: { 66 | val: 1, 67 | next: null, 68 | }, 69 | }; 70 | console.log(deleteDuplicates(h1)); 71 | console.log(deleteDuplicates(h2)); 72 | console.log(deleteDuplicates(h3)); 73 | -------------------------------------------------------------------------------- /algorithm/5.list/4.remove-nth-node-from-end-of-list.js: -------------------------------------------------------------------------------- 1 | // 给你一个链表,删除链表的倒数第 n 个结点,并且返回链表的头结点。 2 | // 示例 1: 3 | // 输入:head = [1,2,3,4,5], n = 2 4 | // 输出:[1,2,3,5] 5 | // 示例 2: 6 | // 输入:head = [1], n = 1 7 | // 输出:[] 8 | // 示例 3: 9 | // 输入:head = [1,2], n = 1 10 | // 输出:[1] 11 | 12 | function ListNode(val, next) { 13 | this.val = val === undefined ? 0 : val; 14 | this.next = next === undefined ? null : next; 15 | } 16 | var removeNthFromEnd = function (head, n) { 17 | let dummy = new ListNode(); 18 | dummy.next = head; 19 | let fast = dummy; 20 | let slow = dummy; 21 | while (n) { 22 | fast = fast.next; 23 | n--; 24 | } 25 | while (fast.next) { 26 | fast = fast.next; 27 | slow = slow.next; 28 | } 29 | slow.next = slow.next.next; 30 | return dummy.next; 31 | }; 32 | let h2 = { 33 | val: 1, 34 | next: { 35 | val: 2, 36 | next: { 37 | val: 3, 38 | next: { 39 | val: 4, 40 | next: { 41 | val: 5, 42 | next: null, 43 | }, 44 | }, 45 | }, 46 | }, 47 | }; 48 | console.log(JSON.stringify(removeNthFromEnd(h2, 2))); 49 | -------------------------------------------------------------------------------- /algorithm/5.list/5.reverse-linked-list.js: -------------------------------------------------------------------------------- 1 | // 给你单链表的头节点 head ,请你反转链表,并返回反转后的链表。 2 | // 示例 1: 3 | // 输入:head = [1,2,3,4,5] 4 | // 输出:[5,4,3,2,1] 5 | // 示例 2: 6 | // 输入:head = [1,2] 7 | // 输出:[2,1] 8 | // 示例 3: 9 | // 输入:head = [] 10 | // 输出:[] 11 | /** 12 | * @param {ListNode} head 13 | * @return {ListNode} 14 | */ 15 | var reverseList = function (head) { 16 | let cur = head 17 | let pre = null 18 | while (cur) { 19 | let next = cur.next 20 | cur.next = pre 21 | pre = cur 22 | cur = next 23 | } 24 | return pre 25 | }; 26 | let list = { 27 | val: 1, 28 | next: { 29 | val: 2, 30 | next: { 31 | val: 3, 32 | next: { 33 | val: 4, 34 | next: { 35 | val: 5, 36 | next: null, 37 | }, 38 | }, 39 | }, 40 | }, 41 | }; 42 | console.log(JSON.stringify(reverseList(list))); 43 | -------------------------------------------------------------------------------- /algorithm/5.list/6.reverse-linked-list-ii.js: -------------------------------------------------------------------------------- 1 | // 给你单链表的头指针 head 和两个整数 left 和 right ,其中 left <= right 。请你反转从位置 left 到位置 right 的链表节点,返回 反转后的链表 。 2 | // 示例 1: 3 | // 输入:head = [1,2,3,4,5], left = 2, right = 4 4 | // 输出:[1,4,3,2,5] 5 | // 示例 2: 6 | // 输入:head = [5], left = 1, right = 1 7 | // 输出:[5] 8 | function ListNode(val, next) { 9 | this.val = val === undefined ? 0 : val; 10 | this.next = next === undefined ? null : next; 11 | } 12 | /** 13 | * @param {ListNode} head 14 | * @param {number} left 15 | * @param {number} right 16 | * @return {ListNode} 17 | */ 18 | var reverseBetween = function (head, left, right) { 19 | let dummy = new ListNode(); 20 | dummy.next = head; 21 | let leftHead = dummy; 22 | for (let i = 0; i < left - 1; i++) { 23 | leftHead = leftHead.next; 24 | } 25 | let start = leftHead.next; 26 | let prev = start; 27 | let cur = prev.next; 28 | for (let i = left; i < right; i++) { 29 | let next = cur.next 30 | cur.next = prev 31 | prev = cur 32 | cur = next 33 | } 34 | leftHead.next = prev 35 | start.next = cur 36 | return dummy.next 37 | }; 38 | let list = { 39 | val: 1, 40 | next: { 41 | val: 2, 42 | next: { 43 | val: 3, 44 | next: { 45 | val: 4, 46 | next: { 47 | val: 5, 48 | next: null, 49 | }, 50 | }, 51 | }, 52 | }, 53 | }; 54 | console.log(JSON.stringify(reverseBetween(list, 2, 4))); 55 | -------------------------------------------------------------------------------- /algorithm/5.list/7.linked-list-cycle.js: -------------------------------------------------------------------------------- 1 | // 给你一个链表的头节点 head ,判断链表中是否有环。 2 | // 如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。注意:pos 不作为参数进行传递 。仅仅是为了标识链表的实际情况。 3 | // 如果链表中存在环 ,则返回 true 。 否则,返回 false 。 4 | // 示例 1: 5 | // 输入:head = [3,2,0,-4], pos = 1 6 | // 输出:true 7 | // 解释:链表中有一个环,其尾部连接到第二个节点。 8 | // 示例 2: 9 | // 输入:head = [1,2], pos = 0 10 | // 输出:true 11 | // 解释:链表中有一个环,其尾部连接到第一个节点。 12 | // 示例 3: 13 | // 输入:head = [1], pos = -1 14 | // 输出:false 15 | // 解释:链表中没有环。 16 | /** 17 | * Definition for singly-linked list. 18 | * function ListNode(val) { 19 | * this.val = val; 20 | * this.next = null; 21 | * } 22 | */ 23 | /** 24 | * @param {ListNode} head 25 | * @return {boolean} 26 | */ 27 | var hasCycle = function (head) { 28 | while (head) { 29 | if (head.flag) { 30 | return true 31 | } 32 | head.flag = true 33 | head = head.next 34 | } 35 | return false 36 | }; 37 | -------------------------------------------------------------------------------- /algorithm/5.list/8.linked-list-cycle-ii.js: -------------------------------------------------------------------------------- 1 | // 给定一个链表的头节点  head ,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。 2 | // 如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。如果 pos 是 -1,则在该链表中没有环。注意:pos 不作为参数进行传递,仅仅是为了标识链表的实际情况。 3 | // 不允许修改 链表。 4 | // 示例 1: 5 | // 输入:head = [3,2,0,-4], pos = 1 6 | // 输出:返回索引为 1 的链表节点 7 | // 解释:链表中有一个环,其尾部连接到第二个节点。 8 | // 示例 2: 9 | // 输入:head = [1,2], pos = 0 10 | // 输出:返回索引为 0 的链表节点 11 | // 解释:链表中有一个环,其尾部连接到第一个节点。 12 | // 示例 3: 13 | // 输入:head = [1], pos = -1 14 | // 输出:返回 null 15 | // 解释:链表中没有环。 16 | /** 17 | * Definition for singly-linked list. 18 | * function ListNode(val) { 19 | * this.val = val; 20 | * this.next = null; 21 | * } 22 | */ 23 | /** 24 | * @param {ListNode} head 25 | * @return {ListNode} 26 | */ 27 | var detectCycle = function (head) { 28 | while (head) { 29 | if (head.flag) { 30 | return head; 31 | } 32 | head.flag = true; 33 | head = head.next; 34 | } 35 | return null; 36 | }; 37 | -------------------------------------------------------------------------------- /algorithm/5.list/9.delete-node-in-a-linked-list.js: -------------------------------------------------------------------------------- 1 | // 请编写一个函数,用于 删除单链表中某个特定节点 。在设计函数时需要注意,你无法访问链表的头节点 head ,只能直接访问 要被删除的节点 。 2 | // 题目数据保证需要删除的节点 不是末尾节点 。 3 | // 示例 1: 4 | // 输入:head = [4,5,1,9], node = 5 5 | // 输出:[4,1,9] 6 | // 解释:指定链表中值为 5 的第二个节点,那么在调用了你的函数之后,该链表应变为 4 -> 1 -> 9 7 | // 示例 2: 8 | // 输入:head = [4,5,1,9], node = 1 9 | // 输出:[4,5,9] 10 | // 解释:指定链表中值为 1 的第三个节点,那么在调用了你的函数之后,该链表应变为 4 -> 5 -> 9 11 | var deleteNode = function (node) { 12 | node.val = node.next.val; 13 | node.next = node.next.next; 14 | }; -------------------------------------------------------------------------------- /algorithm/5.palindrome-number.js: -------------------------------------------------------------------------------- 1 | // 给你一个整数 x ,如果 x 是一个回文整数,返回 true ;否则,返回 false 。 2 | // 回文数是指正序(从左向右)和倒序(从右向左)读都是一样的整数。 3 | // 例如,121 是回文,而 123 不是。 4 | // 5 | // 示例 1: 6 | // 输入:x = 121 7 | // 输出:true 8 | 9 | // 示例 2: 10 | // 输入:x = -121 11 | // 输出:false 12 | // 解释:从左向右读, 为 -121 。 从右向左读, 为 121- 。因此它不是一个回文数。 13 | 14 | // 示例 3: 15 | // 输入:x = 10 16 | // 输出:false 17 | // 解释:从右向左读, 为 01 。因此它不是一个回文数。 18 | 19 | /** 20 | * @param {number} x 21 | * @return {boolean} 22 | */ 23 | var isPalindrome = function (x) { 24 | x = x + ""; 25 | let i = 0, 26 | j = x.length - 1; 27 | while (i < j) { 28 | if (x[i] !== x[j]) { 29 | return false; 30 | } 31 | i++; 32 | j--; 33 | } 34 | return true; 35 | }; 36 | 37 | // 使用 toString + split + reverse + join 38 | // var isPalindrome = function (x) { 39 | // return x.toString() == x.toString().split("").reverse().join('') 40 | // }; 41 | 42 | console.log(isPalindrome(121)); 43 | console.log(isPalindrome(10)); 44 | console.log(isPalindrome(-121)); 45 | -------------------------------------------------------------------------------- /algorithm/6.stack-queue/1.valid-parentheses.js: -------------------------------------------------------------------------------- 1 | // 给定一个只包括 '(',')','{','}','[',']' 的字符串 s ,判断字符串是否有效。 2 | // 有效字符串需满足: 3 | // 左括号必须用相同类型的右括号闭合。 4 | // 左括号必须以正确的顺序闭合。 5 | // 示例 1: 6 | // 输入:s = "()" 7 | // 输出:true 8 | // 示例 2: 9 | // 输入:s = "()[]{}" 10 | // 输出:true 11 | // 示例 3: 12 | // 输入:s = "(]" 13 | // 输出:false 14 | // 示例 4: 15 | // 输入:s = "([)]" 16 | // 输出:false 17 | // 示例 5: 18 | // 输入:s = "{[]}" 19 | // 输出:true 20 | /** 21 | * @param {string} s 22 | * @return {boolean} 23 | */ 24 | var isValid = function (s) { 25 | const obj = { 26 | "(": ")", 27 | "{": "}", 28 | "[": "]", 29 | }; 30 | const stack = []; 31 | for (let i = 0; i < s.length; i++) { 32 | if (obj[s[i]]) { 33 | // 如果是左括号,把对应的右括号push进栈 34 | stack.push(obj[s[i]]); 35 | } else { 36 | if (!stack.length || stack.pop() !== s[i]) { 37 | return false 38 | } 39 | } 40 | } 41 | return stack.length ? false : true; 42 | }; 43 | console.log(isValid("()")) 44 | console.log(isValid("()[]{}")) 45 | console.log(isValid("(]")) 46 | console.log(isValid("([)]")) 47 | console.log(isValid("{[]}")) 48 | -------------------------------------------------------------------------------- /algorithm/6.stack-queue/2.daily-temperatures.js: -------------------------------------------------------------------------------- 1 | // 给定一个整数数组 temperatures ,表示每天的温度,返回一个数组 answer ,其中 answer[i] 是指在第 i 天之后,才会有更高的温度。如果气温在这之后都不会升高,请在该位置用 0 来代替。 2 | // 示例 1: 3 | // 输入: temperatures = [73,74,75,71,69,72,76,73] 4 | // 输出: [1,1,4,2,1,1,0,0] 5 | // 示例 2: 6 | // 输入: temperatures = [30,40,50,60] 7 | // 输出: [1,1,1,0] 8 | // 示例 3: 9 | // 输入: temperatures = [30,60,90] 10 | // 输出: [1,1,0] 11 | /** 12 | * @param {number[]} temperatures 13 | * @return {number[]} 14 | */ 15 | var dailyTemperatures = function (temperatures) { 16 | const len = temperatures.length; 17 | const result = new Array(len).fill(0); 18 | const stack = []; 19 | for (let i = 0; i < len; i++) { 20 | while ( 21 | stack.length && 22 | temperatures[i] > temperatures[stack[stack.length - 1]] 23 | ) { 24 | const pop = stack.pop(); 25 | result[pop] = i - pop; 26 | } 27 | stack.push(i); 28 | } 29 | return result; 30 | }; 31 | 32 | console.log(dailyTemperatures([73, 74, 75, 71, 69, 72, 76, 73])); 33 | console.log(dailyTemperatures([30, 40, 50, 60])); 34 | console.log(dailyTemperatures([30, 60, 90])); 35 | console.log(dailyTemperatures([90, 60, 30, 10])); 36 | -------------------------------------------------------------------------------- /algorithm/6.stack-queue/3.min-stack.js: -------------------------------------------------------------------------------- 1 | // 设计一个支持 push ,pop ,top 操作,并能在常数时间内检索到最小元素的栈。 2 | // 实现 MinStack 类: 3 | // MinStack() 初始化堆栈对象。 4 | // void push(int val) 将元素val推入堆栈。 5 | // void pop() 删除堆栈顶部的元素。 6 | // int top() 获取堆栈顶部的元素。 7 | // int getMin() 获取堆栈中的最小元素。 8 | // 示例 1: 9 | // 输入: 10 | // ["MinStack","push","push","push","getMin","pop","top","getMin"] 11 | // [[],[-2],[0],[-3],[],[],[],[]] 12 | // 输出: 13 | // [null,null,null,null,-3,null,0,-2] 14 | // 解释: 15 | // MinStack minStack = new MinStack(); 16 | // minStack.push(-2); 17 | // minStack.push(0); 18 | // minStack.push(-3); 19 | // minStack.getMin(); --> 返回 -3. 20 | // minStack.pop(); 21 | // minStack.top(); --> 返回 0. 22 | // minStack.getMin(); --> 返回 -2. 23 | var MinStack = function () { 24 | this.stack = []; 25 | this.stack2 = []; // stack2就是用来记录最小值的 26 | }; 27 | MinStack.prototype.push = function (val) { 28 | this.stack.push(val); 29 | // 如果stack2中没有值,或者栈顶元素大于等于val,push到stack2 30 | if (!this.stack2.length || this.stack2[this.stack2.length - 1] >= val) { 31 | this.stack2.push(val); 32 | } 33 | }; 34 | MinStack.prototype.pop = function () { 35 | const val = this.stack.pop(); 36 | if (this.stack2[this.stack2.length - 1] === val) { 37 | this.stack2.pop(); 38 | } 39 | }; 40 | MinStack.prototype.top = function () { 41 | return this.stack[this.stack.length - 1]; 42 | }; 43 | MinStack.prototype.getMin = function () { 44 | return this.stack2[this.stack2.length - 1]; 45 | }; 46 | const minStack = new MinStack(); 47 | minStack.push(-2); 48 | minStack.push(0); 49 | minStack.push(-3); 50 | console.log(minStack.getMin()); // --> 返回 -3. 51 | minStack.pop(); 52 | console.log(minStack.top()); // --> 返回 0. 53 | console.log(minStack.getMin()); // --> 返回 -2. 54 | -------------------------------------------------------------------------------- /algorithm/6.stack-queue/4.implement-queue-using-stacks.js: -------------------------------------------------------------------------------- 1 | // 请你仅使用两个栈实现先入先出队列。队列应当支持一般队列支持的所有操作(push、pop、peek、empty): 2 | // 实现 MyQueue 类: 3 | // void push(int x) 将元素 x 推到队列的末尾 4 | // int pop() 从队列的开头移除并返回元素 5 | // int peek() 返回队列开头的元素 6 | // boolean empty() 如果队列为空,返回 true ;否则,返回 false 7 | // 说明: 8 | // 你 只能 使用标准的栈操作 —— 也就是只有 push to top, peek/pop from top, size, 和 is empty 操作是合法的。 9 | // 你所使用的语言也许不支持栈。你可以使用 list 或者 deque(双端队列)来模拟一个栈,只要是标准的栈操作即可。 10 | // 示例 1: 11 | // 输入: 12 | // ["MyQueue", "push", "push", "peek", "pop", "empty"] 13 | // [[], [1], [2], [], [], []] 14 | // 输出: 15 | // [null, null, null, 1, 1, false] 16 | // 解释: 17 | // MyQueue myQueue = new MyQueue(); 18 | // myQueue.push(1); // queue is: [1] 19 | // myQueue.push(2); // queue is: [1, 2] (leftmost is front of the queue) 20 | // myQueue.peek(); // return 1 21 | // myQueue.pop(); // return 1, queue is [2] 22 | // myQueue.empty(); // return false 23 | // 提示: 24 | // 1 <= x <= 9 25 | // 最多调用 100 次 push、pop、peek 和 empty 26 | // 假设所有操作都是有效的 (例如,一个空的队列不会调用 pop 或者 peek 操作) 27 | // 进阶: 28 | // 你能否实现每个操作均摊时间复杂度为 O(1) 的队列?换句话说,执行 n 个操作的总时间复杂度为 O(n) ,即使其中一个操作可能花费较长时间。 29 | 30 | var MyQueue = function () { 31 | this.stack1 = []; 32 | this.stack2 = []; 33 | }; 34 | MyQueue.prototype.push = function (x) { 35 | this.stack1.push(x); 36 | }; 37 | MyQueue.prototype.pop = function () { 38 | if (!this.stack2.length) { 39 | while (this.stack1.length) { 40 | this.stack2.push(this.stack1.pop()); 41 | } 42 | } 43 | return this.stack2.pop(); 44 | }; 45 | MyQueue.prototype.peek = function () { 46 | if (!this.stack2.length) { 47 | while (this.stack1.length) { 48 | this.stack2.push(this.stack1.pop()); 49 | } 50 | } 51 | return this.stack2[this.stack2.length - 1]; 52 | }; 53 | MyQueue.prototype.empty = function () { 54 | return !this.stack1.length && !this.stack2.length; 55 | }; 56 | const myQueue = new MyQueue(); 57 | myQueue.push(1); // queue is: [1] 58 | myQueue.push(2); // queue is: [1, 2] (leftmost is front of the queue) 59 | console.log(myQueue.peek()); // return 1 60 | console.log(myQueue.pop()); // return 1, queue is [2] 61 | console.log(myQueue.empty()); // return false 62 | console.log(myQueue.pop()); // return 2, queue is [] 63 | console.log(myQueue.empty()); // return false 64 | -------------------------------------------------------------------------------- /algorithm/6.stack-queue/5.sliding-window-maximum.js: -------------------------------------------------------------------------------- 1 | // 给你一个整数数组 nums,有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k 个数字。滑动窗口每次只向右移动一位。 2 | // 返回 滑动窗口中的最大值 。 3 | // 示例 1: 4 | // 输入:nums = [1,3,-1,-3,5,3,6,7], k = 3 5 | // 输出:[3,3,5,5,6,7] 6 | // 解释: 7 | // 滑动窗口的位置 最大值 8 | // --------------- ----- 9 | // [1 3 -1] -3 5 3 6 7 3 10 | // 1 [3 -1 -3] 5 3 6 7 3 11 | // 1 3 [-1 -3 5] 3 6 7 5 12 | // 1 3 -1 [-3 5 3] 6 7 5 13 | // 1 3 -1 -3 [5 3 6] 7 6 14 | // 1 3 -1 -3 5 [3 6 7] 7 15 | // 示例 2: 16 | // 输入:nums = [1], k = 1 17 | // 输出:[1] 18 | 19 | /** 20 | * @param {number[]} nums 21 | * @param {number} k 22 | * @return {number[]} 23 | */ 24 | var maxSlidingWindow = function (nums, k) { 25 | const len = nums.length; 26 | const queue = []; // 维持一个递减的队列,存放的是索引 27 | const result = []; 28 | for (let i = 0; i < len; i++) { 29 | while (queue.length && nums[i] > nums[queue[queue.length - 1]]) { 30 | queue.pop(); 31 | } 32 | queue.push(i); 33 | while (queue.length && i >= k + queue[0]) { 34 | queue.shift(); 35 | } 36 | if (i >= k - 1) { 37 | result.push(nums[queue[0]]); 38 | } 39 | } 40 | return result; 41 | }; 42 | console.log(maxSlidingWindow([1, 3, -1, -3, 5, 3, 6, 7], 3)); 43 | console.log(maxSlidingWindow([1], 1)); 44 | console.log(maxSlidingWindow([9, 8, 7, 6, 5, 4], 3)); 45 | -------------------------------------------------------------------------------- /algorithm/README.md: -------------------------------------------------------------------------------- 1 | ### 链表的插入/删除效率较高,而访问效率较低;数组的访问效率较高,而插入效率较低。 2 | 3 | ### 题目 4 | 5 | 1. 两数之和 6 | 1. 合并两个有序数组 7 | -------------------------------------------------------------------------------- /article.md: -------------------------------------------------------------------------------- 1 | # 参考资料 2 | > 3 | ### 面经 4 | 5 | * [怎样判断面试者是否有扎实的前端基础? - 阿里巴巴淘系技术](https://juejin.cn/post/7033615049721806879#heading-10) 6 | * [金三银四,我先面为敬了(腾讯、美团、商汤科技等七家大厂面试有感) - 小羊快码](https://mp.weixin.qq.com/s/-8yQ6sMsU3zwzf4MgzhBFw) 7 | * []() 8 | * []() 9 | * []() 10 | * []() 11 | * []() 12 | * []() 13 | * []() 14 | * []() 15 | -------------------------------------------------------------------------------- /coding/README.md: -------------------------------------------------------------------------------- 1 | ### 手撸代码 2 | 3 | * 用 setTimeout 实现 setInterval 4 | * 实现一个深拷贝 5 | * 防抖、节流 6 | * 数组扁平化 7 | * 大数相加 -------------------------------------------------------------------------------- /coding/doc/0.reg.js: -------------------------------------------------------------------------------- 1 | // 1、替换日期格式,MM.dd/yyyy 替换成 yyyy-MM-dd 2 | // 2、数字价格千分位分割,将 123456789 变成123,456,789 3 | 4 | 5 | const str1 = "03.25/2022"; 6 | function transform(str) { 7 | return str.replace(/^([\d]{2})\.([\d]{2})\/([\d]{4})$/, '$3-$1-$2') 8 | } 9 | console.log(transform(str1)) 10 | 11 | const str2 = '123456789000' 12 | function f2(str) { 13 | // 前向负查找 (?!) 14 | // 前向查找 (?=) 15 | const reg = /(?!^)(?=([\d]{3})+$)/g 16 | return str.replace(reg, ',') 17 | } 18 | console.log(f2(str2)) 19 | 20 | // function formatNum(num) { 21 | // // (num.toFixed(2) + '') 保留两位小数 22 | // return num && num.toString().replace(/\d{1,3}(?=(\d{3})+(\.\d*)?$)/g, '$&,') 23 | // } 24 | // console.log(formatNum(-123456789.123456)) -------------------------------------------------------------------------------- /coding/doc/1.settimeout-setinterval.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 1、用setTimeout实现setInterval 3 | * 2、根据实施执行情况,动态调整setTimeout的delay 4 | * 3、支持参数接收 args 5 | * 4、支持清除定时器 6 | * 5、支持多个定时器 7 | * @param {*} callback 8 | * @param {*} delay 9 | * @param {...any} args 10 | * @returns 11 | */ 12 | const globalTimerId = {}; 13 | function myInterval(callback, delay, ...args) { 14 | let offset = 0; 15 | let timerId = `${Math.random()}_${Date.now()}`; 16 | let startTime = Date.now(); 17 | let realDelay = delay; 18 | let count = 1; 19 | const fn = () => { 20 | console.log("实际执行时间", Date.now() - startTime); 21 | callback(...args); 22 | // 时间偏差 = 当前时间 - 理论执行时间 23 | // 每执行一次,count + 1 24 | offset = Date.now() - (startTime + count++ * delay); 25 | realDelay = delay - offset; 26 | globalTimerId[timerId] = setTimeout(fn, Math.max(realDelay, 0)); 27 | }; 28 | globalTimerId[timerId] = setTimeout(fn, realDelay); 29 | // 记录timerId,方便清除定时器 30 | return timerId; 31 | } 32 | // 清除定时器方法 33 | function myClearInterval(timerId) { 34 | clearTimeout(globalTimerId[timerId]); 35 | } 36 | function f1() { 37 | const hours = new Date().getHours(); 38 | const minutes = new Date().getMinutes(); 39 | const seconds = new Date().getSeconds(); 40 | console.log(`${hours}:${minutes}:${seconds.toString().padStart("2", "0")}`); 41 | } 42 | const timer1 = myInterval(f1, 1000); 43 | // const timer2 = myInterval(f1, 3000); 44 | setTimeout(() => { 45 | myClearInterval(timer1); 46 | }, 60000); 47 | -------------------------------------------------------------------------------- /coding/doc/10.promise-application.js: -------------------------------------------------------------------------------- 1 | const MyPromise = require("./10.promise-resource"); 2 | 3 | // MyPromise.resolve() 4 | // .then(() => { 5 | // console.log(0); 6 | // return MyPromise.resolve(4); 7 | // }) 8 | // .then((res) => { 9 | // console.log(res); 10 | // }); 11 | 12 | // const promise = new MyPromise((resolve, reject) => { 13 | // resolve("success"); 14 | // // throw new Error('执行器错误') 15 | // }); 16 | 17 | // // 第一个then方法中的错误要在第二个then方法中捕获到 18 | // promise 19 | // .then( 20 | // (value) => { 21 | // console.log(1); 22 | // console.log("resolve", value); 23 | // throw new Error("throw error"); 24 | // }, 25 | // (reason) => { 26 | // console.log(2); 27 | // console.log(reason.message); 28 | // } 29 | // ) 30 | // .then( 31 | // (value) => { 32 | // console.log(3); 33 | // console.log(value); 34 | // }, 35 | // (reason) => { 36 | // console.log(4); 37 | // console.log(reason.message); 38 | // } 39 | // ); 40 | 41 | // const promise = new MyPromise((resolve, reject) => { 42 | // resolve("success"); 43 | // }); 44 | 45 | // // 这个时候将promise定义一个p1,然后返回的时候返回p1这个promise 46 | // const p1 = promise.then((value) => { 47 | // console.log("resolve", value); 48 | // // 如果 then 方法返回的是自己的 Promise 对象,则会发生循环调用,这个时候程序会报错 49 | // return p1; 50 | // }); 51 | 52 | // console.log("p1", p1, p1 === promise); 53 | 54 | // // 运行的时候会走reject 55 | 56 | // p1.then( 57 | // (value) => { 58 | // console.log(2); 59 | // console.log("resolve", value); 60 | // }, 61 | // (reason) => { 62 | // console.log(3); 63 | // console.log(reason.message); 64 | // } 65 | // ); 66 | // const promise = new MyPromise((resolve, reject) => { 67 | // // resolve("666"); 68 | // setTimeout(() => { 69 | // resolve("666"); 70 | // // reject("999"); 71 | // }, 1000); 72 | // }); 73 | // promise.then(res => { 74 | // console.log('+++1') 75 | // }).then(res => { 76 | // console.log('+++2') 77 | // }).then(res => { 78 | // console.log('+++3') 79 | // }) 80 | // promise.then(value => { 81 | // console.log('resolve1', value) 82 | // }).then(res => { 83 | // console.log(9) 84 | // }) 85 | // promise.then(value => { 86 | // console.log('resolve2', value) 87 | // }) 88 | // promise.then(value => { 89 | // console.log('resolve3', value) 90 | // }) 91 | 92 | // const promise = new MyPromise((resolve, reject) => { 93 | // resolve("success"); 94 | // // throw new Error('执行器错误') 95 | // }); 96 | 97 | // // 第一个then方法中的错误要在第二个then方法中捕获到 98 | // promise 99 | // .then( 100 | // (value) => { 101 | // console.log("x", value); 102 | // throw new Error("then error"); 103 | // }, 104 | // (reason) => { 105 | // console.log('y'); 106 | // console.log(reason.message); 107 | // } 108 | // ) 109 | // .then( 110 | // (value) => { 111 | // console.log('m'); 112 | // console.log(value); 113 | // }, 114 | // (reason) => { 115 | // console.log('n'); 116 | // console.log(reason.message); 117 | // } 118 | // ); 119 | 120 | MyPromise.resolve() 121 | .then(() => { 122 | console.log(0); 123 | return MyPromise.resolve(4); 124 | }) 125 | .then((res) => { 126 | console.log(res); 127 | }); 128 | 129 | // Js引擎为了让microtask尽快的输出,做了一些优化 130 | // 连续的多个then(3个)如果没有reject或者resolve会交替执行then而不至于让一个堵太久完成用户无响应 131 | // 不单单v8这样其他引擎也是这样,因为其实promise内部状态已经结束了。这块在v8源码里有完整的体现 132 | 133 | MyPromise.resolve() 134 | .then(() => { 135 | console.log(1); 136 | }) 137 | .then(() => { 138 | console.log(2); 139 | }) 140 | .then(() => { 141 | console.log(3); 142 | }) 143 | .then(() => { 144 | console.log(5); 145 | }) 146 | .then(() => { 147 | console.log(6); 148 | }); 149 | -------------------------------------------------------------------------------- /coding/doc/10.promise-doc.js: -------------------------------------------------------------------------------- 1 | Promise.resolve().then(() => { 2 | console.log(0); 3 | return Promise.resolve(4); 4 | }).then((res) => { 5 | console.log(res) 6 | }) 7 | 8 | Promise.resolve().then(() => { 9 | console.log(1); 10 | }).then(() => { 11 | console.log(2); 12 | }).then(() => { 13 | console.log(3); 14 | }).then(() => { 15 | console.log(5); 16 | }).then(() =>{ 17 | console.log(6); 18 | }) 19 | 20 | // Promise 是一个类,在执行这个类的时候会传入一个执行器,这个执行器会立即执行 21 | // Promise 会有三种状态:1、Pending 等待 2、Fulfilled 完成 3、Rejected 失败 22 | 23 | // 状态只能由 Pending --> Fulfilled 或者 Pending --> Rejected,且一但发生改变便不可二次修改; 24 | // Promise 中使用 resolve 和 reject 两个函数来更改状态; 25 | // then 方法内部做但事情就是状态判断 26 | 27 | // 如果状态是成功,调用成功回调函数 28 | // 如果状态是失败,调用失败回调函数 29 | 30 | // 1、基础架构:class + executor + resolve + reject + then 31 | // 2、异步操作 32 | // 3、then方法多次调用,多次添加 33 | // 4、实现链式调用 34 | // 5、then方法链式调用识别Promise是否返回自己 35 | // 6、捕获错误 36 | // 7、参考 fulfilled 状态下的处理方式,对 rejected 和 pending 状态进行改造 37 | // 8、then 中的参数变为可选 38 | 39 | 40 | -------------------------------------------------------------------------------- /coding/doc/10.promise-question.js: -------------------------------------------------------------------------------- 1 | // 详细说明 https://www.zhihu.com/question/453677175 2 | const MyPromise = require("./10.promise-resource"); 3 | 4 | MyPromise.resolve() // MyPromise.resolve() 返回 「Promise { undefined }」。 5 | .then(() => { 6 | console.log(0); 7 | // MyPromise 产生了 2 次微任务 8 | return MyPromise.resolve(4); 9 | // x.then(resolve, reject); 10 | }) 11 | .then((res) => { 12 | console.log(res); 13 | }); 14 | // 第二个 then 发现 promise0 还在 pending,因此不能直接 enqueue 新任务 15 | // 而是将包含 console.log(res) 回调追加到 promise0 的 PromiseFulfillReactions 列表尾部 16 | // 并返回新的「Promise { }」(设为 promiseRes)(该返回值在代码中被丢弃,但不影响整个过程) 17 | 18 | MyPromise.resolve() 19 | .then(() => { 20 | console.log(1); 21 | }) 22 | .then(() => { 23 | console.log(2); 24 | }) 25 | .then(() => { 26 | console.log(3); 27 | }) 28 | .then(() => { 29 | console.log(5); 30 | }) 31 | .then(() => { 32 | console.log(6); 33 | }); 34 | 35 | // v8源码 36 | // 简单说,就是创建 NewPromiseResolveThenableJob,多了一个 microtask 37 | // 运行 NewPromiseResolveThenableJob 又多了一个 microtask 38 | // 这两个 microtask 不执行 JS 代码。 39 | -------------------------------------------------------------------------------- /coding/doc/11.concurrent-request.js: -------------------------------------------------------------------------------- 1 | const urls = [ 2 | "A", 3 | "B", 4 | "C", 5 | "D", 6 | "E", 7 | "F", 8 | "G", 9 | "H", 10 | "I", 11 | "J", 12 | "K", 13 | "L", 14 | "M", 15 | "N", 16 | ]; 17 | function concurrentRequest(requestList, limit) { 18 | const total = requestList.length; 19 | const result = new Array(total); 20 | // 当前请求的索引,要小于请求总数 21 | // 当前正在请求状态的个数,要小于并发限制数量 22 | let curIndex = 0; 23 | let currentNum = 0; 24 | return new Promise((resolve, reject) => { 25 | while (curIndex < limit) { 26 | next() 27 | } 28 | function next() { 29 | if (curIndex < total && currentNum < limit) { 30 | const options = { 31 | url: requestList[curIndex], 32 | index: curIndex, 33 | }; 34 | // 请求之前,将两个索引++ 35 | currentNum++ 36 | curIndex++; 37 | request(options.url).then((value) => { 38 | // 请求回来,当前正在请求状态的个数-- 39 | currentNum--; 40 | // 递归调用 41 | next(); 42 | result[options.index] = value; 43 | // 如果全部完成,resolve 44 | const finishedNum = result.filter((item) => item).length; 45 | if (finishedNum === total) { 46 | resolve(result); 47 | } 48 | }); 49 | } 50 | } 51 | }); 52 | } 53 | // 模拟请求 54 | function request(options) { 55 | const randomWait = Math.ceil(Math.random() * 3) * 1000; 56 | return new Promise((resolve, reject) => { 57 | console.log(`${options} 开始请求 ${randomWait}`); 58 | setTimeout(() => { 59 | console.log(`${options} 请求完成`); 60 | resolve(Date.now()); 61 | }, randomWait); 62 | }); 63 | } 64 | (async function () { 65 | const result = await concurrentRequest(urls, 3); 66 | console.log("结果", result); 67 | })(); 68 | -------------------------------------------------------------------------------- /coding/doc/12.curry.js: -------------------------------------------------------------------------------- 1 | const curry = () => { 2 | let params = []; 3 | const fn = (...args) => { 4 | params = params.concat(args); 5 | return fn; 6 | }; 7 | fn.toString = () => { 8 | return params.reduce((prev, cur) => { 9 | return prev + cur; 10 | }, 0); 11 | }; 12 | return fn; 13 | }; 14 | const add = curry(); 15 | const res = add(2, 3)(1)()(11); 16 | console.log(res.toString()); 17 | -------------------------------------------------------------------------------- /coding/doc/13.remove-duplicates.js: -------------------------------------------------------------------------------- 1 | const arr = [2, 5, 6, 2, 5, 1, 7, 10, "a", "b", "a"]; 2 | 3 | // 方法一: 4 | const newArr1 = [...new Set(arr)]; 5 | // console.log(newArr1); 6 | 7 | // 方法二: 8 | function filterRemoveDuplicates(arr) { 9 | return arr.filter((item, index) => { 10 | return arr.indexOf(item) === index; 11 | }); 12 | } 13 | const newArr2 = filterRemoveDuplicates(arr); 14 | // console.log(newArr2); 15 | 16 | // 方法三: 17 | function reduceRemoveDuplicates(arr) { 18 | return arr.reduce((newArr, cur) => { 19 | if (!newArr.includes(cur)) { 20 | newArr.push(cur); 21 | } 22 | return newArr; 23 | }, []); 24 | } 25 | const newArr3 = reduceRemoveDuplicates(arr); 26 | console.log(newArr3); 27 | -------------------------------------------------------------------------------- /coding/doc/14.event-emit.js: -------------------------------------------------------------------------------- 1 | class MyEventEmitter { 2 | constructor() { 3 | this.events = {}; 4 | } 5 | on(eventName, cb) { 6 | if (this.events[eventName]) { 7 | this.events[eventName].push(cb); 8 | } else { 9 | this.events[eventName] = [cb]; 10 | } 11 | } 12 | emit(eventName, ...args) { 13 | if (this.events[eventName] && this.events[eventName].length) { 14 | this.events[eventName].forEach((fn) => fn.call(this, ...args)); 15 | } 16 | } 17 | off(eventName, cb) { 18 | if (this.events[eventName] && this.events[eventName].length) { 19 | this.events[eventName] = this.events[eventName].filter((fn) => fn !== cb); 20 | } 21 | } 22 | once(eventName, cb) { 23 | // 新建一个函数,将该函数push进events[eventName] 24 | const fn = (...args) => { 25 | cb.call(this, ...args); 26 | // 将自己off掉 27 | this.off(eventName, fn); 28 | }; 29 | this.on(eventName, fn); 30 | } 31 | } 32 | const f1 = (a, b, c) => { 33 | console.log("f1", a, b, c); 34 | }; 35 | const f2 = (a, b, c) => { 36 | console.log("f2", a, b, c); 37 | }; 38 | const f3 = (a, b, c) => { 39 | console.log("f3", a, b, c); 40 | }; 41 | const myEvent = new MyEventEmitter(); 42 | myEvent.on("event1", f1); 43 | myEvent.on("event1", f2); 44 | myEvent.emit("event1", 3, 4, 5); 45 | console.log("++++++++++++"); 46 | myEvent.on("event2", f2); 47 | myEvent.once("event2", f3); 48 | myEvent.emit("event2", 6, 6, 6); 49 | console.log("++++++++++++"); 50 | myEvent.emit("event2", 6, 6, 6); 51 | console.log("++++++++++++"); 52 | myEvent.off("event1", f2); 53 | myEvent.emit("event1", 3, 4, 5); 54 | -------------------------------------------------------------------------------- /coding/doc/15.array-tree.js: -------------------------------------------------------------------------------- 1 | const arr = [ 2 | { id: 2, name: "部门2", pid: 1 }, 3 | { id: 4, name: "部门4", pid: 3 }, 4 | { id: 6, name: "部门6", pid: 2 }, 5 | { id: 3, name: "部门3", pid: 1 }, 6 | { id: 5, name: "部门5", pid: 4 }, 7 | { id: 1, name: "部门1", pid: 0 }, 8 | { id: 7, name: "部门1", pid: 0 }, 9 | ]; 10 | function arrToTree(arr) { 11 | const map = {}; 12 | const result = []; 13 | for (const item of arr) { 14 | const { id, pid } = item; 15 | map[id] = { 16 | ...item, 17 | children: map[id] !== undefined ? map[id] : [], 18 | }; 19 | const mapItem = map[id]; 20 | if (pid === 0) { 21 | result.push(mapItem); 22 | } else { 23 | if (map[pid]) { 24 | map[pid].children.push(mapItem); 25 | } else { 26 | map[pid] = { 27 | children: [mapItem], 28 | }; 29 | } 30 | } 31 | } 32 | return result; 33 | } 34 | console.dir(arrToTree(arr), { depth: null }); 35 | 36 | const tree = { 37 | id: 1, 38 | name: "部门1", 39 | pid: 0, 40 | children: [ 41 | { 42 | id: 2, 43 | name: "部门2", 44 | pid: 1, 45 | children: [], 46 | }, 47 | { 48 | id: 3, 49 | name: "部门3", 50 | pid: 1, 51 | children: [ 52 | { 53 | id: 4, 54 | name: "部门4", 55 | pid: 3, 56 | children: [ 57 | { 58 | id: 5, 59 | name: "部门5", 60 | pid: 4, 61 | children: [], 62 | }, 63 | ], 64 | }, 65 | ], 66 | }, 67 | ], 68 | }; 69 | /** 70 | * 树形结构转数组 71 | * 使用队列的形式 72 | * 有 children push 进队列中,直到队列清空 73 | * @param {*} tree 74 | * @returns 75 | */ 76 | function treeToArr(tree) { 77 | const res = []; 78 | const queue = [tree]; 79 | while (queue.length) { 80 | const item = queue.shift(); 81 | const { id, name, pid, children } = item; 82 | res.push({ 83 | id, 84 | name, 85 | pid, 86 | }); 87 | while (children && children.length) { 88 | queue.push(children.shift()); 89 | } 90 | } 91 | return res; 92 | } 93 | console.log(treeToArr(tree)); -------------------------------------------------------------------------------- /coding/doc/16.xhr.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 9 | 10 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /coding/doc/17.lazyman.js: -------------------------------------------------------------------------------- 1 | // 核心: 2 | // 1、taskList队列 3 | // 2、next方法 4 | // 3、return this 5 | // 4、异步 6 | class Lazy { 7 | constructor(name) { 8 | this.taskList = []; 9 | const task = () => { 10 | console.log(`Hi, This is ${name}`); 11 | this.next() 12 | }; 13 | this.taskList.push(task); 14 | setTimeout(() => { 15 | this.next(); 16 | }, 0); 17 | } 18 | next = () => { 19 | const task = this.taskList.shift(); 20 | task && task(); 21 | }; 22 | sleep = (time) => { 23 | const task = () => { 24 | setTimeout(() => { 25 | console.log(`Wake up after ${time}`); 26 | this.next(); 27 | }, time * 1000); 28 | }; 29 | this.taskList.push(task); 30 | return this; 31 | }; 32 | eat = (name) => { 33 | const task = () => { 34 | console.log(`Eat ${name}`); 35 | this.next(); 36 | }; 37 | this.taskList.push(task); 38 | return this; 39 | }; 40 | sleepFirst = (time) => { 41 | const task = () => { 42 | setTimeout(() => { 43 | console.log(`Wake up after ${time}`); 44 | this.next(); 45 | }, time * 1000); 46 | }; 47 | this.taskList.unshift(task); 48 | return this; 49 | }; 50 | } 51 | 52 | function LazyMan(name) { 53 | return new Lazy(name); 54 | } 55 | 56 | // class _LazyMan { 57 | // constructor(name) { 58 | // this.taskList = []; 59 | // const task = () => { 60 | // console.log(`Hi! This is ${name}!`); 61 | // this.next(); 62 | // }; 63 | // this.taskList.push(task); 64 | // setTimeout(() => { 65 | // this.next(); 66 | // }, 0); 67 | // } 68 | // next = () => { 69 | // const task = this.taskList.shift(); 70 | // task && task(); 71 | // }; 72 | // sleep = (time) => { 73 | // const task = () => { 74 | // setTimeout(() => { 75 | // console.log(`Wake up after ${time}`); 76 | // this.next(); 77 | // }, time * 1000); 78 | // }; 79 | // this.taskList.push(task); 80 | // return this; 81 | // }; 82 | // eat = (name) => { 83 | // const task = () => { 84 | // console.log(`Eat ${name}`); 85 | // this.next(); 86 | // }; 87 | // this.taskList.push(task); 88 | // return this; 89 | // }; 90 | // sleepFirst = (time) => { 91 | // const task = () => { 92 | // setTimeout(() => { 93 | // console.log(`Wake up after ${time}`); 94 | // this.next(); 95 | // }, time * 1000); 96 | // }; 97 | // this.taskList.unshift(task); 98 | // return this; 99 | // }; 100 | // } 101 | // function LazyMan(name) { 102 | // return new _LazyMan(name); 103 | // } 104 | 105 | // LazyMan("Hank"); 106 | // LazyMan("Hank").sleep(10).eat("dinner"); 107 | // LazyMan('Hank').eat('dinner').eat('supper') 108 | LazyMan("Hank").sleepFirst(5).eat("supper"); 109 | -------------------------------------------------------------------------------- /coding/doc/18.repeat.js: -------------------------------------------------------------------------------- 1 | // 使用JS实现一个repeat方法 2 | // function repeat(fn, times, wait) {} 3 | // const repeatFunc = repeat(alert, 4, 3000); 4 | // 调用这个repeatFunc('Hello World'),会alert4次Hello World,每次间隔3秒 5 | const repeatFunc = repeat(console.log, 4, 1000); 6 | repeatFunc("Hello World"); 7 | 8 | function repeat(fn, times, wait) { 9 | return (message) => { 10 | fn(message); 11 | let interval = setInterval(() => { 12 | fn(message); 13 | if (--times === 1) clearInterval(interval); 14 | }, wait); 15 | }; 16 | } 17 | -------------------------------------------------------------------------------- /coding/doc/19.width:height.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 22 | 23 | 24 |
25 |
26 |
27 | 28 | 29 | -------------------------------------------------------------------------------- /coding/doc/2.deepClone.js: -------------------------------------------------------------------------------- 1 | let obj = { 2 | url: "/api/list", 3 | method: "GET", 4 | cache: false, 5 | timeout: 1000, 6 | key: Symbol("KEY"), 7 | big: 10n, 8 | n: null, 9 | u: undefined, 10 | headers: { 11 | "Content-Type": "application/json", 12 | post: { 13 | "X-Token": "xxx", 14 | }, 15 | }, 16 | arr: [10, 20, 30], 17 | reg: /^\d+$/, 18 | time: new Date(), 19 | fn: function () { 20 | console.log(this); 21 | }, 22 | err: new Error("xxx"), 23 | }; 24 | obj.obj = obj; 25 | /** 26 | * 深拷贝 27 | * @param {*} obj 要拷贝的对象 28 | * @param {*} hash 用来存储拷贝过的对象,解决循环引用的问题 29 | * @returns 30 | */ 31 | function deepClone(obj, hash = new WeakMap()) { 32 | // 1、null 33 | // 2、typeof不为 object 34 | // 3、Error 35 | if (obj === null || typeof obj !== "object" || obj instanceof Error) 36 | return obj; 37 | // Date类型 38 | if (obj instanceof Date) return new Date(obj); 39 | // 正则类型 40 | if (obj instanceof RegExp) return new RegExp(obj); 41 | // 查看hash中,是否存在已拷贝的对象,解决循环应用的问题 42 | if (hash.get(obj)) return hash.get(obj); 43 | // 对象+数组,使用 new obj.constructor 实现 44 | const newObj = new obj.constructor(); 45 | // 使用 WeakMap 保存 46 | hash.set(obj, newObj); 47 | for (const key in obj) { 48 | if (Object.hasOwnProperty.call(obj, key)) { 49 | // 递归拷贝,将hash传进去 50 | newObj[key] = deepClone(obj[key], hash); 51 | } 52 | } 53 | // 返回通过构造函数新建的对象 54 | return newObj; 55 | } 56 | console.log(deepClone(obj)); 57 | -------------------------------------------------------------------------------- /coding/doc/20.jsonp.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/miracle90/Interview/82ecfce0df0d6a1fc7fa6cd40e5348ac317ca77a/coding/doc/20.jsonp.js -------------------------------------------------------------------------------- /coding/doc/21.array-sort.js: -------------------------------------------------------------------------------- 1 | const arr = [1, 0, 3, 9, -2, 5, 99, -90, 100, 8, 9]; 2 | 3 | console.log(mergeSort(arr)); 4 | 5 | function mergeSort(arr) { 6 | let len = arr.length; 7 | if (len <= 1) { 8 | return arr; 9 | } 10 | let midNumber = Math.floor(len / 2); 11 | let leftArr = mergeSort(arr.slice(0, midNumber)); 12 | let rightArr = mergeSort(arr.slice(midNumber, len)); 13 | arr = mergeArr(leftArr, rightArr); 14 | return arr; 15 | } 16 | function mergeArr(arr1, arr2) { 17 | let i = (j = 0), 18 | l1 = arr1.length, 19 | l2 = arr2.length, 20 | result = []; 21 | while (i < l1 && j < l2) { 22 | if (arr1[i] < arr2[j]) { 23 | result.push(arr1[i]); 24 | i++; 25 | } else { 26 | result.push(arr2[j]); 27 | j++; 28 | } 29 | } 30 | if (i < l1) { 31 | return result.concat(arr1.slice(i)) 32 | } else { 33 | return result.concat(arr2.slice(j)) 34 | } 35 | } 36 | 37 | // function mergeSort(arr) { 38 | // const len = arr.length; 39 | // // 处理边界情况 40 | // if (len <= 1) { 41 | // return arr; 42 | // } 43 | // // 计算分割点 44 | // const mid = Math.floor(len / 2); 45 | // // 递归分割左子数组,然后合并为有序数组 46 | // const leftArr = mergeSort(arr.slice(0, mid)); 47 | // // 递归分割右子数组,然后合并为有序数组 48 | // const rightArr = mergeSort(arr.slice(mid, len)); 49 | // // 合并左右两个有序数组 50 | // arr = mergeArr(leftArr, rightArr); 51 | // // 返回合并后的结果 52 | // return arr; 53 | // } 54 | // function mergeArr(arr1, arr2) { 55 | // // 初始化两个指针,分别指向 arr1 和 arr2 56 | // let i = 0, 57 | // j = 0; 58 | // // 初始化结果数组 59 | // const res = []; 60 | // // 缓存arr1的长度 61 | // const len1 = arr1.length; 62 | // // 缓存arr2的长度 63 | // const len2 = arr2.length; 64 | // // 合并两个子数组 65 | // while (i < len1 && j < len2) { 66 | // if (arr1[i] < arr2[j]) { 67 | // res.push(arr1[i]); 68 | // i++; 69 | // } else { 70 | // res.push(arr2[j]); 71 | // j++; 72 | // } 73 | // } 74 | // // 若其中一个子数组首先被合并完全,则直接拼接另一个子数组的剩余部分 75 | // if (i < len1) { 76 | // return res.concat(arr1.slice(i)); 77 | // } else { 78 | // return res.concat(arr2.slice(j)); 79 | // } 80 | // } 81 | -------------------------------------------------------------------------------- /coding/doc/22.inherit.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 原型继承 3 | * 缺点: 4 | * 父类构造函数中的引用类型(比如对象/数组),会被所有子类实例共享 5 | * 其中一个子类实例进行修改,会导致所有其他子类实例的这个值都会改变 6 | */ 7 | function Parent1() { 8 | this.hobbies = ['1', '2', '3']; 9 | } 10 | Parent1.prototype.getHobbies = function () { 11 | return this.hobbies; 12 | }; 13 | function Child1() {} 14 | Child1.prototype = new Parent1(); 15 | var child1 = new Child1(); 16 | child1.hobbies.push('4') 17 | console.log(child1.getHobbies()); 18 | var child2 = new Child1(); 19 | console.log(child2.getHobbies()); 20 | /** 21 | * 构造函数继承 22 | * 优点: 23 | * 原型链继承中构造函数引用类型共享的问题,同时可以向构造函数传参(通过call传参) 24 | * 缺点: 25 | * 所有方法都定义在构造函数中,每次都需要重新创建 26 | * 对比原型链继承的方式,方法直接写在原型上,子类创建时不需要重新创建方法 27 | */ 28 | function Parent2() { 29 | this.hobbies = ["a"]; 30 | } 31 | function Child2() { 32 | Parent2.call(this); 33 | } 34 | var child3 = new Child2(); 35 | child3.hobbies.push("b"); 36 | console.log(child3.hobbies); 37 | var child4 = new Child2(); 38 | console.log(child4.hobbies); 39 | /** 40 | * 组合继承 41 | * 优点: 42 | * 1、解决了构造函数引用类型的问题 43 | * 2、避免了方法会被创建多次的问题 44 | * 缺点: 45 | * 1、父类构造函数被调用了两次 46 | * 2、子类实例以及子类原型对象上存在多余属性的问题 47 | */ 48 | function Parent3() { 49 | this.hobbies = ["a"]; 50 | } 51 | Parent3.prototype.getHobbies = function () { 52 | return this.hobbies; 53 | }; 54 | function Child3() { 55 | Parent3.call(this); 56 | } 57 | Child3.prototype = new Parent3(); 58 | Child3.prototype.constructor = Child3; // 之前的构造函数被重写了,改回来 59 | /** 60 | * 寄生组合继承 61 | * 优点: 62 | * 1、解决了组合继承中的构造函数调用两次 63 | * 2、构造函数引用类型共享 64 | * 3、原型对象上存在多余属性的问题 65 | */ 66 | function Parent4() { 67 | this.name = "parent4"; 68 | } 69 | Parent4.prototype.getName = function () { 70 | return this.name; 71 | }; 72 | function Child4() { 73 | Parent4.call(this); 74 | } 75 | inherit(Child4, Parent4); 76 | function inherit(child, parent) { 77 | // 复制了一份父类的原型对象 78 | var prototype = object(parent.prototype); 79 | prototype.constructor = child; // 把 prototype 的 constructor 指向 Child 80 | child.prototype = prototype; 81 | } 82 | // 复制了一份父类的原型对象 83 | function object(prototype) { 84 | function F() {} 85 | F.prototype = prototype; 86 | return new F(); 87 | } 88 | -------------------------------------------------------------------------------- /coding/doc/3.debounce-throttle.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 9 | 10 | 11 |
12 | 107 | 108 | 109 | -------------------------------------------------------------------------------- /coding/doc/4.flatten.js: -------------------------------------------------------------------------------- 1 | const arr = [ 2 | [1, 2, 2], 3 | [3, "4", 5, 5], 4 | [6, 7, 8, 9, [11, 12, [12, 13, [14]]]], 5 | 10, 6 | ]; 7 | 8 | /** 9 | * 方法一: 10 | * toString().split(',') 11 | * 缺陷:会改变内容的类型,number => string 12 | */ 13 | const res1 = arr.toString().split(","); 14 | console.log(res1); 15 | 16 | /** 17 | * 方法二: 18 | * 判断数组中是否有数组了 19 | * 有的话不断的通过 arr = [].concat(...arr) 解构 20 | */ 21 | function flatten_some(arr) { 22 | while (arr.some((item) => Array.isArray(item))) { 23 | arr = [].concat(...arr); 24 | } 25 | return arr; 26 | } 27 | const res2 = flatten_some(arr); 28 | console.log(res2); 29 | 30 | /** 31 | * 方法三: 32 | * 使用reduce + 递归 33 | */ 34 | function flatten_reduce(arr) { 35 | return arr.reduce((prev, cur) => { 36 | return prev.concat(Array.isArray(cur) ? flatten_reduce(cur) : cur); 37 | }, []); 38 | } 39 | const res3 = flatten_reduce(arr); 40 | console.log(res3); 41 | -------------------------------------------------------------------------------- /coding/doc/5.big-number-sum.js: -------------------------------------------------------------------------------- 1 | const a = "3782647863278468012934670"; 2 | const b = "23784678091370408971329048718239749083"; 3 | 4 | /** 5 | * 步骤 6 | * 1、取两个中最长的长度 7 | * 2、补0 8 | * 3、初始化t、f、res 9 | * 4、从后往前遍历 10 | * 5、计算两个数字的和为t 11 | * 6、是否进1,Math.floor(t/10) 12 | * 7、拼接字符串 13 | * 8、for循环之后,判断是否要在前头补1 14 | * @param {*} a 15 | * @param {*} b 16 | * @returns 17 | */ 18 | // function bigSum(a, b) { 19 | // // 取两数长度最大值 20 | // const maxLength = Math.max(a.length, b.length); 21 | // // 短的补0 22 | // a = a.padStart(maxLength, "0"); 23 | // b = b.padStart(maxLength, "0"); 24 | // // f:是否进1 25 | // // t:当前两个数字的和 26 | // let t = (f = 0); 27 | // // 要返回的结果 28 | // let res = ""; 29 | // for (let i = maxLength - 1; i >= 0; i--) { 30 | // t = parseInt(a[i]) + parseInt(b[i]) + f; 31 | // f = Math.floor(t / 10); 32 | // res = (t % 10) + res; 33 | // } 34 | // // for循环之后,如果f为1,说明要进1,在res前头补一位 35 | // if (f === 1) { 36 | // res = "1" + "res"; 37 | // } 38 | // return res; 39 | // } 40 | function bigSum(a, b) { 41 | // "00000000000003782647863278468012934670" 42 | // "23784678091370408971329048718239749083" 43 | let maxLength = Math.max(a.length, b.length); 44 | a = a.padStart(maxLength, "0"); 45 | b = b.padStart(maxLength, "0"); 46 | let x, 47 | y = 0, 48 | res = ""; 49 | for (let i = maxLength - 1; i >= 0; i--) { 50 | x = parseInt(a[i]) + parseInt(b[i]) + y; 51 | y = Math.floor(x / 10); 52 | res = (x % 10) + res; 53 | } 54 | if (y === 1) { 55 | res = '1' + res 56 | } 57 | return res 58 | } 59 | console.log(bigSum(a, b)); 60 | console.log(BigInt(a) + BigInt(b) + ''); 61 | console.log(bigSum(a, b) === BigInt(a) + BigInt(b) + '') 62 | -------------------------------------------------------------------------------- /coding/doc/6.checkQQDomian.js: -------------------------------------------------------------------------------- 1 | // 如何判断url中只包含qq.com 2 | // url有很多种形式: 3 | // http://www.qq.com //通过 4 | // http://www.qq.com.cn //不通过 5 | // http://www.qq.com/a/b //通过 6 | // http://www.qq.com?a=1 //通过 7 | // http://www.123qq.com?a=1 //不通过 8 | // http://www.qq.cn?redirect=http://www.qq.com/a 不通过 9 | 10 | function checkQQDomian(url) { 11 | // let reg = /.*(\.qq\.com)($|[\?\/])/; 12 | let reg = /^(http:\/\/[^=]+\.qq\.com)($|[\?\/])/; 13 | return reg.test(url); 14 | } 15 | 16 | const url1 = "http://www.qq.com"; 17 | const url2 = "http://www.qq.com.cn"; 18 | const url3 = "http://www.qq.com/a/b"; 19 | const url4 = "http://www.qq.com?a=1"; 20 | const url5 = "http://www.123qq.com?a=1"; 21 | const url6 = "http://www.qq.cn?redirect=http://www.qq.com/a"; 22 | 23 | console.log(checkQQDomian(url1)); 24 | console.log(checkQQDomian(url2)); 25 | console.log(checkQQDomian(url3)); 26 | console.log(checkQQDomian(url4)); 27 | console.log(checkQQDomian(url5)); 28 | console.log(checkQQDomian(url6)); 29 | -------------------------------------------------------------------------------- /coding/doc/7.compose.js: -------------------------------------------------------------------------------- 1 | // compose是函数式编程中一个非常重要的函数, 2 | // compose的函数作用就是组合函数的,将函数串联起来执行。 3 | // 将多个函数组合起来,一个函数的输出结果是另一个函数的输入函数, 4 | // 一旦第一个函数开始执行,就会像多米诺骨牌一样推导执行 5 | 6 | const greeting = (name) => `hello ${name}`; 7 | const toUpper = (str) => str.toUpperCase(); 8 | const addNumber = (str) => `${str},老铁 666`; 9 | const fn = compose(addNumber, toUpper, greeting); 10 | console.log(fn("sunny")); 11 | 12 | // 这就是compose的使用,主要有以下几点: 13 | // compose参数就是函数,返回也是一个函数 14 | // 除了第一个函数接收参数,其他函数接受的都是上一个函数的返回值 15 | // 所以初始函数的参数是多元的,而其他函数的接收值是一元的 16 | // compose函数可以接收任意的参数,所有的参数都是函数 17 | // 且执行方向是自右向左的,初始函数一定到参数的最右面 18 | function compose(...funcs) { 19 | const len = funcs.length; 20 | let curIndex = len - 1; 21 | let result = null; 22 | return function fn(...args) { 23 | result = funcs[curIndex].call(this, ...args); 24 | if (curIndex > 0) { 25 | curIndex--; 26 | return fn.call(null, result); 27 | } else { 28 | return result; 29 | } 30 | }; 31 | } 32 | // 递归实现 33 | // function compose(...funcs) { 34 | // let len = funcs.length, 35 | // curIndex = len - 1, 36 | // result; 37 | // // 首先compse 返回的是一个函数 38 | // return function fn(...args) { 39 | // // 函数体里就是不断执行args函数,将上一个函数的执行结果作为下一个执行函数的输入参数, 40 | // result = funcs[curIndex].apply(this, args); 41 | // // 递归边界 42 | // if (curIndex > 0) { 43 | // curIndex--; 44 | // // 递归 45 | // return fn.call(null, result); 46 | // } else { 47 | // return result; 48 | // } 49 | // }; 50 | // } 51 | // 迭代实现 52 | // function compose(...fns) { 53 | // let isFirst = true; 54 | // return (...args) => { 55 | // return fns.reduceRight((result, fn) => { 56 | // if (!isFirst) return fn(result); 57 | // isFirst = false; 58 | // return fn(...result); 59 | // }, args); 60 | // }; 61 | // } 62 | -------------------------------------------------------------------------------- /coding/doc/8.new.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 1、首先函数接受不定量的参数,第一个参数为构造函数,接下来的参数被构造函数使用 3 | * 2、然后内部创建一个空对象 obj 4 | * 3、因为 obj 对象需要访问到构造函数原型链上的属性,所以我们通过 setPrototypeOf 将两者联系起来 5 | * 这段代码等同于 obj.__proto__ = Con.prototype 6 | * 4、将 obj 绑定到构造函数上,并且传入剩余的参数 7 | * 5、判断构造函数返回值是否为对象,如果为对象就使用构造函数返回的值,否则使用 obj, 8 | * 这样就实现了忽略构造函数返回的原始值 9 | * @param {*} Con 10 | * @param {...any} args 11 | */ 12 | function create(Con, ...args) { 13 | // 然后内部创建一个空对象 obj 14 | let obj = {}; 15 | // 这段代码等同于 obj.__proto__ = Con.prototype 16 | Object.setPrototypeOf(obj, Con.prototype); 17 | // 将 obj 绑定到构造函数上,并且传入剩余的参数 18 | let result = Con.apply(obj, args); 19 | // 判断构造函数返回值是否为对象,如果为对象就使用构造函数返回的值,否则使用 obj, 20 | return result instanceof Object ? result : obj; 21 | } 22 | -------------------------------------------------------------------------------- /coding/doc/9.call-apply-bind.js: -------------------------------------------------------------------------------- 1 | Function.prototype.myCall = function (context, ...args) { 2 | if (typeof this !== "function") { 3 | throw new TypeError("Error"); 4 | } 5 | // 没传值,设置为window 6 | context = context || window; 7 | // 使用symbol避免命名冲突 8 | const fnSymbol = Symbol("fn"); 9 | // 将方法(this),写在作用域上 10 | context[fnSymbol] = this; 11 | const result = context[fnSymbol](...args); 12 | delete context[fnSymbol]; 13 | return result; 14 | }; 15 | Function.prototype.myApply = function (context, argsList) { 16 | if (typeof this !== "function") { 17 | throw new TypeError("Error"); 18 | } 19 | // 没传值,设置为window 20 | context = context || window; 21 | // 使用symbol避免命名冲突 22 | const fnSymbol = Symbol("fn"); 23 | // 将方法(this),写在作用域上 24 | context[fnSymbol] = this; 25 | const result = context[fnSymbol](...argsList); 26 | delete context[fnSymbol]; 27 | return result; 28 | }; 29 | Function.prototype.myBind = function (context, ...args) { 30 | if (typeof this !== "function") { 31 | throw new TypeError("Error"); 32 | } 33 | context = context || window; 34 | const fnSymbol = Symbol("fn"); 35 | context[fnSymbol] = this; 36 | return function (..._args) { 37 | // 拼接参数ƒ 38 | const result = context[fnSymbol](...args.concat(..._args)); 39 | delete context[fnSymbol]; 40 | return result; 41 | }; 42 | }; 43 | 44 | let p1 = { 45 | name: "Tom", 46 | say(age, hobby, hobby1, hobby2) { 47 | console.log( 48 | `我叫${this.name} 我今年${age} 喜欢${hobby}、${hobby1}、${hobby2} ` 49 | ); 50 | }, 51 | }; 52 | let p2 = { 53 | name: "Bob", 54 | }; 55 | // p1.say.myApply(p2, [18, "唱歌"]); 56 | const f = p1.say.myBind(p2, "32", "搞钱"); 57 | f("happy", "打球"); 58 | -------------------------------------------------------------------------------- /coding/doc/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "promise", 3 | "version": "1.0.0", 4 | "description": "my promise", 5 | "main": "10.promise-practice.js", 6 | "scripts": { 7 | "test": "promises-aplus-tests 10.promise-practice.js" 8 | }, 9 | "author": "ITEM", 10 | "license": "ISC", 11 | "devDependencies": { 12 | "promises-aplus-tests": "^2.1.2" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /coding/doc/server.js: -------------------------------------------------------------------------------- 1 | const http = require('http') 2 | 3 | http.createServer((req, res) => { 4 | console.log(req.url) 5 | console.log(req.headers) 6 | res.setHeader('Access-Control-Allow-Origin', '*') 7 | res.end('hello world') 8 | }).listen(3000, () => { 9 | console.log('server started at 3000') 10 | }) -------------------------------------------------------------------------------- /css/css.md: -------------------------------------------------------------------------------- 1 | # CSS 简答题 2 | 3 | ### CSS 有哪些样式可以给子元素继承 4 | * 可继承的: font-size, font-weight, line-height, color, cursor等 5 | 6 | * 不可继承的一般是**会改变盒子模型**的: display, margin,border,padding,height等 7 | 8 | ### 行内元素有哪些?块级元素有哪些? 空(void)元素有那些 9 | * 行内: input,span,a,img,display: inline的元素 10 | * 块级: p,div,header,footer,aside,article,ul 以及 display: block这些 11 | * void: br,hr 12 | 13 | ### box-sizing常用的属性有哪些? 分别有啥作用? 14 | * content-box(W3C标准盒模型) 15 | > content-box的计算公式会把宽高的定义指向 content 16 | > border和 padding 另外计算 17 | 18 | * border-box(怪异模型) 19 | > border-box的计算公式是总的大小涵盖这三者,content 会缩小,来让给另外两者 20 | 21 | ### 清除浮动的方式有哪些?比较好的是哪一种? 22 | 常用的一般为三种 `.clearfix`,`clear: both`,`overflow: hidden` 23 | 24 | 比较好是 .clearfix,伪元素万金油版本 25 | ```css 26 | .clearfix:after { 27 | visibility: hidden; 28 | display: block; 29 | font-size: 0; 30 | content: " "; 31 | clear: both; 32 | height: 0; 33 | } 34 | ``` 35 | clear: both; // 若是用在同一个容器内相邻元素上,那是贼好的...有时候在容器外就有些问题了, 比如相邻容器的包裹层元素塌陷 36 | overflow: hidden; // 这种若是用在同个容器内,可以形成 BFC避免浮动造成的元素塌陷 37 | 38 | ### 样式权重的优先级 39 | !important > 行内样式 > id > class > tag 40 | 41 | 样式权重可以叠加, 比如 id>class 42 | 43 | 44 | -------------------------------------------------------------------------------- /hand-webpack/loaders/logger1-loader.js: -------------------------------------------------------------------------------- 1 | function loader(source) { 2 | return source + "//logger1"; //let name= 'entry1';//logger2//logger1 3 | } 4 | module.exports = loader; 5 | -------------------------------------------------------------------------------- /hand-webpack/loaders/logger2-loader.js: -------------------------------------------------------------------------------- 1 | function loader(source) { 2 | //let name= 'entry1'; 3 | return source + "//logger2"; //let name= 'entry1';//logger2 4 | } 5 | module.exports = loader; -------------------------------------------------------------------------------- /hand-webpack/loaders/runner.js: -------------------------------------------------------------------------------- 1 | let { runLoaders } = require("loader-runner"); 2 | //let runLoaders = require('./loader-runner') 3 | let path = require("path"); 4 | let fs = require("fs"); 5 | let filePath = path.resolve(__dirname, "src", "index.js"); //入口模块 6 | //定在require方法里的 inline Loader 7 | let request = `!!inline-loader1!inline-loader2!${filePath}`; 8 | //不同的loader并不loader的类型属性,而是你在使用 的使用了什么样的enforce 9 | let rules = [ 10 | { 11 | test: /\.js$/, 12 | use: ["norma-loader1", "normal-loader2"], //普通的loader 13 | }, 14 | { 15 | test: /\.js$/, 16 | enforce: "post", 17 | use: ["post-loader1", "post-loader2"], //post的loader 后置 18 | }, 19 | { 20 | test: /\.js$/, 21 | enforce: "pre", 22 | use: ["pre-loader1", "pre-loader2"], //pre的loader 前置 23 | }, 24 | ]; 25 | //loader执行的真正顺序是 26 | // post pitch inline pitch normal pitch pre pitch=>pre loader normal loader inline loader post loader 27 | // pitch很少使用 28 | //顺序是 post(后置)+inline(内联)+normal(普通)+pre(前置) 29 | //parts=['inline1-loader','inline2-loader','src/index.js'] 30 | let parts = request.replace(/^-?!+/, "").split("!"); 31 | let resource = parts.pop(); //'src/index.js' 32 | //解析loader的绝对路径 C:\5.loader\loaders\inline1-loader.js 33 | let resolveLoader = (loader) => path.resolve(__dirname, "loaders", loader); 34 | //inlineLoaders=[inline1-loader绝对路径,inline2-loader绝对路径] 35 | let inlineLoaders = parts; 36 | let preLoaders = [], 37 | normalLoaders = [], 38 | postLoaders = []; 39 | for (let i = 0; i < rules.length; i++) { 40 | let rule = rules[i]; 41 | if (rule.test.test(resource)) { 42 | if (rule.enforce === "pre") { 43 | preLoaders.push(...rule.use); 44 | } else if (rule.enforce === "post") { 45 | postLoaders.push(...rule.use); 46 | } else { 47 | normalLoaders.push(...rule.use); 48 | } 49 | } 50 | } 51 | 52 | /** 53 | * 正常 post(后置)+inline(内联)+normal(普通)+pre(前置) 54 | * Prefixing with ! will disable all configured normal loaders 55 | * post(后置)+inline(内联)+pre(前置) 56 | * Prefixing with !! will disable all configured loaders (preLoaders, loaders, postLoaders) 57 | * inline(内联) 58 | * Prefixing with -! will disable all configured preLoaders and loaders but not postLoaders 59 | * post(后置)+inline(内联) 60 | */ 61 | let loaders = []; //表示最终生效的loader 62 | if (request.startsWith("!!")) { 63 | loaders = [...inlineLoaders]; 64 | } else if (request.startsWith("-!")) { 65 | loaders = [...postLoaders, ...inlineLoaders]; 66 | } else if (request.startsWith("!")) { 67 | loaders = [...postLoaders, ...inlineLoaders, ...preLoaders]; 68 | } else { 69 | loaders = [...postLoaders, ...inlineLoaders, ...normalLoaders, ...preLoaders]; 70 | } 71 | loaders = loaders.map(resolveLoader); 72 | //我们已经 收到了所有的loader绝对路径组成的数组 73 | runLoaders( 74 | { 75 | resource, //要加载和转换的模块 76 | loaders, //loader的数组 77 | context: { name: "zhufeng" }, //基础上下文件对象 78 | readResource: fs.readFile.bind(fs), //读取硬盘文件的方法 79 | }, 80 | (err, result) => { 81 | console.log(err); 82 | console.log(result); 83 | console.log(result.resourceBuffer.toString("utf8")); 84 | } 85 | ); 86 | -------------------------------------------------------------------------------- /hand-webpack/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "webpack", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "keywords": [], 10 | "author": "", 11 | "license": "ISC", 12 | "dependencies": { 13 | "webpack": "^5.58.1", 14 | "webpack-dev-middleware": "^5.2.1", 15 | "webpack-dev-server": "^4.3.1" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /hand-webpack/plugins/1.plugin.js: -------------------------------------------------------------------------------- 1 | let babel = require("@babel/core"); 2 | let types = require("babel-types"); 3 | const visitor = { 4 | ImportDeclaration: { 5 | enter(path, state = { opts }) { 6 | const specifiers = path.node.specifiers; 7 | const source = path.node.source; 8 | if ( 9 | state.opts.libraryName == source.value && 10 | !types.isImportDefaultSpecifier(specifiers[0]) 11 | ) { 12 | const declarations = specifiers.map((specifier, index) => { 13 | return types.ImportDeclaration( 14 | [types.importDefaultSpecifier(specifier.local)], 15 | types.stringLiteral(`${source.value}/${specifier.local.name}`) 16 | ); 17 | }); 18 | path.replaceWithMultiple(declarations); 19 | } 20 | }, 21 | }, 22 | }; 23 | module.exports = function (babel) { 24 | return { 25 | visitor, 26 | }; 27 | }; 28 | -------------------------------------------------------------------------------- /hand-webpack/plugins/2.babel.plugin.js: -------------------------------------------------------------------------------- 1 | const parser = require("@babel/parser"); 2 | const traverse = require("@babel/traverse").default; 3 | const generate = require("@babel/generator").default; 4 | const types = require("@babel/types"); 5 | const sourceCode = `console.log("hello");`; 6 | const ast = parser.parse(sourceCode, { 7 | sourceType: "module", 8 | plugins: ["jsx"], 9 | }); 10 | 11 | traverse(ast, { 12 | CallExpression(path) { 13 | if ( 14 | types.isMemberExpression(path.node.callee) && 15 | path.node.callee.object.name === "console" && 16 | ["log", "info", "error", "debug"].includes(path.node.callee.property.name) 17 | ) { 18 | const { line, column } = path.node.loc.start; 19 | path.node.arguments.unshift( 20 | types.stringLiteral(`filename: (${line}, ${column})`) 21 | ); 22 | } 23 | }, 24 | }); 25 | const { code } = generate(ast); 26 | console.log(code); 27 | -------------------------------------------------------------------------------- /hand-webpack/plugins/done-plugin.js: -------------------------------------------------------------------------------- 1 | class DonePlugin { 2 | //每个插件都是一个类,而每个类都需要定义一个apply方法 3 | apply(compiler) { 4 | compiler.hooks.done.tap("DonePlugin", () => { 5 | console.log("done:结束编译"); 6 | }); 7 | } 8 | } 9 | module.exports = DonePlugin; 10 | -------------------------------------------------------------------------------- /hand-webpack/plugins/run-plugin.js: -------------------------------------------------------------------------------- 1 | class RunPlugin { 2 | //每个插件都是一个类,而每个类都需要定义一个apply方法 3 | apply(compiler) { 4 | compiler.hooks.run.tap("RunPlugin", () => { 5 | console.log("run:开始编译"); 6 | }); 7 | } 8 | } 9 | module.exports = RunPlugin; 10 | -------------------------------------------------------------------------------- /hand-webpack/src/entry1.js: -------------------------------------------------------------------------------- 1 | let title = require("./title"); 2 | console.log("entry12", title); 3 | -------------------------------------------------------------------------------- /hand-webpack/src/entry2.js: -------------------------------------------------------------------------------- 1 | let title = require("./title.js"); 2 | console.log("entry2", title); 3 | -------------------------------------------------------------------------------- /hand-webpack/src/title.js: -------------------------------------------------------------------------------- 1 | module.exports = "title"; 2 | -------------------------------------------------------------------------------- /hand-webpack/webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require("path"); 2 | const DonePlugin = require("./plugins/done-plugin"); 3 | const RunPlugin = require("./plugins/run-plugin"); 4 | 5 | module.exports = { 6 | mode: "devlopment", 7 | devtool: false, 8 | entry: { 9 | entry1: "./src/entry1.js", 10 | entry2: "./src/entry2.js", 11 | }, 12 | output: { 13 | path: path.resolve("dist"), 14 | filename: "[name].js", 15 | }, 16 | resolve: { 17 | extensions: [".js", ".jsx", ".ts", ".tsx", ".json"], 18 | }, 19 | module: { 20 | rules: [ 21 | { 22 | test: /\.js$/, 23 | use: [ 24 | path.resolve(__dirname, "loaders/logger1-loader.js"), 25 | path.resolve(__dirname, "loaders/logger2-loader.js"), 26 | ], 27 | }, 28 | ], 29 | }, 30 | plugins: [ 31 | // 开始编译的时候触发run事件,RunPlugin会监听这个事件执行回调 32 | new DonePlugin(), 33 | // 编译完成的时候会触发done事件,DonePlugin会监听这个done事件的回调 34 | new RunPlugin(), 35 | ], 36 | }; 37 | -------------------------------------------------------------------------------- /hand-webpack/webpack/Compiler.js: -------------------------------------------------------------------------------- 1 | const { SyncHook } = require("tapable"); 2 | const fs = require("fs"); 3 | const path = require("path"); 4 | const Complication = require("./Complication"); 5 | 6 | /** 7 | * Compiler就是编译大管家 8 | * 负责整个编译过程,里面保存整个编译所有的信息 9 | */ 10 | class Compiler { 11 | constructor(options) { 12 | this.options = options; 13 | this.hooks = { 14 | //会在开始编译的时候触发 15 | run: new SyncHook(), 16 | //会在结束编译的时候触发 17 | done: new SyncHook(), 18 | }; 19 | } 20 | //4.执行Compiler对象的run方法开始执行编译 21 | run(callback) { 22 | this.hooks.run.call(); 23 | //5.根据配置中的entry找出入口文件 24 | const onCompiled = (err, stats, fileDependencies) => { 25 | //10在确定好输出内容后,根据配置确定输出的路径和文件名,把文件内容写入到文件系统 26 | for (const filename in stats.assets) { 27 | let filePath = path.join(this.options.output.pathname, filename) 28 | fs.writeFileSync(filePath, stats.assets[filename], 'utf-8') 29 | } 30 | callback(err, { 31 | toJson: () => stats 32 | }) 33 | fileDependencies.forEach(fileDependency => { 34 | // 监听文件改变,实现HMR 35 | fs.watch(fileDependency, () => this.compile(onCompiled)) 36 | }); 37 | }; 38 | this.compile(onCompiled); 39 | this.hooks.done.call(); 40 | } 41 | compile(callback) { 42 | let compilation = new Complication(this.options); 43 | //每次编译都会创建一个新的Compilcation 44 | compilation.build(callback); 45 | } 46 | } 47 | 48 | module.exports = Compiler; 49 | -------------------------------------------------------------------------------- /hand-webpack/webpack/tapable.js: -------------------------------------------------------------------------------- 1 | // tapable 是一个类似于 Node.js 中的 EventEmitter 的库,但更专注于自定义事件的触发和处理 2 | // webpack 通过 tapable 将实现与流程解耦,所有具体实现通过插件的形式存在 3 | class SyncHook { 4 | constructor() { 5 | this.taps = []; 6 | } 7 | tap(name, fn) { 8 | this.taps.push(fn); 9 | } 10 | call() { 11 | this.taps.forEach((tap) => tap()); 12 | } 13 | } 14 | 15 | let hook = new SyncHook(); 16 | hook.tap("some name", () => { 17 | console.log("some name"); 18 | }); 19 | 20 | class Plugin { 21 | apply() { 22 | hook.tap("Plugin", () => { 23 | console.log("Plugin "); 24 | }); 25 | } 26 | } 27 | new Plugin().apply(); 28 | 29 | hook.call(); 30 | -------------------------------------------------------------------------------- /hand-webpack/webpack/webpack.js: -------------------------------------------------------------------------------- 1 | const Compiler = require("./Compiler"); 2 | 3 | function webpack(options) { 4 | // 1.初始化参数:从配置文件和Shell语句中读取并合并参数,得出最终的配置对象 5 | console.log(process.argv); //['node.exe','debugger.js'] 6 | let argv = process.argv.slice(2); 7 | let shellOptions = argv.reduce((shellOptions, option) => { 8 | let [key, value] = option.split("="); 9 | shellOptions[key.slice(2)] = value; 10 | return shellOptions; 11 | }, {}); 12 | 13 | let finalOptions = { ...options, ...shellOptions }; 14 | console.log("finalOptions", finalOptions); 15 | 16 | // 2.用上一步得到的参数初始化Compiler对象 17 | let compiler = new Compiler(finalOptions); 18 | // 3.加载所有配置的插件 19 | let { plugins } = finalOptions; 20 | for (let plugin of plugins) { 21 | plugin.apply(compiler); 22 | } 23 | return compiler; 24 | } 25 | 26 | module.exports = webpack; 27 | -------------------------------------------------------------------------------- /html/html.md: -------------------------------------------------------------------------------- 1 | # HTML 简答题 2 | -------------------------------------------------------------------------------- /js/closure.md: -------------------------------------------------------------------------------- 1 | # 闭包 2 | #### Keywords:执行上下文、执行上下文栈、作用域链、垃圾回收机制、内存泄漏 3 | 4 | ## 定义 5 | 6 | * 垃圾回收机制 7 | 8 | > 在javascript中,如果一个对象不再被引用,那么这个对象就会被垃圾回收机制回收 9 | > 如果两个对象互相引用,而不再被第3者所引用,那么这两个互相引用的对象也会被回收 10 | 11 | * MDN定义的闭包 12 | 13 | 闭包是指那些能够访问自由变量的函数 14 | 15 | MDN 上面这么说:闭包是一种特殊的对象。它由两部分构成:函数,以及创建该函数的环境。环境由闭包创建时在作用域中的任何局部变量组成。 16 | 17 | 闭包的作用域链包含着它自己的作用域,以及包含它的函数的作用域和全局作用域。 18 | 19 | * 那么什么是自由变量? 20 | 21 | 自由变量是指在函数中使用的,但既不是函数参数也不是函数的局部变量的变量 22 | 23 | 如果一个变量的引用不为0,那么他不会被垃圾回收机制回收 24 | 25 | ## 原理 26 | 27 | ## 闭包的应用 28 | 1. 可以读取函数内部的变量 29 | 30 | 2. 让变量的值始终保持在内存中。 31 | 32 | 3. 应用闭包的主要场合是:设计私有的方法和变量。 33 | 34 | 4. 一个闭包计数器 35 | 36 | ## 闭包的注意点 37 | 1. 通常,函数的作用域及其所有变量都会在函数执行结束后被销毁。但是,在创建了一个闭包以后,这个函数的作用域就会一直保存到闭包不存在为止。 38 | 39 | 最后通过 null 释放闭包 40 | 41 | ## 闭包的缺陷 42 | 1. 由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在IE中可能导致内存泄露。解决方法是,在退出函数之前,将不使用的局部变量全部删除 43 | 44 | 2. 闭包会在父函数外部,改变父函数内部变量的值。所以,如果你把父函数当作对象(object)使用,把闭包当作它的公用方法(Public Method),把内部变量当作它的私有属性(private value),这时一定要小心,不要随便改变父函数内部变量的值 45 | 46 | 3. 闭包的缺点就是常驻内存会增大内存使用量,并且使用不当很容易造成内存泄露。 47 | 48 | 4. 如果不是因为某些特殊任务而需要闭包,在没有必要的情况下,在其它函数中创建函数是不明智的,因为闭包对脚本性能具有负面影响,包括处理速度和内存消耗。 49 | 50 | ## 闭包面试题 51 | 1. 52 | ```js 53 | function fun(n,o){ 54 | console.log(o); 55 | return { 56 | fun: function(m){ 57 | return fun(m,n); 58 | } 59 | }; 60 | } 61 | 62 | var a = fun(0); // ? 63 | a.fun(1); // ? 64 | a.fun(2); // ? 65 | a.fun(3); // ? 66 | 67 | var b = fun(0).fun(1).fun(2).fun(3); // ? 68 | 69 | var c = fun(0).fun(1); // ? 70 | c.fun(2); // ? 71 | c.fun(3); 72 | ``` 73 | -------------------------------------------------------------------------------- /js/es6.md: -------------------------------------------------------------------------------- 1 | # ES6简答题 2 | -------------------------------------------------------------------------------- /js/js.md: -------------------------------------------------------------------------------- 1 | # JS简答题 2 | 3 | ### JS有几种数据类型,其中基本数据类型有哪些? 4 | 七种数据类型 5 | 6 | Boolean 7 | Null 8 | Undefined 9 | Number 10 | String 11 | Symbol (ECMAScript 6 新定义) 12 | Object 13 | 14 | (ES6之前)其中5种为基本类型:string,number,boolean,null,undefined 15 | 16 | ES6出来的Symbol也是原始数据类型 ,表示独一无二的值 17 | 18 | Object 为引用类型(范围挺大), 也包括数组、函数 19 | 20 | ### null和undefined的差异 21 | 相同点: 22 | 23 | * 在 if判断语句中,值都默认为 false 24 | * 大体上两者都是代表无,具体看差异 25 | 26 | 差异: 27 | 28 | * null转为数字类型值为0,而undefined转为数字类型为 NaN(Not a Number) 29 | * undefined是代表调用一个值而该值却没有赋值,这时候默认则为undefined 30 | * null是一个很特殊的对象,最为常见的一个用法就是作为参数传入(说明该参数不是对象) 31 | * 设置为null的变量或者对象会被内存收集器回收 32 | 33 | ### JS 的DOM 操作(节点获取及增删查改) 34 | ```js 35 | // 获取 36 | var node = document.getElementById('id'); 37 | var element = document.querySelector('#test'); 38 | 39 | // 追加 40 | element.appendChild(Node); 41 | 42 | // 删除 43 | element.removeChild(Node); 44 | 45 | 46 | // 改 47 | element.setAttribute(name, value); // 增加属性 48 | element.removeAttribute(attrName); //删除属性 49 | 50 | // 以上面的例子为例 51 | /* 52 |
53 | Hello, World 54 |
55 | */ 56 | 57 | var test = document.createElement('div'); // 创建一个块级元素 58 | test.setAttribute("id","test"); // 设置其id 属性 59 | var span = document.createElement('span'); // 创建一个 span 60 | span.innerText = "Hello,world"; // 插入 span 的文本内容 61 | test.appendChild(span); // 组合节点 62 | element.appendChild(test); //追加到某个节点区域 63 | ``` 64 | ### 对数组 ['2018-03-05', '2013-06-12','2019-03-12','2018-03-05','2014-02-22'] 去重且排序 65 | // Set 具有值唯一性 66 | // 结合...解构,可以把可迭代(比如 arguments/nodelist 等)的转为数组 67 | // sort 里面传入两个值比较,返回-1和1是因为1代表这个数大排后(相对),-1代表小(相对),0为相等 68 | 69 | ```js 70 | let arr = [...new Set(['2018-03-05', '2013-06-12','2019-03-12','2018-03-05','2014-02-22'])].sort(function(a,b){ 71 | return a < b ? -1 : 1; // 这里返回的是升序的,降序改下返回值就好了.所以是相对 72 | }) 73 | ``` 74 | 75 | ### 对数组[1,2,3,4,5,'6',7,'8','a','b','z']进行乱序 76 | ```js 77 | let tempArr = [1,2,3,4,5,'6',7,'8','a','b','z'].sort(function(){ 78 | return Math.random() > 0.5 ? -1 : 1; 79 | }) 80 | ``` 81 | 82 | ### JS 实现String.trim()方法 83 | ```js 84 | String.prototype.emuTrim = function(){ 85 | // 这条正则很好理解,就是把头部尾部多余的空格字符去除 86 | return this.replace(/(^\s*)|(\s*$)/g,''); 87 | } 88 | ``` 89 | -------------------------------------------------------------------------------- /network/README.md: -------------------------------------------------------------------------------- 1 | # 一,OSI七层模型 2 | 3 | ### MVC架构 4 | 5 | 视图层 + 控制器 + 服务层 + 模型层 + 数据库 6 | 7 | ### 分层模型 8 | 9 | * 应用层:网络服务与最终用户的一个接口(提供网络与用户应用软件之间的接口服务 => HTTP) 10 | * 表示层:数据的表示、安全、压缩(提供格式化的表示和转换数据服务,如加密和压缩) 11 | * 会话层:建立、管理、终止会话(提供包括访问验证和会话管理在内的建立和维护应用之间通信的机制) 12 | * 传输层:定义传输数据的协议端口号,以及流控和差错校验(提供建立、维护和取消传输连接功能,负责可靠的传输数据 => TCP) 13 | * 网络层:进行逻辑地址寻址,实现不同网络之间的路径选择(处理网络间路由,确保数据及时传送 => 路由器) 14 | * 数据链路层:建立逻辑连接,进行硬件地址寻址,差错校验等功能(负责无错传输数据,确认帧、发错重传等 => 交换机) 15 | * 物理层:建立、维护、断开物理连接(提供机械、电气、功能和过程特性 => 网卡、网线、双绞线、同轴电缆、中继器) 16 | 17 | ### 不同层中的称谓 18 | 19 | * 比特流: 20 | * 数据帧(Frame):是一种信息单位,它的起始点和目的点都是数据链路层。 21 | * 数据包(Packet):也是一种信息单位,它的起始和目的地是网络层。 22 | * 段(Segment):通常是指起始点和目的地都是传输层的信息单元。 23 | * 消息(message):是指起始点和目的地都在网络层以上(经常在应用层)的信息单元。 24 | 25 | # 二、TCP/IP参考模型 26 | 27 | 5层模型 28 | 29 | * 应用层:HTTP、FTP、TFTP、SMTP、SNMP、DNS 30 | * 传输层:TCP、UDP 31 | * 网络层:IP、ARP、RARP、ICMP、IGMP 32 | * 数据链路层 33 | * 物理层 34 | 35 | ### 常见协议 36 | 37 | * HTTP 38 | * FTP 39 | * SMTP 40 | * DNS 41 | * TCP 42 | * UDP 43 | * IP 44 | * ARP:地址解析协议,即ARP(Address Resolution Protocol),是根据IP地址获取物理地址的一个TCP/IP协议 45 | * RARP:反向地址转换协议(RARP:Reverse Address Resolution Protocol) 反向地址转换协议(RARP)允许局域网的物理机器从网关服务器的 ARP 表或者缓存上请求其 IP 地址 46 | 47 | # 三、网络接口层 48 | 49 | 网络接口层是TCP/IP模型的最底层,负责接收从上一层交来的数据报并将数据报通过底层的物理网络发送出去,比较常见的就是设备的驱动程序,此层没有特定的协议 50 | 51 | ### 1、物理层 52 | 53 | * 计算机在传递数据的时候传递的都是0和1的数字,而物理层关心的是用什么信号来表示0和1,是否可以双向通信,最初的连接如何建立以及完成连接如何终止,物理层是为数据传输提供可靠的环境 54 | * 尽可能的屏蔽掉物理设备和传输媒介,使数据链路层不考虑这些差异,只考虑本层的协议和服务 55 | * 为用户提供在一条物理传输媒体上提供传送和接收比特流的能力 56 | * 需要解决物理连接、维护和释放的问题 57 | 58 | #### 1.1 非归零编码 59 | 60 | 0101010110111000 61 | 62 | #### 1.2 曼彻斯特编码 63 | 64 | * 内部自含时钟 65 | * 每个自带变化 66 | 67 | > 缺点:编译码较复杂、占用更多的信道带宽 68 | 69 | ### 2、数据链路层 70 | 71 | ### 2.1 以太网 72 | 73 | 以太网(Ethernet)是一种计算机局域网技术。IEEE组织的IEEE 802.3标准制定了以太网的技术标准,它规定了包括物理层的连线、电子信号和介质访问层协议的内容 74 | 75 | ### 2.2 总线型拓扑 76 | 77 | ### 2.3 MAC地址 78 | 79 | ### 2.4 以太网帧格式 80 | 81 | ### 2.5 ARP协议 82 | 83 | * 地址解析协议,即ARP(Address Resolution Protocol),是根据IP地址获取物理地址的一个TCP/IP协议 84 | * 主机发送信息时将包含目标IP地址的ARP请求广播到网络上的所有主机,并接收返回消息,以此确定目标的物理地址;收到返回消息后将该IP地址和物理地址存入本机ARP缓存中并保留一定时间,下次请求时直接查询ARP缓存以节约资源 85 | * 地址解析协议是建立在网络中各个主机互相信任的基础上的,网络上的主机可以自主发送ARP应答消息,其他主机收到应答报文时不会检测该报文的真实性就会将其记入本机ARP缓存 86 | * 由此攻击者就可以向某一主机发送伪ARP应答报文,使其发送的信息无法到达预期的主机或到达错误的主机,这就构成了一个ARP欺骗 87 | 88 | # 四、网络层 89 | 90 | 位于传输层和网络接口层之间,用于把数据从源主机经过若干个中间节点传送到目标主机,并向传输层提供最基础的数据传输服务,它要提供路由和选址的工作 91 | 92 | ### 1、选址 93 | 94 | 交换机是靠MAC来寻址的,而因为MAC地址是无层次的,所以要靠IP地址来确认计算机的位置,这就是选址 95 | 96 | ### 2、路由 97 | 98 | 在能够选择的多条道路之间选择一条最短的路径就是路由的工作 99 | 100 | ### 3、IP 101 | 102 | 在网络中,每台计算机都有一个唯一的地址,方便别人找到它,这个地址称为IP地址。 103 | 104 | ### 3.4 子网掩码 105 | 106 | ### 3.5 ipv6 107 | 108 | # 五、传输层 109 | 110 | # 六、应用层 111 | 112 | * 路由表 113 | 114 | 115 | 116 | -------------------------------------------------------------------------------- /network/assets/httpwebsocket.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/miracle90/Interview/82ecfce0df0d6a1fc7fa6cd40e5348ac317ca77a/network/assets/httpwebsocket.png -------------------------------------------------------------------------------- /network/cdn.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/miracle90/Interview/82ecfce0df0d6a1fc7fa6cd40e5348ac317ca77a/network/cdn.md -------------------------------------------------------------------------------- /network/http2_http3.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/miracle90/Interview/82ecfce0df0d6a1fc7fa6cd40e5348ac317ca77a/network/http2_http3.md -------------------------------------------------------------------------------- /network/https.md: -------------------------------------------------------------------------------- 1 | ## 一、http通信有什么问题 2 | 3 | ## 二、HTTPS如何解决上述三个问题 4 | 5 | ## 三、SSL和TLS的区别 -------------------------------------------------------------------------------- /network/ip.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/miracle90/Interview/82ecfce0df0d6a1fc7fa6cd40e5348ac317ca77a/network/ip.md -------------------------------------------------------------------------------- /network/questions.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/miracle90/Interview/82ecfce0df0d6a1fc7fa6cd40e5348ac317ca77a/network/questions.md -------------------------------------------------------------------------------- /network/tcp_udp.md: -------------------------------------------------------------------------------- 1 | # 传输层 2 | 3 | * 位于应用层和网络接口层之间 4 | * 是面向连接的、可靠的的进程到进程通信的协议 5 | * TCP提供全双工服务,即数据可在同一时间双向传播 6 | * TCP将若干个字节构成一个分组,此分组称为报文段(Segment) 7 | * 对可靠性要求高的上层协议,实现可靠性的保证,如果数据丢失、损坏的情况下如何保证可靠性,网络层只管传递数据,成功与否并不关心 8 | 9 | 10 | * 单工:单向传输,如广播、电视 11 | * 半双工:在同一个时间点内只能单向通信,如对讲机 12 | * 全双工:任意时间点,双方都可以收发数据,如电话 13 | 14 | ### 1、传输层的功能 15 | 16 | 提供了一种端到端的连接 17 | 18 | ### 2、协议分类 19 | 20 | TCP(Transimision Control Protocal) 21 | 传输控制协议 22 | 可靠的、面向连接的协议 23 | 传输效率低 24 | 25 | UDP(User Datagram Protocal) 26 | 用户数据报协议 27 | 不可靠的、无连接的服务 28 | 传输效率高 29 | 30 | ### 3、TCP协议 31 | 32 | * 将数据进行分段打包传输 33 | * 对每个数据包编号控制顺序 34 | * 运输中丢失、重发和丢弃处理 35 | * 流量控制避免拥塞 36 | 37 | ### 3.2 握手和断开 38 | 39 | ### 4、UDP 40 | 41 | ### 4.3 DNS服务器 42 | 43 | ``` 44 | 网关 => 路由器 45 | 46 | 也可以承担DNS服务器的作用 47 | ``` 48 | 49 | ### 4.4 DHCP服务器 50 | -------------------------------------------------------------------------------- /network/websocket/1.server.js: -------------------------------------------------------------------------------- 1 | const { Server } = require('./ws') 2 | 3 | const wsServer = new Server({ port: 8888 }) 4 | 5 | wsServer.on('connection', (socket) => { 6 | console.log('onconnection~~~~~~~~~~~') 7 | // onmessage监听客户端过来的链接 8 | socket.on('message', (message) => { 9 | console.log(`服务端onmessage:`, message) 10 | socket.send(`message:${message}`) 11 | }) 12 | }) -------------------------------------------------------------------------------- /network/websocket/doc/1.js: -------------------------------------------------------------------------------- 1 | const crypto = require('crypto') 2 | 3 | const CODE = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11' 4 | let SecWebSocketKey = 'IFUnA2e9Ud/h8aMaCN9JBA==' 5 | let SecWebSocketAccept = 'EFKawHQH5PirjVYXJjJ52mnm1HY=' 6 | 7 | 8 | function toAcceptKey(wsKey) { 9 | // md5 hash算法,安全性不高 10 | return crypto.createHash('sha1').update(wsKey + CODE).digest('base64') 11 | } 12 | 13 | let result = toAcceptKey(SecWebSocketKey) 14 | console.log(`Sec-WebSocket-Accept:${result}`) 15 | console.log(result === SecWebSocketAccept) -------------------------------------------------------------------------------- /network/websocket/doc/2.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 1、一个字节是8个位,一个字节表示的数字是 0~255,即 00000000 ~ 11111111 3 | * 2、大端序和小端序 4 | */ -------------------------------------------------------------------------------- /network/websocket/doc/3.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/miracle90/Interview/82ecfce0df0d6a1fc7fa6cd40e5348ac317ca77a/network/websocket/doc/3.js -------------------------------------------------------------------------------- /network/websocket/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 9 | 10 | 11 | 12 | 32 | 33 | -------------------------------------------------------------------------------- /network/websocket/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "websocket", 3 | "version": "1.0.0", 4 | "lockfileVersion": 2, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "version": "1.0.0", 9 | "license": "ISC", 10 | "dependencies": { 11 | "ws": "^7.5.3" 12 | } 13 | }, 14 | "node_modules/ws": { 15 | "version": "7.5.3", 16 | "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.3.tgz", 17 | "integrity": "sha512-kQ/dHIzuLrS6Je9+uv81ueZomEwH0qVYstcAQ4/Z93K8zeko9gtAbttJWzoC5ukqXY1PpoouV3+VSOqEAFt5wg==", 18 | "engines": { 19 | "node": ">=8.3.0" 20 | }, 21 | "peerDependencies": { 22 | "bufferutil": "^4.0.1", 23 | "utf-8-validate": "^5.0.2" 24 | }, 25 | "peerDependenciesMeta": { 26 | "bufferutil": { 27 | "optional": true 28 | }, 29 | "utf-8-validate": { 30 | "optional": true 31 | } 32 | } 33 | } 34 | }, 35 | "dependencies": { 36 | "ws": { 37 | "version": "7.5.3", 38 | "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.3.tgz", 39 | "integrity": "sha512-kQ/dHIzuLrS6Je9+uv81ueZomEwH0qVYstcAQ4/Z93K8zeko9gtAbttJWzoC5ukqXY1PpoouV3+VSOqEAFt5wg==", 40 | "requires": {} 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /network/websocket/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "websocket", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "keywords": [], 10 | "author": "", 11 | "license": "ISC", 12 | "dependencies": { 13 | "ws": "^7.5.3" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /network/websocket/utils.js: -------------------------------------------------------------------------------- 1 | const crypto = require('crypto') 2 | const CODE = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11' 3 | 4 | function toAcceptKey(wsKey) { 5 | // md5 hash算法,安全性不高 6 | return crypto.createHash('sha1').update(wsKey + CODE).digest('base64') 7 | } 8 | 9 | function unmask(buffer, mask) { 10 | const length = buffer.length 11 | for (let i = 0; i < length; i++) { 12 | buffer[i] ^= mask[i % 4] 13 | } 14 | } 15 | 16 | function toHeaders(rows) { 17 | let headers = {} 18 | rows.forEach(row => { 19 | let [key, value] = row.split(': ') 20 | headers[key] = value 21 | }); 22 | return headers 23 | } 24 | 25 | exports.toAcceptKey = toAcceptKey 26 | exports.unmask = unmask 27 | exports.toHeaders = toHeaders -------------------------------------------------------------------------------- /network/websocket/websocket.md: -------------------------------------------------------------------------------- 1 | # 1.websocket介绍 2 | 3 | * WebSockets API是HTML5开始提供的一种浏览器与服务器进行全双工通讯的网络技术 4 | * 通俗地讲:在客户端和服务器保有一个持久的连接,两边可以在任意时间开始发送数据 5 | * 属于应用层协议,它基于TCP传输协议,并复用HTTP的握手通道 6 | 7 | ![](../assets/httpwebsocket.png) 8 | 9 | # 2.websocket实战 10 | 11 | * server.js 12 | 13 | ```js 14 | const { Server } = require('ws'); 15 | const wss = new Server({ port: 8888 }); 16 | wss.on('connection', (socket) => { 17 | socket.on('message', (message) => { 18 | socket.send(message); 19 | }); 20 | }); 21 | ``` 22 | 23 | * client 24 | 25 | ```html 26 | 27 | 28 | 29 | 30 | 31 | 32 | Document 33 | 34 | 35 | 36 | 37 | 57 | 58 | 59 | ``` 60 | 61 | # 3.websocket连接 62 | 63 | * WebSocket复用了HTTP的握手通道 64 | * 具体指的是,客户端通过HTTP请求与WebSocket服务端协商升级协议 65 | * 协议升级完成后,后续的数据交换则遵照WebSocket的协议 66 | 67 | ### 3.1 客户端:申请协议升级 68 | 69 | * 首先客户端发起协议升级请求 70 | * 请求采用的是标准的HTTP报文格式,且只支持GET方法 71 | 72 | ``` 73 | GET ws://localhost:8888/ HTTP/1.1 74 | Host: localhost:8888 75 | Connection: Upgrade 76 | Upgrade: websocket 77 | Sec-WebSocket-Version: 13 78 | Sec-WebSocket-Key: IHfMdf8a0aQXbwQO1pkGdA== 79 | ``` 80 | 81 | ### 3.2 服务端:响应协议升级 82 | 83 | * 服务端返回内容如下:状态代码101表示协议切换 84 | * 到此完成协议升级,后续的数据交互都按照新的协议来 85 | 86 | ``` 87 | HTTP/1.1 101 Switching Protocols 88 | Upgrade: websocket 89 | Connection: Upgrade 90 | Sec-WebSocket-Accept: aWAY+V/uyz5ILZEoWuWdxjnlb7E= 91 | ``` 92 | | 字段 | 含义 | 93 | | - | - | 94 | | Connection: Upgrade | 升级协议 | 95 | | Upgrade: websocket | 升级到websocket协议 | 96 | |Sec-WebSocket-Accept | Accept字符串 | 97 | 98 | ### 3.3 Sec-WebSocket-Accept的计算 99 | 100 | * Sec-WebSocket-Accept根据客户端请求首部的Sec-WebSocket-Key计算出来 101 | * 计算公式为: 102 | 103 | ``` 104 | 将Sec-WebSocket-Key跟258EAFA5-E914-47DA-95CA-C5AB0DC85B11拼接 105 | 106 | 通过SHA1计算出摘要,并转成base64字符串 107 | ``` 108 | 109 | ```js 110 | const crypto = require('crypto'); 111 | const CODE = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11'; 112 | function toAcceptKey(wsKey) { 113 | return crypto.createHash('sha1').update(wsKey + CODE).digest('base64'); 114 | } 115 | const webSocketKey = 'IHfMdf8a0aQXbwQO1pkGdA=='; 116 | console.log(toAcceptKey(webSocketKey)); // aWAY+V/uyz5ILZEoWuWdxjnlb7E= 117 | ``` 118 | 119 | # 4.数据帧格式 120 | 121 | * WebSocket客户端、服务端通信的最小单位是帧,由1个或多个帧组成一条完整的消息(message) 122 | * 发送端 将消息切割成多个帧,并发送给服务端 123 | * 接收端 接收消息帧,并将关联的帧重新组装成完整的消息 124 | 125 | # 5.实现websocket服务器 -------------------------------------------------------------------------------- /network/websocket/ws.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 基于tcp传输层协议实现一个websocket应用服务器 3 | */ 4 | const net = require('net') 5 | const { EventEmitter } = require('events') 6 | const { unmask, toHeaders, toAcceptKey } = require('./utils') 7 | const OPCODE = { 8 | TEXT: 1, 9 | BINARY: 2 10 | } 11 | 12 | // EventEmitter事件库,可以用来发布和订阅事件,进行事件的监听 jquery on trigger addEventListener 13 | class Server extends EventEmitter { 14 | constructor(options) { 15 | super(options) 16 | this.options = options 17 | // 创建一个tcp服务器,每当服务器收到客户端连接后 18 | this.server = net.createServer(this.listener) 19 | this.server.listen(options.port) 20 | } 21 | 22 | // socket 套接字,发送和接收消息 23 | listener = (socket) => { 24 | socket.setKeepAlive(true) // 保持长连接 25 | 26 | socket.send = (payload) => { 27 | let opcode; 28 | if (Buffer.isBuffer(payload)) { 29 | opcode = OPCODE.BINARY 30 | } else { 31 | opcode = OPCODE.TEXT 32 | payload = Buffer.from(payload) 33 | } 34 | let length = payload.length 35 | let buffer = Buffer.alloc(length + 2) 36 | buffer[0] = 0b10000000 | opcode 37 | buffer[1] = length 38 | payload.copy(buffer, 2) 39 | socket.write(buffer) 40 | } 41 | // 当服务器收到客户端发过来的data后,chunk就是客户端发给服务器的数据 42 | socket.on('data', (chunk) => { 43 | console.log('ondata') 44 | // 说明客户端要求升级协议 45 | if (chunk.toString().match(/Upgrade: websocket/)) { 46 | this.upgradeProtocol(socket, chunk.toString()) 47 | } else { 48 | this.onmessage(socket, chunk) 49 | } 50 | }) 51 | // 触发connection事件,并传递socket对象 52 | this.emit('connection', socket) 53 | } 54 | 55 | // 如果不是握手,就是正常的发送消息 56 | onmessage = (socket, chunk) => { 57 | // 是结束帧 58 | let FIN = (chunk[0] & 0b10000000) === 0b10000000 59 | // 第一个字节,与上前4个0,把前4个干掉,得到操作码的10进制数 60 | let opcode = chunk[0] & 0b00001111 61 | // 是否掩码 62 | let masked = (chunk[1] & 0b10000000) === 0b10000000 63 | // 十进制数,数据长度 64 | let payloadLength = chunk[1] & 0b01111111 65 | let payload 66 | if (masked) { 67 | let maskKey = chunk.slice(2, 6) 68 | payload = chunk.slice(6, 6 + payloadLength) 69 | // 反码得到真正的数据 70 | unmask(payload, maskKey) 71 | } else { 72 | payload = chunk.slice(6, 6 + payloadLength) 73 | } 74 | if (FIN) { 75 | switch (opcode) { 76 | case OPCODE.TEXT: 77 | // 文本字符串的话 78 | socket.emit('message', payload.toString('utf8')) 79 | break; 80 | case OPCODE.BINARY: 81 | // 二进制 82 | socket.emit('message', payload) 83 | break; 84 | default: 85 | break; 86 | } 87 | } 88 | } 89 | 90 | upgradeProtocol = (socket, chunk) => { 91 | let rows = chunk.split('\r\n');//按分割符分开 92 | let headers = toHeaders(rows.slice(1, -2));//去掉请求行和尾部的二个分隔符 93 | let wsKey = headers['Sec-WebSocket-Key']; 94 | let acceptKey = toAcceptKey(wsKey) 95 | let response = [ 96 | 'HTTP/1.1 101 Switching Protocols', 97 | 'Upgrade: websocket', 98 | 'Connection: Upgrade', 99 | `Sec-WebSocket-Accept: ${acceptKey}`, 100 | '\r\n' 101 | ].join('\r\n') 102 | socket.write(response) 103 | } 104 | } 105 | 106 | exports.Server = Server -------------------------------------------------------------------------------- /optimize/virtual-list.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 简单实现虚拟列表 7 | 8 | 43 | 44 | 45 |
46 |
47 | 48 |
49 | 50 |
    51 |
  • 56 | {{ item }} 57 |
  • 58 |
59 |
60 |
61 | 62 | 107 | 108 | 109 | -------------------------------------------------------------------------------- /service-worker/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 9 | 10 |
12
11 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /service-worker/index.js: -------------------------------------------------------------------------------- 1 | var myWorker = new Worker("my_task.js"); 2 | 3 | // my_task.js中的代码 4 | var i = 0; 5 | function timedCount(){ 6 | i = i+1; 7 | postMessage(i); 8 | setTimeout(timedCount, 1000); 9 | } 10 | timedCount(); 11 | -------------------------------------------------------------------------------- /service-worker/worker.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/miracle90/Interview/82ecfce0df0d6a1fc7fa6cd40e5348ac317ca77a/service-worker/worker.js -------------------------------------------------------------------------------- /vue/README.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/miracle90/Interview/82ecfce0df0d6a1fc7fa6cd40e5348ac317ca77a/vue/README.md -------------------------------------------------------------------------------- /vue/doc/1.reactive.js: -------------------------------------------------------------------------------- 1 | function cb(val) { 2 | console.log("视图更新啦~", val); // 渲染视图 3 | } 4 | function defineReactive(obj, key, val) { 5 | const dep = new Dep(); // 一个Dep类对象,用来收集 Watcher 对象 6 | Object.defineProperty(obj, key, { 7 | enumerable: true, // 属性可枚举 8 | configurable: true, // 属性可被修改或删除 9 | get: function reactiveGetter() { 10 | dep.addSub(Dep.target); // 将Dep.target(即当前的Watcher对象存入dep的subs中) 11 | return val; 12 | }, 13 | set: function reactiveSetter(newVal) { 14 | if (newVal === val) return; 15 | dep.notify(); // 在set的时候触发dep的notify来通知所有的Watcher对象更新视图 16 | // cb(newVal); 17 | }, 18 | }); 19 | } 20 | function observer(value) { 21 | if (!value || typeof value !== "object") { 22 | return; 23 | } 24 | Object.keys(value).forEach((key) => { 25 | defineReactive(value, key, value[key]); 26 | }); 27 | } 28 | class Vue { 29 | constructor(options) { 30 | this._data = options.data; 31 | observer(this._data); 32 | } 33 | } 34 | // 订阅者 35 | class Dep { 36 | constructor() { 37 | this.subs = []; // 用来存放Watcher对象的实例 38 | } 39 | addSub(sub) { 40 | // 在subs中添加一个Watcher对象 41 | this.subs.push(sub); 42 | } 43 | notify() { 44 | // 通知所有Watcher对象更新视图 45 | this.subs.forEach((sub) => { 46 | sub.update(); 47 | }); 48 | } 49 | } 50 | let uid = 0; 51 | // 观察者 52 | class Watcher { 53 | constructor() { 54 | // 在new一个Watcher对象时将该对象赋值给Dep.target,在get中会用到 55 | // 「依赖收集」的过程就是把 Watcher 实例存放到对应的 Dep 对象中去 56 | Dep.target = this; 57 | this.id = ++uid; 58 | } 59 | // 更新视图的方法 60 | update() { 61 | // console.log("视图更新啦~"); 62 | console.log("watch" + this.id + " update"); 63 | queueWatcher(this); 64 | } 65 | run() { 66 | console.log("watch" + this.id + "视图更新啦~"); 67 | } 68 | } 69 | let has = {}; 70 | let queue = []; 71 | let waiting = false; 72 | function queueWatcher(watcher) { 73 | const id = watcher.id; 74 | if (has[id] == null) { 75 | has[id] = true; 76 | queue.push(watcher); 77 | if (!waiting) { 78 | waiting = true; 79 | nextTick(flushSchedulerQueue); 80 | } 81 | } 82 | } 83 | function flushSchedulerQueue() { 84 | let watcher, id; 85 | for (index = 0; index < queue.length; index++) { 86 | watcher = queue[index]; 87 | id = watcher.id; 88 | has[id] = null; 89 | watcher.run(); 90 | } 91 | waiting = false; 92 | } 93 | 94 | Dep.target = null; 95 | // 虚拟DOM 96 | class VNode { 97 | constructor(tag, data, children, text, elm) { 98 | /*当前节点的标签名*/ 99 | this.tag = tag; 100 | /*当前节点的一些数据信息,比如props、attrs等数据*/ 101 | this.data = data; 102 | /*当前节点的子节点,是一个数组*/ 103 | this.children = children; 104 | /*当前节点的文本*/ 105 | this.text = text; 106 | /*当前虚拟节点对应的真实dom节点*/ 107 | this.elm = elm; 108 | } 109 | } 110 | function render() { 111 | return new VNode( 112 | "span", 113 | { 114 | /* 指令集合数组 */ 115 | directives: [ 116 | { 117 | /* v-show指令 */ 118 | rawName: "v-show", 119 | expression: "isShow", 120 | name: "show", 121 | value: true, 122 | }, 123 | ], 124 | /* 静态class */ 125 | staticClass: "demo", 126 | }, 127 | [new VNode(undefined, undefined, undefined, "This is a span.")] 128 | ); 129 | } 130 | // +++++++++++++++++++++++++++++++++ 分割线 +++++++++++++++++++++++++++++++++++++ 131 | let o = new Vue({ 132 | data: { 133 | test: "I am test.", 134 | }, 135 | }); 136 | o._data.test = "hello,world."; /* 视图更新啦~ */ 137 | -------------------------------------------------------------------------------- /websocket/README.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/miracle90/Interview/82ecfce0df0d6a1fc7fa6cd40e5348ac317ca77a/websocket/README.md -------------------------------------------------------------------------------- /websocket/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 9 | 10 | 68 | 69 | 70 | -------------------------------------------------------------------------------- /websocket/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "websocket", 3 | "version": "1.0.0", 4 | "main": "index.js", 5 | "license": "MIT", 6 | "dependencies": { 7 | "express": "^4.17.3", 8 | "ws": "^8.5.0" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /websocket/reconnect.js: -------------------------------------------------------------------------------- 1 | let socket; 2 | let url = "ws://localhost:8080"; 3 | 4 | class HeartCheck { 5 | time = 6000; 6 | checkTimer = null; 7 | checkServer = null; 8 | startCheck = () => { 9 | this.checkTimer = setTimeout(() => { 10 | socket.send("发送心跳检测"); 11 | this.checkServer = setTimeout(() => { 12 | socket.onclose("心跳检测失败,先断开连接"); 13 | }, this.time); 14 | }, this.time); 15 | }; 16 | resetCheck = () => { 17 | clearTimeout(this.checkTimer); 18 | clearTimeout(this.checkServer); 19 | return this; 20 | }; 21 | reconnect() { 22 | wsConnect(); 23 | } 24 | } 25 | function wsConnect(url) { 26 | socket = new WebSocket(url); 27 | const heartCheck = new HeartCheck(); 28 | socket.onclose = () => { 29 | console.log("连接断开"); 30 | heartCheck.reconnect(); 31 | }; 32 | socket.onerror = (err) => { 33 | console.log("连接出错"); 34 | heartCheck.reconnect(); 35 | }; 36 | socket.onopen = () => { 37 | console.log("建立连接"); 38 | heartCheck.resetCheck().startCheck(); 39 | }; 40 | socket.onmessage = (message) => { 41 | console.log("收到信息", message.data); 42 | heartCheck.resetCheck().startCheck(); 43 | // todo... 44 | }; 45 | } 46 | -------------------------------------------------------------------------------- /websocket/server.js: -------------------------------------------------------------------------------- 1 | var app = require('express')(); 2 | var WebSocket = require('ws'); 3 | 4 | var wss = new WebSocket.Server({ port: 9000 }); 5 | wss.on('connection', function connection(ws) { 6 | console.log('已建立连接'); 7 | ws.on('message', function incoming(message) { 8 | console.log('收到消息:', message.toString()); 9 | ws.send(`你好 ${new Date()}`); 10 | }); 11 | }); 12 | app.get('/', function (req, res) { 13 | res.sendFile(__dirname + '/index.html'); 14 | }); 15 | 16 | app.listen(3000); 17 | -------------------------------------------------------------------------------- /websocket/upgrade.js: -------------------------------------------------------------------------------- 1 | const http = require("http"); 2 | const crypto = require("crypto"); 3 | const MAGIC_KEY = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; 4 | const port = 8080; 5 | 6 | /** 7 | * 根据请求头的key生成accpet 8 | * 采用sha1算法,加盐 9 | * @param {*} secWsKey 10 | * @returns 11 | */ 12 | function generateAcceptValue(secWsKey) { 13 | return crypto 14 | .createHash("sha1") 15 | .update(secWsKey + MAGIC_KEY, "utf-8") 16 | .digest("base64"); 17 | } 18 | const server = http.createServer((req, res) => { 19 | res.writeHead(200, { "Content-Type": "text/plain;charset=utf-8" }); 20 | res.end("创建服务"); 21 | }); 22 | server.on("upgrade", (req, socket) => { 23 | if (req.headers["upgrade"] !== "websocket") { 24 | socket.end("HTTP/1.1 400 Bad Request"); 25 | return; 26 | } 27 | // 读取客户端提供的 Sec-WebSocket-Key 28 | const secWsKey = req.headers["sec-websocket-key"]; 29 | // 使用SHA-1 算法生成Sec-WebSocket-Accept 30 | const hash = generateAcceptValue(secWsKey); 31 | // 设置HTTP响应头 32 | const responseHeaders = [ 33 | "HTTP/1.1 101 Web Socket Protocol Handshake", 34 | "Upgrade: WebSocket", 35 | "Connection: Upgrade", 36 | `Sec-WebSocket-Accept: ${hash}`, 37 | ]; 38 | // 返回握手请求头信息 39 | socket.write(responseHeaders.join("\r\n") + "\r\n\r\n"); 40 | }); 41 | server.listen(port, () => { 42 | console.log(`server is running at http://localhost:${port}`); 43 | }); 44 | -------------------------------------------------------------------------------- /write/01.big-add.js: -------------------------------------------------------------------------------- 1 | const a = "3782647863278468012934670"; 2 | const b = "23784678091370408971329048718239749083"; 3 | 4 | /** 5 | * 步骤 6 | * 1、取两个中最长的长度 7 | * 2、补0 8 | * 3、初始化t、f、res 9 | * 4、从后往前遍历 10 | * 5、计算两个数字的和为t 11 | * 6、是否进1,Math.floor(t/10) 12 | * 7、拼接字符串 13 | * 8、for循环之后,判断是否要在前头补1 14 | * @param {*} a 15 | * @param {*} b 16 | * @returns 17 | */ 18 | function bigSum(a, b) { 19 | // 取两数长度最大值 20 | const maxLength = Math.max(a.length, b.length); 21 | // 短的补0 22 | a = a.padStart(maxLength, "0"); 23 | b = b.padStart(maxLength, "0"); 24 | // f:是否进1 25 | // t:当前两个数字的和 26 | let t = (f = 0); 27 | // 要返回的结果 28 | let res = ""; 29 | for (let i = maxLength - 1; i >= 0; i--) { 30 | t = parseInt(a[i]) + parseInt(b[i]) + f; 31 | f = Math.floor(t / 10); 32 | res = (t % 10) + res; 33 | } 34 | // for循环之后,如果f为1,说明要进1,在res前头补一位 35 | if (f === 1) { 36 | res = "1" + "res"; 37 | } 38 | return res; 39 | } 40 | 41 | console.log(bigSum(a, b)); 42 | -------------------------------------------------------------------------------- /write/02.small-add.js: -------------------------------------------------------------------------------- 1 | function smallAdd(a, b) { 2 | let res = a + b; 3 | console.log(res); 4 | console.log(res.toFixed(12)); 5 | console.log(parseFloat(res.toFixed(12))); 6 | return parseFloat(res.toFixed(12)); 7 | } 8 | 9 | smallAdd(0.1, 0.2); 10 | -------------------------------------------------------------------------------- /write/03.width-height.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 20 | 21 | 22 |
23 |
24 |
25 | 26 | -------------------------------------------------------------------------------- /write/04.deep-clone.js: -------------------------------------------------------------------------------- 1 | // 拷贝目标 2 | let obj = { 3 | url: "/api/list", 4 | method: "GET", 5 | cache: false, 6 | timeout: 1000, 7 | key: Symbol("KEY"), 8 | big: 10n, 9 | n: null, 10 | u: undefined, 11 | headers: { 12 | "Content-Type": "application/json", 13 | post: { 14 | "X-Token": "xxx", 15 | }, 16 | }, 17 | arr: [10, 20, 30], 18 | reg: /^\d+$/, 19 | time: new Date(), 20 | fn: function () { 21 | console.log(this); 22 | }, 23 | err: new Error("xxx"), 24 | }; 25 | obj.obj = obj; 26 | //================ 方法一(最便捷)================= 27 | // let newObj = JSON.parse(JSON.stringify(obj)); 28 | 29 | //================ 方法二(实现数组和对象深拷贝)================= 30 | /** 31 | * 深拷贝 32 | * @param {*} obj 33 | * @param {*} hash 引入WeakMap解决循环引用问题 34 | * @returns 35 | */ 36 | function deepClone(obj, hash = new WeakMap()) { 37 | // 如果是undifined、null、Error类型、基础数据类型,直接return 38 | if (obj === null || obj instanceof Error || typeof obj !== 'object') return obj 39 | // Date和RegExp,重新new 40 | if (obj instanceof Date) return new Date(obj) 41 | if (obj instanceof RegExp) return new RegExp(obj) 42 | // 引入hash,WeakMap,解决循环引用问题 43 | if (hash.get(obj)) return hash.get(obj) 44 | // 针对数组和对象,new实例 45 | const newObj = new obj.constructor 46 | hash.set(obj, newObj) 47 | for (const key in obj) { 48 | if (obj.hasOwnProperty(key)) { 49 | // 递归遍历,拷贝数组和对象,将hash传入,解决循环引用问题 50 | newObj[key] = deepClone(obj[key], hash) 51 | } 52 | } 53 | return obj 54 | } 55 | 56 | let deepObj = deepClone(obj); 57 | console.log(deepObj); 58 | 59 | -------------------------------------------------------------------------------- /write/05.native-ajax.js: -------------------------------------------------------------------------------- 1 | function sendAjax(obj) { 2 | var url = obj.url; 3 | var method = obj.method; 4 | var async = obj.async == undefined ? true : obj.async; 5 | var data = obj.data; 6 | 7 | function splitStr(data) { 8 | let str = ""; 9 | for (var key in data) { 10 | let s = key + "=" + data.key + "&"; 11 | str += s; 12 | } 13 | return str.substring(0, str.length - 1); 14 | } 15 | var xhttp; 16 | if (window.XMLHttpRequest) { 17 | xhttp = new XMLHttpRequest(); 18 | } else { 19 | xhttp = new ActiveXObject(); 20 | } 21 | // send 22 | if (obj.method == "get" || obj.method == "GET") { 23 | if (data == undefined) { 24 | xhttp.open("GET", url, async); 25 | xhttp.send(); 26 | } else { 27 | xhttp.open("GET", url + splitStr(data), async); 28 | xhttp.send(); 29 | } 30 | } else if (method == "post" || method == "POST") { 31 | if (data == undefined) throw "method POST can not without data to send"; 32 | else { 33 | xhttp.open("POST", url, async); 34 | xhttp.setRuquestHeader( 35 | "Content-type", 36 | "application/x-www-form-urlencoded" 37 | ); 38 | xhttp.send(data); 39 | } 40 | } else if (method == undefined || method == "") 41 | throw "method can not be empty"; 42 | 43 | xhr.open(method, url, async); 44 | // response 45 | xhttp.onreadystatechange = function () { 46 | if (xhttp.readyState == 4) { 47 | obj.success(JSON.parse(xhttp.responseText)); 48 | } else if ( 49 | xhttp.readyState == 4 && 50 | (xhttp.status != 200 || xhttp.status != 304) 51 | ) { 52 | obj.error(); 53 | } 54 | }; 55 | } 56 | 57 | sendAjax({ 58 | url: "AJAX.json", 59 | method: "get", 60 | async: true, 61 | data: { 62 | id: 100, 63 | username: "123456", 64 | }, 65 | success: function (data) { 66 | console.log(data); 67 | }, 68 | error: function () { 69 | console.log("error data"); 70 | }, 71 | }); 72 | -------------------------------------------------------------------------------- /write/06.debounce-throttle.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 防抖 3 | * 原理:在时间被触发n秒之后再执行回调,如果在这n秒内又被触发,则重新计时 4 | * 适用场景: 5 | * 按钮提交场景:防止多次提交按钮,只执行最后提交的一次 6 | * 搜索框联想场景:防止联想发送请求,只发送最后一次输入 7 | * @param {*} fn 8 | * @param {*} wait 9 | * @returns 10 | */ 11 | function debounce(fn, wait) { 12 | let timer = null; 13 | return function () { 14 | const context = this; 15 | const args = arguments; 16 | clearTimeout(timer); 17 | timer = setTimeout(() => { 18 | fn.apply(context, args); 19 | }, wait); 20 | }; 21 | } 22 | 23 | window.addEventListener( 24 | "scroll", 25 | debounce(() => { 26 | console.log("debounce 滚动条"); 27 | }, 1000) 28 | ); 29 | /** 30 | * 节流 31 | * 原理: 规定在一个单位时间内,只能触发一次函数。如果这个单位时间内触发多次函数,只有一次生效 32 | * 适用场景: 33 | * 拖拽场景:固定时间内只执行一次,防止高频次触发位置变动 34 | * 缩放场景:监控浏览器resize 35 | * @param {*} fn 36 | * @param {*} wait 37 | * @returns 38 | */ 39 | function throttle(fn, wait) { 40 | // 闭包 41 | let pervious = 0; 42 | let context, args; 43 | return function () { 44 | args = arguments; 45 | context = this; 46 | let now = +new Date(); 47 | if (now - pervious > wait) { 48 | fn.apply(context, args); 49 | pervious = +new Date(); 50 | } 51 | }; 52 | } 53 | window.addEventListener( 54 | "scroll", 55 | throttle(() => { 56 | console.log("throttle 滚动条"); 57 | }, 1000) 58 | ); 59 | // 定时器实现 60 | function throttleTimer(func, wait) { 61 | let timeout; 62 | return function () { 63 | const context = this; 64 | const args = arguments; 65 | if (!timeout) { 66 | timeout = setTimeout(() => { 67 | timeout = null; 68 | func.apply(context, args); 69 | }, wait); 70 | } 71 | }; 72 | } 73 | -------------------------------------------------------------------------------- /write/07.bind-call-apply.js: -------------------------------------------------------------------------------- 1 | Function.prototype.myCall = function (context, ...args) { 2 | if (typeof this !== "function") { 3 | throw new TypeError("Error"); 4 | } 5 | // 没传值,设置为window 6 | context = context || window; 7 | // 使用symbol避免命名冲突 8 | const fnSymbol = Symbol("fn"); 9 | // 将方法(this),写在作用域上 10 | context[fnSymbol] = this; 11 | const result = context[fnSymbol](...args); 12 | delete context[fnSymbol]; 13 | return result; 14 | }; 15 | Function.prototype.myApply = function (context, argsList) { 16 | if (typeof this !== "function") { 17 | throw new TypeError("Error"); 18 | } 19 | // 没传值,设置为window 20 | context = context || window; 21 | // 使用symbol避免命名冲突 22 | const fnSymbol = Symbol("fn"); 23 | // 将方法(this),写在作用域上 24 | context[fnSymbol] = this; 25 | const result = context[fnSymbol](...argsList); 26 | delete context[fnSymbol]; 27 | return result; 28 | }; 29 | Function.prototype.myBind = function (context, ...args) { 30 | if (typeof this !== "function") throw new TypeError("Error"); 31 | context = context || window; 32 | const fnSymbol = Symbol("fn"); 33 | context[fnSymbol] = this; 34 | return function (..._args) { 35 | // 拼接参数ƒ 36 | const result = context[fnSymbol](...args.concat(..._args)); 37 | delete context[fnSymbol]; 38 | return result; 39 | }; 40 | }; 41 | let p1 = { 42 | name: "Tom", 43 | say(age, hobby) { 44 | console.log(`我叫${this.name} 我今年${age} 喜欢${hobby}`); 45 | }, 46 | }; 47 | let p2 = { 48 | name: "Bob", 49 | }; 50 | // p1.say.myApply(p2, [18, "唱歌"]); 51 | const f = p1.say.myBind(p2, "32", "搞钱"); 52 | f(); 53 | -------------------------------------------------------------------------------- /write/08.new.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 1、首先函数接受不定量的参数,第一个参数为构造函数,接下来的参数被构造函数使用 3 | * 2、然后内部创建一个空对象 obj 4 | * 3、因为 obj 对象需要访问到构造函数原型链上的属性,所以我们通过 setPrototypeOf 将两者联系起来 5 | * 这段代码等同于 obj.__proto__ = Con.prototype 6 | * 4、将 obj 绑定到构造函数上,并且传入剩余的参数 7 | * 5、判断构造函数返回值是否为对象,如果为对象就使用构造函数返回的值,否则使用 obj, 8 | * 这样就实现了忽略构造函数返回的原始值 9 | * @param {*} Con 10 | * @param {...any} args 11 | */ 12 | function create(Con, ...args) { 13 | // 然后内部创建一个空对象 obj 14 | let obj = {}; 15 | // 这段代码等同于 obj.__proto__ = Con.prototype 16 | Object.setPrototypeOf(obj, Con.prototype); 17 | // 将 obj 绑定到构造函数上,并且传入剩余的参数 18 | let result = Con.apply(obj, args); 19 | // 判断构造函数返回值是否为对象,如果为对象就使用构造函数返回的值,否则使用 obj, 20 | return result instanceof Object ? result : obj; 21 | } 22 | -------------------------------------------------------------------------------- /write/09.currying.js: -------------------------------------------------------------------------------- 1 | // function sum() { 2 | // let allArgs = Array.prototype.slice.call(arguments); 3 | // let add = function(){ 4 | // allArgs.push(...arguments) // 每次调用 sum 函数都收集参数 5 | // return add 6 | // } 7 | // // 重写 toString 方法,函数执行的时候会自动调用toString()方法,计算返回所有参数结果 8 | // add.toString = function () { 9 | // return allArgs.reduce((a, b) => a+b) 10 | // } 11 | // return add 12 | // } 13 | 14 | 15 | // const s1 = sum(1, 2, 3) 16 | // const s2 = sum(4)(5)(6) 17 | // const s3 = sum(7,8)(9, 10)(11) 18 | 19 | // alert(s3) 20 | 21 | function currying() { 22 | // 转化成数组 23 | const arr = Array.prototype.slice.call(arguments) 24 | console.log('arr ', arr) 25 | const add = function() { 26 | // 每次调用 sum 函数都收集参数 27 | arr.push(...arguments) 28 | return add 29 | } 30 | add.toString = function() { 31 | console.log('toString', arr) // toString (5) [7, 8, 9, 10, 11] 32 | // 重写 toString 方法,函数执行的时候会自动调用toString()方法,计算返回所有参数结果 33 | return arr.reduce((total, item) => (total + item)) 34 | } 35 | return add 36 | } 37 | 38 | const s1 = currying(1, 2, 3) 39 | const s2 = currying(4)(5)(6) 40 | const s3 = currying(7,8)(9, 10)(11) 41 | 42 | console.log(typeof s1) // function 43 | console.log(typeof s2) // function 44 | console.log(typeof s3) // function 45 | console.log(s3) 46 | 47 | -------------------------------------------------------------------------------- /write/10.reduce.js: -------------------------------------------------------------------------------- 1 | // Array.reduce是Array.prototype上的原型方法 2 | // Array.reduce 接收两个参数:callback函数和initialVaule初始值[可选] 3 | // 没有初始值 && 空数字 会进行报错 4 | // 没有初始值 && 数组长度为1 直接返回数组里的这一项,不需要执行callback 5 | // 没有初始值 && 数组长度大于1 正常流程走 index=1 6 | // 有初始值 && 数组长度大于1 正常流程走 index=0 7 | // reduce返回值是pre 8 | Array.prototype.myReduce = function (cb, initValue) { 9 | // if (!Array.isArray(this)) { 10 | // throw new TypeError("not a array"); 11 | // } 12 | // // 数组为空,并且有初始值,报错 13 | // if (this.length === 0 && arguments.length < 2) { 14 | // throw new TypeError("Reduce of empty array with no initial value"); 15 | // } 16 | let arr = this; 17 | let res = null; 18 | let existInitValue = arguments.length > 1; 19 | // 判断有没有初始值 20 | res = existInitValue ? initValue : arr[0]; 21 | arr.forEach((item, index) => { 22 | if (!existInitValue && index === 0) return; 23 | res = cb(res, item, index, arr); // cb 每次执行完都会返回一个新的 res值,覆盖之前的 res 24 | }); 25 | return res; 26 | }; 27 | 28 | var points = { 29 | HarryPotter: 500, 30 | CedricDiggory: 750, 31 | RonaldWeasley: 100, 32 | HermioneGranger: 1270, 33 | }; 34 | 35 | var wizards = [ 36 | { 37 | name: "Harry Potter", 38 | house: "Gryfindor", 39 | }, 40 | { 41 | name: "Cedric Diggory", 42 | house: "Hufflepuff", 43 | }, 44 | { 45 | name: "Tonks", 46 | house: "Hufflepuff", 47 | }, 48 | { 49 | name: "Ronald Weasley", 50 | house: "Gryfindor", 51 | }, 52 | { 53 | name: "Hermione Granger", 54 | house: "Gryfindor", 55 | }, 56 | ]; 57 | 58 | var wizardsAsAnObject = wizards.myReduce(function (obj, wizard) { 59 | // 移除巫师名字中的空格,用来获取对应的 points 60 | var key = wizard.name.replace(" ", ""); 61 | 62 | // 如果wizard有points,则加上它,否则设置为0 63 | if (points[key]) { 64 | wizard.points = points[key]; 65 | } else { 66 | wizard.points = 0; 67 | } 68 | 69 | // 删除 name 属性 70 | delete wizard.name; 71 | 72 | // 把 wizard 数据添加到新对象中 73 | obj[key] = wizard; 74 | 75 | // 返回该对象 76 | return obj; 77 | }, {}); 78 | 79 | console.log(wizardsAsAnObject) 80 | 81 | /** 82 | * 求和 83 | */ 84 | // var total = [1, 2, 3].myReduce(function (sum, current) { 85 | // console.log(sum, current) 86 | // return sum + current; 87 | // }, 0); 88 | // console.log(total) 89 | 90 | /** 91 | * 组合多个数组 92 | */ 93 | // var wizards = [ 94 | // { 95 | // name: "Harry Potter", 96 | // house: "Gryfindor", 97 | // }, 98 | // { 99 | // name: "Cedric Diggory", 100 | // house: "Hufflepuff", 101 | // }, 102 | // { 103 | // name: "Tonks", 104 | // house: "Hufflepuff", 105 | // }, 106 | // { 107 | // name: "Ronald Weasley", 108 | // house: "Gryfindor", 109 | // }, 110 | // { 111 | // name: "Hermione Granger", 112 | // house: "Gryfindor", 113 | // }, 114 | // ]; 115 | // var hufflepuff = wizards.myReduce(function (newArr, wizard) { 116 | // console.log(newArr, wizard); 117 | // if (wizard.house === "Hufflepuff") { 118 | // newArr.push(wizard.name); 119 | // } 120 | // return newArr; 121 | // }, []); 122 | // console.log(hufflepuff); 123 | -------------------------------------------------------------------------------- /write/11.flatten.js: -------------------------------------------------------------------------------- 1 | var arr = [[1, 2, 2], [3, '4', 5, 5], [6, 7, 8, 9, [11, 12, [12, 13, [14]]]], 10]; 2 | 3 | /** 4 | * toString() + split(',') 5 | */ 6 | // console.log(arr.toString().split(",")); 7 | 8 | /** 9 | * 递归 10 | * @param {*} arr 11 | * @returns 12 | */ 13 | // function flatten(arr) { 14 | // if (arr.length) { 15 | // return arr.reduce((prev, current) => { 16 | // return Array.isArray(current) ? [...prev, ...flatten(current)] : [...prev, current] 17 | // }, []) 18 | // } 19 | // } 20 | // console.log(flatten(arr)); 21 | 22 | /** 23 | * 迭代 24 | * @param {*} arr 25 | * @returns 26 | */ 27 | function flatten(arr) { 28 | if (!arr.length) return; 29 | while (arr.some((item) => Array.isArray(item))) { 30 | arr = [].concat(...arr); 31 | } 32 | return arr; 33 | } 34 | console.log( 35 | flatten(arr) 36 | ); 37 | -------------------------------------------------------------------------------- /write/12.jsonp.js: -------------------------------------------------------------------------------- 1 | function jsonp({ url, params, cb }) { 2 | return new Promise((resolve, reject) => { 3 | let script = document.createElement("script"); 4 | window[cb] = function (data) { 5 | resolve(data); 6 | document.body.removeChild(script); 7 | }; 8 | params = { ...params, cb }; // wd=b&cb=show 9 | let arrs = []; 10 | for (let key in params) { 11 | arrs.push(`${key}=${params[key]}`); 12 | } 13 | script.src = `${url}?${arrs.join("&")}`; 14 | document.body.appendChild(script); 15 | }); 16 | } 17 | // 只能发送get请求 不支持post put delete 18 | // 不安全 xss攻击 不采用 19 | jsonp({ 20 | url: "http://localhost:3000/say", 21 | params: { wd: "我爱你" }, 22 | cb: "show", 23 | }).then((data) => { 24 | console.log(data); 25 | }); 26 | -------------------------------------------------------------------------------- /write/13.stringify.js: -------------------------------------------------------------------------------- 1 | // 概念解释 2 | // JSON.stringify([,replacer,[,space]])方法是将一个 JavaScript 值(对象或者数组)转换为一个 JSON 字符串。此处模拟实现,不考虑可选的第二个参数 replacer 和第三个参数 space 3 | 4 | // 转换规则如下: 5 | 6 | // 基本数据类型 7 | 8 | // undefined 转换之后仍是 undefined(类型也是 undefined) 9 | 10 | // boolean 值转换之后是字符串 "false"/"true" 11 | 12 | // number 类型(除了 NaN 和 Infinity)转换之后是字符串类型的数值 13 | 14 | // symbol 转换之后是 undefined 15 | 16 | // null 转换之后是字符串 "null" 17 | 18 | // string 转换之后是字符串 string 19 | 20 | // NaN 和 Infinity 转换之后是字符串 "null" 21 | 22 | // 如果是函数类型 23 | 24 | // 转换之后是 undefined 25 | 26 | // 如果是对象类型(非函数) 27 | 28 | // 如果有 toJSON()方法,那么序列化 toJSON()的返回值 29 | 30 | // 如果是一个数组,如果属性值中出现了 undefined、任意的函数以及 symbol,转换成字符串"null" 31 | 32 | // 如果是 RegExp 对象,返回{}(类型是 string) 33 | 34 | // 如果是 Date 对象,返回 Date 的 toJSON 字符串值 35 | 36 | // 如果是普通对象 37 | 38 | // 如果属性值中出现了 undefined、任意的函数以及 symbol 值,忽略 39 | // 所有以 symbol 为属性键的属性都会被完全会忽略掉 40 | // 对包含循环引用的对象(对象之间相互引用,形成无限循环)执行此方法,会抛出错误 41 | 42 | // 代码实现 43 | if (!window.JSON) { 44 | window.JSON = { 45 | parse: function (sJSON) { 46 | return eval("(" + sJSON + ")"); 47 | }, 48 | stringify: (function () { 49 | var toString = Object.prototype.toString; 50 | var isArray = 51 | Array.isArray || 52 | function (a) { 53 | return toString.call(a) === "[object Array]"; 54 | }; 55 | var escMap = { 56 | '"': '\\"', 57 | "\\": "\\\\", 58 | "\b": "\\b", 59 | "\f": "\\f", 60 | "\n": "\\n", 61 | "\r": "\\r", 62 | "\t": "\\t", 63 | }; 64 | var escFunc = function (m) { 65 | return ( 66 | escMap[m] || 67 | "\\u" + (m.charCodeAt(0) + 0x10000).toString(16).substr(1) 68 | ); 69 | }; 70 | var escRE = /[\\"\u0000-\u001F\u2028\u2029]/g; 71 | return function stringify(value) { 72 | if (value == null) { 73 | return "null"; 74 | } else if (typeof value === "number") { 75 | return isFinite(value) ? value.toString() : "null"; 76 | } else if (typeof value === "boolean") { 77 | return value.toString(); 78 | } else if (typeof value === "object") { 79 | if (typeof value.toJSON === "function") { 80 | return stringify(value.toJSON()); 81 | } else if (isArray(value)) { 82 | var res = "["; 83 | for (var i = 0; i < value.length; i++) 84 | res += (i ? ", " : "") + stringify(value[i]); 85 | return res + "]"; 86 | } else if (toString.call(value) === "[object Object]") { 87 | var tmp = []; 88 | for (var k in value) { 89 | if (value.hasOwnProperty(k)) 90 | tmp.push(stringify(k) + ": " + stringify(value[k])); 91 | } 92 | return "{" + tmp.join(", ") + "}"; 93 | } 94 | } 95 | return '"' + value.toString().replace(escRE, escFunc) + '"'; 96 | }; 97 | })(), 98 | }; 99 | } 100 | -------------------------------------------------------------------------------- /write/14.heart-check.js: -------------------------------------------------------------------------------- 1 | let socket; // websocket的实例 2 | let lockReconnect = false; // 避免重复连接 3 | 4 | //新建websocket的函数 页面初始化 断开连接时重新调用 5 | const getwebsocket = () => { 6 | let wsUrl = "ws://ip"; //url网址 7 | socket = new WebSocket(wsUrl); 8 | // 绑定一些事件,onerror,onclose,onopen,onmessage 9 | // 如果希望WebSocket 连接一直保持,可以在close或者error上绑定重连方法 10 | // 这样一般正常情况下失去连接时,触发onclose方法,就能重连了。 11 | socket.onerror = function (event) { 12 | console.log("websocket服务出错了"); 13 | reconnect(wsUrl); 14 | }; 15 | socket.onclose = function (event) { 16 | console.log("websocket服务关闭了"); 17 | reconnect(wsUrl); 18 | }; 19 | socket.onopen = function (event) { 20 | heartCheck.reset().start(); //传递信息 21 | }; 22 | socket.onmessage = function (event) { 23 | //如果获取到消息,心跳检测重置 24 | //拿到任何消息都说明当前连接是正常的 25 | console.log("websocket服务获得数据了"); 26 | //接受消息后的UI变化 27 | doWithMsg(event.data); 28 | heartCheck.reset().start(); 29 | }; 30 | //收到消息推送 31 | function doWithMsg(msg) { 32 | //执行业务代码..... 33 | } 34 | }; 35 | // 重新连接 36 | const reconnect = (url) => { 37 | if (lockReconnect) return; 38 | lockReconnect = true; 39 | //没连接上会一直重连,设置延迟避免请求过多 40 | setTimeout(function () { 41 | getwebsocket(); 42 | lockReconnect = false; 43 | }, 2000); 44 | }; 45 | //心跳检测 46 | let heartCheck = { 47 | timeout: 60000, //60秒 48 | timeoutObj: null, 49 | serverTimeoutObj: null, 50 | reset: () => { 51 | clearTimeout(this.timeoutObj); 52 | clearTimeout(this.serverTimeoutObj); 53 | return this; 54 | }, 55 | start: () => { 56 | let _this = this; 57 | this.timeoutObj = setTimeout(() => { 58 | //这里发送一个心跳,后端收到后,返回一个心跳消息, 59 | //onmessage拿到返回的心跳就说明连接正常 60 | socket.send("心跳测试"); 61 | //如果超过一定时间还没重置,说明后端主动断开了 62 | _this.serverTimeoutObj = setTimeout(function () { 63 | //这里再onclose里执行了reconnect, 64 | // 所以这里检测到超时之后,执行socket.close()就触发了onclose,进而就执行了reconnect。 65 | socket.close(); 66 | }, _this.timeout); 67 | }, this.timeout); 68 | }, 69 | }; 70 | -------------------------------------------------------------------------------- /write/15.private-variable.js: -------------------------------------------------------------------------------- 1 | function Person(name) { 2 | var _name = name; 3 | this.name = name; 4 | this.getName = function () { 5 | return _name; 6 | // console.log(_name); 7 | }; 8 | } 9 | 10 | var p = new Person("bibibi"); 11 | console.log(p.name); //bibibi 12 | console.log(p._name); //undefined 13 | console.log(p.getName()); //bibibi 14 | -------------------------------------------------------------------------------- /write/16.fill.js: -------------------------------------------------------------------------------- 1 | let arr = new Array(8) 2 | 3 | Array.prototype.myFill = function(value, start = 0, end = this.length) { 4 | if (start < end) { 5 | this[start] = value 6 | this.myFill(value, start + 1, end) 7 | } 8 | return this 9 | } 10 | 11 | let a1 = [...arr].myFill(true) 12 | let a2 = [...arr].myFill('a', 3, 6) 13 | console.log(arr) 14 | console.log(a1) 15 | console.log(a2) -------------------------------------------------------------------------------- /write/17.removing-duplicate.js: -------------------------------------------------------------------------------- 1 | const arr = [2, 5, 6, 2, 5, 1, 7, 10, "a", "b", "a"]; 2 | 3 | // 1、new Set 4 | const newArr1 = [...new Set(arr)]; 5 | console.log(newArr1); 6 | 7 | // 2、filter + indexOf 8 | function unique1(list) { 9 | return list.filter((item, index, array) => array.indexOf(item) === index); 10 | } 11 | const newArr2 = unique1(arr); 12 | console.log(newArr2); 13 | 14 | // 3、循环 + indexOf不存在push进新数组 15 | function unique2(list) { 16 | let arr = []; 17 | list.forEach((item) => { 18 | if (arr.indexOf(item) === -1) arr.push(item); 19 | }); 20 | return arr; 21 | } 22 | const newArr3 = unique2(arr); 23 | console.log(newArr3); 24 | -------------------------------------------------------------------------------- /write/18.sort.js: -------------------------------------------------------------------------------- 1 | /** 2 | * sort 3 | * a - b,由小 => 大 4 | */ 5 | const arr = [5, 2, 1, 9, 0, 10, 100, 6, 3]; 6 | const newArr1 = [...arr].sort((a, b) => a - b); 7 | console.log("sort排序 ", newArr1); 8 | 9 | /** 10 | * 冒泡排序 =》循环,相邻两个比较,如果左边比右边大,调换位置 11 | * 时间复杂度O(n^2) 12 | * @param {*} list 13 | * @returns 14 | */ 15 | function sort1(list) { 16 | for (let i = 0; i < list.length; i++) { 17 | for (let j = i + 1; j < list.length; j++) { 18 | let tempi = list[i]; 19 | let tempj = list[j]; 20 | if (tempi > tempj) { 21 | list[i] = tempj; 22 | list[j] = tempi; 23 | } 24 | } 25 | } 26 | return list; 27 | } 28 | const newArr2 = sort1([...arr]); 29 | console.log("冒泡排序 ", newArr2); 30 | 31 | /** 32 | * 快排,数组中间取值,遍历,比中间值小的放左边边,比中间值大的放右边,递归,concat拼接 33 | * 快排的平均时间复杂度O(nlog2n) 34 | * @param {*} list 35 | * @returns 36 | */ 37 | function fastSort(list) { 38 | // 如果数组长度小于等于1,直接return 39 | if (list.length <= 1) return list; 40 | // 取中间值索引 41 | let mid = Math.floor(list / 2); 42 | // 取中间值 43 | let current = list.splice(mid, 1); 44 | // 声明left、right数组 45 | let left = [], 46 | right = []; 47 | // 循环变量,比大小,小的push进left,大的push进right 48 | for (let i = 0; i < list.length; i++) { 49 | if (list[i] < current) { 50 | left.push(list[i]); 51 | } else { 52 | right.push(list[i]); 53 | } 54 | } 55 | // 按照 left + current + right,拼接数组,递归遍历 56 | return fastSort(left).concat(current, fastSort(right)); 57 | } 58 | const newArr3 = fastSort([...arr]); 59 | console.log("快排 ", newArr3); 60 | -------------------------------------------------------------------------------- /write/19.promise-application.js: -------------------------------------------------------------------------------- 1 | const MyPromise = require("./19.promise-resource"); 2 | 3 | MyPromise.resolve().then(res => { 4 | console.log('1 ', res); 5 | // return MyPromise.resolve(4); 6 | }).then((res) => { 7 | console.log('2 ', res) 8 | }) 9 | 10 | MyPromise.resolve().then(() => { 11 | console.log('3'); 12 | }).then(() => { 13 | console.log('4'); 14 | }).then(() => { 15 | console.log('5'); 16 | }).then(() => { 17 | console.log('6'); 18 | }).then(() =>{ 19 | console.log('7'); 20 | }) 21 | 22 | // let p = new Promise(resolve => { 23 | // resolve(0); 24 | // }); 25 | // var p2 = p.then(data => { 26 | // // 循环引用,自己等待自己完成,一辈子完不成 27 | // return p2; 28 | // }) 29 | 30 | 31 | // // MyPromise.resolve().then(() => { 32 | // // console.log(0); 33 | // // return MyPromise.resolve(4); 34 | // // }).then((res) => { 35 | // // console.log(res) 36 | // // }) 37 | 38 | // // const promise = new MyPromise((resolve, reject) => { 39 | // // resolve(100) 40 | // // // reject('err') 41 | // // }) 42 | 43 | // // promise 44 | // // .then() 45 | // // .then() 46 | // // .then() 47 | // // .then(value => console.log(value), reason => console.log(reason)) 48 | 49 | // const promise = new Promise((resolve, reject) => { 50 | // resolve("success"); 51 | // // setTimeout(() => { 52 | // // 异步方法,then调用的时候,status还是pending,不往下处理 53 | // // resolve("success"); 54 | // // }, 2000); 55 | // // throw new Error('执行器错误') 56 | // }); 57 | 58 | // const p1 = promise.then(value => { 59 | // console.log('1') 60 | // console.log('resolve ', value) 61 | // // Promise.resolve('111111') 62 | // }) 63 | 64 | // p1.then( 65 | // (value) => { 66 | // console.log(2) 67 | // console.log("resolve1", value); 68 | // }, 69 | // (reason) => { 70 | // console.log(3) 71 | // console.log("reject1", reason); 72 | // } 73 | // ); 74 | 75 | // // promise.then( 76 | // // (value) => { 77 | // // console.log(1) 78 | // // console.log("resolve2", value); 79 | // // throw new Error('then error') 80 | // // }, 81 | // // (reason) => { 82 | // // console.log(2) 83 | // // console.log("reject2", reason); 84 | // // } 85 | // // ).then( 86 | // // (value) => { 87 | // // console.log(3) 88 | // // console.log("resolve3", value); 89 | // // }, 90 | // // (reason) => { 91 | // // console.log(4) 92 | // // console.log("reject3", reason); 93 | // // } 94 | // // ).then( 95 | // // (value) => { 96 | // // console.log(5) 97 | // // console.log("resolve4", value); 98 | // // }, 99 | // // (reason) => { 100 | // // console.log(6) 101 | // // console.log("reject4", reason); 102 | // // } 103 | // // ); 104 | 105 | -------------------------------------------------------------------------------- /write/19.promise-es6.js: -------------------------------------------------------------------------------- 1 | class Mypromise { 2 | constructor(fn) { 3 | // 表示状态 4 | this.state = "pending"; 5 | // 表示then注册的成功函数 6 | this.successFun = []; 7 | // 表示then注册的失败函数 8 | this.failFun = []; 9 | 10 | let resolve = (val) => { 11 | // 保持状态改变不可变(resolve和reject只准触发一种) 12 | if (this.state !== "pending") return; 13 | 14 | // 成功触发时机 改变状态 同时执行在then注册的回调事件 15 | this.state = "success"; 16 | // 为了保证then事件先注册(主要是考虑在promise里面写同步代码) promise规范 这里为模拟异步 17 | setTimeout(() => { 18 | // 执行当前事件里面所有的注册函数 19 | this.successFun.forEach((item) => item.call(this, val)); 20 | }); 21 | }; 22 | 23 | let reject = (err) => { 24 | if (this.state !== "pending") return; 25 | // 失败触发时机 改变状态 同时执行在then注册的回调事件 26 | this.state = "fail"; 27 | // 为了保证then事件先注册(主要是考虑在promise里面写同步代码) promise规范 这里模拟异步 28 | setTimeout(() => { 29 | this.failFun.forEach((item) => item.call(this, err)); 30 | }); 31 | }; 32 | // 调用函数 33 | try { 34 | fn(resolve, reject); 35 | } catch (error) { 36 | reject(error); 37 | } 38 | } 39 | 40 | // 实例方法 then 41 | 42 | then(resolveCallback, rejectCallback) { 43 | // 判断回调是否是函数 44 | resolveCallback = 45 | typeof resolveCallback !== "function" ? (v) => v : resolveCallback; 46 | rejectCallback = 47 | typeof rejectCallback !== "function" 48 | ? (err) => { 49 | throw err; 50 | } 51 | : rejectCallback; 52 | // 为了保持链式调用 继续返回promise 53 | return new Mypromise((resolve, reject) => { 54 | // 将回调注册到successFun事件集合里面去 55 | this.successFun.push((val) => { 56 | try { 57 | // 执行回调函数 58 | let x = resolveCallback(val); 59 | //(最难的一点) 60 | // 如果回调函数结果是普通值 那么就resolve出去给下一个then链式调用 如果是一个promise对象(代表又是一个异步) 那么调用x的then方法 将resolve和reject传进去 等到x内部的异步 执行完毕的时候(状态完成)就会自动执行传入的resolve 这样就控制了链式调用的顺序 61 | x instanceof Mypromise ? x.then(resolve, reject) : resolve(x); 62 | } catch (error) { 63 | reject(error); 64 | } 65 | }); 66 | 67 | this.failFun.push((val) => { 68 | try { 69 | // 执行回调函数 70 | let x = rejectCallback(val); 71 | x instanceof Mypromise ? x.then(resolve, reject) : reject(x); 72 | } catch (error) { 73 | reject(error); 74 | } 75 | }); 76 | }); 77 | } 78 | //静态方法 79 | static all(promiseArr) { 80 | let result = []; 81 | //声明一个计数器 每一个promise返回就加一 82 | let count = 0; 83 | return new Mypromise((resolve, reject) => { 84 | for (let i = 0; i < promiseArr.length; i++) { 85 | //这里用 Promise.resolve包装一下 防止不是Promise类型传进来 86 | Promise.resolve(promiseArr[i]).then( 87 | (res) => { 88 | //这里不能直接push数组 因为要控制顺序一一对应(感谢评论区指正) 89 | result[i] = res; 90 | count++; 91 | //只有全部的promise执行成功之后才resolve出去 92 | if (count === promiseArr.length) { 93 | resolve(result); 94 | } 95 | }, 96 | (err) => { 97 | reject(err); 98 | } 99 | ); 100 | } 101 | }); 102 | } 103 | //静态方法 104 | static race(promiseArr) { 105 | return new Mypromise((resolve, reject) => { 106 | for (let i = 0; i < promiseArr.length; i++) { 107 | Promise.resolve(promiseArr[i]).then( 108 | (res) => { 109 | //promise数组只要有任何一个promise 状态变更 就可以返回 110 | resolve(res); 111 | }, 112 | (err) => { 113 | reject(err); 114 | } 115 | ); 116 | } 117 | }); 118 | } 119 | } 120 | 121 | // 使用 122 | let promise1 = new Mypromise((resolve, reject) => { 123 | setTimeout(() => { 124 | resolve(123); 125 | }, 2000); 126 | }); 127 | let promise2 = new Mypromise((resolve, reject) => { 128 | setTimeout(() => { 129 | resolve(1234); 130 | }, 1000); 131 | }); 132 | 133 | Mypromise.all([promise1, promise2]).then((res) => { 134 | console.log(res); 135 | }); 136 | 137 | Mypromise.race([promise1, promise2]).then((res) => { 138 | console.log(res); 139 | }); 140 | 141 | console.log("~~~~~~ ", promise1); 142 | 143 | promise1 144 | .then( 145 | (res) => { 146 | console.log(res); //过两秒输出123 147 | return new Mypromise((resolve, reject) => { 148 | setTimeout(() => { 149 | resolve("success"); 150 | }, 1000); 151 | }); 152 | }, 153 | (err) => { 154 | console.log(err); 155 | } 156 | ) 157 | .then( 158 | (res) => { 159 | console.log(res); //再过一秒输出success 160 | }, 161 | (err) => { 162 | console.log(err); 163 | } 164 | ); 165 | -------------------------------------------------------------------------------- /write/20.defineProperty-proxy.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 9 | 10 | 11 |
12 | 70 | 71 | 72 | -------------------------------------------------------------------------------- /write/22.vue-ssr.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /write/23.36hexadecimal.js: -------------------------------------------------------------------------------- 1 | function nums36() { 2 | let arr = [] 3 | for (let i = 0; i < 36; i++) { 4 | if (i < 10) { 5 | arr.push(i) 6 | } else { 7 | arr.push(String.fromCharCode(i + 87)) 8 | } 9 | } 10 | return arr 11 | } 12 | 13 | function tenTo36(n) { 14 | const res = [] 15 | const num36List = nums36() 16 | console.log(num36List) 17 | while (n) { 18 | // 先取个位 19 | const y = n % 36 20 | res.unshift(num36List[y]) 21 | // 再取前面 22 | n = parseInt(n / 36) 23 | } 24 | return res.join('') 25 | } 26 | console.log(tenTo36(36)); 27 | 28 | 29 | 30 | 31 | 32 | 33 | // function getNums36() { 34 | // var nums36 = []; 35 | // for (var i = 0; i < 36; i++) { 36 | // if (i >= 0 && i <= 9) { 37 | // nums36.push(i); 38 | // } else { 39 | // nums36.push(String.fromCharCode(i + 87)); 40 | // } 41 | // } 42 | // return nums36; 43 | // } 44 | // //十进制数转成36进制 45 | // function scale36(n) { 46 | // var arr = []; 47 | // var nums36 = getNums36(); 48 | // console.log(nums36); 49 | // while (n) { 50 | // var res = n % 36; 51 | // console.log(res) 52 | // //作为下标,对应的36进制数,转换成 53 | // arr.unshift(nums36[res]); 54 | // console.log(arr) 55 | // //去掉个位 56 | // n = parseInt(n / 36); 57 | // console.log(n) 58 | // } 59 | // return arr.join(""); 60 | // } 61 | 62 | -------------------------------------------------------------------------------- /write/24.base64.js: -------------------------------------------------------------------------------- 1 | let arr = [] 2 | 3 | for (let i = 0; i < 62; i++) { 4 | if (i < 10) { 5 | arr.push(i) 6 | } else if (i < 36) { 7 | arr.push(String.fromCharCode(i + 87)) 8 | } else { 9 | arr.push(String.fromCharCode(i - 26 + 87).toUpperCase()) 10 | } 11 | } 12 | 13 | console.log(arr) -------------------------------------------------------------------------------- /write/25.virtual-list.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 简单实现虚拟列表 7 | 8 | 43 | 44 | 45 |
46 |
47 |
48 |
    49 |
  • 54 | {{ item }} 55 |
  • 56 |
57 |
58 |
59 | 60 | 101 | 102 | 103 | -------------------------------------------------------------------------------- /write/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "write", 3 | "version": "1.0.0", 4 | "main": "19.promise-resource.js", 5 | "license": "MIT", 6 | "scripts": { 7 | "test": "promises-aplus-tests 19.promise-resource" 8 | }, 9 | "devDependencies": { 10 | "promises-aplus-tests": "^2.1.2" 11 | } 12 | } 13 | --------------------------------------------------------------------------------