├── docs ├── .nojekyll ├── css │ ├── coding.md │ └── css.md ├── coding-secret │ ├── math.md │ ├── javascript-api.md │ ├── java-api.md │ └── regex.md ├── others │ ├── 至即将毕业的你.md │ └── 春招总结干货.md ├── 404.md ├── _navbar.md ├── index.css ├── data-structure │ ├── write-test.md │ └── dp.md ├── javascript │ ├── standard-build-in-objects.md │ ├── analysis-code.md │ ├── extends.md │ ├── sorts.md │ ├── questions.md │ └── write-code.md ├── FAQ.md ├── model │ └── model.md ├── _coverpage.md ├── db │ └── db.md ├── README.md ├── _sidebar.md ├── speed │ └── speed.md ├── index.html ├── html │ └── questions.md ├── experience │ ├── questions.md │ └── interview-hr.md ├── browser │ └── questions.md ├── webpack │ └── webpack.md ├── node │ └── node.md ├── projects │ └── project.md ├── network │ ├── tcp_udp.md │ └── http.md ├── react │ └── react.md ├── os │ └── os.md └── vue │ └── vue.md ├── .idea ├── codeStyles │ └── codeStyleConfig.xml ├── misc.xml ├── vcs.xml ├── modules.xml ├── my-book.iml └── workspace.xml └── LICENSE /docs/.nojekyll: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/css/coding.md: -------------------------------------------------------------------------------- 1 | 2 | 面试官会考察现场手撸代码的能力 3 | 4 | 5 | -------------------------------------------------------------------------------- /docs/coding-secret/math.md: -------------------------------------------------------------------------------- 1 | 2 | 你可能会用到这些数学知识,想必高中知识大家忘得差不多了,一定要熟悉一遍。 3 | 4 | ## 三角函数 5 | -------------------------------------------------------------------------------- /docs/others/至即将毕业的你.md: -------------------------------------------------------------------------------- 1 | 2 | [一毕业,就该戒掉的学生思维](https://mp.weixin.qq.com/s/PieIV3vpGbCgVRZgXZHnrg) 3 | -------------------------------------------------------------------------------- /docs/404.md: -------------------------------------------------------------------------------- 1 | # 404哎呀,这个页面找不到了 2 | ## 麻烦您,联系作者修复它 3 | 4 | QQ:827652549 5 | 6 | 邮箱:827652549@qq.com 7 | 8 | [github-issue](https://github.com/827652549/my-book/issues) 9 | -------------------------------------------------------------------------------- /.idea/codeStyles/codeStyleConfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /docs/_navbar.md: -------------------------------------------------------------------------------- 1 | * [首页](/) 2 | 3 | * [简介](README.md) 4 | 5 | * [Github](https://github.com/827652549/my-book) 6 | 7 | * [CSDN](https://blog.csdn.net/HuoYiHengYuan) 8 | 9 | * [作者](https://827652549.github.io/) 10 | 11 | -------------------------------------------------------------------------------- /docs/index.css: -------------------------------------------------------------------------------- 1 | body{ 2 | font-size: 1rem; 3 | font-family: -apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen,Ubuntu,Cantarell,'Fira Sans','Droid Sans','Helvetica Neue',sans-serif; 4 | -webkit-font-smoothing: antialiased; 5 | } 6 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /docs/data-structure/write-test.md: -------------------------------------------------------------------------------- 1 | 2 | ## 前端应该掌握哪些算法和数据结构? 3 | 4 | 一切为了能做出来题和Demo: 5 | 6 | - 数据结构:栈、队列、二叉树、链表 7 | - 算法:十大排序、动态规划、基础Api的使用(Arrays、Number、String等相关API)、实际Demo中的算法 8 | 9 | ## 前序遍历、中序遍历、后续遍历 10 | 11 | - 前序遍历:**根**、左、右 12 | - 中序遍历:左、**根**、右 13 | - 后续遍历:左、右、**根** 14 | 15 | > 助记:这个前、中、后指的是**根**的位置。 16 | -------------------------------------------------------------------------------- /docs/javascript/standard-build-in-objects.md: -------------------------------------------------------------------------------- 1 | # js标准内置对象 2 | 3 | ## Boolean 4 | 如果对象无初始值或者为`0`、`-0`、`null`、`undefined`、`false`、`''`、`NaN`、`null`,那么对象的值为**false** 5 | 6 | ## Location 7 | - Location.hash//获取#后的内容 8 | - Location.host//获取“主机名:端口号” 9 | - Location.href//获取整个URL串 10 | - Location.origin//获取“协议+主机+端口号” 11 | -------------------------------------------------------------------------------- /docs/FAQ.md: -------------------------------------------------------------------------------- 1 | ## 为什么这个项目一些知识点没有介绍得很长很深入? 2 | 3 | 本项目,是为了前端面试而存在的,面试官更青睐于"用最少的话,表达出最多的知识点",说得太久面试官会失去耐心,在职场团队里,工作效率至上,这也是本项目"精简话术"的原因之一。经过我多次的面试,所有面试官都有这个特点,所以大家放心应对面试把。 4 | 5 | ## 我有点坚持不下去了……怎么办😭? 6 | 7 | 种树最好的时间是十年前,其次是**现在**! 8 | 9 | 我也曾有过一段时间的"挫败"时光,明明准备了那么久,到最后某位面试官说"你可能选错了方向"。最后,我反思,我没有让面试官看到我的闪光点,回想到座右铭,相信我,怕输的人不会赢。 10 | 11 | 种树最好的时间是十年前,其次是**现在**! 12 | -------------------------------------------------------------------------------- /docs/model/model.md: -------------------------------------------------------------------------------- 1 | 作为一名光荣的前端工程师,这些素养方面的东西当然也要懂。面试问不?大厂会问,你自己掂量吧。 2 | 3 | ## 软件设计原则 4 | 5 | |设计原则|说明| 6 | |-|-| 7 | |开放封闭原则|一个软件实体应当对扩展开发,对修改关闭| 8 | |里式替换原则|子类型必须能够替换他们的基类型,反过来的代换不成立| 9 | |依赖倒置原则|具体要依赖于抽象,抽象不要依赖于具体| 10 | |接口隔离原则|使用多个专门的接口比使用单一的总接口总要好| 11 | |合成/聚合复用原则|在一个新对象里使用一些已有的对象,使之成为新对象的一部分;新对象通过这些对象的委派达到复用已有功能的目的| 12 | |迪米特法则/最少知识原则|一个对象应当对其他对象有尽可能少的了解| 13 | |抽象类原则|抽象类不会有实例,一般作为父类为子类继承,一般包含这个系的共同属性和方法| 14 | -------------------------------------------------------------------------------- /.idea/my-book.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /docs/_coverpage.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # 前端高频面试精粹 4 | ### 持续更新中……(8.8W+字 ) 5 | - 本文档是作者苏一恒(Junking) 校招以来的面试总结,旨在应对前端高频面试问题,精炼话术,以不变应万变。如果本文能为您得到帮助,请给予支持! 6 | 7 | [![stars](https://badgen.net/github/stars/827652549/my-book?icon=github&color=4ab8a1)](https://github.com/827652549/my-book) [![forks](https://badgen.net/github/forks/827652549/my-book?icon=github&color=4ab8a1)](https://github.com/827652549/my-book) 8 | 9 | ### 种树最好的时间是十年前,其次是现在 10 | 11 | [GitHub]() 12 | [开始阅读](README.md) 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /docs/db/db.md: -------------------------------------------------------------------------------- 1 | ## 事务的四大特性 2 | - **原子性(Atomicity)** 3 | - 原子性是指事务包含的所有操作要么全部成功,要么全部失败回滚,这和前面两篇博客介绍事务的功能是一样的概念,因此事务的操作如果成功就必须要完全应用到数据库,如果操作失败则不能对数据库有任何影响。 4 | - **一致性(Consistency)** 5 | - 一致性是指事务必须使数据库从一个一致性状态变换到另一个一致性状态,也就是说一个事务执行之前和执行之后都必须处于一致性状态。 6 | - 拿转账来说,假设用户A和用户B两者的钱加起来一共是5000,那么不管A和B之间如何转账,转几次账,事务结束后两个用户的钱相加起来应该还得是5000,这就是事务的一致性。 7 | - **隔离性(Isolation)** 8 | - 隔离性是当多个用户并发访问数据库时,比如操作同一张表时,数据库为每一个用户开启的事务,不能被其他事务的操作所干扰,多个并发事务之间要相互隔离。 9 | - 即要达到这么一种效果:对于任意两个并发的事务T1和T2,在事务T1看来,T2要么在T1开始之前就已经结束,要么在T1结束之后才开始,这样每个事务都感觉不到有其他事务在并发地执行。 10 | - **持久性(Durability):** 11 | - 持久性是指一个事务一旦被提交了,那么对数据库中的数据的改变就是永久性的,即便是在数据库系统遇到故障的情况下也不会丢失提交事务的操作。 12 | - 例如我们在使用JDBC操作数据库时,在提交事务方法后,提示用户事务操作完成,当我们程序执行完成直到看到提示后,就可以认定事务以及正确提交,即使这时候数据库出现了问题,也必须要将我们的事务完全执行完成,否则就会造成我们看到提示事务处理完毕,但是数据库因为故障而没有执行事务的重大错误。 13 | -------------------------------------------------------------------------------- /docs/coding-secret/javascript-api.md: -------------------------------------------------------------------------------- 1 | 2 | 使用JavaScript常用的api(待排版) 3 | 4 | 5 | **数组**: 6 | 7 | 遍历方法 8 | 9 | - foreach按顺序传入每个值,不返回内容 10 | - reduce,将每个值从第二个参数中获取,在第一个参数中保存,返回最终结果 11 | - filter过滤器,筛选符合条件的值,返回新数组 12 | - map按顺序传入每个值,return的值改变当前值,返回新数组 13 | 14 | 15 | - concat(数/数组)----连接数组,返回一个副本 16 | - sort()----按照字符编码排序 17 | 18 | - splice(start,deleteCount,...['items'])----从start开始删除deleteCount个元素,并添加items 19 | 20 | **字符串**: 21 | 22 | - split(正则)----以表达式拆分字符串,返回数组 23 | - slice(0,length)----按照长度裁剪字符串 24 | - replace(正则/字符串,替换成什么)----替换字符串 25 | - search(正则)----匹配正则的第一个index 26 | 27 | **节点** 28 | 29 | 添加、删除、替换、插入子节点: 30 | 31 | ```javascript 32 | //向末尾添加子元素 33 | Node.appendChild(newNode) 34 | // 方法从DOM中删除子节点。返回删除的节点。 35 | Node.removeChild(oldNode) 36 | //方法用指定的节点替换子节点,并返回被替换掉的节点。 37 | Node.replaceChild(newNode) 38 | //parentDiv中的sp2之前插入节点 39 | parentDiv.insertBefore(sp1, sp2); 40 | ``` 41 | 42 | **object**: 43 | 44 | Object.hasOwnProperty()//判断是否有某个属性 45 | Object.getPrototypeOf(1)//获取对象原型 46 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | #### 前端高频面试精粹「精炼+引导」(持续更新中) 2 | 3 | > 我做这个平台的愿景是:"为前端小白定制战略化斩获Offers的计划"。 4 | 5 | [前端面试精粹](https://827652549.github.io/my-book) 6 | 7 | - 如果你想走前端方向 8 | - 如果你现在前端零offer 9 | - 如果你想进大厂 10 | - 抑或你只是想看看自己的技术栈是不是缺少了什么 11 | 12 | **恭喜你!来对了地方!🎉** 13 | 14 | 作为一个渣本双非二本大三菜鸡儿🐔,本人在面试过程(2020.02-04)中不断总结学习,通过3个月的时间拿到**阿里**和**腾讯**等offer~(运气+努力+意识) 15 | 16 | 本项目旨在应对前端高频面试问题,精炼话术,**以不变应万变🤏**。 17 | 18 | 全文内容很大部分是我精心查阅了很多文档,然后仔细对比之后写的"话术"(不过也有copy,但也是经过精炼之后的,很少有直接copy过来的),一方面回答面试官问题,另一方面避免给自己挖深坑。 19 | 20 | 文章涉及到前端面试中常见的问题和知识点汇总,不求最深最原理,单纯是为了"面向offer备战",尽量避免给自己挖坑。 21 | 22 | 如果你也是求职备战的人,来吧,每天都来攻克知识点,每天进步看得见! 23 | 24 | !> **请大家放心贡献,我承诺一定会终身维护此项目!**本平台都是以作者的技术栈为主来设计的,如果你也想给平台做贡献,请一定仔细推敲话术,我崇尚宁缺毋滥。在项目中有不合适的地方,请读者们积极提出建议,我一定虚心学习。 25 | 26 | 在开始之前,我希望你能先看一篇我的总结文章,请关注公众号"菜鸟offer",历史文章中找到第一篇文章,听我给你好好讲讲我的经验。 27 | 28 | ![菜鸟offer-二维码](https://s1.ax1x.com/2020/04/29/JoeNhq.jpg) 29 | 30 | 不受限于学历,不受限于思维,为每一个有梦想的菜鸟分享大厂的进阶之路! 31 | 32 | !> 本项目免费且开源,仅供学习和参考,不做商业化用途,部分总结来源于网上,如果侵权,请联系我删除,谢谢您。 33 | 34 |
{docsify-updated}
35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Su Yiheng 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /docs/coding-secret/java-api.md: -------------------------------------------------------------------------------- 1 | 使用Java进行算法编程常用到的API; 2 | ### 基础知识 3 | #### 创建数组 4 | 5 | ```java 6 | int[] i = new int[arr.length]; 7 | ``` 8 | #### ASCII 9 | |符号|十进制| 10 | |-|-| 11 | |数字0|48| 12 | |A|65| 13 | |a|97| 14 | |空格|32| 15 | 16 | **A+" "=a** 17 | #### 判断是否除尽 18 | num%i==0 除尽 19 | #### 判断十位数和个位数的数字 20 | 十位数:num/10(利用int/int向下取整的特点) 21 | 22 | 个位数:num%10 23 | #### 栈 24 | ```java 25 | Stack stack = new Stack<>();//创建一个栈,存储Integer型 26 | stack.push(2);//添加2入栈中 27 | stack.pop();//取出一个数 28 | stack.isEmpty();//栈空则返回true 29 | ``` 30 | 31 | ### API技巧 32 | - 使用StringBuilder进行字符串反转,再转String 33 | 34 | ```java 35 | String str = new StringBuilder("123");//转化为StringBuilder 36 | str.reverse();//字符串反转 37 | String strResult = str + "";//StringBuilder转String 38 | ``` 39 | 40 | - 使用split(String 正则)进行字符串分割,返回String[]。注意做好测试,因为分割后末尾端的“”不会被添加到数组中。如果需要去掉首位空格,请用trim(); 41 | 42 | 43 | ```java 44 | String str = "1!2!3"; 45 | str.split("!"); 46 | ``` 47 | 48 | - 使用toCharArray 49 | > 注意处理单字符使用,不然“10”会被搞成“1”,“0” 50 | 51 | 52 | - 使用subString(beginIndex,endIndex) 53 | 54 | 注意 55 | beginIndex -- 起始索引(包括), 索引从 0 开始。 56 | 57 | endIndex -- 结束索引(不包括)。 58 | 59 | 60 | - 十进制转二进制 61 | Integer.toBinaryString(value) 62 | 63 | 传入一个十进制数value,返回一个表示二进制的tring 64 | 65 | - int转char 66 | (char)(number+'a'-97) 67 | -------------------------------------------------------------------------------- /docs/_sidebar.md: -------------------------------------------------------------------------------- 1 | * **前言** 2 | * [简介](README.md) 3 | * [FAQ](FAQ.md) 4 | * **JavaScript篇 🧱** 5 | * [Javascript](javascript/questions.md) 6 | * [js标准内置对象](javascript/standard-build-in-objects.md) 7 | * [八大继承](javascript/extends.md) 8 | * [十大排序](javascript/sorts.md) 9 | * [代码分析](javascript/analysis-code.md) 10 | * [手撸代码](javascript/write-code.md) 11 | * **CSS篇 🎨** 12 | * [CSS](css/css.md) 13 | * [现场撸码](css/coding.md) 14 | * **HTML篇 📄** 15 | * [HTML](html/questions.md) 16 | * **网络篇 🕷️** 17 | * [HTTP](network/http.md) 18 | * [TCP/UDP](network/tcp_udp.md) 19 | * **操作系统篇 🕷️** 20 | * [操作系统](os/os.md) 21 | * **浏览器和新技术 ⚽️** 22 | * [浏览器和新技术](browser/questions.md) 23 | * **React篇 ⚛️** 24 | * [React](react/react.md) 25 | * **Vue篇 🗼** 26 | * [Vue](vue/vue.md) 27 | * **Node篇 ♻️️** 28 | * [Node](node/node.md) 29 | * **数据库篇 🗂️** 30 | * [数据库](db/db.md) 31 | * **Webpack篇 🧳** 32 | * [Webpack](webpack/webpack.md) 33 | * **情景发挥篇 🏠** 34 | * [情景发挥篇](projects/project.md) 35 | * **性能优化篇 ⚡️‍** 36 | * [性能优化](speed/speed.md) 37 | * **数据结构与算法 📈️** 38 | * [常用笔试基础](data-structure/write-test.md) 39 | * [动态规划专题](data-structure/dp.md) 40 | * **前端拓展 👃** 41 | * [设计模式和软件开发规范](model/model.md) 42 | * **面试常见问题 😈** 43 | * [面试解惑一百问](experience/questions.md) 44 | * [HR应对锦囊](experience/interview-hr.md) 45 | * **笔试技巧 ✒️** 46 | * [Java常用API](coding-secret/java-api.md) 47 | * [JavaScript常用API](coding-secret/javascript-api.md) 48 | * [正则表达式](coding-secret/regex.md) 49 | * **总结️** 50 | * [春招总结干货](others/春招总结干货.md) 51 | * [至即将毕业的你](others/至即将毕业的你.md) 52 | -------------------------------------------------------------------------------- /docs/speed/speed.md: -------------------------------------------------------------------------------- 1 | 2 | # 前端的性能优化,可以从下面这几个方向出发: 3 | 4 | 1. 加载篇 5 | 2. 执行篇 6 | 3. 用户体验 7 | 8 | ## 加载 9 | #### 请求服务器资源 10 | - **减少DNS查找**:浏览器在DNS域名解析之前不会下载任何东西,减少域名主机的数量可以减少查询的次数,但可能造成并行下载数的减少,折中方案是内容分布到2-4个不同主机上。 11 | - **重用TCP连接**:尽可能地使用持久连接,以消除TCP握手的延迟。 12 | - **减少重定向**:最简单的比如有时候的跳转页面,直接a标签就行了,不必交给服务器触发重定向 13 | - **使用CDN**:内容分发网络,减少请求延迟 14 | - **删除没必要的资源**:多余资源直接删除 15 | - **客户端缓存资源**:重复数据不必再通过服务器,直接从缓存中读取 16 | - **数据压缩**:开启gzip等等压缩资源 17 | - **减少不必要的请求开销**:如HTTP首部的cookie数据 18 | - **升级HTTP/2.0**:多路复用合并TCP请求、头部压缩、二进制分帧、服务器推送 19 | - **开启负载均衡**:增加并发和吞吐量 20 | #### 页面加载 21 | - **延迟加载组件**:折叠的图片、拖拽相关的库都可以延迟加载 22 | - **预加载组件**:预先加载用户可能用到的资源 23 | - **懒加载**:延迟并非第一视觉展现的组件 24 | - **异步加载js**:异步加载不需要等待的JavaScript文件 25 | - **按需加载CSS**:通过link的media属性按需加载css 26 | - **按需加载组件**:比如使用antd库的时候,使用按需 27 | #### 资源优化 28 | - **图片优化**:响应式图片、压缩图片质量、使用占空间不大的图片格式、尽可能移除颜色为黑白色、循环的HTML5视频代替GIF、css精灵 29 | - **字体优化**:字蛛通过分析本地 CSS 与 HTML 文件获取 WebFont 中没有使用的字符,并将这些字符数据从字体中删除以实现压缩,同时生成跨浏览器使用的格式。 30 | - **音视频优化**:压缩音视频体积 31 | ## 执行 32 | - **Web Worker**创建多线程环境,将高延迟任务交给worker负担 33 | - **动画优化**:高性能消耗的动画,可以使用分层canvas缓存 34 | - **避免回流和重绘**:尽量避免浏览器重绘和回流,盒子模型、定位属性、文字相关属性、颜色相关都有机会出发回流和重绘制 35 | - **开启GPU加速**:设置opacity、transform会获得GPU加速 36 | - **防抖和节流**:给不必频繁调用的监听的执行函数设置防抖和节流 37 | ## 用户体验 38 | - **渐进增强**:先设计和构建核心体验,然后为高级浏览器增强体验 39 | - **白屏loading**:从解析url到第一个元素可见中间是白屏阶段 40 | - **骨架屏**:例如"知乎"等网站在加载阶段就展示的是骨架屏,可以在ant design里实现 41 | 42 | 43 | 44 | 45 | # 首屏渲染优化 46 | 47 | ### 白屏优化 48 | 49 | 我们先梳理下白屏时间内发生了什么: 50 | 51 | - 回车按下,浏览器解析网址,进行 DNS 查询,查询返回 IP,通过 IP 发出 HTTP(S) 请求 52 | - 服务器返回HTML,浏览器开始解析 HTML,此时触发请求 js 和 css 资源 53 | - js 被加载,开始执行 js,调用各种函数创建 DOM 并渲染到根节点,直到第一个可见元素产生 54 | 55 | 优化: 56 | 57 | 1. 增加Loading组件 58 | 59 | webpack 插件叫html-webpack-plugin ,在其中配置 html 就可以在文件中插入 loading 图。 60 | 61 | 2. 开启http2.0 62 | 3. 开启浏览器缓存 63 | 4. 组件、图片的懒加载 64 | 5. cdn 65 | -------------------------------------------------------------------------------- /docs/data-structure/dp.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ### 题目特点 4 | 5 | 动态规划的题,往往都是求一个固定值。 6 | 7 | 1. 计数(return 一个数) 8 | 9 | - 有多少种方式走到右下角 10 | - 有多少种方式选出k个数和为sum 11 | 12 | 2. 求最大值和最小值(return 一个数) 13 | 14 | - 从左上角到右下角路径到最大数字和 15 | - 最长上升子序列长度 16 | 17 | 3. 求存在性(return true/false) 18 | 19 | - 取石子游戏,先手是否必胜 20 | - 能不能选出k个数,使和为sum 21 | 22 | 23 | ### 动态规划5步骤 24 | 1. 确定状态 25 | 2. 转移方程 26 | 3. 初始条件与边界情况 27 | 4. 确定计算顺序 28 | 5. 消除冗余,优化算法 29 | 30 | ### 经典题型 31 | 32 | #### 零钱兑换 33 | 来源:[力扣(LeetCode)在线判题](https://leetcode-cn.com/problems/coin-change) 34 | 35 | 给定不同面额的硬币 coins 和一个总金额 amount。编写一个函数来计算可以凑成总金额所需的最少的硬币个数。如果没有任何一种硬币组合能组成总金额,返回 -1。 36 | 37 | **示例 1:** 38 | 39 | ```javascript 40 | 输入: coins = [1, 2, 5], amount = 11 41 | 输出: 3 42 | 解释: 11 = 5 + 5 + 1 43 | ``` 44 | 45 | **示例 2:** 46 | 47 | ```javascript 48 | 输入: coins = [2], amount = 3 49 | 输出: -1 50 | ``` 51 | 52 | **说明:** 53 | 你可以认为每种硬币的数量是无限的。 54 | 55 | 56 |
57 | 展开答案 58 | 59 | ```javascript 60 | /** 61 | * @param {number[]} coins 62 | * @param {number} amount 63 | * @return {number} 64 | */ 65 | var coinChange = function(coins, amount) { 66 | let f = new Array(amount+1); 67 | f[0] = 0; 68 | for(let i = 1 ; i < f.length;i++){ 69 | f[i] = Number.MAX_VALUE; 70 | for(let j = 0 ; j < coins.length ; j++){ 71 | if(i-coins[j]>=0&&f[i-coins[j]]!==Number.MAX_VALUE){ 72 | //获取到最小值 73 | f[i] = Math.min(f[i-coins[j]]+1,f[i]); 74 | } 75 | } 76 | } 77 | 78 | if(f[amount]!==Number.MAX_VALUE){ 79 | return f[amount]; 80 | }else{ 81 | return -1; 82 | } 83 | 84 | }; 85 | ``` 86 | 87 |
88 | 89 | #### 不同路径 90 | 91 | 来源:[力扣(LeetCode)在线判题](https://leetcode-cn.com/problems/unique-paths) 92 | 93 | ![机器人走网格不同路径例图](https://s1.ax1x.com/2020/04/05/GDGXzF.png) 94 | 95 | 例如,上图是一个7 x 3 的网格。有多少可能的路径? 96 | 97 | **示例 1:** 98 | 99 | ```javascript 100 | 输入: m = 3, n = 2 101 | 输出: 3 102 | 解释: 103 | 从左上角开始,总共有 3 条路径可以到达右下角。 104 | 1. 向右 -> 向右 -> 向下 105 | 2. 向右 -> 向下 -> 向右 106 | 3. 向下 -> 向右 -> 向右 107 | ``` 108 | 109 | **示例 2:** 110 | 111 | ```javascript 112 | 输入: m = 7, n = 3 113 | 输出: 28 114 | ``` 115 | 116 | **提示:** 117 | 118 | - 1 <= m, n <= 100 119 | - 题目数据保证答案小于等于 2 * 10 ^ 9 120 | 121 |
122 | 展开答案 123 | 124 | ```javascript 125 | 待补充。。 126 | ``` 127 | 128 |
129 | 130 | -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 前端面试精粹 6 | 7 | 8 | 9 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 |
一大波offers正在快马加鞭赶来……
20 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | -------------------------------------------------------------------------------- /docs/html/questions.md: -------------------------------------------------------------------------------- 1 | 2 | ## `
`的enctype 3 | 4 | - application/x-www-form-urlencoded(默认),在发送前编码所有字符 5 | - multipart/form-data,不对字符编码,使用文件上传时必须使用改值 6 | 7 | ## Canvas优缺点,应用场景 8 | 9 | **优点:** 10 | - 定制性强,像素级别的绘制可以绘制图像。 11 | - 画布中的形状非DOM节点,用js进行绘制,涉及到动画性能更高。 12 | - 可使用WebGL加速。 13 | 14 | **缺点:** 15 | - 交互性弱,不支持事件处理,交互区域和拖拽等功能需要根据鼠标坐标手动编程。 16 | - 功能简单,只能脚本驱动。 17 | - 弱的文本渲染能力。 18 | - 无法SEO。 19 | 20 | **应用场景:** 21 | - 复杂丰富的数据可视化展示。 22 | - 高性能滤镜,光线追踪器。 23 | - 2D休闲游戏。 24 | - 动态渲染。 25 | 26 | ## Canvas性能优化 27 | 28 | - **剪辑区域:** 假设在某个Canvas动画中,仅有右下角的小人儿展示跳跃的动画,其他画面元素不变,可以通过剪辑区域设置为小人儿的区域,之后的每一帧的重复擦出绘制都仅绘制小人儿区域的画布区域,从而优化性能。 29 | 30 | - **离屏canvas:** 动画中的每种元素(层),对渲染和动画的要求是不一样的。对很多游戏而言,主要角色变化的频率和幅度是很大的(他们通常都是走来走去,打打杀杀的),而背景变化的频率或幅度则相对较小,很明显,我们需要很频繁地更新和重绘人物,但是对于背景,我们也许只需要绘制一次,或者以很慢的频率,但绝对不必达到16ms刷新一次。使用上,分层 Canvas 也很简单。我们需要做的,仅仅是生成多个 Canvas 实例,把它们重叠放置,每个 Canvas 使用不同的 z-index 来定义堆叠的次序。然后仅在需要绘制该层的时候进行重绘。 31 | 32 | - **避免16ms阻塞:** canvas的api毕竟是用js执行的,有时候可能会因为运行了复杂的算法来导致了较短的阻塞,让执行下一帧的绘制超过了16ms,进而导致丢帧,如果算法和下一帧的绘制无关,我们可以将复杂的算法利用Web Worker或者将任务拆分来优化。 33 | 34 | - **开销较大的context赋值:** canvas的api是在context上调用的,我做了对一些常用api进行百万次规模赋值并测试运行时间,比如lineWidth、font、shadowColor等等,发现比对一个普通对象的属性赋值的开销要大1-3个数量级,其中开销最大的是font属性,所以对于优化来说,不要频繁设置绘图上下文的 font 属性,尽可能地避免无谓的绘制操作。 35 | 36 | - **阴影耗时:** 在浏览器渲染canvas的过程中,为了绘制阴影,浏览器需要将阴影先渲染到一个辅助的位图中,在将位图上的内容与屏幕Canvas进行图像合成。由于使用了这种辅助位图,所以绘制阴影可能是一件很耗时的操作。 37 | 38 | 39 | > **16ms:** 大部分显示屏是60hz的刷新率,这是因为人眼与大脑之间的协作无法感知超过60fps的画面更新。12fps大概类似手动快速翻动书籍的帧率,这明显是可以感知到不够顺滑的。24fps使得人眼感知的是连续线性的运动,这其实是归功于运动模糊的 效果。24fps是电影胶圈通常使用的帧率,因为这个帧率已经足够支撑大部分电影画面需要表达的内容,同时能够最大的减少费用支出。但是低于30fps是无法顺畅表现绚丽的画面内容的,为了达到极致的动画效果,此时就需要用到60fps来达到想要的效果 40 | 41 | ## Canvas的浏览器渲染过程原理 42 | 43 | 在向Canvas上绘制图形或图像的时候,浏览器需要按照如下步骤进行操作: 44 | 45 | **详细过程:** 46 | 47 | - 将图形或图像绘制到一个无限大的透明位图中,在绘制时遵从当前的填充模式、描边模式以及线条样式。 48 | - 将图形或图像的阴影绘制到另一幅位图中,在绘制时使用当前绘图环境的阴影设定。 49 | - 将阴影中每一个像素的alpha分量乘以绘图环境对象的globalAlpha属性值。 50 | - 将绘有阴影的位图与经过剪辑区域剪切过的canvas进行图像合成。在操作时使用当前的合成模式参数。 51 | - 将图形或图像的每一个像素颜色分量,乘以绘图环境对象的globalAlpha属性值。 52 | - 将绘有图形或图像的位图,合成到当前经过剪辑区域剪切过的canvas位图之上,在操作时使用当前的合成操作符(composition operator)。 53 | 54 | 只有在启动阴影效果时才会执行2-4步。 55 | 56 | **简单理解:** 57 | 58 | - 将图按照你规定的样式或模式绘制到一张图中(不包括透明度) 59 | - 将阴影按照你规定的绘制样式到另一张图中(不包括透明度) 60 | - 设置阴影透明度 61 | - 将阴影区域和剪辑区域合成 62 | - 设置图的透明度 63 | - 将图和剪辑区域合成 64 | 65 | ## Canvas和SVG的区别 66 | 67 | canvas和svg都是HTML5推荐使用的图形技术,Canvas基于像素,提供2D绘制函数,是一种HTML元素类型,依赖于HTML,只能通过脚本绘制图形;SVG为可伸缩矢量图形,提供一系列图形元素(Rect, Path, Circle, Line …,负责的图形就通过的属性进行解释),还有完整的动画,事件机制,本身就能独立使用,也可以嵌入到HTML中。 68 | 69 | **适用场景:** 70 | 71 | - Canvas提供的功能更原始,适合像素处理,动态渲染和大数据量绘制 72 | - svg适合静态图片展示,高保真文档查看和打印的应用场景 73 | 74 | **方案选型:** 75 | 76 | - 对于canvas,通常随着屏幕尺寸的增加,需要绘制的像素点就增多,画布需要绘制更多的像素。 77 | - 对于svg,随着屏幕上对象数量的增加,我们将其不断添加到dom中,则消耗更多的性能。 78 | 79 | 具体点选型,还是要依赖于现实和平台。 80 | 81 | -------------------------------------------------------------------------------- /docs/coding-secret/regex.md: -------------------------------------------------------------------------------- 1 | 2 | ## 普通字符 3 |   没有显式地指定为元字符的所有打印字符和非打印字符.包括大小写字母、数字、标点、其他符号. 4 | ## 非打印字符 5 | |字符|描述| 6 | |-|:-| 7 | |\cx|匹配一个由x指明的控制字符,如“\cM”代表“Control-M”;x必须为大小写字母| 8 | |\f|匹配换页符| 9 | |\n|匹配换行符| 10 | |\r|匹配回车符| 11 | |\t|匹配制表符| 12 | |\v|匹配垂直制表符| 13 | |\s|匹配任何空白字符,包括等价于 [ \f\n\r\t\v]| 14 | |\S|匹配非空白字符| 15 | 16 | ## 特殊字符 17 | 18 |   所谓特殊字符,就是一些有特殊含义的字符,如 ```runoo*b```中的 ```*```,简单的说就是表示任何字符串的意思。如果要查找字符串中的```*```符号,则需要对```*```进行转义,即在其前加一个 ```\```: ```runo\*ob```匹配 ```runo*ob```。 19 | 20 |   许多元字符要求在试图匹配它们时特别对待。若要匹配这些特殊字符,必须首先使字符"转义",即,将反斜杠字符\ 放在它们前面。下表列出了正则表达式中的特殊字符: 21 | 22 | 23 | |特殊字符|描述| 24 | |---------|:----| 25 | |$|匹配字符串结尾位置| 26 | |()|匹配子表达式的开始和结束位置| 27 | |*|匹配前面的子表达式0次或多次| 28 | |+|匹配前面的子表达式1次或多次| 29 | |.|匹配任何单字符,换行符\n除外| 30 | |[|匹配中括号表达式的开始| 31 | |?|匹配前面的子表达式0次或1次| 32 | |\\|将下一个字符标记为特殊字符「 普通字符**n** => 换行符**\n** 」;或转化为普通字符「 * => \\* 」| 33 | |^|匹配输入字符串的开始位置;另外,在方括号中使用时,表示不接受方括号内表达式的字符集合| 34 | |{|标记限定符表达式的开始| 35 | |\||指明两项之间的一个选择| 36 | |-|例如[0-9]代表任意数字,[1-9]代表除了0外的数字| 37 | 38 | ## 限定符 39 |   限定符用来指定正则表达式的一个给定组件必须要出现多少次才能满足匹配。有 ```* ```或 ```+```或 ```?```或 ```{n}```或 ```{n,}```或 ```{n,m}```共6种。 40 | 41 | |字符|描述| 42 | |-|:-| 43 | |* |匹配前面的子表达式零次或多次.例如,zo* 能匹配 "z" 以及 "zoo"。 等价于{0,}。| 44 | |+|匹配前面的子表达式一次或多次。例如,'zo+' 能匹配 "zo" 以及 "zoo",但不能匹配 "z"。+ 等价于 {1,}。| 45 | |?|匹配前面的子表达式零次或一次。例如,"do(es)?" 可以匹配 "do" 、 "does" 中的 "does" 、 "doxy" 中的 "do" 。? 等价于 {0,1}。| 46 | | {n} |匹配确定的n次,n是非负整数;例如‘o{2}’不能匹配Bob中的‘o’,但能匹配‘food’中的两个‘o’| 47 | |{n,}|n 是一个非负整数。至少匹配n 次。例如,'o{2,}' 不能匹配 "Bob" 中的 'o',但能匹配 "foooood" 中的所有 o。'o{1,}' 等价于 'o+'。'o{0,}' 则等价于 'o*'。| 48 | |{n,m}|m 和 n 均为非负整数,其中n <= m。最少匹配 n 次且最多匹配 m 次。例如,"o{1,3}" 将匹配 "fooooood" 中的前三个 o。'o{0,1}' 等价于 'o?'。请注意在逗号和两个数之间不能有空格。| 49 | 50 | > *、+ 限定符都是贪婪的,因为它们会尽可能多的匹配文字,只有在它们的后面加上一个?就可以实现非贪婪或最小匹配。 51 | 52 | > 对于```

Hello

``` 53 | > 54 | > 贪婪:```/<.*>/```匹配到```

Hello

``` 55 | > 56 | > 非贪婪:```/<.*?>/```匹配到```

``` 57 | > 58 | > 通过在 *、+ 或 ? 限定符之后放置 ?,该表达式从"贪婪"表达式转换为"非贪婪"表达式或者最小匹配。 59 | 60 | ## 定位符 61 |   定位符使您能够将正则表达式固定到行首或行尾。 62 | 63 | |字符|描述| 64 | |-|:-| 65 | |^|匹配字符串开始的位置| 66 | |$|匹配字符串结尾的位置| 67 | |\b|匹配一个单词边界| 68 | |\B|匹配一个非单词边界| 69 | 70 | - 限定符不能和定位符一起使用,因为单词边界不能有1个以上的位置,例如~~`^*`~~是错误的 71 | - 匹配一行开始处的文本```/^章节/``` 72 | - 匹配一行结束处的文本```/章节$/``` 73 | - 匹配单词“Chapter”,可以使用```/\bCha/```,也可以```/ter\b/``` 74 | - 下面的表达式匹配 Chapter 中的字符串 apt,但不匹配 aptitude 中的字符串 apt:```/\Bapt/``` 75 | 76 | ## 选择 77 | 用圆括号将所有选择项括起来,相邻选择项之间用|分隔.但相关匹配会被缓存. 78 | 79 | 如果想消除缓存,可以将```?:```放到第一个选项前; 80 | 81 | 其中```?:```是非捕获元之一,另外两个 82 | 83 | ```?=```正向预查,在任何开始匹配圆括号内的正则表达式模式的位置来匹配搜索字符串 84 | 85 | ```?!```负向预查,在任何开始不匹配该正则表达式模式的位置来匹配搜索字符串 86 | 87 | ## 运算符优先级 88 |   正则表达式从左到右进行计算,并遵循优先级顺序,这与算术表达式非常类似。 89 | 90 |   相同优先级的从左到右进行运算,不同优先级的运算先高后低。下表从最高到最低说明了各种正则表达式运算符的优先级顺序: 91 | 92 | |优先级|运算符|描述| 93 | |-|-|-| 94 | |!!!!!|\\|转义符| 95 | |!!!!|(), ```(?:)```, (?=), []|圆括号、方括号| 96 | |!!!|*, +, ?, {n}, {n,}, {n,m} |限定符| 97 | |!!|^, $, \任何元字符、任何字符 |定位点和序列(即:位置和顺序)| 98 | |!|\||替换,"或"操作字符具有高于替换运算符的优先级,使得"m\|food"匹配"m"或"food"。若要匹配"mood"或"food",请使用括号创建子表达式,从而产生"(m\|f)ood"。| 99 | 100 | ## 经典例题 101 | 102 | ### ip 103 | 104 | ```javascript 105 | let json = str.match(/(\d|[1-9]\d|1[0-9][0-9]|2[0-5][0-5])(\.(\d|[1-9]\d|1[0-9][0-9]|2[0-5][0-5])){3}/g); 106 | ``` 107 | ### 身份证号 108 | 109 | let str = `412727199810310017` 110 | ```javascript 111 | let json = str.match(/\d{18}/g); 112 | ``` 113 | -------------------------------------------------------------------------------- /docs/javascript/analysis-code.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 由于带上题解文件篇幅过长,所以通过detail标签将部分答案隐藏在折叠区,需要的同学可以展开查看答案: 4 | 5 | ?> 这一部分的内容主要还是以理解和掌握为主,面试官更多还是考察你的基础知识的运用。 6 | 7 | ## 循环输出setTimeout 8 | ```javascript 9 | for (var i = 0; i < 5; i++) { 10 | setTimeout(function (i) { 11 | console.log(i); 12 | },1000) 13 | } 14 | //5 5 5 5 5 15 | ``` 16 | 17 | 18 |
19 | 【点击展开】怎么实现按顺序输出怎么输出? 20 |
 21 | 
 22 | 
 23 | 
 24 | 将var改为let
 25 | 
 26 | ```javascript
 27 | for (let i = 0; i < 5; i++) {
 28 |     setTimeout(function (i) {
 29 |         console.log(i);
 30 |     },1000)
 31 | }
 32 | ```
 33 | 
 34 | 立即执行函数
 35 | 
 36 | ```javascript
 37 | for (var i = 0; i < 5; i++) {
 38 |     (function (i) {
 39 |         setTimeout(function () {
 40 |             console.log(i);
 41 |         },1000)
 42 |     })(i)
 43 | }
 44 | ```
 45 | 
 46 | bind
 47 | 
 48 | ```javascript
 49 | for (var i = 0; i < 5; i++) {
 50 |     setTimeout(function (i) {
 51 |         console.log(i);
 52 |     }.bind(this,i),1000)
 53 | }
 54 | ```
 55 | 
 56 | call(bind返回的是函数,而call、apply是立即调用,所以写法要变化)
 57 | 
 58 | ```javascript
 59 | for (var i = 0; i < 5; i++) {
 60 |     setTimeout(function (i) {
 61 |         return function () {
 62 |             console.log(i);
 63 |         }
 64 |     }.call(this,i),1000)
 65 | }
 66 | ```
 67 | 
 68 | apply
 69 | 
 70 | ```javascript
 71 | for (var i = 0; i < 5; i++) {
 72 |     setTimeout(function (i) {
 73 |         return function () {
 74 |             console.log(i);
 75 |         }
 76 |     }.apply(this,[i]),1000)
 77 | }
 78 | ```
 79 | 
 80 | 闭包
 81 | 
 82 | ```javascript
 83 | for (var i = 0; i < 5; i++) {
 84 |     function f(){
 85 |         var j = i;
 86 |         return function () {
 87 |             setTimeout(function () {
 88 |                 console.log(j);
 89 |             },1000)
 90 |         }
 91 |     }
 92 |     f()();
 93 | }
 94 | ```
 95 | 
 96 | setTimeout第三个参数
 97 | 
 98 | ```javascript
 99 | for (var i = 0; i < 5; i++) {
100 |     setTimeout(function (i) {
101 |         console.log(i);
102 |     }, 1000,i)
103 | }
104 | ```
105 | 
106 | 
107 | 
108 | 
109 |
110 | 111 | 112 | ## 连等赋值a=b=c 113 | 114 | ``` 115 | var a = {n:1}; 116 | a.x = a = {n:2}; 117 | console.log(a.x); // 为什么输出undefined? 118 | ``` 119 | 120 | 首先可以确定,连续赋值是从右至左永远只取等号右边的表达式结果赋值到等号左侧。 121 | 122 | - 1、在执行前,会先将a和a.x中的a的引用地址都取出来,此值他们都指向{n:1} 123 | - 2、在内存中创建一个新对象{n:2} 124 | - 3、执行a={n:2},将a的引用从指向{n:1}改为指向新的{n:2} 125 | - 4、执行a.x=a,此时a已经指向了新对象,而a.x因为在执行前保留了原引用,所以a.x的a依然指向原先的{n:1}对象,所以给原对象新增一个属性x,内容为{n:2}也就是现在a 126 | - 5、语句执行结束,原对象由{n:1}变成{n:1,x:{n:2}},而原对象因为无人再引用他,所以被GC回收,当前a指向新对象{n:2} 127 | - 6、再执行a.x,自然就是undefined了 128 | 129 | **‌尽量不要使用JS的连续赋值操作** 130 | 131 | ## 代码实现浏览器事件循环event-loop 132 | 133 | 思路: 134 | 135 | - 主线程=>微任务=>宏任务 136 | - 遇setTimeout、setInterval放入到宏任务中 137 | - new Promise构造函数的内容作为主线程,遇到resolve()则把then的内容放入到微任务中,构造函数中resolve()后的内容继续执行 138 | - async关键字将函数封装成Promise,函数里遇到await则相当于对应的异步函数转化为new Promise构造函数,依据上面的规则判断 139 | 140 | ```javascript 141 | async function async1(){ 142 | console.log('async1 start') 143 | await async2() 144 | console.log('async1 end') 145 | } 146 | 147 | async function async2(){ 148 | console.log('async2') 149 | } 150 | 151 | console.log('script start') 152 | 153 | setTimeout(function(){ 154 | console.log('setTimeOut') 155 | }, 0) 156 | 157 | async1() 158 | 159 | new Promise(function(resolve){ 160 | console.log('promise1') 161 | resolve() 162 | }).then(function(){ 163 | console.log('promise2') 164 | }) 165 | 166 | console.log('script end') 167 | 168 | //script start 169 | //async1 start 170 | //async2 171 | //promise1 172 | //script end 173 | //async1 end 174 | //promise2 175 | //setTimeOut 176 | ``` 177 | -------------------------------------------------------------------------------- /docs/experience/questions.md: -------------------------------------------------------------------------------- 1 | #### 1. 简历上*了解*、*熟悉*、*精通*是一种怎样的界定标准? 2 | 3 | - “**了解”**:是指只是上过课或看过书,没做过实际项目。 4 | - “**熟悉**”:应该占据了简历中描述技能的掌握程度的大部分,已经使用某项技术有较长时间,通过查阅相关文档可以独立解决大部分问题。对于毕业应届生而言,毕业设计所用到的技能可以用“熟悉”,对于已经工作过的,项目开发中所用到的技能也可以成为熟悉。 5 | - “**精通**”:如果对一项技术得心应手,在项目开发中,当同学请教这个领域的问题的时候,我们都有信心也有能力解决的时候,才可以说精通. 6 | 7 | #### 2. 哪些招聘网站值得推荐? 8 | 9 | - **牛客网**:互联网招聘首推,讨论区有大量大厂内推机会不间断。 10 | - **Boss直聘**、**拉勾网**:小公司很多,有很多比较不错的岗位,也有相应的面试机会。 11 | - **脉脉**:能跟很多hr,技术官1对1沟通,善于利用这个平台也能早就机会。 12 | 13 | #### 3. 如何才能拿到大厂的实习Offer? 14 | #### 4. 如何才能拿到大厂的正式校招offer? 15 | #### 5. 如何才能拿到大厂的社招offer? 16 | #### 6. 怎么引导面试官提问你擅长的知识点? 17 | #### 7. 如何选择公司部门和事业群? 18 | #### 8. 大厂对数据结构和算法要求到什么程度? 19 | 20 | "要求不高,但是更倾向于招算法娴熟的"。大厂在面试过程中都会考算法,尤其是字节跳动,就算是前端,基本上每轮面试必问算法,你会遇到力扣middle级别甚至hard级别的。 21 | 22 | #### 9. 怎么提高数据结构和算法能力? 23 | 24 | 当你对数据结构和算法有了一个整体的认知之后,就可以开始练习了。 25 | 注意,一定是**分类练习**!**分类练习**!**分类练习**!重要的事情说三遍。我曾见过非常多的同学带着一腔热血就开始刷题了,从leetcode第一题开始,刚开始往往非常有动力,可能还会发个朋友圈或者空间动态什么的😅,然后就没有然后了。 26 | 因为前几题非常简单,可能会给你一定的自信,但是,按序号来的话,很快就会遇到hard。或者有的人,干脆只刷简单,先把所有的简单刷完。但是,这样盲目的刷题,效果是非常差的,有可能你坚持下来,刷了几百道,也能有点效果,但是整个过程可能非常慢,而且效果远远没有分类练习要好。 27 | 所谓分类练习,即按每种类别练习,例如:这段时间只练习二叉树的题目,后面开始练习回溯算法的题目。在开始练习之前,你往往还需要对这种具体的类别进行一个详细的了解,对其具体的定义、相关的概念和应用、可能出现的题目类型进行梳理,然后再开始。 28 | 29 | 刷题"力扣"是首选,可以针对性的刷自己薄弱的方向,要做到读题后能知道这是哪种题型。如"动态规划、贪心"等等。 30 | 31 | 最次最次,也要把《剑指offer》刷两遍,掌握十大排序算法和基本的数据结构(二叉树、链表、栈、队列) 32 | 33 | #### 10. 如果对方问到岗时间,怎么应对? 34 | 剑谱:人挪活,树挪死,你这个应该是日常实习,你应该说:春节后大概率可以过去实习的,先面试在说,拿到了去不了的话,就说学校事情走不开等等’.主要是积累面试经验,这个在互联网行业内很常见. 35 | 36 | #### 11. 遇到提问不会的内容怎么办? 37 | 就告诉面试官,我会这个相似的,BalaBala…… 38 | 39 | #### 12. 应届生在面试的时候如何谈薪资? 40 | 因为没有经验,很怕在面试时候要求的薪资会被hr减分,从而错失一个工作机会,但是不聊薪资又很被动。 41 | 42 | 如果HR开了一个薪资你不满意,可以尝试性发问:“这个薪资确实离我的预期有点小偏差,不知有无可能帮忙争取一下到xxx,当然我肯定不希望我的这个需求让您的工作产生为难,毕竟相比薪资来讲我还是更看重咱们公司的机会。”这样说HR立马就会明白你什么意思,同时又避免了尴尬。 43 | 44 | #### 13. HR面有哪些需要注意的地方? 45 | 袋鼠云实习hr:*反正我师姐对于候选人昨天聊了一个,说人蛮聪明,但是对自己投的岗位和公司都不了解,并且在海投简历,然后拒绝了。* 46 | 47 | #### 14. 面试官会提问React源码的东西,我应该花精力去读源码吗? 48 | 如果你是为了面试而读源码,不必。同样的精力不如多看几篇面经,进行实战并理解原理。 49 | 50 | 如果你是为了"听起来很专家"而读源码,不必。收益极低,不如带着目的性去总结某一部分源码的设计理念。 51 | 52 | #### 15. 像牛客有很多内推码、内推私人邮箱,怎么利用? 53 | 牛客讨论区有很多内推码,但是不要发了邮件就万事大吉了,很多情况下,你可简历可能压根就没被投递出去,所以,一定要多"麻烦"别人,机会是把握在自己手中的。 54 | 55 | #### 16. 每次面试前需要准备什么? 56 | 不管是视频面试、电话面试还是现场面试,面试之前一定要先把所面公司的情况基本了解一遍,特别是对于中小企业,海投不明白公司业务,可能hr面就被刷下来。 57 | 58 | #### 17. node有必要深入学习吗? 59 | 60 | 非常必要!我在面试美团、阿里、腾讯等大厂的时候,面试官都会提到"会使用node吗",重要性你自己掂量掂量 61 | 62 | #### 18. 怎么才能确定哪些是自己必须掌握的知识点? 63 | 64 | 一份漂亮的简历让你通过初筛,简历上"技能栏"的所有角落,你都要经得起提问。 65 | 66 | #### 19. 视频面试时面试官能看到我的屏幕吗? 67 | 68 | 比如牛客视频面试,你鼠标跳出页面对方是知道的,看不到你屏幕内容,但是知道你跳出去了。所以,不要耍小动作,认真理解和记忆这些知识点,未来的自己会感谢曾经努力的你的💪。 69 | 70 | #### 20. 什么是前端素养? 71 | 72 | 素养就是,你要会设计模式、软件设计原则、IEEE754等等,大厂会问,比如快手。 73 | 74 | #### 21. 前端技能树优先级 75 | 76 | JS第一,其他全是其次。如果你JS还有"怕被问到"的地方,赶快去补习把!!! 77 | 78 | #### 22. 寒假/暑期/日常实习的区别 79 | 80 | [寒假实习,暑期实习,日常实习究竟有什么区别?!](https://www.nowcoder.com/discuss/361127) 81 | 82 | #### 23. 一些面试过程中专业术语是什么意思? 83 | 84 | |缩写|解释| 85 | |-|-| 86 | |offer|录取通知书,一般是邮件发送的pdf文件| 87 | |HC|计划招聘的人员数量| 88 | |HR|专门负责人力资源的人,一般负责最后一面和入职流程,主要考察候选人软素质,有的HR(如阿里)有一票否决权| 89 | |oc|offer call代表着HR给你打电话告诉你被录取了,到这里就稳了| 90 | |om|offer mail代表着HR给你发offer邮件,正式录取的分界线| 91 | |tql|太强了。常见于交流群中,表示感叹!| 92 | |WXG/CDG/IEG/CSIG/PCG/TEG|都是腾讯的事业群缩写| 93 | |花名|常见于阿里系,公司内部互相称呼的名字| 94 | |双非|非985非211| 95 | 96 | #### 24. 春招时间段是什么? 97 | #### 25. 秋招时间段是什么? 98 | ![秋招时间段](https://s1.ax1x.com/2020/04/26/JcTVpt.png) 99 | 100 | #### 26. BAT前端技术哪家强? 101 | 102 | #### 27. 你还有其他公司在面试吗/你拿到其他公司的offer了吗 103 | 104 | 有。 105 | 106 | 1. 说2-3个公司,不宜过多。 107 | 2. 一定要说同行业或者同等级的,这样面试官认为你方向明确 108 | 3. 夸他!表示这家公司是第一选择。大公司夸制度完善,前景好。小公司夸挑战性,工作节奏利于成长。 109 | 110 | #### 28. 你为什么离职? 111 | 112 | 1. 为什么离职 113 | 1. 不要说压力大,会被认为工作能力弱,工作效率低 114 | 2. 不要说在上级 115 | 2. 为什么来这里 116 | 117 | [解密国内BAT等大厂前端技术体系-完结篇](https://juejin.im/post/5e02c0896fb9a0160770ae9e) 118 | 119 | #### 101. 面试有那么难吗? 120 | ![论面试](https://s1.ax1x.com/2020/03/24/8bzb9J.jpg) 121 | -------------------------------------------------------------------------------- /docs/browser/questions.md: -------------------------------------------------------------------------------- 1 | 2 | ## 响应式布局怎么实现 3 | 4 | 1. **使用设备的宽度作为视图宽度并禁止初始的缩放** 5 | 6 | > 7 | 8 | 2. **通过媒体查询来设置样式media query** 9 | 10 | ```javascript 11 | @media screen and (max-width:980px){ 12 | #head { … } 13 | #content { … } 14 | #footer { … } 15 | } 16 | ``` 17 | 18 | 19 | 3. **‌设置多种视图宽度** 20 | 21 | 假如我们要兼容ipad和iphone视图,我们可以这样设置: 22 | 23 | ``` 24 | /** iPad **/ 25 | @media only screen and (min-width: 768px) and (max-width: 1024px) {} 26 | /** iPhone **/ 27 | @media only screen and (min-width: 320px) and (max-width: 767px) {} 28 | ``` 29 | ## SEO 30 | 31 | 新站可以通过外链与站内文章来提升搜索引擎的收录权重,老站可以通过提升用户体验,增加点击率来提升竞争力。 32 | 33 | #### 关键字 34 | - 起一个好的关键字,可尝试用搜索引擎去搜索看是否是相关内容 35 | - 避免“独角兽特征”的关键词,因为可能关键词有个“品牌” 36 | - 可以通过Ahrefs Keywords Explorer找低难度关键词、大流量入口的关键词、实时提醒竞争对手的新关键词 37 | - 找到与竞争对手的内容差距,补充这些内容 38 | - 找到与主题相关的问题类关键词,然后通过产出内容来回答这些问题 39 | 40 | #### 内容 41 | 42 | - 查找已存在的关键词,浏览高质量的内容 43 | - 在内容中包含真实案例研究 44 | - 写好开头,钓住读者的胃口 45 | - 尽量让文章清晰易读 46 | - 添加出站链接 47 | 48 | #### 页面SEO 49 | 50 | - 标题标签中加入核心关键词 51 | - 写出诱人的标题 52 | - 避免Meta标签文字中断,提高页面点击率 53 | > 标题标签在电脑端显示长度约512像素 54 | - 在meta描述标签中推销内容 55 | - 考虑内容的优先级,减少广告和烦人的弹窗 56 | - 确保自适应优化和浏览器兼容 57 | - 正确地设置alt 58 | - 使用精简且富有描述性的urls 59 | - 经常更新内容,保持新鲜度 60 | 61 | #### 外链建设 62 | 63 | - 修复已经失效的外链 64 | - 找竞争对手已经丢失的链接 65 | - 使用关键词锚文本链接 66 | - 去除一切垃圾链接 67 | - 找到链到多个竞争对手的网站,让他们也链给自己 68 | - 找到竞争对手经常产生外链的网站 69 | 70 | #### 其他 71 | 72 | - 像百度、谷歌提交网站收录 73 | - 检查恶意代码和隐藏链接 74 | - 随时关注流量暴跌的现象 75 | - 关注谷歌发送的惩罚通知信息 76 | - 让别人可以很容易地分享网站内容 77 | - 语义化标签 78 | - 保持耐心 79 | 80 | [参考链接](https://ahrefs.com/blog/zh/seo-tips/) 81 | 82 | ## 输入URL之后发生了什么 83 | 84 | > 这是一个综合性比较高的高频问题,任何一个细节都值得深挖,不过由于是面试,我们尽可能用最少的话答出最多的内容,应为面试过程不仅仅是考察个人知识点,同时还有逻辑思维。 85 | 86 | - 用户输入URL 87 | - 浏览器会构建请求(请求方式+路径+版本号) 88 | - 查找强缓存,如果命中直接使用,否则继续执行 89 | - DNS解析域名为ip地址或CNAME,如果使用了CDN,通过CDN服务商域名获取最近的静态资源服务器ip。 90 | - 三次握手建立客户端与服务器之间的TCP连接 91 | - 如果是https的,还需要建立SSL握手过程 92 | - 传输数据,数据包校验,保证数据到达接收方。数据传输过程中如果发生丢包就重新发送数据包,发送过程中有个优化策略,把大包拆成小包一次传输,到目的地后再组装成完整数据包。 93 | - 发送HTTP请求,服务器处理请求,返回响应结果 94 | - 判断Connection字段,如果是keep-alive,则建立持久连接,否则四次挥手断开连接 95 | - 当客户端接收到响应之后,如果响应头Content-Type是text/html,接下来开始解析html字符串 96 | - 标记化算法:输入的HTML文本,输出HTML标记,也即是标记生成器 97 | - 建树算法:解析器创建document对象,标记生成器把每个标记交给建树器创建对应DOM对象,并添加到DOM树中 98 | - 格式化样式表,将CSS文本转化为结构化对象styleSheets 99 | - 标准化样式属性,如em->px、black->#000000、blod->700 100 | - 计算每个节点的具体样式信息,继承父节点的属性和层叠规则 101 | - 遍历DOM树节点,添加到Layout树中 102 | - 对Layout树进行分层,生成Layer Tree分层树 103 | - 为每个图层生成绘制列表,交给合成线程 104 | - 合成线程将图层分成图块,并在光栅化线程池中将图块转化为位图 105 | - 合成线程发送绘制图块命令DrawQuad给浏览器进程 106 | - 浏览器`viz组件`接受命令,把页面绘制到内存 107 | - 发送到显卡成像 108 | 109 | ## 浏览器内核 110 | 111 | 浏览器内核是通过取得页面内容、整理信息(应用CSS)、计算和组合最终输出可视化的图像结果,通常也被称为渲染引擎。 112 | 113 | 浏览器内核是多线程,在内核控制下各线程相互配合以保持同步,一个浏览器通常由以下常驻线程组成: 114 | 115 | - GUI 渲染线程 116 | - JavaScript引擎线程 117 | - 定时触发器线程 118 | - 事件触发线程 119 | - 异步http请求线程 120 | 121 | **1.GUI渲染线程** 122 | 123 | - 主要负责页面的渲染,解析HTML、CSS,构建DOM树,布局和绘制等。 124 | - 当界面需要重绘或者由于某种操作引发回流时,将执行该线程。 125 | - 该线程与JS引擎线程互斥,当执行JS引擎线程时,GUI渲染会被挂起,当任务队列空闲时,主线程才会去执行GUI渲染。 126 | 127 | **2.JS引擎线程** 128 | 129 | - 该线程当然是主要负责处理 JavaScript脚本,执行代码。 130 | - 也是主要负责执行准备好待执行的事件,即定时器计数结束,或者异步请求成功并正确返回时,将依次进入任务队列,等待 JS引擎线程的执行。 131 | - 当然,该线程与 GUI渲染线程互斥,当 JS引擎线程执行 JavaScript脚本时间过长,将导致页面渲染的阻塞。 132 | 133 | **3.定时器触发线程** 134 | 135 | - 负责执行异步定时器一类的函数的线程,如: setTimeout,setInterval。 136 | - 主线程依次执行代码时,遇到定时器,会将定时器交给该线程处理,当计数完毕后,事件触发线程会将计数完毕后的事件加入到任务队列的尾部,等待JS引擎线程执行。 137 | 138 | **4.事件触发线程** 139 | 140 | 主要负责将准备好的事件交给 JS引擎线程执行。比如 setTimeout定时器计数结束, ajax等异步请求成功并触发回调函数,或者用户触发点击事件时,该线程会将整装待发的事件依次加入到任务队列的队尾,等待 JS引擎线程的执行。 141 | 142 | **5.异步http请求线程** 143 | 144 | - 负责执行异步请求一类的函数的线程,如: Promise,axios,ajax等。 145 | - 主线程依次执行代码时,遇到异步请求,会将函数交给该线程处理,当监听到状态码变更,如果有回调函数,事件触发线程会将回调函数加入到任务队列的尾部,等待JS引擎线程执行。 146 | 147 | 148 | 149 | ## CSS和JS阻塞浏览器渲染吗 150 | 151 | - CSS 不会阻塞 DOM 的解析,但会阻塞 DOM 渲染。 152 | - JS 阻塞 DOM 解析,但浏览器会预览DOM,预先下载相关资源。 153 | - 浏览器遇到 ` 225 | ``` 226 | 227 | -------------------------------------------------------------------------------- /docs/node/node.md: -------------------------------------------------------------------------------- 1 | 2 | ## Node/特点/优点 3 | nodeJS 是一门单线程!异步!非阻塞语言! 4 | 5 | ## Node里的模块汇总 6 | 7 | 这里汇总nodeV12.16.2版本的稳定模块,排除了不稳定或者废弃的模块 8 | 9 | |模块|说明| 10 | |-|-| 11 | |assert(断言)|提供断言测试(一般assert用于捕捉程序员自己的错误,Error/Exception捕捉用户或者环境的错误)| 12 | |async_hooks(异步钩子)|用于跟踪应用的异步资源| 13 | |Buffer(缓冲器)|处理二进制数据| 14 | |child_process(子进程)|衍生出子进程来操作| 15 | |cluster(集群)|创建共享服务器端口的子进程,充分利用多核系统| 16 | |console(控制台)|提供简单的调试控制台,类似于web浏览器提供的JavaScript控制台| 17 | |crypto(加密)|提供加密功能,包括对OpenSSL的哈希、HMAC、加密、解密、签名、验证功能的一套封装| 18 | |debugger(调试器)|进程外的调试程序,相当于IDE里的调试功能| 19 | |dgram(数据报)|提供了UDP数据包socket实现| 20 | |dns(域名服务器)|用于启用域名解析| 21 | |Error(错误)|js运行或执行底层过程中抛出的错误| 22 | |events(事件触发器)|绑定事件和触发事件| 23 | |fs(文件系统)|与文件系统进行交互| 24 | |global(全局变量)|全局变量,它的属性都是全局变量,相当于浏览器中的windows| 25 | |http(HTTP)|实用HTTP服务器和客户端| 26 | |http2(HTTP/2)|提供了HTTP/2协议的实现| 27 | |https(HTTPS)|提供了HTTPS(TLS/SSL的HTTP协议)实现| 28 | |module(模块)|实现模块化| 29 | |net(网络)|创建基于流的TCP或IPC服务器和客户端| 30 | |os(操作系统)|提供了与操作系统相关的方法和属性| 31 | |path(路径)|处理文件与目录路径的工具| 32 | |perf_hooks(性能钩子)|监控性能、测量异步操作时长| 33 | |process(进程)|提供当前进程的信息并对其控制| 34 | |querystring(查询字符串)|对HTTP请求所带的数据进行解析| 35 | |readline(逐行读取)|可一行一行读取流| 36 | |repl(交互式解释器)|提供了一种“读取-求职-输出”的循环实现,相当于在控制台直接启动node的`>`操作| 37 | |stream(流)|处理流式数据的对象| 38 | |string_decoder(字符串解码器)|安全地将Buffer对象解码为字符串| 39 | |timer(定时器)|实现了与浏览器API类似的定时器| 40 | |tls(安全传输层)|安全传输层(TLS)及安全套接层(SSL)协议实现,建立在OpenSSL基础上| 41 | |tty(终端)|提供了tty.ReadStream和tty.WriteStream,大多数情况下用户不必手动创建这两个类的实例| 42 | |url(URL)|处理和解析URL| 43 | |util(实用工具)|提供了转换回调风格、调试输出、废弃API等相关的工具| 44 | |v8(V8引擎)|暴露了Node底层二进制文件中的V8版本API| 45 | |vm(虚拟机)|在V8虚拟机上下文中编译和运行代码| 46 | |worker_threads(工作线程)|允许使用并行地执行JavaScript的线程| 47 | |zlib(压缩)|实现压缩功能| 48 | 49 | ## 为什么要用Node 50 | 51 | - 简单强大,轻量可扩展.简单体现在node使用的是javascript,json来进行编码,人人都会; 52 | - 强大体现在非阻塞IO,可以适应分块传输数据,较慢的网络环境,尤其擅长高并发访问; 53 | - 轻量体现在node本身既是代码,又是服务器,前后端使用统一语言; 54 | - 可扩展体现在可以轻松应对多实例,多服务器架构,同时有海量的第三方应用组件. 55 | 56 | ## 什么用Nodejs,它有哪些优缺点? 57 | 简单强大,轻量可扩展 58 | 59 | - 简单体现在node使用的是javascript,json来进行编码,人人都会; 60 | - 强大体现在非阻塞IO,可以适应分块传输数据,较慢的网络环境,尤其擅长高并发访问; 61 | - 轻量体现在node本身既是代码,又是服务器,前后端使用统一语言; 62 | - 可扩展体现在可以轻松应对多实例,多服务器架构,同时有海量的第三方应用组件. 63 | - 不用担心多线程,锁,并行计算的问题 64 | - V8引擎速度非常快 65 | 66 | 缺点: 67 | - nodejs更新很快,可能会出现版本兼容,比如node的事件循环11版本前后就发生了变化 68 | - nodejs还不算成熟,还没有大制作 69 | - nodejs不像其他的服务器,对于不同的链接,不支持进程和线程操作 70 | 71 | ## 什么是错误优先的回调函数? 72 | 73 | 错误优先(Error-first)的回调函数(Error-First Callback)用于同时返回错误和数据。第一个参数返回错误,并且验证它是否出错;其他参数返回数据。 74 | ```javascript 75 | fs.readFile(filePath, function(err, data) 76 | { 77 | if (err) 78 | { 79 | // 处理错误 80 | return console.log(err); 81 | } 82 | console.log(data); 83 | }); 84 | ``` 85 | 86 | ## node架构 87 | 主要分为三层,应用app >> V8及node内置架构 >> 操作系统. 88 | 89 | V8是node运行的环境,可以理解为node虚拟机. 90 | 91 | node内置架构又可分为三层: 核心模块(javascript实现) >> c++绑定 >> libuv + CAes + http. 92 | 93 | ## node有哪些核心模块? 94 | EventEmitter, Stream, FS, Net和全局对象 95 | 96 | ## 事件循环机制在Node和浏览器中有什么区别 97 | 98 | 浏览器和Node 环境下,microtask 任务队列的执行时机不同 99 | 100 | - Node端,microtask 在事件循环的各个阶段之间执行 101 | - 浏览器端,microtask 在事件循环的 macrotask 执行完之后执行 102 | 103 | node版本大于11的,和浏览器表现一致,小于11的,会有这样的策略:若第一个定时器任务出队并执行完,发现队首的任务仍然是一个定时器,那么就将微任务暂时保存,直接去执行新的定时器任务,当新的定时器任务执行完后,再一一执行中途产生的微任务。 104 | 105 | ## process.nextTick 106 | 107 | process.nextTick 是一个独立于 eventLoop 的任务队列。 108 | 109 | 在每一个 eventLoop 阶段完成后会去检查这个队列,如果里面有任务,会让这部分任务优先于微任务执行。 110 | 111 | ## npm模块install机制和原理 112 | **npm 模块安装机制**: 113 | 114 | - 发出npm install命令 115 | - 查询 node_modules 目录之中是否已经存在指定模块 116 | - 若存在,不再重新安装 117 | - 若不存在 118 | - npm 向 registry 查询模块压缩包的网址 119 | - 下载压缩包,存放在根目录下的.npm目录里 120 | - 解压压缩包到当前项目的 node_modules 目录 121 | 122 | **npm 实现原理**: 123 | 124 | 输入 npm install 命令并敲下回车后,会经历如下几个阶段(以 npm 5.5.1 为例): 125 | 126 | - 执行工程自身 preinstall,当前 npm 工程如果定义了 preinstall 钩子此时会被执行。 127 | - **确定首层依赖模块**,首先需要做的是确定工程中的首层依赖,也就是 dependencies 和 devDependencies 属性中直接指定的模块(假设此时没有添加 npm install 参数)。工程本身是整棵依赖树的根节点,每个首层依赖模块都是根节点下面的一棵子树,npm 会开启多进程从每个首层依赖模块开始逐步寻找更深层级的节点。 128 | - **获取模块**,获取模块是一个递归的过程,分为以下几步: 129 | - 获取模块信息。在下载一个模块之前,首先要确定其版本,这是因为 package.json 中往往是 semantic version(semver,语义化版本)。此时如果版本描述文件(npm-shrinkwrap.json 或 package-lock.json)中有该模块信息直接拿即可,如果没有则从仓库获取。如 packaeg.json 中某个包的版本是 ^1.1.0,npm 就会去仓库中获取符合 1.x.x 形式的最新版本。 130 | - 获取模块实体。上一步会获取到模块的压缩包地址(resolved 字段),npm 会用此地址检查本地缓存,缓存中有就直接拿,如果没有则从仓库下载。 131 | - 查找该模块依赖,如果有依赖则回到第1步,没有则停止。 132 | - **安装模块**,这一步将会更新工程中的 node_modules ,并执行模块中的生命周期函数(按照 preinstall、install、postinstall 的顺序)。 133 | - **执行工程自身生命周期**,当前 npm 工程如果定义了钩子此时会被执行(按照 install、postinstall、prepublish、prepare 的顺序)。 134 | 135 | 最后一步是生成或更新版本描述文件,npm install 过程完成。 136 | 137 | **模块扁平化(dedupe)**: 138 | 139 | - 上一步获取到的是一棵完整的依赖树,其中可能包含大量重复模块。比如 A 模块依赖于 loadsh,B 模块同样依赖于 lodash。在 npm3 以前会严格按照依赖树的结构进行安装,因此会造成模块冗余。 140 | - 从 npm3 开始默认加入了一个 dedupe 的过程。它会遍历所有节点,逐个将模块放在根节点下面,也就是 node-modules 的第一层。当发现有重复模块时,则将其丢弃。 141 | - 这里需要对重复模块进行一个定义,它指的是模块名相同且 semver 兼容。每个 semver 都对应一段版本允许范围,如果两个模块的版本允许范围存在交集,那么就可以得到一个兼容版本,而不必版本号完全一致,这可以使更多冗余模块在 dedupe 过程中被去掉。 142 | - 比如 node-modules 下 foo 模块依赖 lodash@^1.0.0,bar 模块依赖 lodash@^1.1.0,则 ^1.1.0 为兼容版本。 143 | - 而当 foo 依赖 lodash@^2.0.0,bar 依赖 lodash@^1.1.0,则依据 semver 的规则,二者不存在兼容版本。会将一个版本放在 node_modules 中,另一个仍保留在依赖树里。 144 | 145 | ## npm版本号 146 | 147 | A.B.C 148 | 149 | - A是大版本号,进行不兼容变更的主要版本, 150 | - B是小版本号,向下兼容的方式**添加功能**小版本 151 | - C是布丁版本号,向后兼容时bug修复的补丁版本 152 | 153 | - `^2.2.1` 大版本号为2,小版本号大于等于2,补丁版本号大于等于1 154 | - `~2.2.1` 大版本号为2,小版本号为2,补丁版本号大于等于1 155 | - `>2.1` 大于2.1的所有版本,不包括2.1,也可以使用<、<=等等 156 | - `1.0.0 - 1.2.0`,范围的所有版本 157 | - `1.0.0-rc.1` 包括预发行的特定版本 158 | - `^2 <2.2 || > 2.3` 可以用||逻辑判定 159 | - `>=1.0.0-rc.0 <1.0.1` 指定一系列预发行版本 160 | 161 | ## 中间件/洋葱模型 162 | 163 | 中间件 在 Node.js 中被广泛使用,它泛指一种特定的设计模式、一系列的处理单元、过滤器和处理程序,以函数的形式存在,连接在一起,形成一个异步队列,来完成对任何数据的预处理和后处理。 164 | 165 | 它的优点在于 灵活性:使用中间件我们用极少的操作就能得到一个插件,用最简单的方法就能将新的过滤器和处理程序扩展到现有的系统上。 166 | 167 | 常规中间件模式:最基础的组成部分就是 中间件管理器,我们可以用它来组织和执行中间件的函数, 168 | 169 | - 可以通过调用use()函数来注册新的中间件 170 | - 当接收到需要处理的新数据时,在注册的中间件中依次调用。每个中间件都接受上一个中间件的执行结果作为输入值; 171 | - 每个中间件都可以停止数据的进一步处理,只需要简单地不调用它的回调函数或者将错误传递给回调函数。当发生错误时,通常会触发执行另一个专门处理错误的中间件。 172 | 173 | 至于怎么处理传递数据,目前没有严格的规则,一般有几种方式: 174 | 175 | - 通过添加属性和方法来增强; 176 | - 使用某种处理的结果来替换 data; 177 | - 保证原始要处理的数据不变,永远返回新的副本作为处理的结果。 178 | 179 | 而具体的处理方式取决于 中间件管理器 的实现方式以及中间件本身要完成的任务类型。 180 | 181 | **洋葱模型的实现原理:** 182 | 183 | 形象思维:一层一层往里进,再一层一层往外出 184 | 185 | 中间件的实现原理,也就是洋葱模型的实现原理,核心在于next的实现。next需要依次调用下一个middleware,当到最后一个的时候结束,这样后面middleware的promise先resolve,然后直到第一个,这样的流程也就是洋葱模型的流程了。 186 | 187 | **缺点:** 188 | 189 | 中间件模型非常好用并且简洁, 甚至在 koa 框架上大放异彩, 但是也有自身的缺陷, 也就是一旦中间件数组过于庞大, 性能会有所下降, 因此我们需要结合自身的情况与业务场景作出最合适的选择. 190 | 191 | -------------------------------------------------------------------------------- /docs/projects/project.md: -------------------------------------------------------------------------------- 1 | 面试官提问的时候,更多倾向于提问: 2 | 3 | (腾讯夺命连环问) 4 | - 说一下你在这个项目中主要负责什么? 5 | - 看到你用到了XXX,为什么使用XXX? 6 | - 除了XXX你还知道什么类似的框架/库/脚本?//回答AAA 7 | - AAA和XXX有什么区别? 8 | - 为什么不使用AAA而使用XXX? 9 | 10 | 11 | ## 直播的解决方案 12 | 13 | 使用过的解决方案:先用vlc执行rtsp协议来读取摄像头视频数据,测试成功。后来找到一个叫做JavaCV的库,它实现了OpenCV, FFmpeg等等。然后通过这个库可以在Java中设置请求连接rtsp,获取到视频流。 14 | 后来也写了保存成录像,后来使用了阿里云的对象存储OSS,设置文件为可写策略,通过阿里云API进行调用并执行"追加上传"的功能,不同于其他文件系统的覆盖上传,它是可以直接在文件对象后追加数据,也正是因此方便了这种类似直播功能的实现。 15 | 16 | 其实这个项目是一个"远程自动控制机器手臂"的javafx项目,通过远程视频监控拖动滑动条组件进行数据信号控制运动,并添加机械手臂的保存功能。 17 | 18 | 19 | ## 购物车,打开多个窗口/tap,如果让数据保持同步 20 | 21 | 就比如商城页面打开两个,第一个窗口里添加商品,第二个窗口怎么能同步更新 22 | 23 | **方案一:localStorage**: 24 | 25 | **简要概述:** 26 | 27 | 数据存储在localStorage,监听storage,在数据变动时能够在storage事件的event参数中识别和获取到变动的数据,进而带动数据的同步。 28 | 29 | **详细阐述:** 30 | 31 | localStorage只能实现同一浏览器相同域名、相同协议、相同端口下的多个标签页之间的通信。不同浏览器没有该效果。 32 | 33 | localStorage是Storage对象的实例。对Storage对象进行任何修改,都会在文档上触发storage事件。当通过属性或者setItem()方法保存数据,使用delete操作符或removeItem()删除数据,或者调用clear()方法时,都会发生该事件。 34 | 35 | storage事件的event对象有以下属性: 36 | 37 | - domin:发生变化的存储空间的域名; 38 | - key:设置或者删除的键名; 39 | - newValue:如果是设置值,则为新值;如果是删除值,则是null; 40 | - oldValue:键被更改之前的值; 41 | 42 | **方案二:cookie+setInterval**: 43 | 44 | **简要概述:** 45 | 46 | 一个页面产生的cookie能被这个页面的统一目录和子目录下的页面访问,通常把cookie的path设置为一个更高级别的目录,从而使更多的页面共享cookie,实现多页面之间相互通信。然后通过setInterval进行轮询获取cookie并加以处理 47 | 48 | **额外补充:** 49 | 50 | cookie的参数: 51 | 52 | - path:cookie所在的目录,默认为/,即根目录, 通常用来解决同域下cookie的访问问题 53 | - domain :cookie所在的域,默认为请求的地址,通过设置document.domain可以实现跨域访问 54 | 55 | ## 设计百度输入框的思路,用到哪些属性和事件 56 | 57 | **UI:** 58 | 59 | - flex垂直居中布局,引入logo,和input框、搜索button、隐藏的搜索展示区div用display:none隐藏 60 | - 搜索展示区的li样式要添加上hover文字高亮 61 | 62 | **事件:** 63 | 64 | - button的onclick事件,请求对应的搜索展示页 65 | - input的onkeyup事件,每次键盘松开的时候进行请求联想列表,将获取的列表在搜索展示区div里用ul展示,并将div的display展示成可见的,这个输入过程可以用防抖优化 66 | - input的onkeydown事件,判断e.keyCode等于13(代表回车)的情况,同样请求对应的搜索页 67 | - 列表li的onclick事件,获取当前的列表内容,并请求对应搜索页 68 | - body的onclick事件,鼠标点击主要搜索区域之外的位置时,让联想区div进行不可见,不过对应的要在input和button事件代码里通过e.stopPropagation()阻止冒泡 69 | 70 | ## 怎么在海量数据中找出重复次数最多的一个? 71 | 72 | 比如:海量日志中ip的统计出现次数最多的那个 73 | 74 | **核心思想:先做hash,然后求模映射为小文件,求出每个小文件中重复次数最多的一个,并记录重复次数。然后找出上一步求出的数据中重复次数最多的一个就是所求** 75 | 76 | **详细逻辑:** 77 | 假设是一个大文件,基于对内存的限制,首先要对于日志进行拆分成小文件,然后统计小文件中的ip最后得到汇总数据。 78 | 79 | - 分而治之/hash映射:针对数据太大,内存受限,只能是:把大文件化成(取模映射)小文件,即16字方针:大而化小,各个击破,缩小规模,逐个解决 80 | - hash_map统计:当大文件转化了小文件,那么我们便可以采用常规的hash_map(ip,value)来进行频率统计。 81 | - 堆/快速排序:统计完了之后,便进行排序(可采取堆排序),得到次数最多的IP。 82 | 83 | 首先是这一天,并且是访问百度的日志中的IP取出来,逐个写入到一个大文件中。注意到IP是32位的,最多有个2^32个IP。同样可以采用映射的方法,比如%1000,把整个大文件映射为1000个小文件,再找出每个小文中出现频率最大的IP(可以采用hash_map对那1000个文件中的所有IP进行频率统计,然后依次找出各个文件中频率最大的那个IP)及相应的频率。然后再在这1000个最大的IP中,找出那个频率最大的IP,即为所求。 84 | 85 | > 1、Hash取模是一种等价映射,不会存在同一个元素分散到不同小文件中的情况,即这里采用的是mod1000算法,那么相同的IP在hash取模后,只可能落在同一个文件中,不可能被分散的。因为如果两个IP相等,那么经过Hash(IP)之后的哈希值是相同的,将此哈希值取模(如模1000),必定仍然相等。 86 | > 2、**hash映射**:简单来说,就是为了便于计算机在有限的内存中处理big数据,从而通过一种映射散列的方式让数据均匀分布在对应的内存位置(如大数据通过取余的方式映射成小树存放在内存中,或大文件映射成多个小文件),而这个映射散列方式便是我们通常所说的hash函数,设计的好的hash函数能让数据均匀分布而减少冲突。尽管数据映射到了另外一些不同的位置,但数据还是原来的数据,只是代替和表示这些原始数据的形式发生了变化而已。 87 | 88 | **上云方案:** 89 | 90 | 这个场景我们可以考虑使用云服务,说一下我了解的阿里云的服务,例如阿里云的批量计算,我们可以将日志文件存储到OSS中,利用它可以弹性扩大文件的特性,通过追加上传的方式不断地向日志文件添加新内容。最后,我们可以直接利用阿里云批量计算服务,将数据进行拆分,然后交给云端分布式并发计算,最后marge合并返回最终结果。 91 | 92 | 不过最好是对每日的文件按照规模进行存储,比如每个日志文件超过50m就进建新log文件,以日期和时间命名,分别放到每日的文件夹中,方便检索和提取。 93 | 94 | ## 亿级别数据排序 95 | 96 | 有最多**1000万**条**不一样的整型**数据存在于硬盘的文件中(数据不超过最大值),如何在**1M内存**的状况下对其进行尽量快的排序。 97 | 98 | - 数据特征:单个数据<=1000万、不一样的(没有重复)、整型(int,4B) 99 | - 要求:1M内存、尽量快 100 | - 分析:1MB = 1*1024*1024 B 能存储大于25万个int类型的整数。因此每次咱们能够排序25万条记录,一共排序40次。 101 | 102 | 一个简单的思路是读1000万条1次,对第i个25万条数据进行排序,并将排好的结果存成外部文件i(这里能够用常见的内部排序,如快排),最后咱们生成了40个排好序的外部文件,而后对这40个文件进行归并排序输出成1个文件。 103 | 104 | > chrome里Array.sort()长度小于10的数组是插入排序,大于10的数组是快速排序。 105 | 106 | ## 上千万或上亿数据(有重复),统计其中出现次数最多的前N个数据。 107 | 108 | **核心方案:** 上千万或上亿的数据,现在的机器的内存应该能存下。所以考虑采用hash_map/搜索二叉树/红黑树等来进行统计次数。然后利用堆取出前N个出现次数最多的数据。 109 | 110 | #### 寻找热门查询,300万个查询字符串中统计最热门的10个查询 111 | 112 | 采用hashmap + 堆 113 | 114 | 1. hash_map统计:先对这批海量数据预处理。具体方法是:维护一个Key为Query字串,Value为该Query出现次数的HashTable,即hash_map(Query,Value),每次读取一个Query,如果该字串不在Table中,那么加入该字串,并且将Value值设为1;如果该字串在Table中,那么将该字串的计数加一即可。最终我们在O(N)的时间复杂度内用Hash表完成了统计; 115 | 2. 堆排序:第二步、借助堆这个数据结构,找出Top K,时间复杂度为N‘logK。即借助堆结构,我们可以在log量级的时间内查找和调整/移动。因此,维护一个K(该题目中是10)大小的小根堆,然后遍历300万的Query,分别和根元素进行对比。所以,我们最终的时间复杂度是:O(N) + N' * O(logK),(N为1000万,N’为300万)。 116 | 117 | #### 海量数据分布在100台电脑中,想个办法高效统计出这批数据的TOP10。 118 | 119 | **‌如果每个数据元素只出现一次,而且只出现在某一台机器中:** 120 | 121 | 1. 堆排序:在每台电脑上求出TOP10,可以采用包含10个元素的堆完成(TOP10小,用最大堆,TOP10大,用最小堆,比如求TOP10大,我们首先取前10个元素调整成最小堆,如果发现,然后扫描后面的数据,并与堆顶元素比较,如果比堆顶元素大,那么用该元素替换堆顶,然后再调整为最小堆。最后堆中的元素就是TOP10大)。 122 | 2. 求出每台电脑上的TOP10后,然后把这100台电脑上的TOP10组合起来,共1000个数据,再利用上面类似的方法求出TOP10就可以了。 123 | 124 | **‌但如果同一个元素重复出现在不同的电脑中呢:** 125 | 126 | - **方法一:** 遍历一遍所有数据,重新hash取摸,如此使得同一个元素只出现在单独的一台电脑中,然后采用上面所说的方法,统计每台电脑中各个元素的出现次数找出TOP10,继而组合100台电脑上的TOP10,找出最终的TOP10。 127 | - **方法二:** 暴力求解:直接统计统计每台电脑中各个元素的出现次数,然后把同一个元素在不同机器中的出现次数相加,最终从所有数据中找出TOP10。 128 | 129 | ## 大文本文件,每行一个词,要求统计频繁出现的前10个词,请给出思想,给出时间复杂度分析。 130 | 131 | - 方案1:如果文件比较大,无法一次性读入内存,可以采用hash取模的方法,将大文件分解为多个小文件,对于单个小文件利用hash_map统计出每个小文件中10个最常出现的词,然后再进行归并处理,找出最终的10个最常出现的词。 132 | - 方案2:通过hash取模将大文件分解为多个小文件后,除了可以用hash_map统计出每个小文件中10个最常出现的词,也可以用trie树统计每个词出现的次数,时间复杂度是O(n\*le)(le表示单词的平准长度),最终同样找出出现最频繁的前10个词(可用堆来实现),时间复杂度是O(n\*lg10)。 133 | 134 | #### 有一个1G大小的一个文件,里面每一行是一个词,词的大小不超过16字节,内存限制大小是1M。返回频数最高的100个词。 135 | 136 | - 分而治之/hash映射:顺序读文件中,对于每个词x,取hash(x)%5000,然后按照该值存到5000个小文件(记为x0,x1,...x4999)中。这样每个文件大概是200k左右。如果其中的有的文件超过了1M大小,还可以按照类似的方法继续往下分,直到分解得到的小文件的大小都不超过1M。 137 | - hash_map统计:对每个小文件,采用trie树/hash_map等统计每个文件中出现的词以及相应的频率。 138 | - 堆/归并排序:取出出现频率最大的100个词(可以用含100个结点的最小堆)后,再把100个词及相应的频率存入文件,这样又得到了5000个文件。最后就是把这5000个文件进行归并(类似于归并排序)的过程了。 139 | 140 | #### 有10个文件,每个文件1G,每个文件的每一行存放的都是用户的query,每个文件的query都可能重复。要求你按照query的频度排序。 141 | 142 | - hash映射:顺序读取10个文件,按照hash(query)%10的结果将query写入到另外10个文件(记为a0,a1,..a9)中。这样新生成的文件每个的大小大约也1G(假设hash函数是随机的)。 143 | - hash_map统计:找一台内存在2G左右的机器,依次对用hash_map(query, query_count)来统计每个query出现的次数。注:hash_map(query,query_count)是用来统计每个query的出现次数,不是存储他们的值,出现一次,则count+1。 144 | - 堆/快速/归并排序:利用快速/堆/归并排序按照出现次数进行排序,将排序好的query和对应的query_cout输出到文件中,这样得到了10个排好序的文件(记为)。最后,对这10个文件进行归并排序(内排序与外排序相结合)。 145 | 146 | ## 连个大文件,找出共同的url 147 | 148 | #### 给定a、b两个文件,各存放50亿个url,每个url各占64字节,内存限制是4G,让你找出a、b文件共同的url? 149 | 150 | 可以估计每个文件安的大小为5G×64=320G,远远大于内存限制的4G。所以不可能将其完全加载到内存中处理。考虑采取分而治之的方法。 151 | 152 | - 分而治之/hash映射:遍历文件a,对每个url求取,然后根据所取得的值将url分别存储到1000个小文件(记为, 这里漏写个了a1)中。这样每个小文件的大约为300M。遍历文件b,采取和a相同的方式将url分别存储到1000小文件中(记为)。这样处理后,所有可能相同的url都在对应的小文件()中,不对应的小文件不可能有相同的url。然后我们只要求出1000对小文件中相同的url即可。 153 | - hash_set统计:求每对小文件中相同的url时,可以把其中一个小文件的url存储到hash_set中。然后遍历另一个小文件的每个url,看其是否在刚才构建的hash_set中,如果是,那么就是共同的url,存到文件里面就可以了。 154 | -------------------------------------------------------------------------------- /docs/network/tcp_udp.md: -------------------------------------------------------------------------------- 1 | # TCP/UDP 2 | ## TCP是什么 3 | 传输层控制协议(TCP Transport Control Protocol),是一种面向连接的、可靠的、基于字节流的传输层通信协议,是为了在不可靠的互联网络上提供可靠的端到端字节流而专门设计的一个传输协议。 4 | 5 | TCP的“连接”,其实是客户端和服务器的内存里保存的一份关于对方的信息,如ip地址、端口号等。通过三次握手建立连接;四次挥手断开连接。 6 | 7 | TCP最核心的三个要素: 8 | 9 | - 面向连接:客户端、服务端交换数据前,需要建立连接 10 | - 可靠:通过特定机制,在不可靠的网络之上,确保报文准确送达 11 | - 字节流:数据的最小单位为字节。至于字节中存储内容的含义,由于应用层的程序决定 12 | 13 | 14 | ## TCP和UDP的区别 15 | 16 | - TCP面向连接(如打电话要先拨号建立连接);UDP是无连接的,即发送数据之前不需要建立连接 17 | - TCP提供可靠的服务。也就是说,通过TCP连接传送的数据,无差错,不丢失,不重复,且按序到达;UDP尽最大努力交付,即不保证可靠交付 18 | - UDP具有较好的实时性,工作效率比TCP高,适用于对高速传输和实时性有较高的通信或广播通信。 19 | - 每一条TCP连接只能是点到点的;UDP支持一对一,一对多,多对一和多对多的交互通信 20 | - TCP对系统资源要求较多,UDP对系统资源要求较少。 21 | - TCP的逻辑通信信道是全双工的可靠信道,UDP则是不可靠信道 22 | 23 | ## 为什么UDP有时比TCP更有优势?/UPD应用场景 24 | 25 | UDP以其简单、传输快的优势,在越来越多场景下取代了TCP,如实时游戏。 26 | 27 | - 网速的提升给UDP的稳定性提供可靠网络保障,丢包率很低,如果使用应用层重传,能够确保传输的可靠性。 28 | 29 | - TCP为了实现网络通信的可靠性,使用了复杂的拥塞控制算法,建立了繁琐的握手过程,由于TCP内置的系统协议栈中,极难对其进行改进。 30 | 31 | 采用TCP,一旦发生丢包,TCP会将后续的包缓存起来,等前面的包重传并接收到后再继续发送,延时会越来越大,基于UDP对实时性要求较为严格的情况下,采用自定义重传机制,能够把丢包产生的延迟降到最低,尽量减少网络问题对游戏性造成影响。 32 | 33 | ## TCP三次握手 34 | 35 | 36 | 最开始的时候,客户端和服务端都是Closed(关闭)状态,准备发送连接请求前,Server会进入LISTEN(监听)状态。 37 | 38 | - **第一次握手:** 客户端(Client)会给服务端(Server)发送请求报文段,并指定同步序列号SYN = 1,ACK = 0, 初始序号为seq = x,(seq里面就是字节的序号),同时TCP的客户端进程进入SYN-SENT(同步已发送)状态。 39 | - **第二次握手:** 服务端收到客户端发送的请求报文SYN后,会向客户端发送一个SYN报文作为应答,表示同意建立连接,同时指定了自己的SYN = 1, ACK = 1,还会向客户端发送seq = y,来表示自己的一个初始序号,同时也会告诉客户端下一次应该从哪开始发送的确认序号,由于客户端发送过来的初始序号seq = x, 所以确认序号ack = x + 1,这时,TCP的服务端进入SYN-RCVD(同步收到)状态。 40 | - **第三次握手:** 客户端收到服务端的确认报文之后,会再次向服务端发送确认信息,表示已经收到。所以ACK = 1, seq = x + 1, ack = y + 1。TCP建立连接,客户端和服务器进入ESTAB-LISTEND(已建立连接状态)状态。 41 | 42 | > ![wAqsLn.jpg](https://s1.ax1x.com/2020/09/05/wAqsLn.jpg) 43 | > 44 | > TCP协议还有一个特点就是面向字节流,它会把数据都分成一个个字节,然后进行分段传输,在分段传输的时候,每一段是由不同的字节序号组成的。 45 | > 46 | > 介绍一下图中的一些字段: 47 | > 48 | > - SYN:同步序列号,是用来建立连接的握手信号。 49 | > - ack:确认序号,当ACK为1时,ack有效,当ACK为0时,ack无效。 50 | > - seq:序号。 51 | > - ACK:确认序号有效。 52 | > - FIN: 结束标志,用来表示断开连接。 53 | 54 | 55 | 谢希仁著《计算机网络》--“为了防止已失效的连接请求报文段突然又传送到了服务端,因而产生错误” 56 | 57 | client发出的第一个连接请求报文段并没有丢失,而是在某个网络结点长时间的滞留了,以致延误到连接释放以后的某个时间才到达server。本来这是一个早已失效的报文段。但server收到此失效的连接请求报文段后,就误认为是client再次发出的一个新的连接请求。于是就向client发出确认报文段,同意建立连接。假设不采用“三次握手”,那么只要server发出确认,新的连接就建立了。由于现在client并没有发出建立连接的请求,因此不会理睬server的确认,也不会向server发送数据。但server却以为新的运输连接已经建立,并一直等待client发来数据。这样,server的很多资源就白白浪费掉了。 58 | 59 | ## TCP是三次握手,而不是两次或四次? 60 | 61 | **假如两次握手:** 62 | 63 | 一句话,主要防止已经失效的连接请求报文突然又传送到了服务器,从而产生错误。 64 | 65 | 客户端第一次向服务端发送了建立连接的请求报文段,有可能因为网络的原因滞留了,客户端就会认为这个请求失效了,会重新向服务端发送一个连接请求,然后服务器正常响应连接。在某个时间段,第一次发送的连接请求才到达了服务端,如果没有第三次握手确认,那么此时服务端会误认为客户端又发了一个新的连接请求,会再一次响应客户端,客户端收到响应请求,发现这个请求刚刚已经发过了,而且也收到了服务端的响应,就忽略这个请求,但此时的服务端却还一直等待客户端的响应,这样就会造成资源的浪费。 66 | 67 | **假如四次握手:** 68 | 69 | 第三次握手,服务端最后一次收到客户端的响应请求之后,如果此时进行第四次握手的话,那么应该是服务端接着去响应刚刚收到的这个请求,这个请求不管失败还是成功,意义都不大。因为三次握手之后,客户端和服务端都已经知道了双方的发送和接收是正常的,是可以进行数据传输的,就不需要再次发送确认请求了 70 | 71 | ## 三次握手过程中可以携带数据吗? 72 | 73 | 第一次、第二次握手不可以携带数据,原因就是会让服务器更加容易受到攻击了。而对于第三次的话,此时客户端已经处于 ESTABLISHED 状态。对于客户端来说,他已经建立起连接了,并且也已经知道服务器的接收、发送能力是正常的了,所以能携带数据也没啥问题。 74 | 75 | ## TCP四次握手 76 | 77 | - **第一次挥手:** 客户端主动断开连接,向服务端发送FIN报文段,即连接释放报文段(FIN=1,序号seq=u),并且状态变为FIN-WAIT-1(终止等待1)。 78 | - **第二次挥手:** 服务端收到客户端的FIN报文段,响应请求,也向客户端发送一个ACK = 1,seq = y, ack = u + 1(三次握手阶段已经解释过seq和ack,没看懂的可以重新看一遍) 的报文段,来表示自己已经收到了客户端的断开连接的请求报文,同时将自己的状态变为CLOSE-WAIT(关闭等待)。 79 | > 客户端收到第二次的挥手报文之后,也会将自身的状态变为FIN-WAIT-2(关闭等待),此时客户端已经断开了对服务端的连接,也就是说,客户端不能再向服务端发送数据了,但是服务端并没有断开连接,它仍然要向客户端发送未发送完的数据,即TCP处于半关闭状态,这也是为什么要四次挥手的原因。 80 | - **第三次挥手:** 等待一段时间,当服务端将剩余数据发送完后,也会向客户端发送一个FIN = 1 的报文段,其中seq = w, ack = u + 1, ACK = 1,同时,自身进入LAST-ACK(最后确认)状态,来等待客户端的ACK。 81 | - **第四次挥手:** 客户端收到服务端的断开释放报文段FIN,同样对它进行响应,向服务段发送ACK = 1 ,seq = u + 1, ack = w + 1的报文段,并且自身进入TIME-WAIT状态,一段时间过后,服务端收到客户端的响应报文,将自身状态变为Closed,客户端等待TIME-WAIT时间过后,也将变为Closed状态。四次挥手完毕,客户端和服务端断开连接。 82 | 83 | ![wALFFf.jpg](https://s1.ax1x.com/2020/09/05/wALFFf.jpg) 84 | 85 | ## 为什么最后一次挥手,客户端还要进入一个2MSL的TIME-WAIT状态? 86 | 87 | MSL(Maximum Segment Lifetime)可译为“最长报文段寿命”,它是任何报文在网络上存在的最长时间,超过这个时间报文将被丢弃。TCP允许不同的实现可以设置不同的MSL值。 88 | 89 | 如果客户端最后一次挥手发送的确认请求报文,服务端没有收到的话,服务端就会认为,是因为它自己发送的FIN报文段没有发送出去,导致客户端没有收到,客户端没有收到,就不会给它发送确认请求报文,于是,服务器会再次发送FIN报文段,所以有一个时长为2MSL的等待时间。 90 | 91 | ## TCP为什么是四次挥手,而不是两次或三次? 92 | 93 | 因为 TCP 是全双工通信。 94 | > **全双工**指在发送数据的同时也能够接收数据,两者同步进行,这好像我们平时打电话一样,说话的同时也能够听到对方的声音。 95 | 96 | **如果是两次:** 97 | 98 | 收到客户端请求断开(FIN)信号时,只是意味着客户端不再发送数据了,但服务端未必所有数据都已经发送到客户端了,甚至服务器如果还有数据要传输,但客户端由于已经断开连接所以接收不到 99 | 100 | **如果是三次:** 101 | 102 | 三次挥手之后,服务器发送断开连接的信号如果过程中出现了丢包,客户端无法确定服务器已经断开连接,另外,服务端也不确定客户端是否已经知道服务端断开连接了。所以,可能导致,服务端断开,而客户端还一直占用资源。 103 | 104 | ## TCP如何保证可靠性 105 | 106 | - 三次握手,保证两端建立连接。 107 | - 确认和重传:接收方收到报文就会确认,发送方发送一段时间后没有收到确认就会重传。 108 | - 数据校验:TCP报文头有校验和,用于校验报文是否损坏 109 | - 数据合理分片和排序: 110 | - 流量控制:当接收方来不及处理发送方的数据,能通过滑动窗口,提示发送方降低发送的速率,防止包丢失。 111 | - 拥塞控制:当网络拥塞时,通过拥塞窗口,减少数据的发送,防止包丢失。 112 | - TCP 的接收端会丢弃重复的数据。 113 | 114 | ## SYN攻击是什么? 115 | 116 | SYN攻击就是Client在短时间内伪造大量不存在的IP地址,并向Server不断地发送SYN包,Server则回复确认包,并等待Client确认,由于源地址不存在,因此Server需要不断重发直至超时,这些伪造的SYN包将长时间占用未连接队列,导致正常的SYN请求因为队列满而被丢弃,从而引起网络拥塞甚至系统瘫痪。SYN 攻击是一种典型的 DoS/DDoS 攻击。 117 | 118 | ## 如果已经建立了连接,但是客户端突然出现故障了怎么办? 119 | 120 | TCP还设有一个保活计时器,显然,客户端如果出现故障,服务器不能一直等下去,白白浪费资源。服务器每收到一次客户端的请求后都会重新复位这个计时器,时间通常是设置为2小时,若两小时还没有收到客户端的任何数据,服务器就会发送一个探测报文段,以后每隔75秒发送一次。若一连发送10个探测报文仍然没反应,服务器就认为客户端出了故障,接着就关闭连接。 121 | 122 | ## TCP 短连接和长连接的区别 123 | 124 | **短连接**:Client 向 Server 发送消息,Server 回应 Client,然后一次读写就完成了,这时候双方任何一个都可以发起 close 操作,不过一般都是 Client 先发起 close 操作。短连接一般只会在 Client/Server 间传递一次读写操作。 125 | 126 | 短连接的优点:管理起来比较简单,建立存在的连接都是有用的连接,不需要额外的控制手段。 127 | 128 | **长连接**:Client 与 Server 完成一次读写之后,它们之间的连接并不会主动关闭,后续的读写操作会继续使用这个连接。 129 | 130 | 在长连接的应用场景下,Client 端一般不会主动关闭它们之间的连接,Client 与 Server 之间的连接如果一直不关闭的话,随着客户端连接越来越多,Server 压力也越来越大,这时候 Server 端需要采取一些策略,如关闭一些长时间没有读写事件发生的连接,这样可以避免一些恶意连接导致 Server 端服务受损;如果条件再允许可以以客户端为颗粒度,限制每个客户端的最大长连接数,从而避免某个客户端连累后端的服务。 131 | 132 | 长连接和短连接的产生在于 Client 和 Server 采取的关闭策略,具体的应用场景采用具体的策略。 133 | 134 | 135 | ## 什么是粘包、拆包? 136 | 137 | 假设客户端分别发送了两个数据包D1和D2给服务端,由于服务端一次读取到的字节数是不确定的,故可能存在以下4种情况。 138 | 139 | - 服务端分两次读取到了两个独立的数据包,分别是D1和D2,没有粘包和拆包; 140 | - 服务端一次接收到了两个数据包,D1和D2粘合在一起,被称为TCP粘包; 141 | - 服务端分两次读取到了两个数据包,第一次读取到了完整的D1包和D2包的部分内容,第二次读取到了D2包的剩余内容,这被称为TCP拆包; 142 | - 服务端分两次读取到了两个数据包,第一次读取到了D1包的部分内容D1_1,第二次读取到了D1包的剩余内容D1_2和D2包的整包。 143 | 144 | 如果此时服务端TCP接收滑窗非常小,而数据包D1和D2比较大,很有可能会发生第五种可能,即服务端分多次才能将D1和D2包接收完全,期间发生多次拆包。 145 | 146 | ## 为什么会发生粘包和拆包 147 | 148 | - 要发送的数据大于 TCP 发送缓冲区剩余空间大小,将会发生拆包。 149 | - 待发送数据大于 MSS(最大报文长度),TCP 在传输前将进行拆包。 150 | - 要发送的数据小于 TCP 发送缓冲区的大小,TCP 将多次写入缓冲区的数据一次发送出去,将会发生粘包。 151 | - 接收数据端的应用层没有及时读取接收缓冲区中的数据,将发生粘包。 152 | 153 | ## 粘包和拆包解决办法 154 | 155 | 由于 TCP 本身是面向字节流的,无法理解上层的业务数据,所以在底层是无法保证数据包不被拆分和重组的,这个问题只能通过上层的应用协议栈设计来解决,根据业界的主流协议的解决方案,归纳如下: 156 | 157 | - **消息定长:**发送端将每个数据包封装为固定长度(不够的可以通过补 0 填充),这样接收端每次接收缓冲区中读取固定长度的数据就自然而然的把每个数据包拆分开来。 158 | - **设置消息边界:**服务端从网络流中按消息边界分离出消息内容。在包尾增加回车换行符进行分割,例如 FTP 协议。 159 | - **将消息分为消息头和消息体:**消息头中包含表示消息总长度(或者消息体长度)的字段。 160 | - 更复杂的应用层协议比如 Netty 中实现的一些协议都对粘包、拆包做了很好的处理。 161 | 162 | ## 为什么常说 TCP 有粘包和拆包的问题而不说 UDP ? 163 | 164 | UDP 是基于报文发送的,UDP首部采用了 16bit 来指示 UDP 数据报文的长度,因此在应用层能很好的将不同的数据报文区分开,从而避免粘包和拆包的问题。 165 | 166 | 而 TCP 是基于字节流的,虽然应用层和 TCP 传输层之间的数据交互是大小不等的数据块,但是 TCP 并没有把这些数据块区分边界,仅仅是一连串没有结构的字节流;另外从 TCP 的帧结构也可以看出,在 TCP 的首部没有表示数据长度的字段,基于上面两点,在使用 TCP 传输数据时,才有粘包或者拆包现象发生的可能。 167 | 168 | ## TCP如何提升网络利用率 169 | 170 | TCP协议中提高网络利用率的机制主要有:Nagle算法,延迟确认应答,捎带应答。 171 | 172 | **Nagle算法**: 173 | 174 | 发送端即使还有应该发送的数据,但如果这部分数据很少的话,则进行延迟发送的一种处理机制。具体来说,就是仅在下列任意一种条件下才能发送数据。如果两个条件都不满足,那么暂时等待一段时间以后再进行数据发送。 175 | 176 | - 已发送的数据都已经收到确认应答。 177 | - 可以发送最大段长度的数据时。 178 | 179 | **延迟确认应答:** 180 | 181 | 接收方收到数据之后可以并不立即返回确认应答,而是延迟一段时间的机制。 182 | 183 | - 在没有收到 2*最大段长度的数据为止不做确认应答。 184 | - 其他情况下,最大延迟 0.5秒 发送确认应答。 185 | - TCP 文件传输中,大多数是每两个数据段返回一次确认应答。 186 | 187 | **捎带应答:** 188 | 189 | 在一个 TCP 包中既发送数据又发送确认应答的一种机制,由此,网络利用率会提高,计算机的负荷也会减轻,但是这种应答必须等到应用处理完数据并将作为回执的数据返回为止。 190 | -------------------------------------------------------------------------------- /docs/javascript/extends.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 正文是讲JavaScript的八大继承、但我仍想和Java对比,最终我获得了一些启示 4 | >   其实这篇文章不是真的要比较出什么,只是我想从java和js设计思想上找到些什么。或许是理念,或许是一种思维,不管如何,可能真正体会到那种“境界”,还需要在未来不断地学习和理解语言,深入浅出,方臻此境。 5 | > 如果你已经理解了JavaScript的继承,可以直接看文章底部的心得。 6 | 7 | 8 | 9 | ## 为什么要将JavaScript的继承和Java的继承做比较? 10 |   Java作为一个面向对象的语言,继承机制浑然天成。而JavaScript的继承,更多情况下是在想尽办法“弥补”没有实现“面向对象”的遗憾。 11 | > 详可参见[Javascript继承机制的设计思想](http://www.ruanyifeng.com/blog/2011/06/designing_ideas_of_inheritance_mechanism_in_javascript.html) 12 | 13 | 14 | ## Java是如何实现继承的? 15 | 通过一个`extends`关键字,很直观地表现出继承关系。 16 | 17 | ```java 18 | class Father { 19 | void work() { 20 | System.out.println("刷碗"); 21 | } 22 | } 23 | 24 | class Son extends Father { 25 | 26 | } 27 | 28 | class Main { 29 | public static void main(String[] args) { 30 | Father father = new Father(); 31 | Son son = new Son(); 32 | 33 | father.work();//刷碗 34 | son.work();//刷碗 35 | 36 | } 37 | } 38 | ``` 39 |   下面即将介绍的所有JavaScript继承的写法,都拿来和Java作一次比较和体会。 40 |   关于对比,我主要考虑三个方面,“简洁性”、“通俗性”和“全面性”。简洁和通俗的继承能让人直观理解,更有利于代码逻辑的书写,不影响构思;而全面性,是能真正把继承的优势给发挥出来。 41 | ## JavaScript是如何实现继承的? 42 | 43 | ### 原型链继承 44 |   js中new实例的过程就是通过prototype的构造函数来创建实例对象,所以继承的本质就是**重写原型对象,代之以一个新类型的实例**。 45 | > 如果不理解,请参阅[什么是原型链、protetype、constructor?](https://blog.csdn.net/cc18868876837/article/details/81211729) 46 | 47 | ```javascript 48 | //父亲的构造函数 49 | function Father() { 50 | } 51 | 52 | //给父添加方法 53 | Father.prototype.work = function () { 54 | console.log('刷碗'); 55 | }; 56 | 57 | //儿子的构造函数 58 | function Son() { 59 | } 60 | 61 | //将父亲的实例覆盖儿子的原型对象,这样儿子的实例就继承了父亲 62 | Son.prototype = new Father(); 63 | 64 | new Father().work();//刷碗 65 | new Son().work();//刷碗 66 | ``` 67 | 68 |   总体来说,和java相比,这种方式是比较松散的,看起来也比较乱。和Java继承在某种程度上比较,原型链继承中JS把方法的定义抽出来,然后利用原型对象机制连接父和子。 69 | ### 借用构造函数来继承(经典继承) 70 |   使用父类的构造函数来增强子类实例,等同于复制父类的实例给子类(不使用原型) 71 | 72 | ```javascript 73 | //父亲的构造函数 74 | function Father() { 75 | this.hobby = ['吃饭','睡觉']; 76 | } 77 | //儿子的构造函数 78 | function Son() { 79 | Father.call(this); 80 | } 81 | 82 | console.log(new Father().hobby); 83 | console.log(new Son().hobby); 84 | ``` 85 | 86 |   核心代码是`Father.call(this)`,创建子类实例时调用`Father`构造函数,于是`Son`的每个实例都会将`Father`中的属性复制一份。换种方式理解,通过call将Father的实例里面的this指向Son。不过这种只能继承父类**实例**的属性和方法,不能继承原型的属性和方法。这里和java没什么关系,就不再对比。 87 | ### 组合继承 88 |   组合上述两种方法就是组合继承。用原型链实现对**原型**属性和方法的继承,用借用构造函数技术来实现**实例**属性的继承。 89 | ```javascript 90 | //父亲的构造函数 91 | function Father() { 92 | this.hobby = ['吃饭','睡觉']; 93 | } 94 | 95 | //给父添加方法 96 | Father.prototype.work = function () { 97 | console.log('刷碗'); 98 | }; 99 | 100 | //儿子的构造函数 101 | function Son() { 102 | Father.call(this); 103 | } 104 | 105 | Son.prototype = new Father(); 106 | Son.prototype.constructor = Son; 107 | 108 | let father = new Father(); 109 | let son = new Son(); 110 | console.log(father.hobby); 111 | console.log(son.hobby); 112 | father.work();//刷碗 113 | son.work();//刷碗 114 | ``` 115 | 116 |   组合继承避免了原型链和借用构造函数的缺陷,融合了它们的优点,成为`JavaScript`中最常用的继承模式。但是我个人觉得,即使这样,对于代码来说仍然显得松散,不能像Java继承一样让人直观理解。 117 | ### 原型式继承 118 |   Object.create(proto)目的就是以proto 为原型对象,创建一个新的实例a。a的改变不会影响 proto,proto 的改变会影响 a 119 | ```javascript 120 | function Father(){ 121 | this.name= 'Default' 122 | this.sayName = function() { 123 | console.log(this.name); 124 | } 125 | } 126 | const son = Object.create(new Father()) 127 | son.name = 'tom' 128 | son.sayName() 129 | ``` 130 | 131 |   和其他的继承方式相比,代码明显简洁了很多,但从理解上仍然不是那么直观,所以我们接着往下看。 132 | 133 | ### 寄生式继承 134 |   核心:在原型式继承的基础上,增强对象,返回构造函数。主要作用就是为构造函数新增属性和方法,以**增强函数**。 135 | ```javascript 136 | // 寄生继承: 137 | // 在原型继承的基础上,增强函数 138 | function Father(){ 139 | this.name = 'tom' 140 | } 141 | function createAnother(obj){ 142 | const newObj = Object.create(obj) 143 | newObj.sayName = ()=>{ 144 | console.log(newObj.name); 145 | } 146 | return newObj 147 | } 148 | const son = createAnother(new Father()) 149 | son.sayName() 150 | ``` 151 | 152 | ### 寄生组合式继承 153 |   这是才是大哥。寄生组合式继承是借用构造函数传递参数和寄生模式实现继承。几乎是融合了以上所有的JS继承的优点。 154 | 155 | ```javascript 156 | function inheritPrototype(son, father){ 157 | let prototype = Object.create(father.prototype); // 创建对象,创建父类原型的一个副本 158 | prototype.constructor = son; // 增强对象,弥补因重写原型而失去的默认的constructor 属性 159 | son.prototype = prototype; // 指定对象,将新创建的对象赋值给子类的原型 160 | } 161 | 162 | // 父类初始化实例属性和原型属性 163 | function father(name){ 164 | this.name = name; 165 | this.hobby = ["吃饭", "睡觉"]; 166 | } 167 | father.prototype.sayName = function(){ 168 | console.log(this.name); 169 | }; 170 | 171 | // 借用构造函数传递增强子类实例属性(支持传参和避免篡改) 172 | function son(name, age){ 173 | father.call(this, name); 174 | this.age = age; 175 | } 176 | 177 | // 将父类原型指向子类 178 | inheritPrototype(son, father); 179 | 180 | // 新增子类原型属性 181 | son.prototype.sayAge = function(){ 182 | console.log(this.age); 183 | }; 184 | 185 | let instance1 = new son("儿子1", 23); 186 | let instance2 = new son("儿子2", 23); 187 | 188 | instance1.hobby.push("打豆豆"); 189 | 190 | console.log(instance1.hobby);// ["吃饭", "睡觉","打豆豆"] 191 | console.log(instance2.hobby);// ["吃饭", "睡觉"] 192 | ``` 193 |   这是最成熟的方法,也是现在很多库实现的方法。这位老大哥继承方式高效、灵活。但是缺点也一目了然,尤其是对于初学者来说,很容易望而却步。 194 | 195 | ### 混入方式实现多继承 196 | ```javascript 197 | function MyClass() { 198 | SuperClass.call(this); 199 | OtherSuperClass.call(this); 200 | } 201 | 202 | // 继承一个类 203 | MyClass.prototype = Object.create(SuperClass.prototype); 204 | // 混合其它 205 | Object.assign(MyClass.prototype, OtherSuperClass.prototype); 206 | // 重新指定constructor 207 | MyClass.prototype.constructor = MyClass; 208 | 209 | MyClass.prototype.myMethod = function() { 210 | // do something 211 | }; 212 | ``` 213 | 214 |   `Object.assign`会把 `OtherSuperClass`原型上的函数拷贝到 `MyClass`原型上,使 `MyClass` 的所有实例都可用 `OtherSuperClass` 的方法。 215 |   不得不说,JS的的确确实现了继承:“让子类对象具有父类的属性和方法”。然而,却都不能睥睨Java的继承实现,做到简洁、直观、通俗、全面。 216 |   难道JS中真的没有一种能和面向对象的Java的继承睥睨打方法吗? 217 |   这时,ES6带着class来了。 218 | 219 | ### ES6中class和extends 220 |   extends关键字主要用于类声明或者类表达式中,以创建一个类,该类是另一个类的子类。其中constructor表示构造函数,一个类中只能有一个构造函数,有多个会报出SyntaxError错误,如果没有显式指定构造方法,则会添加默认的 constructor方法。 221 | 222 | > 这段定义几乎和java一模一样,稍有区别的是,js中`constructor()`是构造函数,java中`类名()`就是构造函数。 223 | 224 | ```javascript 225 | class Father{ 226 | constructor(name){ 227 | this.name = name; 228 | } 229 | 230 | work(){ 231 | console.log(this.name+'刷碗'); 232 | } 233 | } 234 | 235 | class Son extends Father{ 236 | } 237 | 238 | new Son('儿子').work(); 239 | ``` 240 |   如果你是第一次见到class和extends来实现继承,你可能会和我一样惊讶。这和Java几乎吻合了,通过这种语法,成功地让JavaScript好像拥有"类"、“面向对象”的真实概念了。也做到了Java一样简洁、直观、全面。 241 |   extends的核心代码,实际上就是包装了老大哥(寄生组合式继承),巧妙的实现了这种通俗易懂的逻辑。 242 | 243 | ## 心得 244 |   从JavaScript问世以来,好像后人一直在“补坑”中,实现继承也好,安全漏洞也好,至少从结果来看,这门语言在全世界的努力下,并没有没落下去,反而因为它的“松散”慢慢发展了更多的分支。随着编程世界的发展,出现了Node、出现了npm、出现了TypeScript等等,让JavaScript从当年的一个“小毛孩”,变成一个“大男孩”了。 245 | 246 |   返璞归真,一门语言是为了需求而服务的,能生存下去,也是让自己不断更新来顺应新的需求。就拿js的继承来说,从"原型链继承"到成长为"寄生组合式继承",最后到"extends继承",这是时代的产物。不难想象,JavaScript在未来的发展方向只有一个,**更方便、更灵活、更人性化来实现需求**。 247 | 248 |   相信看这篇文章的朋友都是和我一样,在走这条路,那么对于我们个人来说,能有什么**启示**呢? 249 | 250 |   对于我来说,如果我们也想在今后的路中游刃有余,也不能止于现状,要顺应时代的发展而自我发展。举个简单的例子,我们要根据自己的职业和方向来构建自己的知识体系、打造方法论,甚至写出一套方便工作的“框架”。如果你真的要输出1-10,你要考虑写个循环而不是switch,用更短、更简洁、更人性化的代码来打造这个世界。 251 | 252 |   这里突然想到产品的交互式设计,也跟大家分享一下:功能手机正在淘汰,因为它不如智能机的功能更加方便人的生活;老式电视遥控器正在被新遥控器淘汰,老式功能机功能按键又多又复杂,新遥控器仅有的几个按键刚好满足了人们95%的需求,剩下5%在电视设置里也能找到。**功能不在多少,合适就好**。对我们人来说,可以让生活和工作都变得“刚好合适”,对于JavaScript语言来说,不同的分支也能各自发展出“刚好合适”的地步。 253 | 254 |   不固执历史、不停止当下、不畏惧发展。 255 | 256 |   Come on!World! 257 | 258 |   Come on!Me! 259 | -------------------------------------------------------------------------------- /docs/react/react.md: -------------------------------------------------------------------------------- 1 | 在前端面试中,很多大公司都是React技术栈,一份扎实的React基础必不可少,这里汇总常见的React问题和回答,以及作者亲身经历的React问题。 2 | 3 | ?> **避坑指南**:本文已经尽力避免了“多说一些细节点”,只是在“听起来这个人基础还不错”的状态下完成回答,尽力避免给自己挖坑,以至于让面试官深挖,最后万劫不复。当然,如果你对自己有信心,可以尝试多说一些,只要能保证你补充的地方还能深挖,并且能答上来,那妥妥的加分项。但是如果从概率上考虑,可能数学期望为负数,你可要好好斟酌斟酌。 4 | 5 | ## 什么是React/优点? 6 | React是一个用于构建用户界面的js库。使用JSX语法,专注于view层,遵循组件设计模式、声明式编程范式、函数式编程概念,使前端应用更加高效。使用虚拟DOM来有效操作DOM,单向数据流流动,同时React有很好的生态,提供了丰富的解决方案。 7 | 8 | ## 说一下React的声明周期 9 | 10 | 目前React的生命周期分为3个阶段: 挂载阶段(Mount)、更新阶段(Update)、卸载阶段(Unmount) 11 | 12 | #### 挂载阶段(Mount): 13 | 14 | - **constructor**:构造函数,最先被执行,通常在state里初始化state对象或者给自定义方法绑定this。 15 | - **getDerivedStateFromProps(nextProps,prevState)**:将传入的props映射到state上面 16 | - **render**:render渲染 17 | - **componentDidMount**:这里可以发送服务器请求、获取DOM节点、对canvas进行操作。 18 | 19 | #### 更新阶段(Update): 20 | 21 | - **getDerivedStateFromProps**:新的props会传过来 22 | - **shouldComponentUpdate(nextProps,nextState)**:返回一个布尔值,true表示触发重新渲染,false表示不会触发重新渲染,默认返回true 23 | - **render**:更新阶段也会触发生命周期 24 | - **getSnapshotBeforeUpdate(prevProps,prevState)**:返回值会传入componentDidUpdate作为第三个参数,必须和componentDidUpdate一起使用 25 | - **componentDidUpdate(prevProps,prevState,snapshot)**:如果触发某些回调函数时需要用到DOM的状态(如滚动位置),则将对比和计算的过程写在getSnapshotBeforeUpdate,然后在componentDidUpdate里统一触发回调或更新状态 26 | 27 | #### 卸载阶段(Unmount): 28 | 29 | - **componentWillUnmount**:当组件卸载和销毁时调用,可以在这个函数里去清除一些定时器,取消网络请求,清除无效DOM等垃圾清理工作 30 | 31 |
32 | 点击展开React最新生命周期图 33 |
 34 | 
 41 | >
 42 | 
43 |
44 | 45 | ## React请求放到哪个生命周期中? 46 | 挂载阶段渲染之后的componentDidMount 47 | ## React的合成事件? 48 | 例如`` 49 | React并非是直接在真实DOM上绑定事件,为了避免DOM事件滥用,在document监听所有事件,当事件冒泡至document处时,React将事件内容交给中间层SyntheticEvent负责事件合成,当事件触发时,对统一分发函数dispatchEvent指定函数运行。 50 | 51 | 在React里也可以使用原生事件(addEventListener),但最好不要混用,一旦stopPropagation阻止了冒泡,会导致其他React事件失效。 52 | ## setState是同步的还是异步的? 53 | - 生命周期函数、合成函数中是**异步**的 54 | - 原生事件、setTimeout、setInterval是**同步**的。 55 | 56 | 这里的异步其实是指将多个state合成到一起进行批量更新。 57 | 58 | 原理:setState会根据一个变量isBatchingUpdates判断是直接更新this.state还是放到队列回头再说,在React调用合成事件之前会调用batchedUpdates(),通过修改变量isBatchingUpdates为ture,使之为异步。 59 | 60 | ## 为什么JSX组件名要以大写字母开头? 61 | 62 | React要知道要渲染的是组件还是HTML元素 63 | 64 | ## refs 的作用是什么? 65 | 66 | Refs 是 React 提供给我们的安全访问 DOM 元素或者某个组件实例的句柄。 67 | 68 | 我们可以为元素添加ref属性然后在回调函数中接受该元素在 DOM 树中的句柄,该值会作为回调函数的第一个参数返回 69 | 70 | ## 为什么使用Redux? 71 | (Redux是什么) 72 | Redux是为了解决组件间通信和组件状态共享。 73 | 74 | (Redux带来了什么好处) 75 | 在我的项目中,涉及到很多物理实验,不同实验还有很多复杂的数据,又考虑到页面深度和未来的易扩展性,因为未来还要有很多物理实验要添加进去,决定统一使用Redux进行状态管理。 76 | 77 | (我是如何使用Redux的) 78 | view的组件派发action(用dispatch方法)给Store,借助reducer里根据action的type判断执行哪种操作,最后将新State返回给store进而转给component 79 | ## Redux如何实现异步操作? 80 | ?> Action 发出以后,Reducer 立即算出 State,这叫做同步;Action 发出以后,过一段时间再执行 Reducer,这就是异步。 81 | 82 | 我使用的是redux-thunk,通过Redux里applyMiddlewares()来添加中间件redux-thunk,通过改造store.dispatch()可以让它接收一个函数参数(正常情况下参数只能是对象),这个函数是一个可以返回函数的Action Creator,在这里面进行异步操作。 83 | ## 你还知道其他的Redux异步处理方式吗? 84 | 85 | - **redux-promise**改造dispatch方法接收Promise对象作为参数 86 | - **redux-saga**通过把异步处理剥离出来单独执行,利用generator实现异步处理 87 | 88 | ## Redux和Mobx区别 89 | - Redux数据保存在单一store中,Mobx数据保存在多个store中 90 | - redux使用是不可变状态,使用的是纯函数,而mobx的状态是可变的 91 | - redux提供时间回溯的工具,调试更加容易,mobx更多的是抽象和封装,调试比较困难 92 | 93 | (使用场景) 94 | 95 | Mobx更适合数据不复杂的应用,面对复杂度高的应用,难以调试,状态无法回溯,会力不从心。 96 | 97 | Redux适合有回溯需求的项目,比如画板,有时候需要撤销功能,就算是更复杂的项目,统一store管理数据,还有方便的调试工具,至少开发者不会感到无从下手。 98 | 99 | ## React如何实现组件间通讯 100 | - 父组件向子组件通讯:直接通过props属性 101 | - 子组件向父组件通讯:通过props传入一个函数,子组件通过函数参数传递信息到父组件作用域中 102 | - 兄弟组件通讯:先通过父组件,在到子组件 103 | - 跨层级通信:可以使用Context,但是会是组件的**复用性**变差 104 | - 借助全局状态管理工具:Redux、Mobx 105 | 106 | ## React如何性能优化? 107 | 针对于React的优化可考虑通过shouldComponentUpdate控制不必要的渲染 108 | 其他的可参考浏览器的优化 109 | 110 | ## 为什么虚拟DOM能够提高性能? 111 | 因为DOM操作是很消耗性能的,而虚拟DOM相当于在js和真实DOM中间加了一个缓存,通过diff算法避免没必要的DOM操作,从而提高性能。 112 | 113 | ## 说一下diff算法? 114 | 将树形结构按照层级分解,只比较同级元素,并给每一个单元添加key属性,方便比较。调用setState的时候,给需要重新渲染的位置标记脏值dirty,最后会检查所有标记dirty的component重新绘制 115 | 116 | ## React Class component和function component有什么区别? 117 | function comconent是一种无状态组件,更好体验容器和表现分离,直接接收props作为参数,return返回代码片段,没有生命周期,**提倡使用**。 118 | 119 | class component有生命周期,用state改变内部状态,可以利用shouldComponentUpdate优化性能 120 | 121 | ## 高阶组件HOC和render props是什么?带来了什么问题 122 | 123 | #### 高阶组件 124 | 125 | 高阶组件可以看作 React对装饰模式的一种实现,高阶组件是一个函数,接收一个组件,然后返回一个新的组件。 126 | 127 | > 高阶组件( HOC)是 React中的高级技术,用来重用组件逻辑。但高阶组件本身并不是 ReactAPI。它只是一种模式,这种模式是由 React自身的组合性质必然产生的。 128 | 129 | **手写一个高阶组件:** HOC的简单应用,函数接收一个组件作为参数,并返回一个新组件,新组建可以接收一个 visible props,根据 visible的值来判断是否渲染Visible。 130 | 131 | ```javascript 132 | // 手写一个高阶组件 133 | function visible(WrappedComponent) { 134 | return class extends Component { 135 | render() { 136 | const { visible, ...props } = this.props; 137 | if (visible === false) return null; 138 | return ; 139 | } 140 | } 141 | } 142 | ``` 143 | 144 | **高阶组件的优点:** 145 | 146 | - 不会影响内层组件的状态, 降低了耦合度 147 | 148 | **高阶组件的缺点:** 149 | 150 | - 相同的props会发生覆盖:如果一个已经封装好的高阶组件,在外用了name属性,如果被包裹组件也用了name属性,则会造成覆盖,增加了调试和代码修复的时间。 151 | - 被包裹组件的静态方法会消失:静态方法本来在组件的statics对象内,由于组件被包裹,返回的组件已经是新组件。如果需要保留,可以手动将原组件方法拷贝给新组件。 152 | - 高阶组件和 Render Props 一样,本质上都是将复用逻辑提升到父组件中,很容易产生很多包装组件,带来的「嵌套地域」。 153 | 154 | #### render props 155 | 156 | 将一个组件内的 state 作为 props 传递给调用者, 调用者可以动态的决定如何渲染. 157 | 158 | **缺点:** 159 | - 无法在 return 语句外访问数据 160 | - 导致嵌套地狱 161 | 162 | ## React hooks解决了什么问题 163 | 164 | **发展由来,为什么要有React Hooks:** 165 | 166 | React的核心是组件,在V16.8之前,组件的标准写法是类(class),而由类写成的组件,其实是相对较“重”的,随着React应用的负复杂层级不断叠加,就会更复杂。 167 | 168 | 缺点: 169 | 170 | - 组件类引入了复杂的编程模式,比如 render props 和高阶组件。 171 | - 大型组件难以拆分和重构,和难以测试 172 | - 业务逻辑分散在各个方法中,导致重复逻辑或关联逻辑 173 | 174 | React团队希望:**组件的最佳写法应该是函数,而不是类。** 175 | 176 | 虽然React早期就支持函数组件,但是必须是纯函数,不能包含状态,也不支持生命周期方法,所以不能取代类。 177 | 178 | 所以**React Hooks 的设计目的,就是加强版函数组件,完全不使用"类",就能写出一个全功能的组件。** 179 | 180 | ## react hooks 181 | 182 | React Hooks 的意思是,组件尽量写成纯函数,如果需要外部功能和副作用,就用钩子把外部代码"钩"进来。 183 | 184 | |hook|说明| 185 | |-|-| 186 | |useState🏁|给组件添加内部state| 187 | |useEffect🏁|相当于合并了componentDidMount、componentDidUpdate、componentWillUnmount,用于添加“副作用”;React 将按照 effect 声明的顺序依次调用组件中的每一个 effect;可根据第二个数组参数有选择调用,传入[]意味着只执行一次 188 | |useContext🏁|不用组件嵌套就可以订阅React的Context| 189 | |useReducer|通过reducer来管理本地复杂的state| 190 | |useCallback|缓存函数的引用,把内联回调函数及依赖项数组作为参数传入 useCallback,它将返回该回调函数的 memoized 版本,该回调函数仅在某个依赖项改变时才会更新。第二个参数依赖项(deps)是一个空数组它代表这个函数在组件的生成周期内会永久缓存。| 191 | |useMemo|缓存计算数据的值| 192 | |useLayoutEffect|和useEffect类似| 193 | 194 | hook的规则: 195 | - 只能在**函数最外层**调用hook,不要在循环、条件判断、子函数中调用 196 | - 只能在**React函数组件**和**自定义hook**中调用hook 197 | 198 | ## Hooks写输入组件,实现change进行请求接口 199 | 200 | ```javascript 201 | import React, { useEffect, useState } from 'react' 202 | 203 | //url从外部传入 204 | export default function Search(props) { 205 | //设置初始值 206 | const [value,setValue] = useState('') 207 | //value设置成当前改变后的值 208 | const setSearchText = e=>{ 209 | setValue(e.target.value) 210 | } 211 | useEffect(()=>{ 212 | //请求搜索接口 213 | axios.post(props.url,{value},{headers:{ 214 | 'content-type':'application/json' 215 | }}). 216 | then(function (response) { 217 | console.log(response); 218 | }) 219 | .catch(function (error) { 220 | console.log(error); 221 | }); 222 | }) 223 | return 224 | } 225 | ``` 226 | 227 | ## useCallback和useMemo的区别 228 | 229 | 先说结论:useCallback和useMemo都可缓存函数的引用或值,但是从更细的使用角度来说useCallback缓存函数的引用,useMemo缓存计算数据的值。 230 | 231 | useCallback: 232 | 父组件重新渲染,本来子组件也要全部重新渲染,可以通过 useCallback 保护一些函数不必重新渲染,以此优化性能。 233 | 234 | useMemo: 235 | 缓存值的计算结果 236 | 237 | **useCallback:** 238 | 239 | 240 | 假设一个累加器,我们对count使用useState,那么我们或许定一个handleCount来执行setCount(count+1)。实际上,每一次重新渲染组件时,handleCount都是新创建的函数,那么将handleCount作为props传递给子组件时,shouldComponentUpdate相关优化就会失效,因为每次都是不同的函数。而如果使用了useCallback,那么则得到的是一个缓存函数,只有依赖项发生改变时才会更新。 241 | 242 | **useMemo:** 243 | 244 | useMemo和useCallback很像,区别是 245 | 246 | - useCallback是根据第二个参数依赖(deps)缓存第一个入参的(callback),是回调函数的缓存版本。 247 | - useMemo是根据依赖(deps)缓存第一个入参(callback)执行后的值,可用于密集型计算大的一些缓存。 248 | 249 | **useCallback(fn, deps) 相当于 useMemo(() => fn, deps)。** 250 | 251 | ## useEffect和useLayoutEffect 252 | 253 | useEffect 用来取代 componentDidMount 和 componentDidUpdate。主要作用是当页面渲染后,进行一些副作用操作(比如访问 DOM,请求数据)。 254 | 255 | **区别:** 256 | 257 | useLayoutEffect 的作用和 useEffect 几乎差不多,不过useLayoutEffect 可以使用它来读取 DOM 布局并同步触发重渲染。因为useLayoutEffect会在组件树构建完毕后同步立刻执行,而useEffect会等其他js代码执行完毕后执行(或者遇到下一次刷新任务前),也就是说等待 useLayoutEffect 内部状态修改后,才会去更新页面,因此可以通过这个机制,来解决一些特性场景下的页面闪烁问题, 258 | 259 | -------------------------------------------------------------------------------- /docs/network/http.md: -------------------------------------------------------------------------------- 1 | 本文旨在用“通俗”一点、又不失专业的话术,来说明白这些知识点。 2 | 3 | ?> 如果你是为了**面试**而看本文的话,我建议你先去通读一遍《图解HTTP》,然后再通过这篇文章来巩固知识点,可达到事半功倍的效果。 4 | 5 | ### 网络分层和意义 6 | 7 | - 原理型网络分层 8 | - 物理层:采集到的数据转换成二进制数据。以及网络采用什么介质。 9 | - 数据链路层:数据帧级别的传输问题。负责错误重发,流量控制,拥堵控制等 10 | - 网络层:负责点对点的传播,两个主机之间的识别。Ip协议 11 | - 传输层:负责两个主机之间建立连接。拥有tcp/udp协议 12 | - 应用层:负责应用之间的打包和解析。http协议 13 | 14 | - ISO标准分层 15 | - 物理层 16 | - 数据链路层 17 | - 网络层 18 | - 传输层 19 | - 会话层:确定会话的连接和中断 20 | - 表示层:对数据进行翻译、加密、压缩等操作 21 | - 应用层:为特定类型的网络应用提供访问OSI环境的手段 22 | 23 | ### ISO网络层/应用层……实现了哪些协议? 24 | 25 | ![wAO22F.jpg](https://s1.ax1x.com/2020/09/05/wAO22F.jpg) 26 | 27 | ### HTTP请求方法 28 | HTTP/1.0 29 | 30 | - **GET**:向服务器请求,获取资源。eg:请求某个页面。 31 | - **POST**:传输实体主体,将数据发送给服务器。eg:把登录信息发送给服务器验证是否可以登录。 32 | - **HEAD**:请求资源头部信息,和GET的资源头部信息一致,只是不返回报文主体。eg:下载大文件之前,先获取其大小,再决定是否要下载,以此节省带宽资源。 33 | 34 | HTTP/1.1 35 | 36 | - **PUT**:传输文件给服务器,然后保存到URI指定位置。eg:博客添加新文章。 37 | - **DELETE**:删除指定文件,与PUT相反。eg:博客删除已存在的文章。 38 | - **OPTIONS**:获取目标资源所支持的请求方法。eg:黑客常用OPTIONS请求来查看目标服务器支持的HTTP请求方法。 39 | - **TRACE**:原样返回客户端请求的内容。eg:测试代理服务器中转时,请求是否被篡改。 40 | - **CONNECT**:与代理服务器建立隧道来通信(网页开发用不到)。eg:翻墙时,为了通过国外服务器进行中转,首先需要客户端与国外服务器建立连接,这时使用CONNECT方法。 41 | - **PATCH**:局部更新资源,是对PUT方法对补充。eg:局部更新指定文章的标题。 42 | 43 | ### URL和URI的区别 44 | - URI是统一资源标识符,只是一个字符串的格式标准,并没有指定它的用途。 45 | - URL是统一资源定位符,是资源定位的规范,包括网址、ftp服务器、文件路径等。 46 | 47 | 可以把URI理解成URL更高层次的抽象,其实URI本来是由[URL和URN(统一资源名称)](https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Basics_of_HTTP/Identifying_resources_on_the_Web)组成的,但URN没流行起来,导致现在的URI基本都是URL。 48 | ### GET和POST有什么区别? 49 | - 数据传输方式不同:get在URL中,post在请求体中 50 | - 安全性不同:get在URL中,很容易通过历史记录、缓存查到数据信息 51 | - 数据类型不同:get只允许ASCII字符,post无限制 52 | - 特性:get安全幂等。post非安全非幂等。这里的安全是指不会引起服务器状态变化。 53 | 54 | > 幂等(idempotence):多次请求结果和一次请求结果相同。 55 | > 56 | > “Methods can also have the property of "idempotence" in that (aside from error or expiration issues) the side-effects of N > 0 identical requests is the same as for a single request.” 57 | 58 | ### PUT和POST都可以给服务器新增资源,有什么区别? 59 | - PUT:幂等,URI指向单一资源。 60 | - POST:非幂等,URI指向资源集合。 61 | 62 | eg:比如博客系统,创建新文章,用`POST https://www.blog.com/articles`,多次调用,就创建多个文章。如果更新指定文章的标题,用`PUT https://www.blog.com/articles/000001`,多次调用,和第一次调用效果相同。 63 | ### PUT和PATCH都可以向服务器更新资源,有什么区别? 64 | PATCH是对PUT的补充。PUT是更新整条记录,对重复字段也进行覆盖。PATCH对资源进行“局部更新”。 65 | 66 | ### HTTP请求报文是什么样的? 67 | 请求报文4部分组成: 68 | 69 | - 请求行 70 | - 请求头部 71 | - 空行 72 | - 请求体 73 | 74 | ![请求报文的组成](https://s1.ax1x.com/2020/03/25/8veMXq.png) 75 | 76 | ![《图解HTTP》——请求报文](https://s1.ax1x.com/2020/03/25/8vV8K0.jpg) 77 | 78 | 上图来自《图解HTTP》,GET方法无请求体,如果是POST方法,可能会有形如name=admin&password=admin的请求体。 79 | 80 | ### HTTP响应报文是什么样的? 81 | 请求报文有4部分组成: 82 | 83 | - 响应行 84 | - 响应头 85 | - 空行 86 | - 响应体 87 | 88 | ![响应报文组成](https://s1.ax1x.com/2020/03/25/8vmcZV.png) 89 | 90 | ![《HTTP图解》响应报文](https://s1.ax1x.com/2020/03/25/8ve64e.jpg) 91 | 92 | ### 聊一聊HTTP首部有哪些? 93 | #### RFC2616定义的47种首部字段 94 | ##### 通用首部(General Header Fields)9个 95 | |首部字段名|说明| 96 | |-|-| 97 | |Cache-Control✨|控制缓存| 98 | |Connection✨|控制代理不再转发的首部字段、管理持久连接| 99 | |Transfor-Encoding✨|指定报文主体的传输编码方式| 100 | |Date|创建报文的日期| 101 | |Via|代理服务器相关信息| 102 | |Warning|错误通知| 103 | |Upgrade|升级为其他协议| 104 | |Trailer|记录了报文主体之后的首部字段| 105 | |Pragma|报文指令,HTTP/1.1之前的历史遗留字段| 106 | 107 | ##### 请求首部(Request Header Fields)19个 108 | |首部字段名|说明| 109 | |-|-| 110 | |Accept✨|客户端或代理能够处理的媒体类型| 111 | |Accept-Encoding|优先的内容编码(gzip、deflate……)| 112 | |Accept-Language|优先的语言(中文、英文……)| 113 | |Accept-Charset|优先的字符集(ISO-8859-1……)| 114 | |If-Match✨|比较实体标记(ETage),匹配一致则同意请求| 115 | |If-None-Match✨|比较实体标记,匹配不一致则同意请求| 116 | |If-Modified-Since✨|比较资源更新时间,如果当前在指定日期之后,则接受请求| 117 | |If-Unmodified-Since✨|比较资源更新时间,与If-Modified-Since相反| 118 | |Range✨|获取部分资源的范围。eg:bytes=5001-10000| 119 | |If-Range|资源未更新时发送实体byte的范围请求| 120 | |Authorization✨|Web的认证信息| 121 | |Proxy-Authorization|代理服务器要求Web认证信息| 122 | |Host✨|请求资源所在的服务器| 123 | |From|用户的邮箱地址| 124 | |User-Agent|客户端程序信息| 125 | |Max-Forwrads|最大逐跳次数| 126 | |TE|传输编码的优先级| 127 | |Referer|请求中URI的原始获取方| 128 | |Expect|期待服务器的特定行为| 129 | 130 | ##### 响应首部(Response Header Fields)9个 131 | |首部字段名|说明| 132 | |-|-| 133 | |Accept-Range|是否接受字节范围请求| 134 | |Age|推算资源创建经过的时间| 135 | |ETag✨|实体标记,资源的匹配信息| 136 | |Location✨|令客户端重定向到指定URI| 137 | |Proxy-Authenticate|代理服务器对客户端的认证信息| 138 | |Retry-After|下次请求服务器的时间,与503一起使用| 139 | |Server✨|HTTP服务器的安装信息| 140 | |Vary|代理服务器的缓存信息| 141 | |WWW-Authenticate|服务器对客户端的认证信息| 142 | 143 | ##### 实体首部(Entiy Header Fields)10个 144 | 实体首部字段是包含在请求报文和响应报文中实体部分所用的首部,用于补充内容的更新时间等与实体相关的信息 145 | 。 146 | 147 | |首部字段名|说明| 148 | |-|-| 149 | |Allow✨|资源可支持的HTTP方法| 150 | |Expires✨|实体主体过期的日期时间| 151 | |Last-Modified✨|资源最后的修改日期时间| 152 | |Content-Encoding|实体主体使用的编码方式| 153 | |Content-Language|实体主体的自然语言| 154 | |Content-Length|实体主体的大小(单位:字节)| 155 | |Content-Location|代替对应资源的URI| 156 | |Content-MD5|实体主体的报文摘要| 157 | |Content-Range|实体主体的位置范围| 158 | |Content-Type|实体主体的媒体类型| 159 | 160 | #### 非HTTP/1.1的首部字段 161 | Cookie、Set-Cookie为cookie服务,Content-Disposition来指示如何显示附加的文件…… 162 | 163 | ### HTTP状态码 164 | 165 | |状态码|说明| 166 | |-|-| 167 | |200 Ok✨|客户端请求在服务器被正确处理| 168 | |201 Created|请求已经实现,并且有新资源已经依据请求的需要被建立| 169 | |202 Accepted|请求已接受,但还没执行,不保证完成请求| 170 | |204 Not Content|请求成功,但相应报文不包含实体的主体部分| 171 | |206 Partial Content✨|进行范围请求| 172 | |301 Moved Permanently|永久重定向,资源被分配了新的URL| 173 | |302 Found✨|临时重定向,资源被临时分配了新的URL| 174 | |303 See Other|资源存在着另一个URL,应使用GET获取资源| 175 | |304 Not Modified✨|服务器允许访问资源,但请求未满足条件| 176 | |307 Temporary Redirect|临时重定向,和302含义相同| 177 | |400 Bad Request✨|请求报文存在语法错误| 178 | |401 Unauthorized✨|请求需要通过有HTTP认证信息| 179 | |403 Forbidden✨|请求资源访问被服务器拒绝| 180 | |404 Not Found✨|资源未找到| 181 | |405 method not allowed|请求方式与后台规定的不符合| 182 | |408 Request Timeout|客户端请求超时| 183 | |409 Confict|请求资源可能引起冲突| 184 | |500 Internal Sever Error✨|服务器在执行请求时发生了错误| 185 | |503 Service Unavailable|服务器正超负载或停机维护| 186 | 187 | ### HTTP的keep-alive是干什么的? 188 | 早期HTTP/1.0每次请求都要重新建立一次连接,为了减少资源消耗、缩短响应时间,就要重用连接,想要保持长连接,需要在请求头上加上Connection:keep-alive。 189 | 190 | ### HTTP和HTTPS的区别? 191 | https是安全版的http,http协议都是明文传输,https的加密传输可以最大程度保证通信安全。 192 | 193 | ### HTTP/1.0和HTTP/1.1有什么区别? 194 | HTTP/1.0有`GET`、`POST`、`HEAD`三个请求方法,HTTP/1.1新增了`PUT`、`DELETE`、`PATCH`、`OPTIONS`、`TRACE`、`CONNECT` 195 | 请求方法,并且支持了持久连接。 196 | ### HTTP2相对于HTTP/1.X有什么优势? 197 | - 多路复用,合并TCP请求✨ 198 | - 头部压缩,只发送差异数据,减少头部信息量 199 | - 二进制分帧解析更高效 200 | - 服务器主动推送 201 | 202 | > - **多路复用:** 就是在一个TCP连接中可以存在多条流。换句话说,也就是可以发送多个请求,对端可以通过帧中的标识知道属于哪个请求。通过这个技术,可以避免HTTP旧版本中的队头阻塞问题,即消除了因多个TCP连接而带来的延时和内存消耗,极大的提高传输性能。 203 | 204 | ### HTTPS的加密通信是怎么样的? 205 | HTTPS使用了两种加密,对称加密和非对称加密。 206 | 207 | - 对称加密:通讯双方使用同一密钥。性能好,但不安全。 208 | - 非对称加密:通讯双方各有一个私钥,共享一个公钥。安全,但性能差。 209 | 210 | 解决方案(混合加密): 211 | 将*对称加密的密钥*通过*非对称加密的公钥*进行**加密**,并发送出去,通信双方在通过各自的*私钥*进行**解密**获得*对称加密的密钥*,然后双方就可以使用对称加密来沟通。 212 | 213 | 混合加密+数字证书保证安全性: 214 | 215 | 1. 服务器把自己的公钥登录到CA(数字证书认证机构)。 216 | 2. CA用自己的私钥部署数字签名并颁发公钥证书。 217 | > 公钥证书=服务器的公开密钥+CA的数字签名。 218 | 3. 客户端拿到服务器的公钥证书后,用CA公钥进行验证真实性。 219 | > CA的公钥,已经事先写进浏览器了。 220 | 4. 客户端使用服务器公钥加密报文。 221 | 5. 服务器使用自己的私钥进行解密。 222 | 223 | ### HTTP缓存过程是怎样的? 224 | 1. 客户端向服务器请求资源 225 | 2. 服务器缓存资源,并通过响应头决定缓存策略 226 | 3. 客户端根据响应头的缓存策略决定是否缓存(这里假设是),并将响应头与资源缓存下来 227 | 4. 当客户端再次请求命中资源的时候,检查缓存策略,根据策略不同、过期时间等判断直接读取本地缓存还是服务器协商缓存。 228 | 229 | ![缓存过程](https://s1.ax1x.com/2020/03/26/8z0ikT.png) 230 | 231 | ### 强缓存和协商缓存? 232 | 强缓存(Cache-Control):*只有首次请求和服务器通信*,读取缓存不用发送请求。返回200 233 | 234 | > 如果资源没过期,则直接从缓存读取(强制缓存),此时在Network一栏可以看到资源对应的状态码为200(from disk cache)或者是 200 (from memory cache) 235 | > 236 | >比如,资源没过期的时候我们打开新的页面,资源会从硬盘缓存中读取(from disk cache);如果我们此时又刷新页面,资源会从内存缓存中读取(from memory cache) 237 | 238 | 协商缓存(ETag/If-None-Match):*总会与服务器交互*,第一次是拿数据和ETag,之后凭ETag询问是否更新。返回304 239 | 240 | ### 常见的网络攻击方法和解决方案? 241 | ##### SQL注入 242 | 攻击手段: 243 | 244 | 某些服务器处理用户账号,是直接将账号字符串拼接到SQL语句中,如果黑客利用规则拼接类似1=1的判断语句,数据库中可能会产生异常行为。 245 | 246 | 解决方案: 247 | 248 | 在Java中使用prepareStatement类预编译SQL语句,把固定格式的SQL编译后放入数据库缓冲池中,之后传入的字符串都当作参数来处理,而不是SQL指令。 249 | 250 | ##### XSS(跨站脚本攻击) 251 | 攻击手段: 252 | 253 | 攻击者往Web页面插入恶意` 34 | ``` 35 | # Object.defineProperty() 除get/set方法外,其他属性方法(属性描述) 36 | 37 | - get 38 | - set 39 | - value:该属性对应的值 40 | - writable:值为 true 时value才能被赋值改变 41 | - configurable:值为 true 时,该属性的描述符才能够被改变 42 | - enumerable:值为 true 时,该属性才会出现在对象的枚举属性中 43 | 44 | # vue组件通讯方式 45 | 46 | #### props和$emit 47 | 48 | 数据通过props从父组件流向子组件,子组件通过$emit触发父组件的对应事件,通过传递参数的形式拿到子组件的数据 49 | 50 | #### $attrs和$listeners 51 | 52 | $attrs可以收集父作用域中传过来的属性,并过滤掉被props中定义的部分。(通过v-bind="$attrs"传入内部组件) 53 | 54 | 类似的,$listeners包含了父作用域中的事件监听器,通过v-on="$listeners"传入内部组件 55 | 56 | #### 中央事件总线EventBus 57 | 58 | 实例化新的vue实例,作为中央事件总线EventBus,添加到Vue的prototype上,供所有组件访问。通过bus.$emit触发事件,通过bus.$on监听事件,最后通过回调函数进行逻辑处理。 59 | 60 | 可用于任意组件之间的通讯,适合小型的项目,如果项目过大还是推荐使用vuex 61 | 62 | #### provide和inject 63 | 64 | 类似于React的Context API,父组件通过provide以对象的形式向子组件暴露一些属性,子组件通过inject注入相应属性。 65 | 66 | ?> provide 和 inject 主要在开发高阶插件/组件库时使用。并不推荐用于普通应用程序代码中。 67 | 68 | #### v-model 69 | 70 | 给组件添加v-model属性时,默认会把value作为组件的属性,把input作为给组件绑定事件时的事件名. 71 | 72 | #### $parent 和 $children 73 | 74 | 直接操作父子组件的实例。$parent 就是父组件的实例对象,而 $children 就是当前实例的直接子组件实例了,不过这个属性值是数组类型的,且并不保证顺序,也不是响应式的。 75 | 76 | #### $boradcast 和 $dispatch 77 | 78 | > 不过只是在 Vue1.0 中提供了,而 Vue2.0 被废弃了,但是还是有很多开源软件都自己封装了这种组件通信的方式,比如 Mint UI、Element UI 和 iView 等 79 | 80 | #### Vuex 状态管理 81 | 82 | 类似redux 83 | 84 | # vue生命周期 85 | 86 | #### beforeCreate 87 | 88 | 创建一个Vue实例,此时实例上只有一些生命周期函数和默认的事件 89 | 此时data computed watch methods上的方法和数据均不能访问 90 | 91 | #### created 92 | 93 | 可以对data数据进行操作,可进行一些请求,请求不易过多,避免白屏时间太长。 94 | 95 | #### beforeMount 96 | 97 | 判断el的挂载方式 98 | 判断是否有template设置 99 | 将template进行渲染保存到内存当中,还未挂载在页面上 100 | 101 | #### mounted 102 | 103 | 将内存中的模版挂载到页面上 104 | 此时可以操作页面上的DOM节点 105 | 此时组件从创建阶段进入运行阶段 106 | 107 | #### beforeUpdate 108 | 109 | 页面显示的数据是旧的,此时data里面的数据是最新,页面数据和data数据暂未同步 110 | 111 | #### updated 112 | 113 | 根据data里的最新数据渲染出最新的DOM树,然后将最新的DOM挂载到页面 114 | 此时data和页面数据一致,都是最新的 115 | 116 | #### beforeDestroy 117 | 118 | 此时组件从运行阶段进入到销毁阶段 119 | 组件上的data和methods以及过滤器等都出于可用状态,销毁还未执行 120 | 121 | #### destroyed 122 | 123 | 组件已经被完全销毁,组件中所有的数据、方法、指令、过滤器等,都已不可用 124 | 125 | --- 126 | #### activated 127 | 在加载对应模块时会被调用,页面第一次加载时也会被调用 128 | 129 | #### deactivated 130 | 组件切换出去时被调用 131 | 132 | # 组件生命周期调用顺序 133 | 134 | 组件的调用顺序都是先父后子,渲染完成的顺序是先子后父。 135 | 136 | 组件的销毁操作是先父后子,销毁完成的顺序是先子后父。 137 | 138 | 加载渲染过程 139 | 父beforeCreate->父created->父beforeMount->子beforeCreate->子created->子beforeMount- >子mounted->父mounted 140 | 141 | 子组件更新过程 142 | 父beforeUpdate->子beforeUpdate->子updated->父updated 143 | 144 | 父组件更新过程 145 | 父 beforeUpdate -> 父 updated 146 | 147 | 销毁过程 148 | 父beforeDestroy->子beforeDestroy->子destroyed->父destroyed 149 | 150 | # computed与watch 151 | 152 | #### computed: 153 | computed是计算属性,依赖其他的属性值,并且computed的属性值有缓存属性,当属性值变化的时候,下一次获取computed属性的时候才会重新计算computed的值。 154 | 155 | #### watch: 156 | 更多的是一种观察的作用,用于监听某些数据的回调。每当所监听的数据发生变化时才能执行回调处理后续操作。 157 | 158 | # 侦听属性watch如何监听一个对象内部的变化。 159 | 160 | - 如果只是监听obj内的某一个属性变化,可以直接obj.key进行监听。 161 | - 如果对整个obj深层监听,将deep设为true 162 | 163 | # 侦听属性watch的immediate的作用 164 | 165 | 我们在watch中的默认写的方法就是handler方法。 166 | 167 | 当值第一次进行绑定的时候并不会触发watch监听,使用immediate:true则可以在最初绑定的时候执行handler。 168 | # 计算属性computed以及里面的getter和setter 169 | 170 | computed是计算属性,依赖其他的属性值,并且计算属性是基于它们的响应式依赖进行缓存的。只在相关响应式依赖发生改变时它们才才会重新求值。 171 | 172 | getter作为默认值常常省略不写,所依赖的变量发生变化时相应地改变自身的值(这里依赖的变量一定要在模版中引用) 173 | 174 | 当前变量直接被设置成其他值时会触发setter,新值会作为参数传到set中,然后再进行逻辑处理(`姓名`分为`姓`和`名`重新赋值到data中)。 175 | 176 | 官方例子: 177 | ```JSX 178 | 186 | 187 | var vm = new Vue({ 188 | el: '#demo', 189 | data: { 190 | firstName: 'zhang', 191 | lastName: 'san' 192 | }, 193 | computed: { 194 | fullName: { 195 | //getter 方法 196 | get(){ 197 | console.log('computed getter...') 198 | return this.firstName + ' ' + this.lastName 199 | }, 200 | //setter 方法 201 | set(newValue){ 202 | console.log('computed setter...') 203 | var names = newValue.split(' ') 204 | this.firstName = names[0] 205 | this.lastName = names[names.length - 1] 206 | return this.firstName + ' ' + this.lastName 207 | } 208 | 209 | } 210 | }, 211 | updated () { 212 | console.log('updated') 213 | } 214 | }) 215 | ``` 216 | 217 | 218 | 1. 如果一个数据依赖于其他数据,那么把这个数据设计为computed的,因为可以利用 computed 的缓存特性,避免每次获取值时,都要重新计算; 219 | 2. 当我们需要在数据变化时执行异步或开销较大的操作时,应该使用 watch,使用 watch 选项允许我们执行异步操作 (访问一个API),限制我们执行该操作的频率,并在我们得到最终结果前,设置中间状态。这些都是计算属性无法做到的。 220 | 221 | # 接口请求一般放在哪个生命周期中 222 | 223 | 接口请求一般放在mounted中,但需要注意的是服务端渲染时不支持mounted,需要放到created中。 224 | 225 | # v-if和v-show 226 | 227 | 当条件不成立时,v-if不会渲染DOM元素,v-show操作的是样式(display),切换当前DOM的显示和隐藏。 228 | 229 | # 组件中的data为什么是一个函数 230 | 231 | 一个组件被复用多次的话,也就会创建多个实例。本质上,这些实例用的都是同一个构造函数。如果data是对象的话,对象属于引用类型,会影响到所有的实例。所以为了保证组件不同的实例之间data不冲突,data必须是一个函数。 232 | 233 | # v-model的原理 234 | 235 | v-model本质就是一个语法糖,可以看成是value + input方法的语法糖。 236 | 可以通过model属性的prop和event属性来进行自定义。原生的v-model,会根据标签的不同生成不同的事件和属性。 237 | 238 | # Vue事件绑定原理说一下 239 | 240 | 原生事件绑定是通过addEventListener绑定给真实元素的,组件事件绑定是通过Vue自定义的$on实现的。 241 | 242 | # Vue模版编译原理知道吗 243 | 244 | 简单说,Vue的编译过程就是将template转化为render函数的过程。会经历以下阶段: 245 | 246 | - 生成AST树 247 | - 优化 248 | - codegen 249 | 250 | 251 | 首先解析模版,生成AST语法树(一种用JavaScript对象的形式来描述整个模板)。 252 | 使用大量的正则表达式对模板进行解析,遇到标签、文本的时候都会执行对应的钩子进行相关处理。 253 | 254 | Vue的数据是响应式的,但其实模板中并不是所有的数据都是响应式的。有一些数据首次渲染后就不会再变化,对应的DOM也不会变化。那么优化过程就是深度遍历AST树,按照相关条件对树节点进行标记。这些被标记的节点(静态节点)我们就可以跳过对它们的比对,对运行时的模板起到很大的优化作用。 255 | 256 | 编译的最后一步是将优化后的AST树转换为可执行的代码。 257 | 258 | # 为什么v-for要加key 259 | 260 | 最大化利用dom节点,提升性能。diff算法默认使用“就地复用”的策略,是一个首尾交叉对比的过程。 261 | 262 | 在vue的源码中,有个sameVnode()函数,用于判断两个节点是否为同一节点(也就是是否可复用),他会先判断key和tag等是否相同,如果不加key,key都是undefined,默认相同,就会就地复用相同类型的元素,效率很低,并且有可能会因为数据和UI不一致带来一些问题。如果添加了key属性,它会基于key的变化重新排列元素顺序,并且会移除 key 不存在的元素。 263 | 264 | 举个例子: 265 | 266 | 有一个列表我们现在在中间插入了一个元素,diff算法会默认复用之前的列表并在最后追加一个,如果列表存在选中一类的状态则会随着复用出现绑定错误的情况而不是跟着原元素,key的作用就可以给他一个标识,让状态跟着数据渲染。 267 | 268 | 要注意:有相同父元素的子元素必须有独特的 key。重复的 key 会造成渲染错误。 269 | 270 | # 为什么不建议用index作为key 271 | 272 | **index作为key,其实就等于不加key:**虽然加上了key,但key是就是当前的顺序索引值,此时sameNode(A,B)依然是为true,所以与不加key时一样。 273 | 274 | index作为key,只适用于不依赖子组件状态或临时 DOM 状态 的列表渲染输出。(比如表单输入值) 275 | 276 | # Vue2.x和Vue3.x渲染器的diff算法分别说一下 277 | 简单来说,diff算法有以下过程 278 | 279 | - 同级比较,再比较子节点 280 | - 先判断一方有子节点一方没有子节点的情况(如果新的children没有子节点,将旧的子节点移除) 281 | - 比较都有子节点的情况(核心diff) 282 | - 递归比较子节点 283 | 284 | 正常Diff两个树的时间复杂度是O(n^3),但实际情况下我们很少会进行跨层级的移动DOM,所以Vue将Diff进行了优化,从O(n^3) -> O(n),只有当新旧children都为多个子节点时才需要用核心的Diff算法进行同层级比较。 285 | 286 | Vue2的核心Diff算法采用了双端比较的算法,同时从新旧children的两端开始进行比较,借助key值找到可复用的节点,再进行相关操作。相比React的Diff算法,同样情况下可以减少移动节点次数,减少不必要的性能损耗,更加的优雅。 287 | 288 | Vue3.x借鉴了 289 | ivi算法和 inferno算法 290 | 291 | 在创建VNode时就确定其类型,以及在 mount/patch 的过程中采用位运算来判断一个VNode的类型,在这个基础之上再配合核心的Diff算法,使得性能上较Vue2.x有了提升。 292 | 293 | 该算法中还运用了动态规划的思想求解最长递归子序列。 294 | 295 | # hash路由和history路由实现原理说一下 296 | 297 | hash模式:在浏览器中符号“#”,#以及#后面的字符称之为hash,用window.location.hash读取,可以使用`hashchange`监听hash事件变更;hash虽然在URL中,但不被包括在HTTP请求中;用来指导浏览器动作,对服务端安全,hash不会重新加载页面。 298 | 299 | history模式:history采用HTML5的新特性;且提供了两个新方法:pushState(),replaceState()可以对浏览器历史记录栈进行修改,以及popState事件的监听到状态变更。 300 | 301 | # Vue2.x响应式数据原理 302 | 303 | Vue在初始化数据时,会使用Object.defineProperty重新定义data中的所有属性,当页面使用对应属性时,首先会进行依赖收集(收集当前组件的watcher)如果属性发生变化会通知相关依赖进行更新操作(发布订阅)。 304 | 305 | # Vue3.x响应式数据原理 306 | 307 | Vue3.x改用Proxy替代Object.defineProperty。因为Proxy可以直接监听对象和数组的变化,并且有多达13种拦截方法。并且作为新标准将受到浏览器厂商重点持续的性能优化。 308 | 309 | 但是,Proxy只会代理对象的第一层,Vue3判断当前Reflect.get的返回值是否为Object,如果是则再通过reactive方法做代理, 310 | 这样就实现了深度观测。 311 | 312 | 有时候,监测数组的时候可能触发多次get/set,为了防止触发多次, 313 | 我们可以判断key是否为当前被代理对象target自身属性,也可以判断旧值与新值是否相等,只有满足以上两个条件之一时,才有可能执行trigger。 314 | # vue2.x中如何监测数组变化 315 | 316 | 使用了函数劫持的方式,重写了数组的方法,Vue将data中的数组进行了原型链重写,指向了自己定义的数组原型方法。这样当调用数组api时,可以通知依赖更新。如果数组中包含着引用类型,会对数组中的引用类型再次递归遍历进行监控。这样就实现了监测数组变化。 317 | 318 | # nextTick知道吗,实现原理是什么? 319 | 320 | 在下次 DOM 更新循环结束之后执行延迟回调。nextTick主要使用了宏任务和微任务。根据执行环境分别尝试采用 321 | 322 | - Promise 323 | - MutationObserver 324 | - setImmediate 325 | - 如果以上都不行则采用setTimeout 326 | 327 | 定义了一个异步方法,多次调用nextTick会将方法存入队列中,通过这个异步方法清空当前队列。 328 | # 那你能讲一讲MVVM吗 329 | 330 | MVVM是Model-View-ViewModel缩写,也就是把MVC中的Controller演变成ViewModel。Model层代表数据模型,View代表UI组件,ViewModel是View和Model层的桥梁,数据会绑定到viewModel层并自动将数据渲染到页面中,视图变化的时候会通知viewModel层更新数据。 331 | 332 | # keep-alive了解吗 333 | 334 | keep-alive可以实现组件缓存,当组件切换时不会对当前组件进行卸载。 335 | 336 | 常用的两个属性include/exclude,允许组件有条件的进行缓存。 337 | 338 | 两个生命周期activated/deactivated,用来得知当前组件是否处于活跃状态。 339 | 340 | keep-alive的中还运用了LRU(Least Recently Used)算法。 341 | # SSR服务器端渲染了解吗? 342 | 343 | SSR也就是服务端渲染,也就是将Vue在客户端把标签渲染成HTML的工作放在服务端完成,然后再把html直接返回给客户端。 344 | 345 | SSR有着更好的SEO、并且首屏加载速度更快等优点。不过它也有一些缺点,比如我们的开发条件会受到限制,服务器端渲染只支持beforeCreate和created两个钩子,当我们需要一些外部扩展库时需要特殊处理,服务端渲染应用程序也需要处于Node.js的运行环境。还有就是服务器会有更大的负载需求。 346 | 347 | # vue2.X和vue3的区别/vue3新特性 348 | 349 | - 监测机制的改变 350 | 351 | - 3.0 将带来基于代理 Proxy的 observer 实现,提供全语言覆盖的反应性跟踪。 352 | - 消除了 Vue 2 当中基于 Object.defineProperty 的实现所存在的很多限制: 353 | - 只能监测属性,不能监测对象 354 | 355 | - 检测属性的添加和删除; 356 | - 检测数组索引和长度的变更; 357 | - 支持 Map、Set、WeakMap 和 WeakSet。 358 | - 模板 359 | 360 | - 模板方面没有大的变更,只改了作用域插槽,2.x 的机制导致作用域插槽变了,父组件会重新渲染,而 3.0 把作用域插槽改成了函数的方式,这样只会影响子组件的重新渲染,提升了渲染的性能。 361 | - 同时,对于 render 函数的方面,vue3.0 也会进行一系列更改来方便习惯直接使用 api 来生成 vdom 。 362 | - 对象式的组件声明方式 363 | 364 | - vue2.x 中的组件是通过声明的方式传入一系列 option,和 TypeScript 的结合需要通过一些装饰器的方式来做,虽然能实现功能,但是比较麻烦。 365 | - 3.0 修改了组件的声明方式,改成了类式的写法,这样使得和 TypeScript 的结合变得很容易 366 | - 其它方面的更改 367 | 368 | - 支持自定义渲染器,从而使得 weex 可以通过自定义渲染器的方式来扩展,而不是直接 fork 源码来改的方式。 369 | - 支持 Fragment(多个根节点)和 Protal(在 dom 其他部分渲染组建内容)组件,针对一些特殊的场景做了处理。 370 | - 基于 tree shaking 优化,提供了更多的内置功能。 371 | 372 | # vue和react区别 373 | 374 | - 同 375 | 376 | - 使用 Virtual DOM 377 | - 提供了响应式 (Reactive) 和组件化 (Composable) 的视图组件。 378 | - 将注意力集中保持在核心库,而将其他功能如路由和全局状态管理交给相关的库。 379 | - 异 380 | 381 | - 在 React 应用中,当某个组件的状态发生变化时,它会以该组件为根,重新渲染整个组件子树(除非使用PureComponent/shouldComponentUpdate),在 Vue 应用中,组件的依赖是在渲染过程中自动追踪的,所以系统能精确知晓哪个组件确实需要被重渲染 382 | - 在 React 中,一切都是 JavaScript。不仅仅是 HTML 可以用 JSX 来表达,现在的潮流也越来越多地将 CSS 也纳入到 JavaScript 中来处理 383 | - Vue 的路由库和状态管理库都是由官方维护支持且与核心库同步更新的。React 则是选择把这些问题交给社区维护,因此创建了一个更分散的生态系统,所以有更丰富的生态系统 384 | - Vue 提供了CLI 脚手架,能让你通过交互式的脚手架引导非常容易地构建项目。你甚至可以使用它快速开发组件的原型。React 在这方面也提供了create-react-app,但是现在还存在一些局限性 385 | - React Native 能使你用相同的组件模型编写有本地渲染能力的 APP,Vue 和Weex会进行官方合作,Weex 是阿里巴巴发起的跨平台用户界面开发框架,同时也正在 Apache 基金会进行项目孵化,另一个选择是NativeScript-Vue,一个用 Vue.js 构建完全原生应用的NativeScript插件 386 | 387 | # 路由懒加载/按需加载 388 | 389 | 结合 Vue 的异步组件和 Webpack 的代码分割功能,轻松实现路由组件的懒加载。 390 | 391 | 异步组件定义为返回一个 Promise 的工厂函数,在Webpack中,我们可以使用动态import返回Promise,简化之后的写法就是 392 | 393 | ``` 394 | const Foo = () => import('./Foo.vue') 395 | ``` 396 | 397 | # 组件懒加载/按需加载 398 | 399 | webpack 中提供了 require.ensure()来实现按需加载。 400 | 401 | # vue.use()做了什么 402 | 官网给出的解释是:通过全局方法 Vue.use() 使用插件, 403 | 404 | - 如果插件是一个对象,必须提供 install 方法。如果插件是一个函数,它会被作为 install 方法。install 方法调用时,会将 Vue 作为参数传入。 405 | - 通过全局方法 Vue.use() 使用插件 406 | - 该方法需要在调用 new Vue() 之前被调用。 407 | - 当 install 方法被同一个插件多次调用,插件将只会被安装一次。 408 | 409 | **Vue.use()有什么用:** 410 | 411 | 在 install 里我们可以拿到 Vue 那么和 Vue 相关的周边工作都可以考虑放在 Vue.use() 方法里,比如: 412 | 413 | - directive注册 414 | - mixin注册 415 | - filters注册 416 | - components注册 417 | - prototype挂载 418 | - …… 419 | -------------------------------------------------------------------------------- /.idea/workspace.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 14 | 15 | 21 | 22 | 23 | 27 | 28 | 29 | 30 | 31 | 32 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 1584807469964 74 | 117 | 118 | 1584811656527 119 | 124 | 125 | 1584811901036 126 | 131 | 132 | 1584814714265 133 | 138 | 139 | 1584839228277 140 | 145 | 146 | 1584851415517 147 | 152 | 153 | 1584858008033 154 | 159 | 160 | 1584858431757 161 | 166 | 167 | 1584858579629 168 | 173 | 174 | 1584859791157 175 | 180 | 181 | 1584864328910 182 | 187 | 188 | 1584864536761 189 | 194 | 195 | 1584865260159 196 | 201 | 202 | 1584869466780 203 | 208 | 209 | 1584873682165 210 | 215 | 216 | 1584887455177 217 | 222 | 223 | 1584888269872 224 | 229 | 232 | 233 | 235 | 236 | 238 | 239 | 248 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 260 | -------------------------------------------------------------------------------- /docs/javascript/sorts.md: -------------------------------------------------------------------------------- 1 | 2 | ## 介绍 3 |   排序算法是《数据结构与算法》中最基本的算法之一。 4 | 5 | > tips:如果你是第一次学习算法,大可不必顾虑本教程是使用JavaScript语言进行编码,因为算法只是一种思想逻辑,你当然可以用你喜欢的语言来实现算法。 6 | 7 | >所以,**学习算法的关键**是能有一篇浅显易懂的讲解教程,是骡子是马,看我拉出来溜溜。 8 | 9 | 当然,我建议你先了解关于排序算法的一些基础知识: 10 | 11 | ### 什么是排序算法? 12 |   排序算法可以分为内部排序和外部排序,内部排序是数据记录在内存中进行排序,而外部排序是因排序的数据很大,一次不能容纳全部的排序记录,在排序过程中需要访问外存。常见的内部排序算法有:插入排序、希尔排序、选择排序、冒泡排序、归并排序、快速排序、堆排序、基数排序等。用一张图概括: 13 | 14 | ![十大排序算法汇总](https://www.runoob.com/wp-content/uploads/2019/03/sort.png) 15 | 16 | 名词解释: 17 | 18 | - n:数据规模 19 | 20 | - k:"桶"的个数 21 | 22 | - In-place:占用常数内存,不占用额外内存 23 | 24 | - Out-place:占用额外内存 25 | 26 | - 稳定性:排序后 2 个相等键值的顺序和排序之前它们的顺序相同 27 | 28 | > [In-place和Out-place是什么意思?](https://blog.csdn.net/HuoYiHengYuan/article/details/104479754) 29 | 30 | 31 | ### 什么是时间复杂度? 32 | 这里我们着重讨论**时间复杂度**,至于空间复杂度请自行Google。 33 | 34 |   计算时间复杂度注意: 35 | 36 | 1. 去掉系数,再去掉低阶项,再去掉常数项 37 | 2. 如果是常数,直接为O(1) 38 | 3. n代表一种数据级别,我们在讨论算法快不快的时候默认n取得是正无穷 39 | 40 |   我们接下来只讨论**平均时间**和**最坏情况**复杂度,因为最好情况的复杂度一般没有参考意义,除非你能保证数据能够达成最好情况的要求。 41 | 42 | ### 什么是算法稳定性? 43 |   如果两个具有相同键的对象在排序后的输出中以相同的顺序出现,则它们出现在要排序的输入数组中。有些排序算法本质上是稳定的,如插入排序、合并排序、气泡排序等,而有些排序算法则不稳定,如堆排序、快速排序等。 44 | 45 | 背景: 46 | 47 |   “稳定”排序算法使具有相同排序键的项保持顺序。假设我们有一个由5个字母组成的单词列表: 48 | 49 | - peach 50 | - straw 51 | - apple 52 | - spork 53 | 54 |   如果我们只按每个单词的第一个字母对列表进行排序,那么稳定的排序就会产生: 55 | 56 | - apple 57 | - peach 58 | - straw 59 | - spork 60 | 61 | 62 |   在不稳定排序算法,straw或spork可能是互换的,但在稳定的情况下,它们保持在相同的相对位置上(也就是说,因为straw出现在前面spork在输入中,它也出现在前面。spork在输出中)。 63 | 64 |   我们可以使用该算法对单词列表进行排序:按列5、4、3、2、1进行稳定排序,最后将其正确排序。让自己相信这一点。(顺便说一句,该算法称为基排序) 65 | 66 |   假设我们有一个名字和姓氏的列表。我们被要求“按姓氏,然后按第一名”排序。我们可以先根据名字进行排序(稳定的或不稳定的),然后根据姓氏进行稳定的排序。在这些排序之后,列表主要按照姓氏进行排序。但是,在姓氏相同的地方,名字是排序的。 67 | 68 |   你不能以同样的方式堆叠不稳定的种类。 69 | ## 算法 70 |   以下排序算法皆是用JavaScript实现。 71 | ### 冒泡排序 72 |   冒泡排序是一种简单直观的排序算法,它重复性地访问要排序的数列。通过在访问过程中不断地判断和交换相邻数列,从而使最大/小的数往末端转移,最终得到有序数列。 73 | 74 | #### 思路 75 | 76 | 1. 从前两个元素`arr[0]`,`arr[1]`开始,按照顺序两两比较相邻的元素,如果顺序错误就交换。 77 | 2. 当第一遍全部比较一遍之后,整个数列中最大的数就会被交换到最后到位置`arr[length-1]`。 78 | 3. 第二遍我们重复步骤1的交换,比较到倒数第二个元素位置,因为最末元素已经确定是最终结果了,所以没必要再进行比较处理。 79 | 4. 像步骤3一样,接下来的每一次都重复比较,每次比较都会少比较一次。 80 | 5. 当完成最后两个数的比较之后,冒泡排序结束。 81 | 82 | #### 动画演示 83 | ![冒泡排序动画](https://www.runoob.com/wp-content/uploads/2019/03/bubbleSort.gif) 84 | 85 | ![“冒泡”示意图](https://img-blog.csdnimg.cn/20190102171133188.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3pob3U5MjA3ODYzMTI=,size_16,color_FFFFFF,t_70) 86 | #### 代码实现 87 | ```javascript 88 | /** 89 | * 冒泡排序 90 | * @param {Array} arr 传入一个数组,按照从小到大排序 91 | * @returns {Array} 返回排序后的数组 92 | */ 93 | let bubbleSort = (arr) => { 94 | let len = arr.length; 95 | let temp; 96 | for (let i = 0; i < len - 1; i++) { 97 | for (let j = 0; j < len - i - 1; j++) { 98 | if (arr[j] > arr[j + 1]) { 99 | temp = arr[j]; 100 | arr[j] = arr[j + 1]; 101 | arr[j + 1] = temp; 102 | } 103 | } 104 | } 105 | new Array(arr.length); 106 | return arr; 107 | }; 108 | ``` 109 | 110 | #### 时间复杂度O(n^2) 111 | for-i循环代码执行了n次,所以在for-i循环的每一次中,分别执行for-j循环`n-1`、`n-2`、`n-3`、……、`1`次 112 | 113 | 代码执行次数 =(n-1)+(n-2)+……+2+1= n(n-1)/ 2=`(1/2)n^2 + (1/2)n` 114 | 115 | 所以时间复杂度O(n^2) 116 | > if语句内的内容与n无关,时间复杂度为**O(1)**。 117 | 118 | 虽然我们不讨论最好情况,但细心的你可能会思考到,为什么最好情况的时间复杂度是O(n)?其实上述代码的最好情况时间复杂度其实也是(n^2),至于O(n)则需要[对代码进行改进](https://www.cnblogs.com/xfcao/p/10282522.html)。 119 | #### 稳定性:稳定 120 |   冒泡排序就是把小的元素往前调或者把大的元素往后调。比较是相邻的两个元素比较,交换也发生在这两个元素之间。所以,如果两个元素相等,是不会再交换的;如果两个相等的元素没有相邻,那么即使通过前面的两两交换把两个相邻起来,这时候也不会交换,所以相同元素的前后顺序并没有改变,所以冒泡排序是一种稳定排序算法。 121 | 122 | 123 | ### 选择排序 124 |   选择排序也是一种简单直观的排序算法,它从左往右依次将每个元素作为“基准数”,对于每个基准数都会依次比对后面的元素,找到最小的数来交换,使每次剩余数列里最小的数不断安置到正确的位置,最终得到有序数列。 125 | #### 思路 126 | (本例我们按照从小到大排列) 127 | 128 | 1. 标识数列的第一个元素`arr[0]`中的“0”为当前基准数下标,然后利用循环语句开始对**剩下**的数列进行遍历。 129 | 2. 遍历过程中如果比对出一个元素`arr[n]`<`arr[0]`则将基准数下标设为n,然后接着在本次循环中比较。 130 | 3. 本次循环结束后,将`arr[n]`(此时是当前循环中的最小数)与初始基准数`arr[0]`交换。此时当初始基准数的位置上即是正确的数值。 131 | 4. 然后开始第二个元素重复1-4步骤。 132 | 5. 当完成最后一次循环的时候,最后两个数比对交换,得到最终排序后的数列。 133 | 134 | #### 动画演示 135 | ![选择排序](https://www.runoob.com/wp-content/uploads/2019/03/selectionSort.gif) 136 | 137 | #### 代码实现 138 | ```javascript 139 | /** 140 | * 选择排序 141 | * @param {Array} arr 传入一个数组,按照从小到大排序 142 | * @returns {Array} 返回排序后的数组 143 | */ 144 | let selectionSort=(arr)=>{ 145 | let len = arr.length; 146 | //minIndex是最小值第下标 147 | let minIndex, 148 | tempNum; 149 | //需要以len-1个数为基准来判断剩下的数是否需要交换 150 | for (let i = 0; i arr[j]){ 157 | //将最小的数的下标保存 158 | minIndex=j; 159 | } 160 | } 161 | //将最小值与基准值交换 162 | tempNum = arr[minIndex]; 163 | arr[minIndex] = arr[i]; 164 | arr[i] = tempNum; 165 | } 166 | return arr; 167 | }; 168 | ``` 169 | 170 | #### 时间复杂度O(n^2) 171 |   从动画上我们能很轻易地看出,循环运行次数和问题规模的关系是: 172 | 173 | 循环执行次数 =(n-1)+(n-2)+……+2+1= n(n-1)/ 2=`(1/2)n^2 + (1/2)n` 174 | 175 | 所以复杂度O(n^2) 176 | 177 | #### 稳定性:不稳定 178 |   举个例子,序列5 8 5 2 9,我们知道第一遍选择第1个元素5会和2交换,那么原序列中两个5的相对前后顺序就被破坏了,所以选择排序是一个不稳定的排序算法。 179 | 180 | ### 插入排序 181 |   插入排序虽然没有冒泡和选择那么简单粗暴,但是也是比较好理解的一种排序方式了。他就像我们平常打扑克时“整理起手牌”的动作。顺序遍历每一个数,将当前数和之前的数依次比较,并插入到之前到正确位置。 182 | #### 思路 183 | 1. 标识当前判断的元素。 184 | 2. 遍历数组中每一个数,并执行步骤3 185 | 3. 对于每一个数,都比较和前一个相邻值的大小,如果源值小,则不是当前插入的位置,继续向前比较,一直比较到源值大的那个位置,插入进去。如果一直比对到arr[0]的位置仍是源值小,则直接插入到最前面。 186 | #### 动画演示 187 | ![插入排序](https://www.runoob.com/wp-content/uploads/2019/03/insertionSort.gif) 188 | #### 代码实现 189 | ```javascript 190 | /** 191 | * 插入排序 192 | * tip:就想玩扑克牌的时候整理顺序一样 193 | * @param {Array} arr 传入一个数组,按照从小到大排序 194 | * @returns {Array} 返回排序后的数组 195 | */ 196 | let insertionSort=(arr)=>{ 197 | let length = arr.length; 198 | //current为本次循环中源位置的值 199 | let current,preIndex; 200 | for (let i = 1; i < length; i++) { 201 | //preIndex为相邻的前一个下标 202 | preIndex = i-1; 203 | current=arr[i]; 204 | //元素向后移动 205 | while (preIndex>=0&&arr[preIndex]>current){ 206 | arr[preIndex+1] = arr[preIndex]; 207 | preIndex--; 208 | } 209 | //插入 210 | arr[preIndex+1] = current; 211 | } 212 | return arr; 213 | }; 214 | ``` 215 | #### 时间复杂度O(n^2) 216 |   最坏情况是,原数组是降序排列,则`次数=n(n-1)/2`;对于最好情况来说本身就是正序,也就不必专门向前插入了,省了一层循环,那直接O(n)就行了。 217 | 218 |   但是还是最坏情况比较有参考意义,所以还是O(n^2)。因而,插入排序不适合对于数据量比较大的排序应用。但是,如果需要排序的数据量很小,例如,量级小于千,那么插入排序还是一个不错的选择。 219 | 220 | #### 稳定性:稳定 221 |   每次插入到前面的小值之后,就算遇到相等的值,也是放到之后,所以相等元素的前后顺序没有改变,所以插入排序是稳定的。 222 | 223 | ### 希尔排序 224 |   希尔排序是插入排序的升级版,先将整个待排序的记录序列分割成为若干子序列分别进行直接插入排序,待整个序列中的记录"基本有序"时,再对全体记录进行依次直接插入排序(最后一次插入排序即是正常的直接插入排序,但此时因为已经基本有序,排序会很快)。 225 | 226 |   希尔排序优点是:间隔大的时候移动次数少,间隔小的时候移动距离短,所以总体来说比插入排序要优良,美中不足的是该排序是不稳定的。 227 | 228 | > 大家可以先看看这个,大概了解一下什么是希尔排序: 229 | > 230 | > [希尔排序真人版视频](https://v.youku.com/v_show/id_XNTA3NDUxMjcy.html) 231 | 232 | 233 | #### 思路 234 | 1. 设置增量gap,每次循环递减到之前到一半 235 | 2. 基于每层循环的gap值,每次遍历都进行一次插入排序_insert() 236 | 3. 执行每层都_insert,进行分组插入排序 237 | 4. 直到gap==1时,即为常规插入排序,也即是最后一轮循环 238 | #### 动画演示 239 | ![希尔排序动画](https://oscimg.oschina.net/oscnet/fdb4e6a26640101db6eb74ed9901114b049.jpg) 240 | #### 代码实现 241 | ```javascript 242 | /** 243 | * 希尔排序 244 | * tip:插入排序的升级版 245 | * @param {Array} arr 传入一个数组,按照从小到大排序 246 | * @returns {Array} 返回排序后的数组 247 | */ 248 | let shellSort = (arr) => { 249 | let N = arr.length; 250 | //开始分组,初始状态,增量(gap)为数组长度的一半,当递减到增量为1时,即是最后一次排序 251 | for (let gap = Math.floor(N / 2); gap >= 1; gap = Math.floor(gap / 2)) { 252 | //对各个分组进行插入排序 253 | for (let i = gap; i < N; i++) { 254 | //将arr[i]插入到正确对位置上 255 | _insert(arr, gap, i); 256 | } 257 | } 258 | return arr; 259 | }; 260 | /** 261 | * 希尔排序中将arr[i]插入到正确到位置上 262 | *(即插入排序,带有增量) 263 | * arr[i]所在到分组是:...arr[i-2*gap],arr[i-gap],arr[i],arr[i+gap],arr[i+2*gap]...当 264 | * @param arr 需要进行插入排序对数组 265 | * @param gap 增量 266 | * @param i 指定位置的下标 267 | */ 268 | let _insert = (arr, gap, i) => { 269 | let inserted = arr[i], j; 270 | //插入的时候分组插入(组内元素两两相隔gap) 271 | for (j = i - gap; j >= 0 && inserted < arr[j]; j -= gap) { 272 | arr[j + gap] = arr[j]; 273 | } 274 | arr[j + gap] = inserted; 275 | }; 276 | ``` 277 | #### 时间复杂度O(nlog^2 n) 278 |   步长的选择是希尔排序的重要部分。只要最终步长为1任何步长序列都可以工作。算法最开始以一定的步长进行排序。然后会继续以一定步长进行排序,最终算法以步长为1进行排序。当步长为1时,算法变为普通插入排序,这就保证了数据一定会被排序。 279 | 280 |   Donald Shell最初建议步长选择为n/2,并且对步长取半直到步长达到1。虽然这样取可以比O(n^2)类的算法(插入排序)更好,但这样仍然有减少平均时间和最差时间的余地。 281 | 282 | ![希尔排序三种布长的时间复杂度](https://upload-images.jianshu.io/upload_images/17652162-02b6bbc195bae9a2.png) 283 | 284 |   已知的最好步长序列是由Sedgewick提出的(1, 5, 19, 41, 109,...),他的这研究也表明“比较在希尔排序中是最主要的操作,而不是交换。”用这样步长序列的希尔排序比插入排序要快,甚至在小数组中比快速排序和堆排序还快,但是在涉及大量数据时希尔排序还是比快速排序慢。 285 | 286 |   另一个在大数组中表现优异的步长序列是(斐波那契数列除去0和1将剩余的数以黄金分割比的两倍的幂进行运算得到的数列):(1, 9, 34, 182, 836, 4025, 19001, 90358, 428481, 2034035, 9651787, 45806244, 217378076, 1031612713,…) 287 | 288 | 参考链接:[维基百科](https://zh.wikipedia.org/wiki/%E5%B8%8C%E5%B0%94%E6%8E%92%E5%BA%8F) 289 | 290 | #### 稳定性:不稳定 291 |   由于多次插入排序,我们知道一次插入排序是稳定的,不会改变相同元素的相对顺序,但在不同的插入排序过程中,相同的元素可能在各自的插入排序中移动,最后其稳定性就会被打乱,所以shell排序是不稳定的。 292 | 293 | ### 归并排序 294 |   归并排序是建立在归并操作上的一种有效的排序算法,是分治法的一个经典应用。 295 | 296 |   所谓**分治**,将问题分(divide)成一些小的问题然后递归求解,而治(conquer)的阶段则将分的阶段得到的各答案"修补"在一起,即分而治之。 297 | 298 | ![分治法](https://images2015.cnblogs.com/blog/1024555/201612/1024555-20161218163120151-452283750.png) 299 | 300 | #### 思路 301 | 1. 申请空间,使其为两个已经排序序列之和,该空间用来存放合并后的序列; 302 | 2. 设定两个指针,最初位置分别为两个已经排序的序列的起始位置; 303 | 3. 比较两个指针所指向的元素,选择相对较小的放到合并空间,并移动指针到下一位置; 304 | 4. 重复3步骤直到某一指针达到序列尾; 305 | 5. 将另一序列剩下的元素直接复制到合并序列尾; 306 | #### 动画演示 307 | 308 | ![归并排序](https://www.runoob.com/wp-content/uploads/2019/03/mergeSort.gif) 309 | 310 | ![归并排序2](https://upload-images.jianshu.io/upload_images/17652162-e4f9b3ae3542b599.gif?imageMogr2/auto-orient/strip|imageView2/2/w/300/format/webp) 311 | 312 | #### 代码实现 313 | ```javascript 314 | /** 315 | * 归并排序 316 | * 317 | * @param {Array} arr 传入一个数组,按照从小到大排序 318 | * @returns {Array} 返回排序后的数组 319 | */ 320 | let mergeSort = (arr) => { 321 | let len = arr.length; 322 | if (len < 2) { 323 | return arr; 324 | } 325 | let middle = Math.floor(len / 2), 326 | left = arr.slice(0, middle), 327 | right = arr.slice(middle); 328 | 329 | return _merge(mergeSort(left), mergeSort(right)); 330 | }; 331 | 332 | /** 333 | * 合并(归并)两个数组 334 | * @param left 335 | * @param right 336 | * @returns {[]} 337 | * @private 338 | */ 339 | let _merge = (left, right) => { 340 | //合并空间 341 | let result = []; 342 | while (left.length && right.length) { 343 | if (left[0] <= right[0]) { 344 | //移除left的第一个元素,并且添加到合并空间中 345 | result.push(left.shift()); 346 | }else{ 347 | //移除right到第一个元素,并且添加到合并空间中 348 | result.push(right.shift()); 349 | } 350 | } 351 | //假如左数组还有剩余,则把左数组增加到合并空间中 352 | while (left.length){ 353 | result.push(left.shift()); 354 | } 355 | //假如右数组还有剩余,则把右数组增加到合并空间中 356 | while (right.length){ 357 | result.push(right.shift()); 358 | } 359 | return result; 360 | }; 361 | ``` 362 | #### 时间复杂度O(nlogn) 363 |   归并的时间复杂度分析:主要是考虑两个函数的时间花销,一、数组划分函数mergeSort();二、有序数组归并函数_merge(); 364 | 365 |   简单的分析下元素长度为n的归并排序所消耗的时间 T[n]:调用mergeSort()函数划分两部分,那每一小部分排序好所花时间则为T[n/2],而最后把这两部分有序的数组合并成一个有序的数组_merge()函数所花的时间为 O(n); 366 | 367 |   公式:`T[n] = 2T[n/2] + O(n)` 368 | 369 |   公式就不仔细推导了,可以[参考链接](http://blog.csdn.net/yuzhihui_no1/article/details/44198701#t2) 370 | 371 |   所以得出的结果为:`T[n] = O( nlogn )` 372 | 373 |   从另一个角度思考,假设解决最后的子问题用时为常数c,则对于n个待排序记录来说整个问题的规模为cn。 374 | 375 | ![归并排序复杂度](https://img-blog.csdn.net/20170909101802866?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvc2luYXRfMzYzMDY0NzQ=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast) 376 | 377 |   从这个递归树可以看出,第一层时间代价为cn,第二层时间代价为cn/2+cn/2=cn…..每一层代价都是cn,总共有logn层。所以总的时间代价为cn*(logn).时间复杂度是O(nlogn)。 378 | 379 | 380 | #### 稳定性:稳定 381 |   归并排序是稳定的排序.即相等的元素的顺序不会改变。即是原数组有相等数字,在排序成新数组后,也会保持着之前到相等数字的前后位置。 382 | 383 | 384 | --- 385 | **更新中ing** 386 | 387 | 388 | ### 快速排序 389 | 1. 算法步骤 390 | - 从数列中挑出一个元素,称为 "基准"(pivot); 391 | 392 | - 重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)。在这个分区退出之后,该基准就处于数列的中间位置。这个称为分区(partition)操作; 393 | 394 | - 递归地(recursive)把小于基准值元素的子数列和大于基准值元素的子数列排序; 395 | 396 | ```javascript 397 | function quickSort(arr, left, right) { 398 | var len = arr.length, 399 | partitionIndex, 400 | left = typeof left != 'number' ? 0 : left, 401 | right = typeof right != 'number' ? len - 1 : right; 402 | 403 | if (left < right) { 404 | partitionIndex = partition(arr, left, right); 405 | quickSort(arr, left, partitionIndex-1); 406 | quickSort(arr, partitionIndex+1, right); 407 | } 408 | return arr; 409 | } 410 | 411 | function partition(arr, left ,right) { // 分区操作 412 | var pivot = left, // 设定基准值(pivot) 413 | index = pivot + 1; 414 | for (var i = index; i <= right; i++) { 415 | if (arr[i] < arr[pivot]) { 416 | swap(arr, i, index); 417 | index++; 418 | } 419 | } 420 | swap(arr, pivot, index - 1); 421 | return index-1; 422 | } 423 | 424 | function swap(arr, i, j) { 425 | var temp = arr[i]; 426 | arr[i] = arr[j]; 427 | arr[j] = temp; 428 | } 429 | 430 | let arr = [2,43,2,32,55,1,-6,-77,5,434]; 431 | console.log(quickSort(arr,0,arr.length-1)); 432 | 433 | ``` 434 | 435 | 436 | ### 堆排序 437 | ### 计数排序 438 | ### 桶排序 439 | ### 基数排序 440 | -------------------------------------------------------------------------------- /docs/javascript/questions.md: -------------------------------------------------------------------------------- 1 | 2 | 说句真心话,一切博文和专栏都不如红宝书《JavaScript高级程序设计》和犀牛书《JavaScript权威指南》,这两本书将会伴随你一生的前端生涯,**选一本**,**吃透它**,从此前端之路有条不紊! 3 | 4 | ## ES6新语法,包含es7、es8、es9更新的语法 5 | 6 | - `let`声明变量,`const`声明常量,let声明的变量在未声明前不允许使用,即“暂时性死区”。 7 | - 解构赋值 8 | - 模版字符串、字符串被for…of遍历 9 | - 字符串的正则 10 | - Number的二进制前缀0b/0B和八进制前缀0o/0O 11 | - 数组的map、forEach等便利方法,另外Array.from()可以将类似数组的对象和可以遍历的对象转化为数组;Array.of()将一组值转化为数组 12 | - 函数参数设置默认值、箭头函数、扩展运算符(...) 13 | - Object.assign()合并对象 14 | - Symbol 15 | - Promise 16 | - Set:类似于数组,成员值唯一 17 | - Map:键值对数据结构 18 | - Proxy和Reflect。Proxy用于对编程语言编程,对外界对访问进行过滤和改写。Reflect是为了代替Object操作对象提供的新API,比如异常情况下Object.defineProperty会抛出错误,而Reflect.defineProperty会返回false 19 | - Iterator和for...of循环 20 | - Generator/yield用于异步编程 21 | - async与await。async是Generator的语法糖,完全可以看作多个异步操作,包装成的一个Promise对象,而await命令就是内部then命令的语法糖。 22 | - class 23 | - 模块化(Module),主要由export 和 import组成。 24 | - 修饰器(Decorator) 25 | 26 | ## 箭头函数和普通函数的区别 27 | 28 | 1、没有自己的this、super、arguments和new.target绑定。 29 | 2、不能使用new来调用。 30 | 3、没有原型对象。 31 | 4、不可以改变this的绑定。 32 | 5、形参名称不能重复。 33 | 34 | 箭头函数中没有this绑定,必须通过查找作用域链来决定其值。 35 | 36 | 如果箭头函数被非箭头函数包含,则this绑定的是最近一层非箭头函数的this,否则this的值则被设置为全局对象。 37 | 38 | ## Promise.all和Promise.race 39 | 40 | Promise.all可以将多个Promise实例包装成一个新的Promise实例。同时,成功和失败的返回值是不同的,成功的时候返回的是一个结果数组,而失败的时候则返回最先被reject失败状态的值。 41 | 42 | Promise.race就是赛跑的意思,意思就是说,Promise.race([p1, p2, p3])里面哪个结果获得的快,就返回那个结果,不管结果本身是成功状态还是失败状态。 43 | 44 | race场景: 给异步请求设定一个超时时间. 45 | 46 | ## 跨域原理,解决方案 47 | 48 | [跨域参考链接](https://segmentfault.com/a/1190000011145364) 49 | 50 | 浏览器的“同源策略”会导致跨域,其中同源是指“协议、域名、端口”都相同。 51 | 52 | 跨域解决方案 53 | 54 | 1. **通过jsonp跨域** 55 | 56 | jsonp原理:静态文件不受同源政策影响,我可以返回一个script里面有一个回调函数,函数的里面是我要的东西。 57 | 58 | 2. **document.domain + iframe跨域** 59 | 60 | 此方案仅限主域相同,子域不同的跨域应用场景。 61 | 62 | 实现原理:两个页面都通过js强制设置document.domain为基础主域,就实现了同域。 63 | 64 | 3. **location.hash + iframe** 65 | 66 | 实现原理: a欲与b跨域相互通信,通过中间页c来实现。 三个页面,不同域之间利用iframe的location.hash传值,相同域之间直接js访问来通信。 67 | 68 | 具体实现:A域:a.html -> B域:b.html -> A域:c.html,a与b不同域只能通过hash值单向通信,b与c也不同域也只能单向通信,但c与a同域,所以c可通过parent.parent访问a页面所有对象。 69 | 70 | 4. **window.name + iframe跨域** 71 | 72 | window.name属性的独特之处:name值在不同的页面(甚至不同域名)加载后依旧存在,并且可以支持非常长的 name 值(2MB)。 73 | 74 | 通过iframe的src属性由外域转向本地域,跨域数据即由iframe的window.name从外域传递到本地域。这个就巧妙地绕过了浏览器的跨域访问限制,但同时它又是安全操作。 75 | 76 | 5. **postMessage跨域** 77 | 78 | postMessage是HTML5 XMLHttpRequest Level 2中的API,且是为数不多可以跨域操作的window属性之一,它可用于解决以下方面的问题: 79 | 80 | 81 | - 页面和其打开的新窗口的数据传递 82 | - 多窗口之间消息传递 83 | - 页面与嵌套的iframe消息传递 84 | - 上面三个场景的跨域数据传递 85 | 86 | 用法:postMessage(data,origin)方法接受两个参数 87 | 88 | data: html5规范支持任意基本类型或可复制的对象,但部分浏览器只支持字符串,所以传参时最好用JSON.stringify()序列化。 89 | 90 | origin: 协议+主机+端口号,也可以设置为"*",表示可以传递给任意窗口,如果要指定和当前窗口同源的话设置为"/"。 91 | 92 | 6. **跨域资源共享(CORS)** 93 | 94 | **为什么选择CORS?** 因为jsonp只支持get请求,但我们还有post请求,CORS就满足了我的要求。服务端设置响应头中的Access-Control-Allow-Origin为对应的域名。 95 | 96 | - (必须)Access-Control-Allow-Origin: * 97 | - (可选)Access-Control-Allow-Credentials: true//表示允许是否发送cookie,默认情况下cookie不包含cros请求中 98 | - (可选)Access-Control-Expose-Headers: name//携带上制定响应头的字段 99 | 100 | 如果CORS请求了非简单请求,还会带来进行option的问题 101 | 102 | 7. **nginx代理跨域** 103 | 104 | ##### nginx反向代理接口跨域 105 | 106 | 浏览器跨域访问js、css、img等常规静态资源被同源策略许可,但iconfont字体文件(eot|otf|ttf|woff|svg)例外,此时可在nginx的静态资源服务器中加入以下配置。 107 | 108 | ```‌ 109 | location / { 110 | add_header Access-Control-Allow-Origin *; 111 | } 112 | ``` 113 | ##### nginx配置解决iconfont跨域 114 | 115 | 跨域原理: 同源策略是浏览器的安全策略,不是HTTP协议的一部分。服务器端调用HTTP接口只是使用HTTP协议,不会执行JS脚本,不需要同源策略,也就不存在跨越问题。 116 | 117 | 实现思路:通过nginx配置一个代理服务器(域名与domain1相同,端口不同)做跳板机,反向代理访问domain2接口,并且可以顺便修改cookie中domain信息,方便当前域cookie写入,实现跨域登录。 118 | 119 | 8. **nodejs中间件代理跨域** 120 | 121 | node中间件实现跨域代理,原理大致与nginx相同,都是通过启一个代理服务器,实现数据的转发,也可以通过设置cookieDomainRewrite参数修改响应头中cookie中域名,实现当前域的cookie写入,方便接口登录认证。 122 | 123 | 9. **WebSocket协议跨域** 124 | 125 | WebSocket protocol是HTML5一种新的协议。它实现了浏览器与服务器全双工通信,同时允许跨域通讯,是server push技术的一种很好的实现。 126 | 127 | 原生WebSocket API使用起来不太方便,我们使用Socket.io,它很好地封装了webSocket接口,提供了更简单、灵活的接口,也对不支持webSocket的浏览器提供了向下兼容。 128 | 129 | ## 简单请求和非简单请求,Option请求 130 | 131 | 浏览器将CORS请求分成两类:简单请求(simple request)和非简单请求(not-so-simple request)。 132 | 133 | - 请求方法是以下三种方法之一: 134 | - HEAD 135 | - GET 136 | - POST 137 | - HTTP的头信息不超出以下几种字段: 138 | - Accept//客户端或代理能够处理的媒体类型 139 | - Accept-Language//优先的语言(中文、英文……) 140 | - Content-Language// 实体主体的自然语言 141 | - Content-Type:只限于三个值application/x-www-form-urlencoded、multipart/form-data、text/plain 142 | 143 | 凡是不同时满足上面两个条件,就属于非简单请求。 144 | 145 | - 对于简单请求,在头信息里加一个Origin字段(协议 + 域名 + 端口),直接请求, 146 | - 对于非简单请求,需要先用options请求“预检”, 147 | 148 | 预检的请求头里有两个字段: 149 | - Access-Control-Request-Method//列出浏览器的CORS请求会用到哪些HTTP方法 150 | - Access-Control-Request-Headers//浏览器CORS请求会额外发送的头信息字段 151 | 152 | 服务器收到"预检"请求以后,检查了Origin、Access-Control-Request-Method和Access-Control-Request-Headers字段以后,确认允许跨源请求,就可以做出回应。如果服务器否定了"预检"请求,会返回一个正常的HTTP回应,但是没有任何CORS相关的头信息字段。这时,浏览器就会认定,服务器不同意预检请求,因此触发一个错误,被XMLHttpRequest对象的onerror回调函数捕获。 153 | 154 | 155 | ## 原型链✨ 156 | 157 | 绝大部分函数都有一个`prototype`属性,含义是**函数的原型对象**,所有被创建的对象都可以访问原型对象的属性和方法。在new的过程中,会将函数的prototype赋值给对象的`__proto__`属性,当访问一个对象的属性的时候,如果对象内部不存在这个属性,就从`__proto__`所指的父对象寻找,一直找到终点null,通过`__proto__`属性连接的这条链就是原型链。 158 | 159 | > 原型链最顶端:`Object.prototype.__proto__=null` 160 | 161 | 162 | ## new一个对象发生了什么? 163 | 164 | 三步: 165 | 166 | 1. 创建以这个函数为原型的空对象; 167 | 168 | 2. 将函数的`prototype`赋值给对象的`__proto__`属性; 169 | 170 | 3. 将对象作为函数的this传进去,如果有return就返回return里面的内容,如果没有就创建这个对象。 171 | 172 | ## 基本类型和引用类型 173 | 174 | **基本类型:** 175 | 176 | `String`、`Boolean`、`Number`、`Null`、`Undefined`、`Symbol`、`BigInt`。 177 | 178 | `Symbol`可以用来创建唯一常量。 179 | 180 | - 基本类型的值不能被方法来改变,只能通过赋值的方式来改变,所谓“赋值”就是“指针指向的改变”, 181 | - 基本数据类型的值不能添加方法和属性 182 | - 一个变量向另一个变量赋值,是会在变量对象上创建新值,把新值复制到新变量分配到位置上,两个变量独立且不互相影响 183 | - 基本数据类型的比较是值的比较 184 | - 基本数据类型是存放在栈区的,变量的标识符和变量的值 185 | 186 | **引用类型:** 187 | 188 | 引用类型统称为Object 类型,细分的话包括Object 类型、Array 类型、Date 类型、RegExp 类型、Function 类型 189 | 190 | - 引用类型的值是可以改变的 191 | - 引用类型可以添加属性和方法 192 | - 引用类型的值是按照引用访问的,当从一个变量向另一个变量赋值引用类型的值时,同样也会将储存在变量中的对象的值复制一份放到为新变量分配的空间中.引用类型保存在变量中的是对象在堆内存中的地址,所以,与基本数据类型的简单赋值不同,这个值的副本实际上是一个指针,而这个指针指向存储在堆内存的一个对象.那么赋值操作后,两个变量都保存了同一个对象地址,而这两个地址指向了同一个对象.因此,改变其中任何一个变量,都会互相影响 193 | - 引用类型的比较是引用的比较 194 | - 引用类型的值是同时保存在栈区和堆区中的 195 | 196 | **补充:基本包装类型** 197 | 198 | 字符串是基本类型,按理说不该有方法,但我们可以调用split()等等方法,主要是因为ECMAScript还提供了三个特殊的引用类型**Boolean**,**String**,**Number**.我们称这三个特殊的引用类型为**基本包装类型**,也叫**包装对象**. 199 | 200 | 也就是说当读取string,boolean和number这三个基本数据类型的时候,后台就会创建一个对应的基本包装类型对象,从而让我们能够调用一些方法来操作这些数据,最后再销毁基本包装类型的实例。正因为有这个销毁的动作,所以基本数据类型不可以添加属性和方法, 201 | 202 | **生存周期**: 203 | 对象的生存期,使用new操作符创建的引用类型的实例,在执行流离开当前作用域之前都是一直保存在内存中;而自动创建的基本包装类型的对象,则只存在于一行代码的执行瞬间,然后立即被销毁 204 | 205 | ## 装箱和拆箱 206 | 207 | **装箱:** 208 | 基本数据类型转换为对应的引用类型的操作 209 | 210 | - **隐式装箱:** 在基本数据类型调用一些方法时,后台会创建对应的基本包装类型实例来操作,最后再销毁,这个过程就是个装箱。 211 | - **显式装箱:** 当我们显式得把字符串传入new String()时就是显式装箱 212 | 213 | **拆箱:** 214 | 215 | 拆箱就和装箱相反,是指把引用类型转换成基本的数据类型,JS标准规定了ToPrimitive用于拆箱转换。 216 | 217 | ## ToPrimitive 218 | 219 | ToPrimitive 算法在执行时,会被传递一个参数 hint,表示这是一个什么类型的运算(也可以叫运算的期望值),根据这个 hint 参数,ToPrimitive 算法来决定内部的执行逻辑。 220 | 221 | - ToPrimitive如果传入的hint为基本数据类型,则直接返回;Number先调用valueOf(),String先调用toString()。 222 | - 若两属性均无,或没有返回基本类型,则会产生类型错误 TypeError: Cannot convert object to primitive value 223 | 224 | ## 作用域 225 | 在ES5中,只有函数作用域和全局作用域。 226 | 227 | 在ES6中,let和const拥有了块级作用域{}。 228 | 229 | ## 事件循环、宏任务和微任务 230 | 231 | 浏览器端事件循环中的异步队列有两种:macro(宏任务)队列和 micro(微任务)队列。 232 | 233 | - 常见的 macro-task 比如:setTimeout、setInterval、script(整体代码)、 I/O 操作、UI 渲染等。 234 | - 常见的 micro-task 比如: new Promise().then(回调)、MutationObserver(html5新特性,提供了监视对DOM树所做更改的能力) 等。 235 | 236 | 当某个宏任务执行完后,会查看是否有微任务队列。如果有,先执行微任务队列中的所有任务,如果没有,会读取宏任务队列中排在最前的任务,执行宏任务的过程中,遇到微任务,依次加入微任务队列。执行栈空后,再次读取微任务队列里的任务,依次类推。 237 | 238 | ## this的指向问题 239 | 240 | this在函数里:函数的拥有者默认绑定this 241 | 例如在对象的方法,this指向对象 242 | 243 | 如果要判断一个运行中函数的 this 绑定, 就需要找到这个函数的直接调用位置。 找到之后 244 | 就可以顺序应用下面这四条规则来判断 this 的绑定对象。 245 | 246 | 1. new 调用:绑定到新创建的对象,注意:显示return函数或对象,返回值不是新创建的对象,而是显式返回的函数或对象。 247 | 2. call 或者 apply( 或者 bind) 调用:严格模式下,绑定到指定的第一个参数。非严格模式下,null和undefined,指向全局对象(浏览器中是window),其余值指向被new Object()包装的对象。 248 | 3. 对象上的函数调用:绑定到那个对象。 249 | 4. 普通函数调用: 在严格模式下绑定到 undefined,否则绑定到全局对象。 250 | ES6 中的箭头函数:不会使用上文的四条标准的绑定规则, 而是根据当前的词法作用域来决定this, 具体来说, 箭头函数会继承外层函数,调用的 this 绑定( 无论 this 绑定到什么),没有外层函数,则是绑定到全局对象(浏览器中是window)。 这其实和 ES6 之前代码中的 self = this 机制一样。 251 | 252 | DOM事件函数:一般指向绑定事件的DOM元素,但有些情况绑定到全局对象(比如IE6~IE8的attachEvent)。 253 | 254 | ## apply,call,bind区别 255 | 256 | 三者都是用于改变函数体内this的指向,但是bind与apply和call的最大的区别是:bind不会立即调用,而是返回一个新函数,称为绑定函数,其内的this指向为创建它时传入bind的第一个参数,而传入bind的第二个及以后的参数作为原函数的参数来调用原函数。 257 | 258 | apply和call都是为了改变某个函数运行时的上下文而存在的(就是为了改变函数内部this的指向);apply和call的调用返回函数执行结果; 259 | 260 | 如果使用apply或call方法,那么this指向他们的第一个参数,apply的第二个参数是一个参数数组,call的第二个及其以后的参数都是数组里面的元素,就是说要全部列举出来; 261 | ## 解释下变量提升✨ 262 | 263 | JS引擎的工作方式是,先解析代码,获取所有被声明的变量,再一行一行运行,所有变量的声明语句会被提升带代码的头部,这叫做变量提升(hoisting)。 264 | 265 | ```javascript 266 | console.log(a);//undefined 267 | var a = 1; 268 | ``` 269 | 270 | 代码的实际执行顺序是这样的: 271 | 272 | ```javascript 273 | var a; 274 | console.log(a); 275 | a=1; 276 | ``` 277 | ## 声明一个函数有几种方式,有什么区别 278 | 279 | **函数式声明** 280 | 281 | ``` 282 | //ES5 283 | funciton name(){} 284 | function (){}//匿名函数 285 | //ES6 286 | ()=>{} 287 | ``` 288 | 289 | **函数表达式(函数字面量)** 290 | 291 | ``` 292 | let fun = function(){} 293 | let fun = ()=>{} 294 | let fun = function name(){} 295 | 296 | (function name(){})() 297 | ``` 298 | 299 | **Function构造** 300 | 301 | let fun = new Function('arg1','arg2','alert(arg1+","+arg2)') 302 | 303 | 304 | **区别**: 305 | 306 | - 构造函数需要被解析两次,第一次解析js代码,第二次解析传入构造函数的字符串,影响性能。 307 | - 基于变量提升和函数提升的机制,函数的声明优先级更高,会直接提升当前作用域的顶端,然后表达式和变量按顺序执行。 308 | 309 | ## 理解闭包吗?✨ 310 | 311 | ?> 这个问题是在问:闭包是什么?闭包有什么作用? 312 | 313 | 闭包的关键是函数有自己的作用域,这个作用域的寿命可可以存活的比函数本身还要长。 314 | 315 | 闭包:"有权访问另一个函数作用域中变量的函数"(来自红宝书) 316 | 317 | 闭包的最大作用是“隐藏变量”,一大特性是**内部函数总是可以访问外部函数中声明的参数和变量,甚至在其外部函数被返回(寿终正寝)之后**,比如可以利用闭包访问私有变量。 318 | 319 | 缺点也很明显: 320 | - 常驻内存,增加内存使用量 321 | - 使用不当造成内存泄漏。 322 | 323 | **写个闭包:** 324 | 325 | ```javascript 326 | function fun(){ 327 | var num = 1; 328 | return function () { 329 | console.log(++num) 330 | } 331 | } 332 | 333 | var count = fun() 334 | count()//2 335 | count()//3 336 | count()//4 337 | ``` 338 | 339 | ## 作用域链理解吗? 340 | 341 | > 区别于原型链,作用域链是为了访问**变量**而存在的链,原型链是为了访问**对象的属性**而存在的链。 342 | 343 | 专业回答:JS在执行的过程中会创建**可执行上下文**,可执行上下文的词法环境中含有外部词法环境的引用,通过这个引用可以获取到外部词法环境的变量的声明,这些引用串联起来会一直指向全局的词法环境,因此形成了作用域链。 344 | 345 | ![作用域和执行上下文环境](https://s1.ax1x.com/2020/03/26/GpzpN9.jpg) 346 | > 通俗理解:按照我们的常规思路,其实完全可以把“作用域”和“执行上下文环境”当作一个东西来理解,毕竟只是概念性的东西,不用过多深究。真实情况下“作用域”是在函数定义的时候存在的,而“执行上下文环境”是JS执行之前确定的,从内到外查找变量,找不到就向外层去找同名变量,这种作用域产生的“由内向外”的过程就是作用域链。 347 | 348 | ## Module模块化的意义 349 | 350 | - 解决命名冲突,防止全局变量的污染 351 | - 管理文件依赖 352 | - 保护私有属性 353 | - 易于代码的扩展和维护 354 | 355 | ## 前端模块化有几种 356 | 357 | ES6(ES2017)之前,前端模块规范有三种: CommandJS、AMD和CMD 358 | 359 | CommandJS用在服务端模块化规范,AMD和CMD用在浏览器模块化规范 360 | 361 | ES6之后,就可以直接食用import和export来实现模块化 362 | 363 | 364 | CommandJS:同步加载---NodeJS 365 | AMD:提前执行(异步加载,回调执行)---RequireJS 366 | CMD:延迟执行(运行时按需加载,顺序执行)---SeaJS 367 | 368 | 369 | ## ES6模块和CommonJS模块有什么区别? 370 | CommonJS是对模块的浅拷贝,可以改变require引入的变量,是node广泛使用的模块化机制。 371 | 372 | ES6模块是对模块的引用,通过import引入的是只读状态,不允许修改只读变量的值。 373 | 374 | CommonJS模块是对象,运行时加载,ES6模块不是对象,编译时加载。 375 | 376 | ## 聊一聊BigInt 377 | 但即将加入标准的基本类型,它是为了解决超过Number的最大安全数字就会出现计算不准确的情况。 378 | 379 | 声明方法是,"阿拉伯数字+n",如`let b = 10n`; 380 | 381 | > 不建议BigInt和Number进行转换,因为会损失精度。 382 | 383 | ## null和undefined有什么区别? 384 | - null表示“没有对象”,即该处不应该有值 385 | - undefined表示“缺少值”,此处应该有一个值,只是没有定义 386 | 387 | ## 轮询和长轮询 388 | 389 | 轮询:客户端定时向服务器发送请求,服务器接收到请求后马上返回响应信息并关闭连接。 390 | 391 | 长轮询:客户端向服务器发送请求,服务器接收到请求之后并不马上响应,等待资源更新时再响应并关闭连接。 392 | 393 | 轮询请求大半无用,浪费带宽,适用于中小项目。 394 | 395 | 长轮询看起来美好,但占用服务器资源,用于实时性比较高的项目,比如WebQQ。 396 | 397 | ## map、reduce、foreach、filter区别 398 | 399 | - foreach按顺序传入每个值,不返回内容 400 | - reduce,将每个值从第二个参数中获取,在第一个参数中保存 401 | - filter过滤器,只返回符合条件的值 402 | - map按顺序传入每个值,return的值改变当前值 403 | 404 | filter和map是返回新数组,reduce返回结果值,foreach不返回 405 | 406 | ## 深拷贝和浅拷贝 407 | 408 | - 浅拷贝(shallow copy):只复制指向某个对象的指针,而不复制对象本身,新旧对象共享一块内存; 409 | - 深拷贝(deep copy):复制并创建一个一摸一样的对象,不共享内存,修改新对象,旧对象保持不变。 410 | 411 | ## Object.assign()是深拷贝还是浅拷贝 412 | 413 | 当对象中只有一级属性,没有二级属性的时候,此方法为深拷贝,但是对象中有对象的时候,此方法,在二级属性以后就是浅拷贝。 414 | 415 | ## ==和===和Obejct.is()的区别 416 | 417 | 三者都用于相等性比较: 418 | 419 | - ==比较宽松,会自动转化数据类型,含义是“值相等” 420 | - ===严格相等,要求值和数据类型都相等 421 | - Object.is(v1,v2)是ES6新语法,传入两个被比较的元素,返回布尔值。类似于===,但有点区别。 422 | 423 | ```javascript 424 | 425 | +0 === -0 //true 426 | NaN === NaN // false 427 | Object.is(+0, -0) // false 428 | Object.is(NaN, NaN) // true 429 | 430 | ``` 431 | ## ==隐式转化 432 | 433 | 如果双等号两边有下列情形,则会依次发生这样的判定转化: 434 | 435 | - **其中一个是布尔**:布尔=>数值(false->0、true->1) 436 | - **一个字符串一个数值**:字符串=>数值 437 | - **一个是对象另一个不是**:调用对象的valueOf()方法,如果不是基本类型,基于返回值再调用ToPrimitive算法,这是一个内部的转化为基本类型值的算法,得到了基本类型值再基于前面的规则进行比较。 438 | - **两个都是对象:** 如果他们指向同一个对象,则返回true,否则返回false 439 | - null===undefined//true 440 | - **如果包含NaN:**如果有一个是NaN,甚至NaN==NaN都返回false,而NaN!=NaN返回true 441 | 442 | >例题: 443 | > 444 | >[]==![]//true 445 | > 446 | >- !运算符的优先级大于 ==,会将后面的值转化为布尔值。即![]变成!Boolean([]), 也就是!true,也就是false。 447 | >- 实际上是对比 [] == false; 448 | >- 运用上面的顺序,false是布尔值,所以转化为数值Number(flase), 为0。 449 | >- 对比[] == 0; 450 | >- 满足第三条规则[] 是对象(数组也属于对象),0不是对象。所以ToPrimitive([])是"" 451 | >- 对比"" == 0; 452 | >- 满足第二条规则,"" 是字符串,0是数值,对比Number("") == 0, 也就是 0 == 0,结果为true 453 | >- 所以得出 [] == ![]为true 454 | 455 | ## js垃圾回收和内存管理 456 | 457 | 关键:标记清除、V8 新生代和老生代内存、新生代 From 空间和 To 空间的逻辑和好处(拾荒者算法更快+内存碎片)、内存泄露 458 | 459 | JS的垃圾回收机制是为了以防内存泄漏。变量生命周期结束后会被释放内存,全局变量的生命周期持续到浏览器关闭页面,局部变量的生命周期在函数执行后就结束了。 460 | 461 | > 内存泄漏的含义就是当已经不需要某块内存时这块内存还存在着,垃圾回收机制就是间歇的不定期的寻找到不再使用的变量,并释放掉它们所指向的内存。 462 | 463 | js垃圾回收有两种方式:**标记清除**、**引用计数** 464 | 465 | **标记清除**: 466 | 467 | - 标记阶段:垃圾回收器从根对象开始遍历,每一个可以从根对象访问到的对象都会被添加一个可到达对象的标识。 468 | - 清除阶段:垃圾回收器会对堆内存从头到尾进行线性遍历,如果有对象没有被标识为可到达对象,那么就将对应的内存回收,并清除可到达对象的标识,以便下次垃圾回收。 469 | 470 | **引用计数**: 471 | 472 | 低版本的IE使用这种方式,但常常会引起内存泄露。原理是跟踪一个值的引用次数,当声明一个变量并将一个引用类型赋值给该变量时引用次数就是1,同一个值又被赋给另一个变量,引用次数+1,包含这个值当引用的变量取得新值,则引用次数-1,当垃圾回收器下次运行时,就会释放那些引用次数0的值占用的内存。 473 | 474 | 内存泄漏:循环引用指的是对象A中包含一个指向对象B的指针,而对象B中也包含一个指向对象A的引用。 475 | ``` 476 | function problem(){ 477 | var objectA = new Object(); 478 | var objectB = new Object(); 479 | 480 | objectA.someOtherObject = objectB; 481 | objectB.anotherObject = objectA; 482 | } 483 | ``` 484 | 485 | ## js如何判断数据类型。如何判断数组 486 | **typeof**(不可以) 487 | 488 | typeof [] === "object" 489 | typeof Null === "object" 490 | 491 | **instanceof** 492 | 493 | 检测构造函数的 prototype 属性是否出现在某个实例对象的原型链上。 494 | 495 | ``` 496 | const a = [],b={}; 497 | console.log(a instanceof Array);//true 498 | console.log(a instanceof Object);//true,在数组的原型链上也能找到Object构造函数 499 | console.log(b instanceof Array);//false 500 | ``` 501 | 502 | **constructor** 503 | 504 | ``` 505 | const a = []; 506 | console.log(a.constructor);//function Array(){ [native code] } 507 | ``` 508 | 509 | 但不能保证constructor属性被改写的情况。 510 | 511 | **Object.toString()和call/apply改写isArray** 512 | 513 | 除了对象之外,其他的数据类型的toString返回的都是内容的字符串,只有对象的toString方法会返回对象的类型,可以通过call/appy改变toString的执行上下文。 514 | 515 | ``` 516 | const isArray = (something)=>{ 517 | return Object.prototype.toString.call(something) === '[object Array]'; 518 | } 519 | 520 | cosnt a = []; 521 | const b = {}; 522 | isArray(a);//true 523 | isArray(b);//false 524 | ``` 525 | 526 | **Array.isArray()**最靠谱 527 | 528 | ``` 529 | const a = []; 530 | const b = {}; 531 | Array.isArray(a);//true 532 | Array.isArray(b);//false 533 | ``` 534 | 535 | ## 事件委托 536 | 537 | 事件委托就是利用事件冒泡,只指定一个事件处理程序,就可以管理某一类型的所有事件。 538 | 539 | 事件委托的好处 540 | 541 | - 只绑定一次事件,无频繁访问DOM,性能较高 542 | - 当有新DOM生成时,无需重复绑定事件,代码清晰简洁 543 | 544 | (联想到React的合成事件) 545 | 546 | ## 前端发送请求的多种方法fetch/ajax/axios 547 | 548 | **‌Ajax**: 549 | 550 | 异步网络请求。区别于传统web开发中采用的同步方式。 551 | 552 | Ajax带来的最大影响就是页面可以无刷新的请求数据。以往,页面表单提交数据,在用户点击完”submit“按钮后,页面会强制刷新一下,体验十分不友好。 553 | 554 | ```javascript 555 | var request = new XMLHttpRequest(); // 创建XMLHttpRequest对象 556 | 557 | //ajax是异步的,设置回调函数 558 | request.onreadystatechange = function () { // 状态发生变化时,函数被回调 559 | if (request.readyState === 4) { // 成功完成 560 | // 判断响应状态码 561 | if (request.status === 200) { 562 | // 成功,通过responseText拿到响应的文本: 563 | return success(request.responseText); 564 | } else { 565 | // 失败,根据响应码判断失败原因: 566 | return fail(request.status); 567 | } 568 | } else { 569 | // HTTP请求还在继续... 570 | } 571 | } 572 | 573 | // 发送请求: 574 | request.open('GET', '/api/categories'); 575 | request.setRequestHeader("Content-Type", "application/json") //设置请求头 576 | request.send();//到这一步,请求才正式发出 577 | ``` 578 | 579 | **axios**: 580 | 581 | axios不是一种新的技术,axios 是一个基于Promise 用于浏览器和 nodejs 的 HTTP 客户端,本质上也是对原生XHR的封装,只不过它是Promise的实现版本,符合最新的ES规范, 582 | 583 | > 实际上,axios可以用在浏览器和 node.js 中是因为,它会自动判断当前环境是什么,如果是浏览器,就会基于XMLHttpRequests实现axios。如果是node.js环境,就会基于node内置核心模块http实现axios 584 | > 585 | 有以下特点: 586 | 587 | - 从浏览器中创建 XMLHttpRequests 588 | - 从 node.js 创建 http 请求 589 | - 支持 Promise API 590 | - 拦截请求和响应 591 | - 转换请求数据和响应数据 592 | - 取消请求 593 | - 自动转换 JSON 数据 594 | - 客户端支持防御 CSRF/XSRF 595 | 596 | **fetch**: 597 | 598 | fetch是前端发展的一种新技术产物。Fetch API提供了访问和操控HTTP流的js接口,例如请求和响应。它还提供了一个全局 fetch()方法,该方法提供了一种简单,合理的方式来跨网络异步获取资源。 599 | 600 | fetch() 会返回一个 promise。然后我们用then()方法编写处理函数来处理promise中异步返回的结果。处理函数会接收fetch promise的返回值,这是一个 Response 对象。 601 | 602 | 在使用fetch的时候需要注意: 603 | 604 | - 接受到错误状态码时,Promise还是resolve(但是会将 resolve 的返回值的 ok 属性设置为 false )仅当网络故障时或请求被阻止时,才会标记为 reject。 605 | - 默认情况下,fetch 不会从服务端发送或接收任何 cookies, 如果站点依赖于用户 session,则会导致未经认证的请求(要发送 cookies,必须设置 credentials 选项)。 606 | 607 | fetch代表着更先进的技术方向,但是目前兼容性不是很好,在项目中使用的时候得慎重。 608 | 609 | **缺点:** 610 | 611 | 虽然fetch比XHR有极大的提高,特别是它在Service Worker中的集成,但是 Fetch 现在还没有方法中止一个请求,除非使用实验性功能, AbortController 和 AbortSignal,这是个通用的API 来通知 中止 事件。另外用 Fetch 不能监测上传进度。如果需要的话,还是使用axios 612 | 613 | ```javascript 614 | fetch('http://example.com/movies.json') 615 | .then(function(response) { 616 | return response.json(); 617 | }) 618 | .then(function(myJson) { 619 | console.log(myJson); 620 | }); 621 | ``` 622 | 623 | **另外还有JQuery,request封装了网络请求** 624 | 625 | ## 什么是同步和异步 626 | 627 | 我们可以通俗理解为异步就是一个任务分成两段,先执行第一段,然后转而执行其他任务,等做好了准备,再回过头执行第二段。排在异步任务后面的代码,不用等待异步任务结束会马上运行,也就是说,异步任务不具有”堵塞“效应。不连续的执行,就叫做异步。相应地,连续的执行,就叫做同步 628 | 629 | "异步模式"非常重要。在浏览器端,耗时很长的操作都应该异步执行,避免浏览器失去响应,最好的例子就是Ajax操作。在服务器端,"异步模式"甚至是唯一的模式,因为执行环境是单线程的,如果允许同步执行所有http请求,服务器性能会急剧下降,很快就会失去响应。 630 | 631 | ## js中实现异步编程的六种方案 632 | 633 | #### 回调函数 634 | 635 | 回调是一个简单的函数,它作为参数传递给另一个函数,并且在事件发生的时候会执行。在 JavaScript中,函数可以赋值给一个变量,作为其他函数的参数。 636 | 637 | 回调函数的优点是简单、容易理解和实现,缺点是不利于代码的阅读和维护,各个部分之间高度耦合,使得程序结构混乱、流程难以追踪(尤其是多个回调函数嵌套的情况),而且每个任务只能指定一个回调函数。此外它不能使用 try catch 捕获错误,不能直接 return。 638 | 639 | #### 事件监听 640 | 641 | 这种方式下,异步任务的执行不取决于代码的顺序,而取决于某个事件是否发生。 642 | 643 | 这种方法的优点是比较容易理解,可以绑定多个事件,每个事件可以指定多个回调函数,而且可以"去耦合",有利于实现模块化。缺点是整个程序都要变成事件驱动型,运行流程会变得很不清晰。 644 | 645 | #### 发布订阅 646 | 647 | 我们假定,存在一个"信号中心",某个任务执行完成,就向信号中心"发布"(publish)一个信号,其他任务可以向信号中心"订阅"(subscribe)这个信号,从而知道什么时候自己可以开始执行。这就叫做"发布/订阅模式"(publish-subscribe pattern) 648 | 649 | #### Promise 650 | 651 | **Promise的三种状态:** 652 | 653 | - Pending----Promise对象实例创建时候的初始状态 654 | - Fulfilled----可以理解为成功的状态 655 | - Rejected----可以理解为失败的状态 656 | 657 | 这个promise一旦从等待状态变成为其他状态就永远不能更改状态了 658 | 659 | promise的链式调用: 660 | 661 | - 每次调用返回的都是一个新的Promise实例(这就是then可用链式调用的原因) 662 | - 如果then中返回的是一个结果的话会把这个结果传递下一次then中的成功回调 663 | - 如果then中出现异常,会走下一个then的失败回调 664 | - 在then中使用了return,那么return的值会被Promise.resolve() 包装 665 | - then中可以不传递参数,如果不传递会透到下一个then中 666 | - catch 会捕获到没有捕获的异常 667 | 668 | #### 生成器Generators/ yield 669 | 670 | Generator 函数是 ES6 提供的一种异步编程解决方案,语法行为与传统函数完全不同,Generator 最大的特点就是可以控制函数的执行。 671 | 672 | - 语法上,首先可以把它理解成,Generator 函数是一个状态机,封装了多个内部状态。 673 | - Generator 函数除了状态机,还是一个遍历器对象生成函数。 674 | - 可暂停函数, yield可暂停,next方法可启动,每次返回的是yield后的表达式结果。 675 | - yield表达式本身没有返回值,或者说总是返回undefined。next方法可以带一个参数,该参数就会被当作上一个yield表达式的返回值。 676 | 677 | ```javascript 678 | function *foo(x) { 679 | let y = 2 * (yield (x + 1)) 680 | let z = yield (y / 3) 681 | return (x + y + z) 682 | } 683 | let it = foo(5) 684 | console.log(it.next()) // => {value: 6, done: false} 685 | console.log(it.next(12)) // => {value: 8, done: false} 686 | console.log(it.next(13)) // => {value: 42, done: true} 687 | ``` 688 | 689 | 手动迭代Generator 函数很麻烦,而实际开发一般会配合 co 库去使用。co是一个为Node.js和浏览器打造的基于生成器的流程控制工具,借助于Promise,你可以使用更加优雅的方式编写非阻塞代码。 690 | 691 | #### async/await 692 | 693 | - async/await是基于Promise实现的,它不能用于普通的回调函数。 694 | - async/await与Promise一样,是非阻塞的。 695 | - async/await使得异步代码看起来像同步代码,写法优雅,处理 then 的调用链,能够更清晰准确的写出代码 696 | 697 | ## async/await比Generator好在哪 698 | 699 | - 内置执行器。Generator 函数的执行必须靠执行器,所以才有了 co 函数库,而 async 函数自带执行器。也就是说,async 函数的执行,与普通函数一模一样,只要一行。 700 | - 更广的适用性。 co 函数库约定,yield 命令后面只能是 Thunk 函数或 Promise 对象,而 async 函数的 await 命令后面,可以跟 Promise 对象和原始类型的值(数值、字符串和布尔值,但这时等同于同步操作)。 701 | - 更好的语义。 async 和 await,比起星号和 yield,语义更清楚了。async 表示函数里有异步操作,await 表示紧跟在后面的表达式需要等待结果。 702 | 703 | ## 浏览器实现提供的通信手段 704 | 705 | [各种浏览器通信测试网站](https://alienzhou.github.io/cross-tab-communication) 706 | 707 | |通信手段|常用场景|同源同 Tab|同源跨 Tab|跨域| 708 | |-|-|-|-|-| 709 | |`SessionStorage`|单页面临时状态|✔️||| 710 | |`Web Workers`|独立线程,复杂计算|✔️||| 711 | |`ServiceWorker`|独立线程,离线/弱网|✔️||| 712 | |`Shared Workers`|多标签页共享的线程|✔️|✔️|| 713 | |`BroadcastChannel API`|多标签页广播通信|✔️|✔️|| 714 | |`localStorage`|长期本地存储|✔️|✔️|| 715 | |`IndexedDB`|大量结构化数据存储|✔️|✔️|| 716 | |`cookies`|用户身份认证、会话持久化|✔️|✔️|| 717 | |`postMessage`|不同窗口间安全通信|✔️|✔️|✔️| 718 | |`WebSocket`|C/S双向通信|✔️|✔️|✔️| 719 | |`CORS+Fetch/XMLHttpRequest`|跨域常用解决方案|✔️|✔️|✔️| 720 | |`JSONP`|get 跨域|✔️|✔️|✔️| 721 | |`WebRTC`|C/C的P2P通信|✔️|✔️|✔️| 722 | 723 | 724 | ## localStorage和sessionStorage 725 | 726 | 727 | localStorage和sessionStorage一样都是用来存储客户端临时信息的对象。 728 | 729 | - localStorage生命周期是永久,这意味着除非用户主动在浏览器上清除localStorage信息,否则这些信息将永远存在。 730 | - sessionStorage生命周期为当前窗口或标签页,一旦窗口或标签页被永久关闭了,那么所有通过sessionStorage存储的数据也就被清空了。 731 | 732 | 不同浏览器无法共享localStorage或sessionStorage中的信息。相同浏览器的不同页面间可以共享相同的 localStorage(页面属于相同域名和端口),但是不同页面或标签页间无法共享sessionStorage的信息。这里需要注意的是,页面及标签页仅指顶级窗口,如果一个标签页包含多个iframe标签且他们属于同源页面,那么他们之间是可以共享sessionStorage的。 733 | 734 | 使用时使用相同的API: 735 | 736 | - localStorage.setItem('myCat', 'Tom'); 737 | - let cat = localStorage.getItem('myCat'); 738 | - localStorage.removeItem('myCat'); 739 | - localStorage.clear();// 移除所有 740 | 741 | localStorage的除了get的API都会触发storage事件,可以利用这个来做不同标签页的通信,比如多个页面的购物车数据同步。 742 | 743 | ## cookie和session的区别 744 | 745 | Cookie与Session都能够进行会话跟踪,普通状况下二者均能够满足需求 746 | 747 | - cookie数据存放在客户的浏览器(客户端)上,session数据放在服务器上,但是服务端的session的实现对客户端的cookie有依赖关系的; 748 | - cookie不是很安全,别人可以分析存放在本地的COOKIE并进行COOKIE欺骗,考虑到安全应当使用session; 749 | - session会在一定时间内保存在服务器上。当访问增多,会比较占用你服务器的性能。考虑到减轻服务器性能方面,应当使用COOKIE; 750 | - 单个cookie在客户端的限制是3K,就是说一个站点在客户端存放的COOKIE不能超过3K; 751 | 752 | ## 冒泡和捕获 753 | **冒泡:** 754 | 755 | 微软提出了事件冒泡的事件流,事件会从最内层的元素开始发生,一直向上传播,直到document对象。p元素上发生click事件的顺序应该是p -> body -> html -> document 756 | 757 | **捕获:** 758 | 759 | 网景提出了事件捕获的事件流,事件冒泡相反,事件会从最外层开始发生,直到最具体的元素。p元素上发生click事件的顺序应该是document -> html -> body -> div -> p 760 | 761 | W3C制定了统一的标准,**先捕获再冒泡**。 762 | 763 | 可以通过addEventListener()的第三个参数来确定捕获还是冒泡,第一个参数是要绑定的事件,第二个参数是回调函数,第三个参数默认是false,代表**事件冒泡阶段**调用事件处理函数,如果设置成true,则在**事件捕获阶段**调用处理函数。 764 | 765 | 在浏览器中,如果父子元素**都设置了捕获和冒泡**的输出,当点击子元素时: 766 | 767 | - 先捕获父元素 768 | - 子元素输出先注册的事件,如果是捕获先输出捕获,如果是冒泡先输出冒泡 769 | - 在输出父元素的冒泡事件 770 | 771 | **阻止冒泡和捕获:** 772 | event.stopPropagation(); 以阻止事件的继续传播。 773 | 774 | ## ECMAScript 新特性&&常用 775 | 776 | ECMAScript 2022: 777 | Array.at(-1)从后向前取 778 | 779 | ECMAScript 2021: 780 | - Promise.any()和AggregateError 781 | - 逻辑赋值运算符 (&&=, ||=, ??=) 782 | - 数字字面量中使用下划线(_)作为分隔符,以增加可读性 783 | 784 | ES 2020: 785 | - 新的逻辑操作符??,只有当左侧的表达式结果为 null 或者 undefined 时,才会返回右侧的表达式。 786 | - BigInt 787 | - Promise.allSettled() 788 | - 查询嵌套结构时,允许a?.b的语法,避开报错或特殊处理 789 | 790 | ES 2021: 791 | - Object.fromEntries(ECMAScript 2019):该方法把键值对列表转换为一个对象 792 | -------------------------------------------------------------------------------- /docs/javascript/write-code.md: -------------------------------------------------------------------------------- 1 | 2 | 手撸代码不仅考察代码逻辑,还考察格式规范,这个注意一下。 3 | 4 | 5 | ## 实现生物上的S型增长曲线 6 | 7 | **美团点评** 8 | 9 | 面试官:"在实现动画的过程中,想要控制某些动画速度先快后慢,假如说现在要自定义一个速度曲线,应该怎么实现?比如**先慢速增加,再快速增加,再慢速增加**" 10 | 11 | 我提到了动画的`animation-timing-function`,大概就是类似`linear`、`ease`、`ease-in-out`……这种,不过意思是要实现定制化 12 | 13 | 思路:曲线其实就是"sin(t)"的变形,然后取中间一部分 14 | ![sin函数](https://s1.ax1x.com/2020/04/04/GwyBLT.jpg) 15 | 16 | 以便于调节单位长度的变化,我们最好将曲线变形成这样: 17 | ![sin函数变形](https://s1.ax1x.com/2020/04/04/G0ti8S.jpg) 18 | 19 | 我已经将函数变形完成,公式如下:(这里将3.14=Math.PI)突然懵逼的小伙伴还请复习三角函数知识。 20 | 21 | ![变形公式](https://s1.ax1x.com/2020/04/05/GBmRMV.jpg) 22 | 23 | 转化为代码: 24 | 25 | ```javascript 26 | function l(x) { 27 | let {sin,PI} = Math; 28 | let y = 0.5*((sin(PI*x-PI/2.0)+1)); 29 | return y; 30 | } 31 | ``` 32 | 33 | 传入{0-1},按照速度曲线变化,输出{0-1} 34 | 35 | ## 深拷贝 36 | 37 | 百度二面 38 | 39 | 注意JSON.parse(JSON.stringify(json));这个 40 | 41 | ```javascript 42 | 43 | // 深拷贝 44 | // 局限性:循环引用,不能处理无原型链的对象 45 | let deepCopy = (obj: any): any => { 46 | if (obj === null) return null 47 | let clone = Object.assign({}, obj) 48 | // 拷贝属性 49 | Object.keys(obj).forEach(k => { 50 | // 特殊对象还是需要特殊处理,需要拷贝更多,需要添加更多 51 | if (typeof obj[k] === 'function') { 52 | clone[k] = obj[k].bind(clone) 53 | } else if (obj[k] instanceof RegExp) { 54 | clone[k] = new RegExp(obj[k]) 55 | } else if (obj[k] instanceof Date) { 56 | clone[k] = new Date(obj[k]) 57 | } else if (obj[k] instanceof Set) { 58 | clone[k] = new Set(deepCopy(Array.from(obj[k]))) 59 | } else if (obj[k] instanceof Map) { 60 | clone[k] = new Map(deepCopy(Array.from(obj[k]))) 61 | } else if (typeof obj[k] === 'object') { 62 | clone[k] = deepCopy(obj[k]) 63 | } else { 64 | clone[k] = obj[k] 65 | } 66 | }) 67 | 68 | if (Array.isArray(obj)) { 69 | clone.length = obj.length 70 | return Array.from(clone) 71 | } 72 | } 73 | 74 | 75 | ``` 76 | 77 | ## 数组乱序 78 | 79 | 百度二面 80 | 81 | ```javascript 82 | 83 | let arr = [1,2,3,4,4,5,6,7,7,5,5,2,3]; 84 | 85 | //方法一:sort随机排序 86 | function fun(arr){ 87 | arr.sort(()=>) 88 | } 89 | 90 | /** 91 | * 方法二:随机交换每个下表值 92 | * @param arr 93 | * @returns {*} 94 | */ 95 | function fun(arr) { 96 | 97 | let len = arr.length; 98 | for (let i = 0; i < len; i++) { 99 | let handleIndex = Math.floor(Math.random() * len); 100 | 101 | let temp = arr[i]; 102 | arr[i] = arr[handleIndex]; 103 | arr[handleIndex] = temp; 104 | temp = arr[i]; 105 | } 106 | 107 | return arr; 108 | } 109 | 110 | console.log(fun(arr)); 111 | 112 | ``` 113 | 114 | ## 数组去重 115 | 116 | ```javascript 117 | let arr = [1, 2, 3, 4, 5, 5, 3, 2, 1]; 118 | 119 | function removeRepeat(arr) { 120 | let newArr = []; 121 | 122 | //排序数组 123 | arr.sort(); 124 | 125 | //当前值 126 | let curr = null; 127 | 128 | for (let i = 0; i < arr.length; i++) { 129 | if (curr===arr[i]){ 130 | continue; 131 | } 132 | 133 | curr = arr[i]; 134 | newArr.push(arr[i]); 135 | } 136 | return newArr; 137 | } 138 | 139 | console.log(removeRepeat(arr)); 140 | ``` 141 | 142 | ## 数组扁平化 143 | 144 | ```javascript 145 | let arr = [1, 2, 3, [4, 5], [6, [7, 8]],]; 146 | 147 | //方法一:flat扁平化数组,默认为1 148 | console.log(arr.flat(Infinity)); 149 | 150 | //方法二:递归 151 | function fun(arr){ 152 | let newArr = []; 153 | arr.forEach(value => { 154 | if (Array.isArray(value)){ 155 | newArr = newArr.concat(fun(value)); 156 | }else { 157 | newArr =newArr.concat(value); 158 | } 159 | }); 160 | return newArr; 161 | } 162 | console.log(fun(arr)); 163 | 164 | //方法三:toString() 165 | //实现到Array上 166 | Array.prototype.f2=function(){ 167 | let arr=this; 168 | return arr.toString().split(',') 169 | } 170 | console.log(f2(arr)); 171 | 172 | 173 | ``` 174 | 175 | ## 手撸继承 176 | 177 | 创建一个 Person 类,其包含公有属性 name 和私有属性 age 以及公有方法 setAge ;创建一个 Teacher 类,使其继承 Person ,并包含私有属性 studentCount 和私有方法 setStudentCount 178 | 179 | ```javascript 180 | 181 | const [Person, Teacher] = (function () { 182 | const _age = Symbol('_age'); 183 | 184 | const _studentCount = Symbol('_studentCount'); 185 | const _setStudentCount = Symbol('_setStudentCount'); 186 | 187 | 188 | class Person { 189 | constructor(name, age) { 190 | this.name = name; 191 | this[_age] = age; 192 | } 193 | 194 | setAge(age) { 195 | this[_age] = age; 196 | } 197 | } 198 | 199 | class Teacher extends Person{ 200 | constructor(name,age,studentCount){ 201 | super(name,age); 202 | this[_studentCount] = studentCount; 203 | } 204 | 205 | /** 206 | * 私有方法,设置学生数量 207 | * @param studentCount 208 | */ 209 | [_setStudentCount](studentCount){ 210 | this[_studentCount] = studentCount; 211 | } 212 | 213 | setCount(count){ 214 | this[_setStudentCount](count); 215 | } 216 | 217 | } 218 | 219 | return [Person, Teacher]; 220 | })(); 221 | 222 | const p = new Person('初始名字',0); 223 | const t = new Teacher('老师',24,55); 224 | 225 | ``` 226 | 227 | 其实未来js是可以使用#来实现私有属性和私有方法的,截止2020年4月,这个提案已经被审核到[stage3](https://github.com/tc39/proposal-private-methods)阶段,即作为候选的完善阶段。 228 | 229 | ## 实现Promise 230 | 231 | **第一版 Promise:能保存回调方法** 232 | 233 | 思路是在 .then() 方法中, 将 fullfill 和 reject 结果的回调函数保存下来, 然后在异步方法中调用. 因为是异步调用, 根据 event-loop 的原理, promiseAsyncFunc().then(fulfillCallback, rejectCallback) 传入的 callback 在异步调用结束时一定是已经赋值过了. 234 | 235 | ```javascript 236 | // Promise 形式的异步方法定义 237 | var promiseAsyncFunc = function() { 238 | var fulfillCallback 239 | var rejectCallback 240 | 241 | setTimeout(() => { 242 | var randomNumber = Math.random() 243 | if (randomNumber > 0.5) fulfillCallback(randomNumber) 244 | else rejectCallback(randomNumber) 245 | }, 1000) 246 | return { 247 | then: function(_fulfillCallback, _rejectCallback) { 248 | fulfillCallback = _fulfillCallback 249 | rejectCallback = _rejectCallback 250 | } 251 | } 252 | } 253 | 254 | // Promise 形式的异步方法调用 255 | promiseAsyncFunc().then(fulfillCallback, rejectCallback) 256 | 257 | ``` 258 | 259 |
260 | 【点击展开】更高级的实现Promise的方法 261 |
 262 | **‌第二版 Promise:实构造函数:**
 263 | 
 264 | 当前我们的实现 Promise 中,异步逻辑代码和 Promise 的代码是杂糅在一起的,让我们将其区分开:
 265 | 
 266 | 定义一个新方法 ownPromise() 用于创建 Promise,并在promiseAsyncFunc() 中暴露出 fulfill 和 reject 接口方便异步代码去调用。
 267 | 
 268 | ```javascript
 269 | var promiseAsyncFunc = function() {
 270 |   var fulfillCallback
 271 |   var rejectCallback
 272 | 
 273 |   return {
 274 |     fulfill: function(value) {
 275 |       if (fulfillCallback && typeof fulfillCallback === 'function') {
 276 |         fulfillCallback(value)
 277 |       }
 278 |     },
 279 |     reject: function(err) {
 280 |       if (rejectCallback && typeof rejectCallback === 'function') {
 281 |         rejectCallback(err)
 282 |       }
 283 |     },
 284 |     promise: {
 285 |       then: function(_fulfillCallback, _rejectCallback) {
 286 |         fulfillCallback = _fulfillCallback
 287 |         rejectCallback = _rejectCallback
 288 |       }
 289 |     }
 290 |   }
 291 | }
 292 | 
 293 | let ownPromise = function(asyncCall) {
 294 |   let defer = promiseAsyncFunc()
 295 |   asyncCall(defer.fulfill, defer.reject)
 296 |   return defer.promise
 297 | }
 298 | 
 299 | // Promise 形式的异步方法调用
 300 | ownPromise(function(fulfill, reject) {
 301 |   setTimeout(() => {
 302 |     var randomNumber = Math.random()
 303 |     if (randomNumber > 0.5) fulfill(randomNumber)
 304 |     else reject(randomNumber)
 305 |   }, 1000)
 306 | })
 307 | 
 308 | ```
 309 | 
 310 | **‌第三版 Promise: 支持状态管理**
 311 | 
 312 | 为了实现规范中对于 Promise 状态变化的要求, 我们需要为 Promise 加入状态管理, 可以利用 Symbol 来表示状态常量
 313 | 
 314 | 为了判断 Promise 的状态, 我们加入了 fulfill 和 reject 两个方法。并在其中判断 promise 当前状态。如果不是 pending 状态则直接 return(因为 Promise 状态只可能改变一次)。
 315 | 
 316 | 要实现这样的规范:
 317 | 
 318 | - 只允许改变一次状态
 319 | - 只能从 pending => fulfilled 或 pending => rejected
 320 | 
 321 | ```javascript
 322 | const PENDING = Symbol('pending')
 323 | const FULFILLED = Symbol('fulfilled')
 324 | const REJECTED = Symbol('rejected')
 325 | 
 326 | // Promise 形式的异步方法定义
 327 | var promiseAsyncFunc = function() {
 328 |   var status = PENDING
 329 |   var fulfillCallback
 330 |   var rejectCallback
 331 | 
 332 |   return {
 333 |     fulfill: function(value) {
 334 |       if (status !== PENDING) return
 335 |       if (typeof fulfillCallback === 'function') {
 336 |         fulfillCallback(value)
 337 |         status = FULFILLED
 338 |       }
 339 |     },
 340 |     reject(error) {
 341 |       if (status !== PENDING) return
 342 |       if (typeof rejectCallback === 'function') {
 343 |         rejectCallback(error)
 344 |         status = REJECTED
 345 |       }
 346 |     },
 347 |     promise: {
 348 |       then: function(_fulfillCallback, _rejectCallback) {
 349 |         fulfillCallback = _fulfillCallback
 350 |         rejectCallback = _rejectCallback
 351 |       }
 352 |     }
 353 |   }
 354 | }
 355 | 
 356 | let ownPromise = function(asyncCall) {
 357 |   let defer = promiseAsyncFunc()
 358 |   asyncCall(defer.fulfill, defer.reject)
 359 |   return defer.promise
 360 | }
 361 | 
 362 | // Promise 形式的异步方法调用
 363 | ownPromise(function(fulfill, reject) {
 364 |   setTimeout(() => {
 365 |     var randomNumber = Math.random()
 366 |     if (randomNumber > 0.5) fulfill(randomNumber)
 367 |     else reject(randomNumber)
 368 |   }, 1000)
 369 | }).then(data => console.log(data), err => console.log(err))
 370 | ```
 371 | 
 372 | 
373 |
374 | 375 | ## Promise.all() 376 | promise.all 是解决并发问题的,多个异步并发获取最终的结果(如果有一个失败则失败)。 377 | 378 | ```javascript 379 | Promise.all = function(values) { 380 | if (!Array.isArray(values)) { 381 | const type = typeof values; 382 | return new TypeError(`TypeError: ${type} ${values} is not iterable`) 383 | } 384 | 385 | return new Promise((resolve, reject) => { 386 | let resultArr = []; 387 | let orderIndex = 0; 388 | const processResultByKey = (value, index) => { 389 | resultArr[index] = value; 390 | if (++orderIndex === values.length) { 391 | resolve(resultArr) 392 | } 393 | } 394 | for (let i = 0; i < values.length; i++) { 395 | let value = values[i]; 396 | if (value && typeof value.then === 'function') { 397 | value.then((value) => { 398 | processResultByKey(value, i); 399 | }, reject); 400 | } else { 401 | processResultByKey(value, i); 402 | } 403 | } 404 | }); 405 | } 406 | 407 | ``` 408 | ## Promise.race() 409 | 410 | Promise.race 用来处理多个请求,采用最快的(谁先完成用谁的)。 411 | 412 | ``` 413 | Promise.race = function(promises) { 414 | return new Promise((resolve, reject) => { 415 | // 一起执行就是for循环 416 | for (let i = 0; i < promises.length; i++) { 417 | let val = promises[i]; 418 | if (val && typeof val.then === 'function') { 419 | val.then(resolve, reject); 420 | } else { // 普通值 421 | resolve(val) 422 | } 423 | } 424 | }); 425 | } 426 | 427 | ``` 428 | 429 | 430 | ## 防抖和节流 431 | 432 | 防抖:在事件被触发n秒后再执行回调,如果在这n秒内又被触发,则重新计时。适用于:输入框联想,防止多次点击提交按钮。 433 | 节流:规定在一个单位时间内,只能触发一次函数。如果这个单位时间内触发多次函数,只有一次生效。适用于:浏览器resize、onscroll。 434 | 435 | ```javascript 436 | // 防抖函数 437 | const debounce = (fn, delay) => { 438 | let timer = null; 439 | return (...args) => { 440 | clearTimeout(timer); 441 | timer = setTimeout(() => { 442 | fn.apply(this, args); 443 | }, delay); 444 | }; 445 | }; 446 | 447 | 448 | // 节流函数 449 | const throttle = (fn, delay = 500) => { 450 | let flag = true; 451 | return (...args) => { 452 | if (!flag) return; 453 | flag = false; 454 | setTimeout(() => { 455 | fn.apply(this, args); 456 | flag = true; 457 | }, delay); 458 | }; 459 | }; 460 | 461 | ``` 462 | 463 | 防抖节流演示: 464 | 465 | 466 | 467 | ## 实现new操作符 468 | 469 | ```javascript 470 | /** 471 | * 模拟实现 new 操作符 472 | * @param {Function} ctor [构造函数] 473 | * @return {Object|Function|Regex|Date|Error} [返回结果] 474 | */ 475 | function newOperator(ctor){ 476 | if(typeof ctor !== 'function'){ 477 | throw 'newOperator function the first param must be a function'; 478 | } 479 | // ES6 new.target 是指向构造函数 480 | newOperator.target = ctor; 481 | // 1.创建一个全新的对象, 482 | // 2.并且执行[[Prototype]]链接 483 | // 4.通过`new`创建的每个对象将最终被`[[Prototype]]`链接到这个函数的`prototype`对象上。 484 | var newObj = Object.create(ctor.prototype); 485 | // ES5 arguments转成数组 当然也可以用ES6 [...arguments], Aarry.from(arguments); 486 | // 除去ctor构造函数的其余参数 487 | var argsArr = [].slice.call(arguments, 1); 488 | // 3.生成的新对象会绑定到函数调用的`this`。 489 | // 获取到ctor函数返回结果 490 | var ctorReturnResult = ctor.apply(newObj, argsArr); 491 | // 小结4 中这些类型中合并起来只有Object和Function两种类型 typeof null 也是'object'所以要不等于null,排除null 492 | var isObject = typeof ctorReturnResult === 'object' && ctorReturnResult !== null; 493 | var isFunction = typeof ctorReturnResult === 'function'; 494 | if(isObject || isFunction){ 495 | return ctorReturnResult; 496 | } 497 | // 5.如果函数没有返回对象类型`Object`(包含`Functoin`, `Array`, `Date`, `RegExg`, `Error`),那么`new`表达式中的函数调用会自动返回这个新的对象。 498 | return newObj; 499 | } 500 | ``` 501 | 502 | ## 实现bind 503 | 504 | **简单版本**: 505 | 506 | ```javascript 507 | Function.prototype.myBind = function(thisArg) { 508 | if (typeof this !== 'function') { 509 | return; 510 | } 511 | let _self = this; 512 | let args = Array.prototype.slice.call(arguments, 1) 513 | let fnBound = function () { 514 | // 检测 New 515 | // 如果当前函数的this指向的是构造函数中的this 则判定为new 操作 516 | let _this = this instanceof _self ? this : thisArg; 517 | return _self.apply(_this, args.concat(Array.prototype.slice.call(arguments))); 518 | } 519 | // 为了完成 new操作 需要原型链接 520 | fnBound.prototype = this.prototype; 521 | return fnBound; 522 | } 523 | 524 | 525 | ``` 526 | 527 |
528 | 【点击展开】更高级的实现bind()的方法 529 |
 530 | 
 531 | **bind高级版本:**
 532 | 
 533 | ```javascript
 534 | // 第三版 实现new调用
 535 | Function.prototype.bindFn = function bind(thisArg){
 536 |     if(typeof this !== 'function'){
 537 |         throw new TypeError(this + ' must be a function');
 538 |     }
 539 |     // 存储调用bind的函数本身
 540 |     var self = this;
 541 |     // 去除thisArg的其他参数 转成数组
 542 |     var args = [].slice.call(arguments, 1);
 543 |     var bound = function(){
 544 |         // bind返回的函数 的参数转成数组
 545 |         var boundArgs = [].slice.call(arguments);
 546 |         var finalArgs = args.concat(boundArgs);
 547 |         // new 调用时,其实this instanceof bound判断也不是很准确。es6 new.target就是解决这一问题的。
 548 |         if(this instanceof bound){
 549 |             // 这里是实现上文描述的 new 的第 1, 2, 4 步
 550 |             // 1.创建一个全新的对象
 551 |             // 2.并且执行[[Prototype]]链接
 552 |             // 4.通过`new`创建的每个对象将最终被`[[Prototype]]`链接到这个函数的`prototype`对象上。
 553 |             // self可能是ES6的箭头函数,没有prototype,所以就没必要再指向做prototype操作。
 554 |             if(self.prototype){
 555 |                 // ES5 提供的方案 Object.create()
 556 |                 // bound.prototype = Object.create(self.prototype);
 557 |                 // 但 既然是模拟ES5的bind,那浏览器也基本没有实现Object.create()
 558 |                 // 所以采用 MDN ployfill方案 https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/create
 559 |                 function Empty(){}
 560 |                 Empty.prototype = self.prototype;
 561 |                 bound.prototype = new Empty();
 562 |             }
 563 |             // 这里是实现上文描述的 new 的第 3 步
 564 |             // 3.生成的新对象会绑定到函数调用的`this`。
 565 |             var result = self.apply(this, finalArgs);
 566 |             // 这里是实现上文描述的 new 的第 5 步
 567 |             // 5.如果函数没有返回对象类型`Object`(包含`Functoin`, `Array`, `Date`, `RegExg`, `Error`),
 568 |             // 那么`new`表达式中的函数调用会自动返回这个新的对象。
 569 |             var isObject = typeof result === 'object' && result !== null;
 570 |             var isFunction = typeof result === 'function';
 571 |             if(isObject || isFunction){
 572 |                 return result;
 573 |             }
 574 |             return this;
 575 |         }
 576 |         else{
 577 |             // apply修改this指向,把两个函数的参数合并传给self函数,并执行self函数,返回执行结果
 578 |             return self.apply(thisArg, finalArgs);
 579 |         }
 580 |     };
 581 |     return bound;
 582 | }
 583 | ```
 584 | 
 585 | 
586 |
587 | 588 | 589 | ## 实现apply和call方法 590 | 591 | **简单的ES6实现call** 592 | 593 | ```javascript 594 | /** 595 | * @description 使用ES6函数的rest参数和数组的扩展运算符实现call方法 596 | * @param {Object} context call方法一个指定的this值 597 | * @param {Object, String, Number, Boolean} context call方法一个指定的this值 598 | * @returns {Object, String, Number, Boolean} 返回调用函数的值 599 | */ 600 | Function.prototype.call = function(context, ...args) { 601 | // 使用ES6函数的rest参数(形式为...变量名),args是数组 602 | // context为null的时候,context为window 603 | var context = context || window 604 | // 获取调用call的函数 605 | context.fn = this 606 | // 使用ES6扩展运算符(...)执行函数,返回结果 607 | var result = context.fn(...args) 608 | // 删除fn属性 609 | delete context.fn 610 | // 返回结果 611 | return result; 612 | } 613 | ``` 614 | 615 | **使用ES3实现call**: 616 | 617 | ```javascript 618 | 619 | /** 620 | * @description 使用ES3实现call方法 621 | * @param {Object} context call方法一个指定的this值 622 | * @returns {Object, String, Number, Boolean} 返回调用函数的值 623 | */ 624 | Function.prototype.call = function (context) { 625 | // context为null的时候,context为window 626 | var context = context || window 627 | // 获取调用call的函数 628 | context.fn = this 629 | // 获取call方法的不定长参数 630 | var args = [] 631 | for(var i = 1, len = arguments.length; i < len; i++) { 632 | args.push('arguments[' + i + ']') 633 | } 634 | // 运行fn函数并返回结果, 635 | // eval(string)通过计算string得到的值 636 | var result = eval('context.fn(' + args +')') 637 | // 删除fn属性 638 | delete context.fn 639 | // 返回结果 640 | return result; 641 | } 642 | /** 643 | * @description 测试call方法 644 | * @param {Number} c,d 函数的参宿 645 | * @returns {Number} 返回add函数的计算结果 646 | */ 647 | function add(c, d){ 648 | return this.a + this.b + c + d 649 | } 650 | var o = {a:1, b:3} 651 | add.call(o, 5, 7) // 16 652 | 653 | ``` 654 | 655 | **ES6实现apply**: 656 | 657 | ```javascript 658 | /** 659 | * @description 使用ES6数组的扩展运算符实现apply方法 660 | * @param {Object} context apply方法一个指定的this值 661 | * @param {Array} arr apply方法传递给调用函数的参数 662 | * @returns {Object, String, Number, Boolean} 返回调用函数的值 663 | */ 664 | Function.prototype.apply = function(context, arr) { 665 | // context为null的时候,context为window 666 | var context = context || window 667 | // 获取调用apply的函数 668 | context.fn = this 669 | // 使用ES6扩展运算符(...)执行函数,返回结果 670 | var result = context.fn(...arr) 671 | // 删除fn属性 672 | delete context.fn 673 | // 返回结果 674 | return result 675 | } 676 | ``` 677 | 678 | **ES3实现apply**: 679 | 680 | ```javascript 681 | /** 682 | * @description 使用ES3实现apply方法 683 | * @param {Object} context apply方法一个指定的this值 684 | * @param {Array} arr apply方法传递给调用函数的参数 685 | * @returns {Object, String, Number, Boolean} 返回调用函数的值 686 | */ 687 | Function.prototype.apply= function (context, arr) { 688 | // context为null的时候,context为window 689 | var context = context || window 690 | // 获取调用apply的函数 691 | context.fn = this 692 | var result 693 | // 判断apply是否只有一个参数 694 | if (!arr) { 695 | // 执行函数 696 | result = context.fn(); 697 | } else { 698 | // 获取参数 699 | var args = []; 700 | for (var i = 0, len = arr.length; i < len; i++) { 701 | args.push('arr[' + i + ']'); 702 | } 703 | // 执行函数 704 | result = eval('context.fn(' + args + ')') 705 | } 706 | // 删除fn属性 707 | delete context.fn 708 | // 返回结果 709 | return result; 710 | } 711 | /** 712 | * @description 测试apply方法 713 | * @param {Number} c,d 函数的参宿 714 | * @returns {Number} 返回add函数的计算结果 715 | */ 716 | function add(c, d){ 717 | return this.a + this.b + c + d 718 | } 719 | var o = {a:1, b:3} 720 | add.apply(o, [5, 7]) // 16 721 | ``` 722 | 723 |
724 | 【点击展开】更高级的实现apply()和call()的方法 725 |
 726 | 
 727 | **apply**
 728 | 
 729 | ```javascript
 730 | // 浏览器环境 非严格模式
 731 | function getGlobalObject(){
 732 |     return this;
 733 | }
 734 | function generateFunctionCode(argsArrayLength){
 735 |     var code = 'return arguments[0][arguments[1]](';
 736 |     for(var i = 0; i < argsArrayLength; i++){
 737 |         if(i > 0){
 738 |             code += ',';
 739 |         }
 740 |         code += 'arguments[2][' + i + ']';
 741 |     }
 742 |     code += ')';
 743 |     // return arguments[0][arguments[1]](arg1, arg2, arg3...)
 744 |     return code;
 745 | }
 746 | Function.prototype.applyFn = function apply(thisArg, argsArray){ // `apply` 方法的 `length` 属性是 `2`。
 747 |     // 1.如果 `IsCallable(func)` 是 `false`, 则抛出一个 `TypeError` 异常。
 748 |     if(typeof this !== 'function'){
 749 |         throw new TypeError(this + ' is not a function');
 750 |     }
 751 |     // 2.如果 argArray 是 null 或 undefined, 则
 752 |     // 返回提供 thisArg 作为 this 值并以空参数列表调用 func 的 [[Call]] 内部方法的结果。
 753 |     if(typeof argsArray === 'undefined' || argsArray === null){
 754 |         argsArray = [];
 755 |     }
 756 |     // 3.如果 Type(argArray) 不是 Object, 则抛出一个 TypeError 异常 .
 757 |     if(argsArray !== new Object(argsArray)){
 758 |         throw new TypeError('CreateListFromArrayLike called on non-object');
 759 |     }
 760 |     if(typeof thisArg === 'undefined' || thisArg === null){
 761 |         // 在外面传入的 thisArg 值会修改并成为 this 值。
 762 |         // ES3: thisArg 是 undefined 或 null 时它会被替换成全局对象 浏览器里是window
 763 |         thisArg = getGlobalObject();
 764 |     }
 765 |     // ES3: 所有其他值会被应用 ToObject 并将结果作为 this 值,这是第三版引入的更改。
 766 |     thisArg = new Object(thisArg);
 767 |     var __fn = '__' + new Date().getTime();
 768 |     // 万一还是有 先存储一份,删除后,再恢复该值
 769 |     var originalVal = thisArg[__fn];
 770 |     // 是否有原始值
 771 |     var hasOriginalVal = thisArg.hasOwnProperty(__fn);
 772 |     thisArg[__fn] = this;
 773 |     // 9.提供 `thisArg` 作为 `this` 值并以 `argList` 作为参数列表,调用 `func` 的 `[[Call]]` 内部方法,返回结果。
 774 |     // ES6版
 775 |     // var result = thisArg[__fn](...args);
 776 |     var code = generateFunctionCode(argsArray.length);
 777 |     var result = (new Function(code))(thisArg, __fn, argsArray);
 778 |     delete thisArg[__fn];
 779 |     if(hasOriginalVal){
 780 |         thisArg[__fn] = originalVal;
 781 |     }
 782 |     return result;
 783 | };
 784 | ```
 785 | 
 786 | **call**
 787 | 
 788 | 
 789 | ```javascript
 790 | Function.prototype.callFn = function call(thisArg){
 791 |     var argsArray = [];
 792 |     var argumentsLength = arguments.length;
 793 |     for(var i = 0; i < argumentsLength - 1; i++){
 794 |         // argsArray.push(arguments[i + 1]);
 795 |         argsArray[i] = arguments[i + 1];
 796 |     }
 797 |     console.log('argsArray:', argsArray);
 798 |     return this.applyFn(thisArg, argsArray);
 799 | }
 800 | ```
 801 | 
 802 | 
803 |
804 | 805 | ## 大数相加 806 | 807 | ```javascript 808 | function addBigNum(a,b){ 809 | let res = '' 810 | loc = 0 811 | a = a.split('') 812 | b = b.split('') 813 | while(a.length || b.length || loc){ 814 | //~~把字符串转换为数字,用~~而不用parseInt,是因为~~可以将undefined转换为0,当a或b数组超限,不用再判断undefined 815 | //注意这里的+=,每次都加了loc本身,loc为true,相当于加1,loc为false,相当于加0 816 | loc += ~~a.pop() + ~~b.pop() 817 | //字符串连接,将个位加到res头部 818 | res = (loc % 10) + res 819 | //当个位数和大于9,产生进位,需要往res头部继续加1,此时loc变为true,true + 任何数字,true会被转换为1 820 | loc = loc > 9 821 | } 822 | return res.replace(/^0+/,'') 823 | } 824 | 825 | ``` 826 | ## 大数相乘 827 | 828 | 时间复杂度O(n^2) 829 | 830 | ``` 831 | const multiply = (num1, num2) => { 832 | const len1 = num1.length; 833 | const len2 = num2.length; 834 | const pos = new Array(len1 + len2).fill(0); 835 | 836 | for (let i = len1 - 1; i >= 0; i--) { 837 | const n1 = +num1[i]; 838 | for (let j = len2 - 1; j >= 0; j--) { 839 | const n2 = +num2[j]; 840 | const multi = n1 * n2; 841 | const sum = pos[i + j + 1] + multi; 842 | 843 | pos[i + j + 1] = sum % 10; 844 | pos[i + j] += sum / 10 | 0; 845 | } 846 | } 847 | while (pos[0] == 0) { 848 | pos.shift(); 849 | } 850 | return pos.length ? pos.join('') : '0'; 851 | }; 852 | 853 | ``` 854 | 855 | ## 函数链式调用/自调用 856 | 857 | 实现: 858 | - add(1)(2)(3).print()//6 859 | - add(1)(2)(3)//6 860 | - add(1)(2)//3 861 | 862 | 863 | 思路: 864 | 865 | 创建闭包,将变量保存在内存中,如果直接输出,则重写toString()返回结果;如果通过print()则写成函数属性 866 | 867 | ```javascript 868 | function add(num){ 869 | let total = num 870 | 871 | function _add (n) { 872 | total += n 873 | return _add 874 | } 875 | 876 | // 直接返回最终结果 877 | _add.toString=function () { 878 | return total 879 | } 880 | 881 | // 通过print()返回最终结果 882 | _add.print = function () { 883 | return total 884 | } 885 | 886 | return _add 887 | } 888 | 889 | console.log(add(1)(2)(3).print())//6 890 | console.log(add(1)(2)(3))//6 891 | console.log(add(1)(2))//3 892 | ``` 893 | ## 实现一个axios 894 | 895 | **思路:** 896 | axios还是属于 XMLHttpRequest, 因此需要实现一个ajax。或者基于http 。还需要一个promise对象来对结果进行处理。 897 | 898 | `myAxios.js` 899 | 900 | ```javascript 901 | class Axios { 902 | constructor() { 903 | 904 | } 905 | 906 | request(config) { 907 | return new Promise(resolve => { 908 | const {url = '', method = 'get', data = {}} = config; 909 | // 发送ajax请求 910 | const xhr = new XMLHttpRequest(); 911 | xhr.open(method, url, true); 912 | xhr.onload = function() { 913 | console.log(xhr.responseText) 914 | resolve(xhr.responseText); 915 | } 916 | xhr.send(data); 917 | }) 918 | } 919 | } 920 | 921 | // 最终导出axios的方法,即实例的request方法 922 | function CreateAxiosFn() { 923 | let axios = new Axios(); 924 | let req = axios.request.bind(axios); 925 | return req; 926 | } 927 | 928 | // 得到最后的全局变量axios 929 | let axios = CreateAxiosFn(); 930 | 931 | ``` 932 | 933 | 然后可以在html上进行测试 934 | 935 | ```html 936 | //index.html 937 | 938 | 939 | 940 | 951 | 952 | ``` 953 | 954 | 955 | ## 观察者模式 956 | 957 | 观察者直接订阅目标,当目标触发事件时,通知观察者进行更新 958 | 959 | 简单实现 960 | 961 | ```javascript 962 | class Observer { 963 | constructor(name) { 964 | this.name = name; 965 | } 966 | 967 | update() { 968 | console.log(`${this.name} update`) 969 | } 970 | } 971 | 972 | class subject { 973 | constructor() { 974 | this.subs = []; 975 | } 976 | 977 | add(observer) { 978 | this.subs.push(observer); 979 | } 980 | 981 | notify() { 982 | this.subs.forEach(item => { 983 | item.update(); 984 | }); 985 | } 986 | } 987 | 988 | const sub = new subject(); 989 | const ob1 = new Observer('ob1'); 990 | const ob2 = new Observer('ob2'); 991 | 992 | // 观察者订阅目标 993 | sub.add(ob1); 994 | sub.add(ob2); 995 | 996 | // 目标触发事件 997 | sub.notify(); 998 | ``` 999 | ## 手写发布者-订阅者模式 1000 | 1001 | 发布订阅模式通过一个调度中心进行处理,使得订阅者和发布者分离开来,互不干扰。 1002 | 1003 | 简单实现: 1004 | 1005 | ```javascript 1006 | class Event { 1007 | constructor() { 1008 | this.lists = new Map(); 1009 | } 1010 | 1011 | on(type, fn) { 1012 | if (!this.lists.get(type)) { 1013 | this.lists.set(type, []); 1014 | } 1015 | 1016 | this.lists.get(type).push(fn); 1017 | } 1018 | 1019 | emit(type, ...args) { 1020 | const arr = this.lists.get(type); 1021 | arr && arr.forEach(fn => { 1022 | fn.apply(null, args); 1023 | }); 1024 | } 1025 | } 1026 | 1027 | const ev = new Event(); 1028 | 1029 | // 订阅 1030 | ev.on('msg', (msg) => console.log(msg)); 1031 | 1032 | // 发布 1033 | ev.emit('msg', '发布'); 1034 | ``` 1035 | 1036 | 另一种实现: 1037 | 1038 | ```javascript 1039 | // 事件对象 1040 | let eventEmitter = {}; 1041 | 1042 | // 缓存列表,存放 event 及 fn 1043 | eventEmitter.list = {}; 1044 | 1045 | // 订阅 1046 | eventEmitter.on = function (event, fn) { 1047 | let _this = this; 1048 | // 如果对象中没有对应的 event 值,也就是说明没有订阅过,就给 event 创建个缓存列表 1049 | // 如有对象中有相应的 event 值,把 fn 添加到对应 event 的缓存列表里 1050 | (_this.list[event] || (_this.list[event] = [])).push(fn); 1051 | return _this; 1052 | }; 1053 | 1054 | // 发布 1055 | eventEmitter.emit = function () { 1056 | let _this = this; 1057 | // 第一个参数是对应的 event 值,直接用数组的 shift 方法取出 1058 | let event = [].shift.call(arguments), 1059 | fns = [..._this.list[event]]; 1060 | // 如果缓存列表里没有 fn 就返回 false 1061 | if (!fns || fns.length === 0) { 1062 | return false; 1063 | } 1064 | // 遍历 event 值对应的缓存列表,依次执行 fn 1065 | fns.forEach(fn => { 1066 | fn.apply(_this, arguments); 1067 | }); 1068 | return _this; 1069 | }; 1070 | 1071 | function user1 (content) { 1072 | console.log('用户1订阅了:', content); 1073 | }; 1074 | 1075 | function user2 (content) { 1076 | console.log('用户2订阅了:', content); 1077 | }; 1078 | 1079 | // 订阅 1080 | eventEmitter.on('article', user1); 1081 | eventEmitter.on('article', user2); 1082 | 1083 | // 发布 1084 | eventEmitter.emit('article', 'Javascript 发布-订阅模式'); 1085 | 1086 | /* 1087 | 用户1订阅了: Javascript 发布-订阅模式 1088 | 用户2订阅了: Javascript 发布-订阅模式 1089 | */ 1090 | ``` 1091 | 1092 | ## 手撸一个事件机制 1093 | 1094 | **关键词:发布-订阅模式** 1095 | 1096 | 其实核心就是维护一个对象,对象的 key 存的是事件 type,对应的 value 为触发相应 type 的回调函数,即 listeners,然后 trigger 时遍历通知,即 forEach 进行回调执行。 1097 | 1098 | ```javascript 1099 | class EventTarget { 1100 | constructor() { 1101 | this.listeners = {}; // 储存事件的对象 1102 | } 1103 | 1104 | // 注册事件的回调函数 1105 | on(type, callback) { 1106 | if (!this.listeners[type]) this.listeners[type] = []; // 如果是第一次监听该事件,则初始化数组 1107 | this.listeners[type].push(callback); 1108 | } 1109 | // 注册事件的回调函数,只执行一次 1110 | once(type, callback) { 1111 | if (!this.listeners[type]) this.listeners[type] = []; 1112 | callback._once = true; // once 只触发一次,触发后 off 即可 1113 | this.listeners[type].push(callback); 1114 | } 1115 | // 删除一个回调函数 1116 | off(type, callback) { 1117 | const listeners = this.listeners[type]; 1118 | if (Array.isArray(listeners)) { 1119 | // filter 返回新的数组,会每次对 this.listeners[type] 分配新的空间 1120 | // this.listeners[type] = listeners.filter(l => l !== callback); 1121 | const index = listeners.indexOf(callback); // 根据 type 取消对应的回调 1122 | this.listeners[type].splice(index, 1); // 用 splice 要好些,直接操作原数组 1123 | 1124 | if (this.listeners[type].length === 0) delete this.listeners[type]; // 如果回调为空,删除对该事件的监听 1125 | } 1126 | } 1127 | // 触发注册的事件回调函数执行 1128 | trigger(event) { 1129 | const { type } = event; // type 为必传属性 1130 | if (!type) throw new Error('没有要触发的事件!'); 1131 | 1132 | const listeners = this.listeners[type]; // 判断是否之前对该事件进行监听了 1133 | if (!listeners) throw new Error(`没有对象监听 ${type} 事件!`); 1134 | 1135 | if (!event.target) event.target = this; 1136 | 1137 | listeners.forEach(l => { 1138 | l(event); 1139 | if (l._once) this.off(type, l); // 如果通过 once 监听,执行一次后取消 1140 | }); 1141 | } 1142 | } 1143 | 1144 | // 测试 1145 | function handleMessage(event) { 1146 | console.log(`message received: ${ event.message }`); 1147 | } 1148 | 1149 | function handleMessage2(event) { 1150 | console.log(`message2 received: ${ event.message }`); 1151 | } 1152 | 1153 | const target = new EventTarget(); 1154 | 1155 | target.on('message', handleMessage); 1156 | target.on('message', handleMessage2); 1157 | target.trigger({ type: 'message', message: 'hello custom event' }); // 打印 message,message2 1158 | 1159 | target.off('message', handleMessage); 1160 | target.trigger({ type: 'message', message: 'off the event' }); // 只打印 message2 1161 | 1162 | target.once('words', handleMessage); 1163 | target.trigger({ type: 'words', message: 'hello2 once event' }); // 打印 words 1164 | target.trigger({ type: 'words', message: 'hello2 once event' }); // 报错:没有对象监听 words 事件! 1165 | ``` 1166 | 1167 | ## 实现一个sleep函数 1168 | 1169 | ```javascript 1170 | //Promise 1171 | const sleep = time => { 1172 | return new Promise(resolve => setTimeout(resolve,time)) 1173 | } 1174 | sleep(1000).then(()=>{ 1175 | console.log(1) 1176 | }) 1177 | 1178 | //Generator 1179 | function* sleepGenerator(time) { 1180 | yield new Promise(function(resolve,reject){ 1181 | setTimeout(resolve,time); 1182 | }) 1183 | } 1184 | sleepGenerator(1000).next().value.then(()=>{console.log(1)}) 1185 | 1186 | //async 1187 | function sleep(time) { 1188 | return new Promise(resolve => setTimeout(resolve,time)) 1189 | } 1190 | async function output() { 1191 | let out = await sleep(1000); 1192 | console.log(1); 1193 | return out; 1194 | } 1195 | output(); 1196 | 1197 | //ES5 1198 | function sleep(callback,time) { 1199 | if(typeof callback === 'function') 1200 | setTimeout(callback,time) 1201 | } 1202 | 1203 | function output(){ 1204 | console.log(1); 1205 | } 1206 | sleep(output,1000); 1207 | ``` 1208 | 1209 | ## 实现add(1)(2,3)(1)//6函数 1210 | 1211 | ```javascript 1212 | function add(){ 1213 | let args = [...arguments]; 1214 | let addfun = function(){ 1215 | args.push(...arguments); 1216 | return addfun; 1217 | } 1218 | addfun.toString = function(){ 1219 | return args.reduce((a,b)=>{ 1220 | return a + b; 1221 | }); 1222 | } 1223 | return addfun; 1224 | } 1225 | 1226 | add(1); // 1 1227 | add(1)(2); // 3 1228 | add(1)(2)(3); // 6 1229 | add(1)(2, 3); // 6 1230 | add(1, 2)(3); // 6 1231 | add(1, 2, 3); // 6 1232 | ``` 1233 | 1234 | ## 控制最大(请求)并发数量 1235 | 1236 | 假设最大并发数是n,那就通过`n--`发n个异步请求,同时将任务数组arr传进去,并通过shift()取arr首元素,虽然异步调用的顺序不同,但操作的是同一个数组,然后将元素进行处理之后,判断当前数组是否为空,否则按照上面的方法进行递归,知道数组为空为止。 1237 | 1238 | ```javascript 1239 | /** 1240 | * @params list {Array} - 要迭代的数组 1241 | * @params limit {Number} - 并发数量控制数 1242 | * @params asyncHandle {Function} - 对`list`的每一个项的处理函数,参数为当前处理项,必须 return 一个Promise来确定是否继续进行迭代 1243 | * @return {Promise} - 返回一个 Promise 值来确认所有数据是否迭代完成 1244 | */ 1245 | let mapLimit = (list, limit, asyncHandle) => { 1246 | let recursion = (arr) => { 1247 | return asyncHandle(arr.shift()).then(() => { 1248 | if (arr.length !== 0) return recursion(arr) // 数组还未迭代完,递归继续进行迭代 1249 | else return 'finish' 1250 | }) 1251 | } 1252 | 1253 | let listCopy = [].concat(list) 1254 | let asyncList = [] // 正在进行的所有并发异步操作 1255 | while (limit--) { 1256 | asyncList.push(recursion(listCopy)) 1257 | } 1258 | return Promise.all(asyncList) // 所有并发异步操作都完成后,本次并发控制迭代完成 1259 | } 1260 | ``` 1261 | 1262 | 以下是手动测试 1263 | 1264 | ``` 1265 | let list = [1, 2, 3, 4, 5,4,333,222,122,23,54,64] 1266 | let limit = 3//限制的并发数量 1267 | let count = 0//记数当前并发量 1268 | 1269 | let callback = (curItem) => { 1270 | return new Promise(resolve => { 1271 | count++ 1272 | setTimeout(() => { 1273 | console.log(curItem, '当前并发量:', count--) 1274 | resolve() 1275 | }, Math.random() * 5000) 1276 | }) 1277 | } 1278 | 1279 | mapLimit(list, limit, callback) 1280 | //完成之后 1281 | .then(()=>{ 1282 | console.log('所有并发执行完成') 1283 | }) 1284 | ``` 1285 | 1286 | ## 实现instanceof 1287 | 1288 | instanceof 运算符用于检测构造函数的 prototype 属性是否出现在某个实例对象的原型链上。 1289 | 1290 | ```javascript 1291 | //instanceof的使用方法 1292 | function Car() {} 1293 | const benz = new Car(); 1294 | console.log(benz instanceof Car);//true 1295 | console.log(auto instanceof Object);//true 1296 | ``` 1297 | 1298 | 实现instance_of(L,R) 1299 | 1300 | ```javascript 1301 | function instance_of(L, R) {//L 表示左表达式,R 表示右表达式 1302 | var O = R.prototype; // 取 R 的显示原型 1303 | L = L.__proto__; // 取 L 的隐式原型 1304 | while (true) { 1305 | if (L === null) 1306 | return false; 1307 | if (O === L) // 当 O 显式原型 严格等于 L隐式原型 时,返回true 1308 | return true; 1309 | L = L.__proto__; 1310 | } 1311 | } 1312 | ``` 1313 | 1314 | ## 手写一个jsonp 1315 | 1316 | ```javascript 1317 | //根据指定的URL发送一个JSONP请求 1318 | //然后把解析得到的响应数据传递给回调函数 1319 | //在URL中添加一个名为jsonp的查询参数,用于指定该请求的回调函数的名称 1320 | function getJSONP(url, callback) { //为本次请求创建一个唯一的回调函数名称 1321 | var cbnum = "cb" + getJSONP.counter++; //每次自增计数器 1322 | var cbname = "getJSONP." + cbnum; //作为JSONP函数的属性 1323 | //将回调函数名称以表单编码的形式添加到URL的查询部分中 1324 | //使用jsonp作为参数名,一些支持JSONP的服务 1325 | //可能使用其他的参数名,比如callback 1326 | if (url.indexOf("?") === -1) //URL没有查询部分 1327 | url += "?jsonp=" + cbname; //作为查询部分添加参数 1328 | else //否则 1329 | url += "&jsonp=" + cbname; //作为新的参数添加它 1330 | //创建script元素用于发送请求 1331 | var script = document.createElement("script"); //定义将被脚本执行的回调函数 1332 | getJSONP[cbnum] = function (response) { 1333 | try { 1334 | callback(response); //处理响应数据 1335 | } finally { //即使回调函数或响应抛出错误 1336 | delete getJSONP[cbnum]; //删除该函数 1337 | script.parentNode.removeChild(script); //移除script元素 1338 | } 1339 | }; //立即触发HTTP请求 1340 | script.src = url; //设置脚本的URL 1341 | document.body.appendChild(script); //把它添加到文档中 1342 | } 1343 | getJSONP.counter = 0; //用于创建唯一回调函数名称的计数器 1344 | ``` 1345 | --------------------------------------------------------------------------------