├── HTML+CSS面试题 ├── HTML+CSS面试题37题.md └── 代码 │ ├── 0.5px的线.html │ ├── css怎么实现三角形.html │ ├── 三栏布局的实现.html │ ├── 两栏布局的实现.html │ ├── 实现一个宽高自适应的正方形.html │ ├── 实现一个扇形.html │ ├── 宫格布局.html │ ├── 懒加载的实现原理.html │ └── 水平垂直居中的实现.html ├── JS全部面试题 └── JS全部面试题69题.md ├── JS详解面试题 ├── 1.7手写.js ├── JS执行.js ├── JS详解面试题46题.md ├── image.png ├── tempCodeRunnerFile.js └── 手写js.js ├── README.md ├── React面试题 └── React面试题103题.md ├── TS全部面试题 └── TS面试题.md ├── Vue面试题 └── Vue面试题82题.md ├── 代码输出题 └── 代码输出题.md ├── 前端工程化、性能优化面试题 └── 前端工程化、性能优化面试题44题.md ├── 小程序面试题 └── 小程序面试题.md ├── 常见手写题 ├── js基础手写 │ ├── 1.16手写.js │ ├── 2.22手写.js │ ├── index.js │ └── tempCodeRunnerFile.js └── 常见手写题.md ├── 常见算法题 └── 常见算法题.md ├── 浏览器原理面试题 └── 浏览器原理面试题58题.md └── 计算机网络面试题 └── 计算机网络面试题42题.md /HTML+CSS面试题/HTML+CSS面试题37题.md: -------------------------------------------------------------------------------- 1 | ## 1.说一下 web worker 2 | 3 | **在 HTML 页面中,如果在执行脚本时,页面的状态是不可响应的,直到脚本执行完成后,页面才变成可响应。web worker 是运行在后台的 js,独立于其他脚本,不会影响页面的性能。 并且通过 postMessage 将结果回传到主线程。这样在进行复杂操作的时候,就不会阻塞主线程了。** 4 | 5 | **如何创建 web worker:** 6 | 7 | 1. **检测浏览器对于 web worker 的支持性** 8 | 2. **创建 web worker 文件(js,回传函数等)** 9 | 3. **创建 web worker 对象** 10 | 11 | ## 2.行内元素有哪些?块级元素有哪些? 空(void)元素有那些? 12 | 13 | - **行内元素有:a b span img input select strong;** 14 | - **块级元素有:div ul ol li dl dt dd h1 h2 h3 h4 h5 h6 p;** 15 | 16 | **空元素,即没有内容的HTML元素。空元素是在开始标签中关闭的,也就是空元素没有闭合标签:** 17 | 18 | - **常见的有:`
`、`
`、``、``、``、``;** 19 | - **鲜见的有:``、``、``、``、``、``、``、``、``、``、``。** 20 | 21 | ## 3. CSS选择器及其优先级 22 | 23 | | **选择器** | **格式** | **优先级权重** | 24 | | -------------- | ------------- | -------------- | 25 | | id选择器 | #id | 100 | 26 | | 类选择器 | .classname | 10 | 27 | | 属性选择器 | a[ref=“eee”] | 10 | 28 | | 伪类选择器 | li:last-child | 10 | 29 | | 标签选择器 | div | 1 | 30 | | 伪元素选择器 | li:after | 1 | 31 | | 相邻兄弟选择器 | h1+p | 0 | 32 | | 子选择器 | ul>li | 0 | 33 | | 后代选择器 | li a | 0 | 34 | | 通配符选择器 | * | 0 | 35 | 36 | **对于选择器的优先级:** 37 | 38 | - **标签选择器、伪元素选择器:1** 39 | - **类选择器、伪类选择器、属性选择器:10** 40 | - **id 选择器:100** 41 | - **内联样式:1000** 42 | 43 | **注意事项:** 44 | 45 | - **!important声明的样式的优先级最高;** 46 | - **如果优先级相同,则最后出现的样式生效;** 47 | - **继承得到的样式的优先级最低;** 48 | - **通用选择器(\*)、子选择器(>)和相邻同胞选择器(+)并不在这四个等级中,所以它们的权值都为 0 ;** 49 | - **样式表的来源不同时,优先级顺序为:内联样式 > 内部样式 > 外部样式 > 浏览器用户自定义样式 > 浏览器默认样式。** 50 | 51 | 52 | 53 | ## 4.display的属性值及其作用 54 | 55 | | **属性值** | **作用** | 56 | | ------------ | :--------------------------------------------------------: | 57 | | none | 元素不显示,并且会从文档流中移除。 | 58 | | block | 块类型。默认宽度为父元素宽度,可设置宽高,换行显示。 | 59 | | inline | 行内元素类型。默认宽度为内容宽度,不可设置宽高,同行显示。 | 60 | | inline-block | 默认宽度为内容宽度,可以设置宽高,同行显示。 | 61 | | list-item | 像块类型元素一样显示,并添加样式列表标记。 | 62 | | table | 此元素会作为块级表格来显示。 | 63 | | inherit | 规定应该从父元素继承display属性的值。 | 64 | | flex | flex布局 | 65 | 66 | ## 5.display的block、inline和inline-block的区别 67 | 68 | **(1)**block:会独占一行,多个元素会另起一行,可以设置width、height、margin和padding属性; 69 | 70 | **(2)**inline:元素不会独占一行,设置width、height属性无效。但可以设置水平方向的margin和padding属性,不能设置垂直方向的padding和margin; 71 | 72 | **(3)**inline-block:将对象设置为inline对象,但对象的内容作为block对象呈现,之后的内联对象会被排列在同一行内。 73 | 74 | **对于行内元素和块级元素,其特点如下:** 75 | 76 | **(1)行内元素** 77 | 78 | - **设置宽高无效;** 79 | - **可以设置水平方向的margin和padding属性,不能设置垂直方向的padding和margin;** 80 | - **不会自动换行;** 81 | 82 | **(2)块级元素** 83 | 84 | - **可以设置宽高;** 85 | - **设置margin和padding都有效;** 86 | - **可以自动换行;** 87 | - **多个块状,默认排列从上到下。** 88 | 89 | ## 6. 隐藏元素的方法有哪些 90 | 91 | 1. **display: none:渲染树不会包含该渲染对象,因此该元素不会在页面中占据位置,也不会响应绑定的监听事件。** 92 | 2. **visibility: hidden:元素在页面中仍占据空间,但是不会响应绑定的监听事件。** 93 | 3. **opacity: 0:将元素的透明度设置为 0,以此来实现元素的隐藏。元素在页面中仍然占据空间,并且能够响应元素绑定的监听事件。** 94 | 4. **position: absolute:通过使用绝对定位将元素移除可视区域内,以此来实现元素的隐藏。** 95 | 5. **z-index: 负值:来使其他元素遮盖住该元素,以此来实现隐藏。** 96 | 6. **clip/clip-path** **:使用元素裁剪的方法来实现元素的隐藏,这种方法下,元素仍在页面中占据位置,但是不会响应绑定的监听事件。** 97 | 7. **transform: scale(0,0):将元素缩放为 0,来实现元素的隐藏。这种方法下,元素仍在页面中占据位置,但是不会响应绑定的监听事件** 98 | 99 | ## 7. link和@import的区别 100 | 101 | **两者都是外部引用CSS的方式,它们的区别如下:** 102 | 103 | - **link是XHTML标签,除了加载CSS外,还可以定义RSS等其他事务;@import属于CSS范畴,只能加载CSS。** 104 | - **link引用CSS时,在页面载入时同时加载;@import需要页面网页完全载入以后加载。** 105 | - **link是XHTML标签,无兼容问题;@import是在CSS2.1提出的,低版本的浏览器不支持。** 106 | - **link支持使用Javascript控制DOM去改变样式;而@import不支持。** 107 | 108 | ## 8. display:none与visibility:hidden的区别 109 | 110 | **这两个属性都是让元素隐藏,不可见。两者区别如下:** 111 | 112 | **(1)在渲染树中** 113 | 114 | - **display:none会让元素完全从渲染树中消失,渲染时不会占据任何空间;** 115 | - **visibility:hidden不会让元素从渲染树中消失,渲染的元素还会占据相应的空间,只是内容不可见。** 116 | 117 | **(2)是否是继承属性** 118 | 119 | - **display:none是非继承属性,子孙节点会随着父节点从渲染树消失,通过修改子孙节点的属性也无法显示;** 120 | - **visibility:hidden** **是继承属性,子孙节点消失是由于继承了** **hidden,通过设置** **visibility:visible** **可以让子孙节点显示;** 121 | 122 | **(3)修改常规文档流中元素的** **display通常会造成文档的重排,但是修改** **visibility** **属性只会造成本元素的重绘;** 123 | 124 | **(4)如果使用读屏器,设置为** **display:none** **的内容不会被读取,设置为** **visibility:hidden** **的内容会被读取。** 125 | 126 | ## 9. 伪元素和伪类的区别和作用? 127 | 128 | - **伪元素:在内容元素的前后插入额外的元素或样式,但是这些元素实际上并不在文档中生成。它们只在外部显示可见,但不会在文档的源代码中找到它们,因此,称为“伪”元素。例如:** 129 | 130 | ```plain 131 | p::before {content:"第一章:";} 132 | p::after {content:"Hot!";} 133 | p::first-line {background:red;} 134 | p::first-letter {font-size:30px;} 135 | ``` 136 | 137 | - **伪类:将特殊的效果添加到特定选择器上。它是已有元素上添加类别的,不会产生新的元素。例如:** 138 | 139 | ```plain 140 | a:hover {color: #FF00FF} 141 | p:first-child {color: red} 142 | ``` 143 | 144 | **总结:伪类是通过在元素选择器上加⼊伪类改变元素状态,⽽伪元素通过对元素的操作进⾏对元素的改变。** 145 | 146 | ## 10. 对盒模型的理解 147 | 148 | **CSS3中的盒模型有以下两种:标准盒子模型、IE盒子模型** 149 | 150 | **盒模型都是由四个部分组成的,分别是margin、border、padding和content。** 151 | 152 | **标准盒模型和IE盒模型的区别在于设置width和height时,所对应的范围不同:** 153 | 154 | - **标准盒模型的width和height属性的范围只包含了content,** 155 | - **IE盒模型的width和height属性的范围包含了border、padding和content。** 156 | 157 | **可以通过修改元素的box-sizing属性来改变元素的盒模型:** 158 | 159 | - **box-sizeing: content-box表示标准盒模型(默认值)** 160 | - **box-sizeing: border-box表示IE盒模型(怪异盒模型)** 161 | 162 | ## 11. 对 CSSSprites 的理解 163 | 164 | **CSSSprites(精灵图),将一个页面涉及到的所有图片都包含到一张大图中去,然后利用CSS的 background-image,background-repeat,background-position属性的组合进行背景定位。** 165 | 166 | **优点:** 167 | 168 | - **利用** **CSS Sprites能很好地减少网页的http请求,从而大大提高了页面的性能,这是** **CSS Sprites最大的优点;** 169 | - **CSS Sprites能减少图片的字节,把3张图片合并成1张图片的字节总是小于这3张图片的字节总和。** 170 | 171 | **缺点:** 172 | 173 | - **在图片合并时,要把多张图片有序的、合理的合并成一张图片,还要留好足够的空间,防止板块内出现不必要的背景。在宽屏及高分辨率下的自适应页面,如果背景不够宽,很容易出现背景断裂;** 174 | - **CSSSprites在开发的时候相对来说有点麻烦,需要借助** **photoshop或其他工具来对每个背景单元测量其准确的位置。** 175 | - **维护方面:CSS Sprites在维护的时候比较麻烦,页面背景有少许改动时,就要改这张合并的图片,无需改的地方尽量不要动,这样避免改动更多的** **CSS,如果在原来的地方放不下,又只能(最好)往下加图片,这样图片的字节就增加了,还要改动** **CSS。** 176 | 177 | ## 12. CSS预处理器/后处理器是什么?为什么要使用它们? 178 | 179 | **预处理器,如:less,sass,stylus,用来预编译** **sass或者** **less,增加了** **css代码的复用性。层级,mixin, 变量,循环, 函数等对编写以及开发UI组件都极为方便。** 180 | 181 | **后处理器,** **如:** **postCss,通常是在完成的样式表中根据** **css规范处理** **css,让其更加有效。目前最常做的是给** **css属性添加浏览器私有前缀,实现跨浏览器兼容性的问题。** 182 | 183 | **css预处理器为** **css增加一些编程特性,无需考虑浏览器的兼容问题,可以在** **CSS中使用变量,简单的逻辑程序,函数等在编程语言中的一些基本的性能,可以让** **css更加的简洁,增加适应性以及可读性,可维护性等。** 184 | 185 | **其它** **css预处理器语言:Sass(Scss),** **Less,** **Stylus,** **Turbine,** **Swithch css,** **CSS Cacheer,** **DT Css。** 186 | 187 | **使用原因:** 188 | 189 | - **结构清晰, 便于扩展** 190 | - **可以很方便的屏蔽浏览器私有语法的差异** 191 | - **可以轻松实现多重继承** 192 | - **完美的兼容了** **CSS代码,可以应用到老项目中** 193 | 194 | ## 13. display:inline-block 什么时候会显示间隙? 195 | 196 | - **有空格时会有间隙,可以删除空格解决;** 197 | - **margin正值时,可以让** **margin使用负值解决;** 198 | - **使用** **font-size时,可通过设置** **font-size:0、letter-spacing、word-spacing解决;** 199 | 200 | ## 14. 单行、多行文本溢出隐藏 201 | 202 | - **单行文本溢出** 203 | 204 | ```css 205 | overflow: hidden; // 溢出隐藏 206 | text-overflow: ellipsis; // 溢出用省略号显示 207 | white-space: nowrap; // 规定段落中的文本不进行换行 208 | ``` 209 | 210 | - **多行文本溢出** 211 | 212 | ```css 213 | overflow: hidden; // 溢出隐藏 214 | text-overflow: ellipsis; // 溢出用省略号显示 215 | display:-webkit-box; // 作为弹性伸缩盒子模型显示。 216 | -webkit-box-orient:vertical; // 设置伸缩盒子的子元素排列方式:从上到下垂直排列 217 | -webkit-line-clamp:3; // 显示的行数 218 | ``` 219 | 220 | **注意:由于上面的三个属性都是 CSS3 的属性,没有浏览器可以兼容,所以要在前面加一个** **-webkit-** **来兼容一部分浏览器。** 221 | 222 | ## 15. 如何判断元素是否到达可视区域 223 | 224 | **以图片显示为例:** 225 | 226 | - **window.innerHeight** **是浏览器可视区的高度;** 227 | - **document.body.scrollTop || document.documentElement.scrollTop** **是浏览器滚动的过的距离;** 228 | - **imgs.offsetTop** **是元素顶部距离文档顶部的高度(包括滚动条的距离);** 229 | - **内容达到显示区域的:img.offsetTop < window. innerHeight + document.body.scrollTop;** 230 | 231 | ## 16. z-index属性在什么情况下会失效 232 | 233 | **通常 z-index 的使用是在有两个重叠的标签,在一定的情况下控制其中一个在另一个的上方或者下方出现。z-index值越大就越是在上层。z-index元素的position属性需要是relative,absolute或是fixed。** 234 | 235 | **z-index属性在下列情况下会失效:** 236 | 237 | - **父元素position为relative时,子元素的z-index失效。解决:父元素position改为absolute或static;** 238 | - **元素没有设置position属性为非static属性。解决:设置该元素的position属性为relative,absolute或是fixed中的一种;** 239 | - **元素在设置z-index的同时还设置了float浮动。解决:float去除,改为display:inline-block;** 240 | 241 | 242 | 243 | ## 17. 两栏布局的实现 244 | 245 | 总结: 246 | 247 | > /* 第一种 左边元素固定并浮动,右边设置margin-left(margin-left设置为左边的宽度) */ 248 | 249 | > /* 第二种触发BFC ,左边元素固定并浮动,右overflow:hidden */ 250 | 251 | > /* 第三种利用绝对定位 */ margin-left 252 | 253 | > /* 第四种利用绝对定位 */ left 254 | 255 | > /* 第五种 flex布局 */ 256 | 257 | ```css 258 | .left{ 259 | width: 100px; 260 | height: 200px; 261 | background: red; 262 | float: left; 263 | } 264 | .right{ 265 | height: 300px; 266 | background: blue; 267 | overflow: hidden; 268 | } 269 | ``` 270 | 271 | - **利用flex布局,将左边元素设置为固定宽度200px,将右边的元素设置为flex:1。** 272 | 273 | ```css 274 | .outer { 275 | display: flex; 276 | height: 100px; 277 | } 278 | .left { 279 | width: 200px; 280 | background: tomato; 281 | } 282 | .right { 283 | flex: 1; 284 | background: gold; 285 | } 286 | ``` 287 | 288 | - **利用绝对定位,将父级元素设置为相对定位。左边元素设置为absolute定位,并且宽度设置为200px。将右边元素的margin-left的值设置为200px。** 289 | 290 | ```css 291 | .outer { 292 | position: relative; 293 | height: 100px; 294 | } 295 | .left { 296 | position: absolute; 297 | width: 200px; 298 | height: 100px; 299 | background: tomato; 300 | } 301 | .right { 302 | margin-left: 200px; 303 | background: gold; 304 | } 305 | ``` 306 | 307 | - **利用绝对定位,将父级元素设置为相对定位。左边元素宽度设置为200px,右边元素设置为绝对定位,左边定位为200px,其余方向定位为0。** 308 | 309 | ```css 310 | .outer { 311 | position: relative; 312 | height: 100px; 313 | } 314 | .left { 315 | width: 200px; 316 | background: tomato; 317 | } 318 | .right { 319 | position: absolute; 320 | top: 0; 321 | right: 0; 322 | bottom: 0; 323 | left: 200px; 324 | background: gold; 325 | } 326 | ``` 327 | 328 | ## 18. 三栏布局的实现 329 | 330 | 总结: 331 | 332 | > /* 第一种利用浮动,记住中间一栏必须放在最后 */ 333 | 334 | > /* 第二种利用绝对定位 */left 中间使用margin-left,margin-right right 335 | 336 | > /* 第三种方式 使用flex */ 中间一栏设置flex:1 337 | 338 | > /* 第四种方式 使用calc */ 原理也是flex布局,中间一栏设置 width:calc(100% - 200px) 339 | 340 | > /* 第五种圣杯布局,也可以叫做双飞翼布局 */ 中间一栏放在最上面 341 | 342 | **三栏布局一般指的是页面中一共有三栏,左右两栏宽度固定,中间自适应的布局,三栏布局的具体实现:** 343 | 344 | - **利用绝对定位,左右两栏设置为绝对定位,中间设置对应方向大小的margin的值。** 345 | 346 | ```css 347 | .outer { 348 | position: relative; 349 | height: 100px; 350 | } 351 | 352 | .left { 353 | position: absolute; 354 | width: 100px; 355 | height: 100px; 356 | background: tomato; 357 | } 358 | 359 | .right { 360 | position: absolute; 361 | top: 0; 362 | right: 0; 363 | width: 200px; 364 | height: 100px; 365 | background: gold; 366 | } 367 | 368 | .center { 369 | margin-left: 100px; 370 | margin-right: 200px; 371 | height: 100px; 372 | background: lightgreen; 373 | } 374 | ``` 375 | 376 | - **利用flex布局,左右两栏设置固定大小,中间一栏设置为flex:1。** 377 | 378 | ```css 379 | .outer { 380 | display: flex; 381 | height: 100px; 382 | } 383 | 384 | .left { 385 | width: 100px; 386 | background: tomato; 387 | } 388 | 389 | .right { 390 | width: 100px; 391 | background: gold; 392 | } 393 | 394 | .center { 395 | flex: 1; 396 | background: lightgreen; 397 | } 398 | ``` 399 | 400 | - **利用浮动,左右两栏设置固定大小,并设置对应方向的浮动。中间一栏设置左右两个方向的margin值,注意这种方式,中间一栏必须放到最后:** 401 | 402 | ```css 403 | .outer { 404 | height: 100px; 405 | } 406 | 407 | .left { 408 | float: left; 409 | width: 100px; 410 | height: 100px; 411 | background: tomato; 412 | } 413 | 414 | .right { 415 | float: right; 416 | width: 200px; 417 | height: 100px; 418 | background: gold; 419 | } 420 | 421 | .center { 422 | height: 100px; 423 | margin-left: 100px; 424 | margin-right: 200px; 425 | background: lightgreen; 426 | } 427 | ``` 428 | 429 | - **圣杯布局,利用浮动和负边距来实现。父级元素设置左右的 padding,三列均设置向左浮动,中间一列放在最前面,宽度设置为父级元素的宽度,因此后面两列都被挤到了下一行,通过设置 margin 负值将其移动到上一行,再利用相对定位,定位到两边。** 430 | 431 | ```css 432 | .outer { 433 | height: 100px; 434 | padding-left: 100px; 435 | padding-right: 200px; 436 | } 437 | 438 | .left { 439 | position: relative; 440 | left: -100px; 441 | 442 | float: left; 443 | margin-left: -100%; 444 | 445 | width: 100px; 446 | height: 100px; 447 | background: tomato; 448 | } 449 | 450 | .right { 451 | position: relative; 452 | left: 200px; 453 | 454 | float: right; 455 | margin-left: -200px; 456 | 457 | width: 200px; 458 | height: 100px; 459 | background: gold; 460 | } 461 | 462 | .center { 463 | float: left; 464 | 465 | width: 100%; 466 | height: 100px; 467 | background: lightgreen; 468 | } 469 | ``` 470 | 471 | - **双飞翼布局,双飞翼布局相对于圣杯布局来说,左右位置的保留是通过中间列的 margin 值来实现的,而不是通过父元素的 padding 来实现的。本质上来说,也是通过浮动和外边距负值来实现的。** 472 | 473 | ```css 474 | .outer { 475 | height: 100px; 476 | } 477 | 478 | .left { 479 | float: left; 480 | margin-left: -100%; 481 | 482 | width: 100px; 483 | height: 100px; 484 | background: tomato; 485 | } 486 | 487 | .right { 488 | float: left; 489 | margin-left: -200px; 490 | 491 | width: 200px; 492 | height: 100px; 493 | background: gold; 494 | } 495 | 496 | .wrapper { 497 | float: left; 498 | 499 | width: 100%; 500 | height: 100px; 501 | background: lightgreen; 502 | } 503 | 504 | .center { 505 | margin-left: 100px; 506 | margin-right: 200px; 507 | height: 100px; 508 | } 509 | ``` 510 | 511 | ## 19.水平垂直居中的实现 512 | 513 | 总结: 514 | 515 | > /* 居中元素定宽高 516 | 517 | > /* 第一种 position:absolute margin负值为元素的一半*/ 518 | 519 | ```css 520 | 设置position:absolute; top:50%;left:50%; margin-left:width/2,margin-top:height/2 521 | .element { 522 | position: absolute; 523 | top: 50%; 524 | left: 50%; 525 | width: 200px; /* 定宽 */ 526 | height: 200px; /* 定高 */ 527 | margin-top: -100px; /* 高度的一半 */ 528 | margin-left: -100px; /* 宽度的一半 */ 529 | } 530 | ``` 531 | 532 | > /* 第二种 absolute 和 margin:auto*/ 533 | 534 | ```css 535 | .element { 536 | position: absolute; 537 | top: 0; 538 | bottom: 0; 539 | left: 0; 540 | right: 0; 541 | margin: auto; 542 | width: 200px; /* 定宽 */ 543 | height: 200px; /* 定高 */ 544 | } 545 | ``` 546 | 547 | > /* 第三种 absolute 和 calc */ 548 | 549 | ```css 550 | .element { 551 | position: absolute; 552 | top: calc(50% - 100px); /* 50%减去高度的一半 */ 553 | left: calc(50% - 100px); /* 50%减去宽度的一半 */ 554 | width: 200px; /* 定宽 */ 555 | height: 200px; /* 定高 */ 556 | } 557 | ``` 558 | 559 | > /* 居中元素不定宽高 */ 560 | 561 | > /* 第一种 absolute 和 transform:translate */ 562 | 563 | ```css 564 | .element { 565 | position: absolute; 566 | top: 50%; 567 | left: 50%; 568 | transform: translate(-50%, -50%); 569 | } 570 | ``` 571 | 572 | > /* flex布局 */ 573 | 574 | > /* grid布局 */ 575 | 576 | ```css 577 | .wp{ 578 | display: grid; 579 | } 580 | .box{ 581 | align-self: center; 582 | justify-self: center; 583 | } 584 | ``` 585 | 586 | - **利用绝对定位,先将元素的左上角通过top:50%和left:50%定位到页面的中心,然后再通过translate来调整元素的中心点到页面的中心。该方法需要考虑浏览器兼容问题。** 587 | 588 | ```css 589 | .parent { 590 | position: relative; 591 | } 592 | 593 | .child { 594 | position: absolute; 595 | left: 50%; 596 | top: 50%; 597 | transform: translate(-50%,-50%); 598 | } 599 | ``` 600 | 601 | - **利用绝对定位,设置四个方向的值都为0,并将margin设置为auto,由于宽高固定,因此对应方向实现平分,可以实现水平和垂直方向上的居中。该方法适用于盒子有宽高的情况:** 602 | 603 | ```css 604 | .parent { 605 | position: relative; 606 | } 607 | 608 | .child { 609 | position: absolute; 610 | top: 0; 611 | bottom: 0; 612 | left: 0; 613 | right: 0; 614 | margin: auto; 615 | } 616 | ``` 617 | 618 | - **利用绝对定位,先将元素的左上角通过top:50%和left:50%定位到页面的中心,然后再通过margin负值来调整元素的中心点到页面的中心。该方法适用于盒子宽高已知的情况** 619 | 620 | ```css 621 | .parent { 622 | position: relative; 623 | } 624 | 625 | .child { 626 | position: absolute; 627 | top: 50%; 628 | left: 50%; 629 | margin-top: -50px; /* 自身 height 的一半 */ 630 | margin-left: -50px; /* 自身 width 的一半 */ 631 | } 632 | ``` 633 | 634 | - **使用flex布局,通过align-items:center和justify-content:center设置容器的垂直和水平方向上为居中对齐,然后它的子元素也可以实现垂直和水平的居中。该方法要考虑兼容的问题,该方法在移动端用的较多:** 635 | 636 | ```css 637 | .parent { 638 | display: flex; 639 | justify-content:center; 640 | align-items:center; 641 | } 642 | ``` 643 | 644 | ## 20. 对Flex布局的理解及其使用场景 645 | 646 | **Flex是FlexibleBox的缩写,意为"弹性布局",用来为盒状模型提供最大的灵活性。任何一个容器都可以指定为Flex布局。行内元素也可以使用Flex布局。注意,设为Flex布局以后,子元素的float、clear和vertical-align属性将失效。采用Flex布局的元素,称为Flex容器(flex container),简称"容器"。它的所有子元素自动成为容器成员,称为Flex项目(flex item),简称"项目"。容器默认存在两根轴:水平的主轴(main axis)和垂直的交叉轴(cross axis),项目默认沿水平主轴排列。** 647 | 648 | **以下6个属性设置在容器上:** 649 | 650 | - **flex-direction属性决定主轴的方向(即项目的排列方向)。** 651 | - **flex-wrap属性定义,如果一条轴线排不下,如何换行。** 652 | - **flex-flow属性是flex-direction属性和flex-wrap属性的简写形式,默认值为row nowrap。** 653 | - **justify-content属性定义了项目在主轴上的对齐方式。** 654 | - **align-items属性定义项目在交叉轴上如何对齐。** 655 | - **align-content属性定义了多根轴线的对齐方式。如果项目只有一根轴线,该属性不起作用。** 656 | 657 | **以下6个属性设置在项目上:** 658 | 659 | - **order属性定义项目的排列顺序。数值越小,排列越靠前,默认为0。** 660 | - **flex-grow属性定义项目的放大比例,默认为0,即如果存在剩余空间,也不放大。** 661 | - **flex-shrink属性定义了项目的缩小比例,默认为1,即如果空间不足,该项目将缩小。** 662 | - **flex-basis属性定义了在分配多余空间之前,项目占据的主轴空间。浏览器根据这个属性,计算主轴是否有多余空间。它的默认值为auto,即项目的本来大小。** 663 | - **flex属性是flex-grow,flex-shrink和flex-basis的简写,默认值为0 1 auto。** 664 | - **align-self属性允许单个项目有与其他项目不一样的对齐方式,可覆盖align-items属性。默认值为auto,表示继承父元素的align-items属性,如果没有父元素,则等同于stretch。** 665 | 666 | **简单来说:** 667 | 668 | **flex布局是CSS3新增的一种布局方式,可以通过将一个元素的display属性值设置为flex从而使它成为一个flex容器,它的所有子元素都会成为它的项目。一个容器默认有两条轴:一个是水平的主轴,一个是与主轴垂直的交叉轴。可以使用flex-direction来指定主轴的方向。可以使用justify-content来指定元素在主轴上的排列方式,使用align-items来指定元素在交叉轴上的排列方式。还可以使用flex-wrap来规定当一行排列不下时的换行方式。对于容器中的项目,可以使用order属性来指定项目的排列顺序,还可以使用flex-grow来指定当排列空间有剩余的时候,项目的放大比例,还可以使用flex-shrink来指定当排列空间不足时,项目的缩小比例。** 669 | 670 | ## 21. 为什么需要清除浮动?清除浮动的方式 671 | 672 | **浮动的定义:** **非IE浏览器下,容器不设高度且子元素浮动时,容器高度不能被内容撑开。 此时,内容会溢出到容器外面而影响布局。这种现象被称为浮动(溢出)。** 673 | 674 | **浮动的工作原理:** 675 | 676 | - **浮动元素脱离文档流,不占据空间(引起“高度塌陷”现象)** 677 | - **浮动元素碰到包含它的边框或者其他浮动元素的边框停留** 678 | 679 | **浮动元素可以左右移动,直到遇到另一个浮动元素或者遇到它外边缘的包含框。浮动框不属于文档流中的普通流,当元素浮动之后,不会影响块级元素的布局,只会影响内联元素布局。此时文档流中的普通流就会表现得该浮动框不存在一样的布局模式。当包含框的高度小于浮动框的时候,此时就会出现“高度塌陷”。** 680 | 681 | **浮动元素引起的问题?** 682 | 683 | - **父元素的高度无法被撑开,影响与父元素同级的元素** 684 | - **与浮动元素同级的非浮动元素会跟随其后** 685 | - **若浮动的元素不是第一个元素,则该元素之前的元素也要浮动,否则会影响页面的显示结构** 686 | 687 | **清除浮动的方式如下:** 688 | 689 | - **给父级div定义** **height属性** 690 | - **最后一个浮动元素之后添加一个空的div标签,并添加** **clear:both样式** 691 | - **包含浮动元素的父级标签添加** **overflow:hidden或者** **overflow:auto** 692 | - **使用 :after 伪元素。** 693 | 694 | ```css 695 | // 在style标签内加上 696 | .clear::after{ 697 | content: ''; 698 | clear: both; 699 | display: block; 700 | } 701 | ``` 702 | 703 | ## 22. 对BFC的理解,如何创建BFC 704 | 705 | - **Box: Box 是 CSS 布局的对象和基本单位,⼀个⻚⾯是由很多个 Box 组成的,这个Box就是我们所说的盒模型。** 706 | - **Formatting context:块级上下⽂格式化,它是⻚⾯中的⼀块渲染区域,并且有⼀套渲染规则,它决定了其⼦元素将如何定位,以及和其他元素的关系和相互作⽤。** 707 | 708 | **块格式化上下文(Block Formatting Context,BFC)是Web页面的可视化CSS渲染的一部分,是布局过程中生成块级盒子的区域,也是浮动元素与其他元素的交互限定区域。** 709 | 710 | **通俗来讲:BFC是一个独立的布局环境,可以理解为一个容器,在这个容器中按照一定规则进行物品摆放,并且不会影响其它环境中的物品。如果一个元素符合触发BFC的条件,则BFC中的元素布局不受外部影响。** 711 | 712 | **创建BFC的条件:** 713 | 714 | - **根元素:body;** 715 | - **元素设置浮动:float 除 none 以外的值;** 716 | - **元素设置绝对定位:position (absolute、fixed);** 717 | - **display 值为:inline-block、table-cell、table-caption、flex等;** 718 | - **overflow 值为:hidden、auto、scroll;** 719 | 720 | **BFC的特点:** 721 | 722 | - **垂直方向上,自上而下排列,和文档流的排列方式一致。** 723 | - **在BFC中上下相邻的两个容器的margin会重叠** 724 | - **计算BFC的高度时,需要计算浮动元素的高度** 725 | - **BFC区域不会与浮动的容器发生重叠** 726 | - **BFC是独立的容器,容器内部元素不会影响外部元素** 727 | - **每个元素的左margin值和容器的左border相接触** 728 | 729 | **BFC的作用:** 730 | 731 | - **解决margin的重叠问题:由于BFC是一个独立的区域,内部的元素和外部的元素互不影响,将两个元素变为两个BFC,就解决了margin重叠的问题。** 732 | - **解决高度塌陷的问题:在对子元素设置浮动后,父元素会发生高度塌陷,也就是父元素的高度变为0。解决这个问题,只需要把父元素变成一个BFC。常用的办法是给父元素设置** **overflow:hidden。** 733 | - **创建自适应两栏布局:可以用来创建自适应两栏布局:左边的宽度固定,右边的宽度自适应。** 734 | 735 | ```css 736 | .left{ 737 | width: 100px; 738 | height: 200px; 739 | background: red; 740 | float: left; 741 | } 742 | .right{ 743 | height: 300px; 744 | background: blue; 745 | overflow: hidden; 746 | } 747 | 748 |
749 |
750 | ``` 751 | 752 | **左侧设置** **float:left,右侧设置** **overflow: hidden。这样右边就触发了BFC,BFC的区域不会与浮动元素发生重叠,所以两侧就不会发生重叠,实现了自适应两栏布局。** 753 | 754 | ## 23. 什么是margin重叠问题?如何解决? 755 | 756 | **问题描述:** 757 | 758 | **两个块级元素的上外边距和下外边距可能会合并(折叠)为一个外边距,其大小会取其中外边距值大的那个,这种行为就是外边距折叠。需要注意的是,浮动的元素和绝对定位这种脱离文档流的元素的外边距不会折叠。重叠只会出现在垂直方向。** 759 | 760 | **计算原则:** 761 | 762 | **折叠合并后外边距的计算原则如下:** 763 | 764 | - **如果两者都是正数,那么就取最大者** 765 | - **如果是一正一负,就会正值减去负值的绝对值** 766 | - **两个都是负值时,用0减去两个中绝对值大的那个** 767 | 768 | **解决办法:** 769 | 770 | **对于折叠的情况,主要有两种:兄弟之间重叠和父子之间重叠** 771 | 772 | **(1)兄弟之间重叠** 773 | 774 | - **底部元素变为行内盒子:display: inline-block** 775 | - **底部元素设置浮动:float** 776 | - **底部元素的position的值为** **absolute/fixed** 777 | 778 | **(2)父子之间重叠** 779 | 780 | - **父元素加入:overflow: hidden** 781 | - **父元素添加透明边框:border:1px solid transparent** 782 | - **子元素变为行内盒子:display: inline-block** 783 | - **子元素加入浮动属性或定位** 784 | 785 | ## 24. position的属性有哪些,区别是什么 786 | 787 | **sition有以下属性值:** 788 | 789 | | **属性值** | **概述** | 790 | | ---------- | :----------------------------------------------------------: | 791 | | absolute | 生成绝对定位的元素,相对于static定位以外的一个父元素进行定位。元素的位置通过left、top、right、bottom属性进行规定。 | 792 | | relative | 生成相对定位的元素,相对于其原来的位置进行定位。元素的位置通过left、top、right、bottom属性进行规定。 | 793 | | fixed | 生成绝对定位的元素,指定元素相对于屏幕视⼝(viewport)的位置来指定元素位置。元素的位置在屏幕滚动时不会改变,⽐如回到顶部的按钮⼀般都是⽤此定位⽅式。 | 794 | | static | 默认值,没有定位,元素出现在正常的文档流中,会忽略 top, bottom, left, right 或者 z-index 声明,块级元素从上往下纵向排布,⾏级元素从左向右排列。 | 795 | | inherit | 规定从父元素继承position属性的值 | 796 | 797 | **前面三者的定位方式如下:** 798 | 799 | **relative:元素的定位永远是相对于元素自身位置的,和其他元素没关系,也不会影响其他元素。** 800 | 801 | **fixed:元素的定位是相对于 window (或者 iframe)边界的,和其他元素没有关系。但是它具有破坏性,会导致其他元素位置的变化。** 802 | 803 | **absolute:元素的定位相对于前两者要复杂许多。如果为 absolute 设置了 top、left,浏览器会根据什么去确定它的纵向和横向的偏移量呢?答案是浏览器会递归查找该元素的所有父元素,如果找到一个设置了** **position:relative/absolute/fixed**的元素,就以该元素为基准定位,如果没找到,就以浏览器边界定位。如下两个图所示: 804 | 805 | ## 25. display、float、position的关系 806 | 807 | **(1)首先判断display属性是否为none,如果为none,则position和float属性的值不影响元素最后的表现。** 808 | 809 | **(2)然后判断position的值是否为absolute或者fixed,如果是,则float属性失效,并且display的值应该被设置为table或者block,具体转换需要看初始转换值。** 810 | 811 | **(3)如果position的值不为absolute或者fixed,则判断float属性的值是否为none,如果不是,则display的值则按上面的规则转换。注意,如果position的值为relative并且float属性的值存在,则relative相对于浮动后的最终位置定位。** 812 | 813 | **(4)如果float的值为none,则判断元素是否为根元素,如果是根元素则display属性按照上面的规则转换,如果不是,则保持指定的display属性值不变。** 814 | 815 | **总的来说,可以把它看作是一个类似优先级的机制,"position:absolute"和"position:fixed"优先级最高,有它存在的时候,浮动不起作用,'display'的值也需要调整;其次,元素的'float'特性的值不是"none"的时候或者它是根元素的时候,调整'display'的值;最后,非根元素,并且非浮动元素,并且非绝对定位的元素,'display'特性值同设置值。** 816 | 817 | ## 26、css怎么实现三角形? 818 | 819 | **CSS绘制三角形主要用到的是border属性,也就是边框。** 820 | 821 | **平时在给盒子设置边框时,往往都设置很窄,就可能误以为边框是由矩形组成的。实际上,border属性是右三角形组成的,下面看一个例子:** 822 | 823 | ```css 824 | div { 825 | width: 0; 826 | height: 0; 827 | border: 100px solid; 828 | border-color: orange blue red green; 829 | } 830 | ``` 831 | 832 | **将元素的长宽都设置为0,显示出来的效果是这样的:** 833 | 834 | **所以可以根据border这个特性来绘制三角形:** 835 | 836 | **(1)三角1** 837 | 838 | ```css 839 | div { 840 | width: 0; 841 | height: 0; 842 | border-top: 50px solid red; 843 | border-right: 50px solid transparent; 844 | border-left: 50px solid transparent; 845 | } 846 | ``` 847 | 848 | **(2)三角2** 849 | 850 | ```css 851 | div { 852 | width: 0; 853 | height: 0; 854 | border-bottom: 50px solid red; 855 | border-right: 50px solid transparent; 856 | border-left: 50px solid transparent; 857 | } 858 | ``` 859 | 860 | **(3)三角3** 861 | 862 | ```css 863 | div { 864 | width: 0; 865 | height: 0; 866 | border-left: 50px solid red; 867 | border-top: 50px solid transparent; 868 | border-bottom: 50px solid transparent; 869 | } 870 | ``` 871 | 872 | **(4)三角4** 873 | 874 | ```css 875 | div { 876 | width: 0; 877 | height: 0; 878 | border-right: 50px solid red; 879 | border-top: 50px solid transparent; 880 | border-bottom: 50px solid transparent; 881 | } 882 | ``` 883 | 884 | **(5)三角5** 885 | 886 | ```css 887 | div { 888 | width: 0; 889 | height: 0; 890 | border-top: 100px solid red; 891 | border-right: 100px solid transparent; 892 | } 893 | ``` 894 | 895 | **还有很多,就不一一实现了,总体的原则就是通过上下左右边框来控制三角形的方向,用边框的宽度比来控制三角形的角度。** 896 | 897 | ## 27. 实现一个扇形 898 | 899 | **用CSS实现扇形的思路和三角形基本一致,就是多了一个圆角的样式,实现一个90°的扇形:** 900 | 901 | ```css 902 | div{ 903 | border: 100px solid transparent; 904 | width: 0; 905 | height: 0; 906 | border-radius: 100px; 907 | border-top-color: red; 908 | } 909 | ``` 910 | 911 | ## 28. 实现一个宽高自适应的正方形 912 | 913 | - **利用vw来实现:** 914 | 915 | ```css 916 | .square { 917 | width: 10%; 918 | height: 10vw; 919 | background: tomato; 920 | } 921 | ``` 922 | 923 | - **利用元素的margin/padding百分比是相对父元素width的性质来实现:** 924 | 925 | ```css 926 | .square { 927 | width: 20%; 928 | height: 0; 929 | padding-top: 20%; 930 | background: orange; 931 | } 932 | ``` 933 | 934 | - **利用子元素的margin-top的值来实现:** 935 | 936 | ```css 937 | .square { 938 | width: 30%; 939 | overflow: hidden; 940 | background: yellow; 941 | } 942 | .square::after { 943 | content: ''; 944 | display: block; 945 | margin-top: 100%; 946 | } 947 | ``` 948 | 949 | ## 29. 画一条0.5px的线 950 | 951 | - **采用transform: scale()的方式,该方法用来定义元素的2D 缩放转换:** 952 | 953 | ```css 954 | transform: scale(0.5,0.5); 955 | ``` 956 | 957 | - **采用meta viewport的方式** 958 | 959 | ```css 960 | 961 | ``` 962 | 963 | **这样就能缩放到原来的0.5倍,如果是1px那么就会变成0.5px。viewport只针对于移动端,只在移动端上才能看到效果** 964 | 965 | ## 30. 设置小于12px的字体 966 | 967 | **在谷歌下css设置字体大小为12px及以下时,显示都是一样大小,都是默认12px。** 968 | 969 | **解决方法:** 970 | 971 | - **使用Webkit的内核的-webkit-text-size-adjust的私有CSS属性来解决,只要加了-webkit-text-size-adjust:none;字体大小就不受限制了。但是chrome更新到27版本之后就不可以用了。所以高版本chrome谷歌浏览器已经不再支持-webkit-text-size-adjust样式,所以要使用时候慎用。** 972 | - **使用css3的transform缩放属性-webkit-transform:scale(0.5); 注意-webkit-transform:scale(0.75);收缩的是整个元素的大小,这时候,如果是内联元素,必须要将内联元素转换成块元素,可以使用display:block/inline-block/...;** 973 | - **使用图片:如果是内容固定不变情况下,使用将小于12px文字内容切出做图片,这样不影响兼容也不影响美观。** 974 | 975 | ## 31. 如何解决 1px 问题? 976 | 977 | **直接把 1px 改成 1/devicePixelRatio 后的值,这是目前为止最简单的一种方法。这种方法的缺陷在于兼容性不行,IOS 系统需要8及以上的版本,安卓系统则直接不兼容。** 978 | 979 | **思路二:伪元素先放大后缩小** 980 | 981 | **这个方法的可行性会更高,兼容性也更好。唯一的缺点是代码会变多。** 982 | 983 | **思路是先放大、后缩小:在目标元素的后面追加一个 ::after 伪元素,让这个元素布局为 absolute 之后、整个伸展开铺在目标元素上,然后把它的宽和高都设置为目标元素的两倍,border值设为 1px。接着借助 CSS 动画特效中的放缩能力,把整个伪元素缩小为原来的 50%。此时,伪元素的宽高刚好可以和原有的目标元素对齐,而 border 也缩小为了 1px 的二分之一,间接地实现了 0.5px 的效果。** 984 | 985 | **代码如下:** 986 | 987 | ```css 988 | #container[data-device="2"] { 989 | position: relative; 990 | } 991 | #container[data-device="2"]::after{ 992 | position:absolute; 993 | top: 0; 994 | left: 0; 995 | width: 200%; 996 | height: 200%; 997 | content:""; 998 | transform: scale(0.5); 999 | transform-origin: left top; 1000 | box-sizing: border-box; 1001 | border: 1px solid #333; 1002 | } 1003 | } 1004 | ``` 1005 | 1006 | **思路三:viewport 缩放来解决** 1007 | 1008 | **这个思路就是对 meta 标签里几个关键属性下手:** 1009 | 1010 | 1011 | 1012 | **这里针对像素比为2的页面,把整个页面缩放为了原来的1/2大小。这样,本来占用2个物理像素的 1px 样式,现在占用的就是标准的一个物理像素。根据像素比的不同,这个缩放比例可以被计算为不同的值,用 js 代码实现如下:** 1013 | 1014 | ```js 1015 | const scale = 1 / window.devicePixelRatio; 1016 | // 这里 metaEl 指的是 meta 标签对应的 Dom 1017 | metaEl.setAttribute('content', `width=device-width,user-scalable=no,initial-scale=${scale},maximum-scale=${scale},minimum-scale=${scale}`); 1018 | ``` 1019 | 1020 | **这样解决了,但这样做的副作用也很大,整个页面被缩放了。这时 1px 已经被处理成物理像素大小,这样的大小在手机上显示边框很合适。但是,一些原本不需要被缩小的内容,比如文字、图片等,也被无差别缩小掉了。** 1021 | 1022 | ## 32.CSS 中可继承与不可继承属性有哪些? 1023 | 1024 | 这里其实考察的是可继承与不可继承属性在表现上的一些差异。 1025 | 一些常见的可继承属性包括: 1026 | 1027 | - 字体相关属性(font、font-size、font-weight等) 1028 | 1029 | - 文本相关属性(color、text-align、line-height等) 1030 | 1031 | - 元素可见性相关属性(visibility) 1032 | 1033 | - 元素间距属性(margin、padding) 1034 | 1035 | 剩下的都可理解为是不可继承属性,它们的特性: 1036 | 1037 | - 不可继承属性不会被子元素继承,子元素不会自动继承父元素上设置的样式属性。 1038 | 1039 | - 子元素需要通过自身的样式来显式地设置不可继承属性。 1040 | 1041 | - 子元素的样式会独立于父元素的样式,不会受到父元素设置的影响。 1042 | 1043 | 一些常见的可继承属性包括: 1044 | 1045 | - 字体相关属性(font、font-size、font-weight等) 1046 | 1047 | - 文本相关属性(color、text-align、line-height等) 1048 | 1049 | - 元素可见性相关属性(visibility) 1050 | 1051 | - 元素间距属性(margin、padding) 1052 | 1053 | 一些常见的不可继承属性包括: 1054 | 1055 | - 盒模型属性(width、height、border、margin、padding等) 1056 | 1057 | - 定位属性(position、top、right、bottom、left等) 1058 | 1059 | - 背景相关属性(background、background-color等) 1060 | 1061 | - 清除浮动的属性(clear) 1062 | 1063 | ## 33.line-height: 100% 和 line-height: 1 有什么不一样? 1064 | 1065 | 常见细节题目,CSS 中存在很多取值即可以是绝对值,也可以是百分比的情况。 1066 | 1067 | 1. line-height: 100% 表示行高与当前字体的实际大小相等,即行高与字体大小相同。 1068 | 2. line-height: 1 表示行高为字体大小的倍数,具体倍数取决于字体的特性和浏览器的默认样式。 1069 | 1070 | 基于 line-height,可以自己再去梳理,padding、margin,当取值为绝对值(px) 或者是百分比(%) 时的异同。 1071 | 1072 | ## 34.如果在伪元素中不写 content 会发生什么 1073 | 1074 | 如果在伪元素中不写 content,那么该伪元素将不会被渲染或显示在页面上。content 属性是伪元素的必需属性,它定义了伪元素的内容。 1075 | 1076 | ## 35.flex: shrink 和 flex-grow 的默认值是多少?它们的作用是什么?flex: 1 表示什么? 1077 | 1078 | flex: shrink 的默认值是 1,flex-grow 的默认值是 0。 1079 | 1080 | - flex-shrink 定义了项目在空间不足时的收缩能力,如果父容器的空间不足,它会根据 flex-shrink 的比例进行收缩,默认情况下等比例收缩。 1081 | - flex-grow 定义了项目在空间有剩余时的扩展能力,如果父容器有多余的空间,它会根据 flex-grow 的比例进行扩展,默认情况下不进行扩展。 1082 | 1083 | 除了auto (1 1 auto) 和 none (0 0 auto)这两个快捷值外,还有以下设置方式: 1084 | 当 flex 取值为一个非负数字,则该数字为 flex-grow 值,flex-shrink 取 1,flex-basis 取 0%,如下是等同的: 1085 | 1086 | ```css 1087 | .item {flex: 1;} 1088 | .item { 1089 | flex-grow: 1; 1090 | flex-shrink: 1; 1091 | flex-basis: 0%; 1092 | } 1093 | ``` 1094 | 1095 | 当 flex 取值为 0 时,对应的三个值分别为 0 1 0% 1096 | 1097 | ```css 1098 | .item {flex: 0;} 1099 | .item { 1100 | flex-grow: 0; 1101 | flex-shrink: 1; 1102 | flex-basis: 0%; 1103 | } 1104 | ``` 1105 | 1106 | 当 flex 取值为一个长度或百分比,则视为 flex-basis 值,flex-grow 取 1,flex-shrink 取 1,有如下等同情况(注意 0% 是一个百分比而不是一个非负数字) 1107 | 1108 | ```css 1109 | .item-1 {flex: 0%;} 1110 | .item-1 { 1111 | flex-grow: 1; 1112 | flex-shrink: 1; 1113 | flex-basis: 0%; 1114 | } 1115 | 1116 | .item-2 {flex: 24px;} 1117 | .item-2 { 1118 | flex-grow: 1; 1119 | flex-shrink: 1; 1120 | flex-basis: 24px; 1121 | } 1122 | ``` 1123 | 1124 | 当 flex 取值为两个非负数字,则分别视为 flex-grow 和 flex-shrink 的值,flex-basis 取 0%,如下是等同的: 1125 | 1126 | ```css 1127 | .item {flex: 2 3;} 1128 | .item { 1129 | flex-grow: 2; 1130 | flex-shrink: 3; 1131 | flex-basis: 0%; 1132 | } 1133 | ``` 1134 | 1135 | ## 36.如何快速选取同一批兄弟元素的偶数序号元素。 1136 | 1137 | 这里本质是考察各种 CSS 选择器。这里需要使用 even 关键字。 1138 | 1139 | ```css 1140 | /* 从 1 开始计数,选取偶数序号的兄弟元素 */ 1141 | :nth-child(even) { 1142 | /* 添加样式 */ 1143 | } 1144 | ``` 1145 | 1146 | 除此之外,你需要尽可能的了解其他 CSS 选择器,以达到快速选择各种不同的选择器。 1147 | 可以尝试挑战这个 -- https://flukeout.github.io/#。 1148 | 1149 | ## 37.CSS 中是否存在父选择器?其背后的原因是什么? 1150 | 1151 | 1. 伪类选择器 :focus-within: 1152 | 1153 | 1. 这个属性有点类似 Javascript 的事件冒泡,从可获焦元素开始一直冒泡到根元素 html,都可以接收触发 :focus-within 事件,类似下面这个简单的例子这样: 1154 | 2. 子元素的 :focus 状态触发,可以同时触发所有父元素的 :focus-within 伪类,以此变相实现父选择器的功能。当然,这种方法限制还是很大的。 1155 | 1156 | 2. 伪类选择器 :has() 1157 | 1158 | 1. :has 伪类接受一个选择器组作为参数,该参数相对于该元素的 [:scope](https://developer.mozilla.org/zh-CN/docs/Web/CSS/:scope) 至少匹配一个元素。 1159 | 2. 我们通过 div:has(.g-test-has) 选择器,意思是,选择 div 下存在 class 为 .g-test-has 的 div 元素。 1160 | 注意,这里选择的不是 :has() 内包裹的选择器选中的元素,而是使用 :has() 伪类的宿主元素被选中。 1161 | 1162 | > 在之前,是没有父选择器的!这个问题的答案和“为何CSS相邻兄弟选择器只支持后面的元素,而不支持前面的兄弟元素?”是一样的。 1163 | > 浏览器解析HTML文档,是从前往后,由外及里的。所以,我们时常会看到页面先出现头部然后主体内容再出现的加载情况。 1164 | > 但是,如果CSS支持了父选择器,那就必须要页面所有子元素加载完毕才能渲染HTML文档,因为所谓“父选择器”,就是后代元素影响祖先元素,如果后代元素还没加载处理,如何影响祖先元素的样式?于是,网页渲染呈现速度就会大大减慢,浏览器会出现长时间的白板。加载多少 HTML 就可以渲染多少 HTML,在网速不是很快的时候,就显得尤为的必要。如果支持父选择器,则整个文档不能有阻塞,页面的可访问性则要大大降低。 1165 | 1166 | ## 38.说一下宫格布局? 1167 | 1168 | > div{ 1169 | > display: grid;/*指定一个容器采用网格布局。*/ 1170 | > display: inline-grid;/*该元素内部采用网格布局*/ 1171 | > } 1172 | 1173 | 默认情况下,容器元素都是块级元素,但也可以设成行内元素。 1174 | 1175 | **注意:** 1176 | 1177 | 设为网格布局以后,容器子元素(项目)的float、display: inline-block、display: table-cell、vertical-align和column-*等设置都将失效。 1178 | 1179 | **一、容器属性(写在父级容器中)(以3x3的网格举例)** 1180 | 1181 | 1.设置列columns 行的高度rows 1182 | 1183 | > ​ grid-template-columns: 100px 100px 100px; 1184 | > 1185 | > ​ grid-template-rows: 100px 100px 100px; 1186 | 1187 | 2.repeat repeat()函数 1:重复值 2:重复模式 1188 | 1189 | ​ 参数1:重复次数 参数2:像素值 1190 | 1191 | > ​ grid-template-columns: repeat(2,100px 200px); 1192 | > 1193 | > ​ grid-template-rows: repeat(2,100px 200px); 1194 | 1195 | 3.fr 关键字 通过关键字划分比例 1196 | 1197 | > ​ grid-template-rows: repeat(2,1fr 2fr); 1198 | > 1199 | > ​ grid-template-columns: repeat(2,1fr 2fr); 1200 | 1201 | 4.auto-fit 自动填充每一行 或 每一列 1202 | 1203 | > grid-template-columns: repeat(auto-fit,120px); 1204 | 1205 | 5.minmax(最小值 最大值)函数 1206 | 1207 | > grid-template-columns: 1fr minmax(200px 1fr) 1fr; 1208 | > 1209 | > grid-template-rows: repeat(3, 1fr); 1210 | 1211 | 6.auto关键字 自动适应窗口大小(没有最大最小值) 1212 | 1213 | > grid-template-columns: 100px auto 100px; 1214 | 1215 | 7. grid-template-columns 网格线 [c1] ~[c4] 中括号内是网格线的名字 1216 | 1217 | > grid-template-columns: [c1] 100px [c2] auto [c3] 100px [c4]; 1218 | 1219 | 8. gap 间隙 1220 | 1221 | > ​ /* 列间距 */ 1222 | > 1223 | > ​ /* column-gap: 10px; */ 1224 | > 1225 | > ​ /*行间距 */ 1226 | > 1227 | > ​ /* row-gap: 10px; */ 1228 | > 1229 | > ​ /*合并写法: gap:行间距 列间距 */ 1230 | > 1231 | > ​ gap:10px 20px; 1232 | 1233 | 9.area 区域:代表单个或者多个单元格,单引号内分别是单元格的名字(3x3的网格,九个名字) 1234 | 1235 | ​ 别名一样相当于合并啊,但是要和grid-area一起用,将元素放进去 1236 | 1237 | > grid-template-areas: 'a b c ' 'h j k' 'd e f '; 1238 | 1239 | 10 grid-auto-flow 项目的放置顺序:默认横向排列 column竖着排列 1240 | 1241 | > grid-auto-flow: column; 1242 | 1243 | 11.row dense水平方向填充 column dense垂直方向填充 1244 | 1245 | > grid-auto-flow: row dense; 1246 | 1247 | 12.缩小内容宽高来使得内容居中(单元格的内容)(宽度和高度只有内容大小) 1248 | 1249 | ​ place-items(合并写法) 1250 | 1251 | > ​ justify-items: center; 1252 | > 1253 | > ​ align-items: center; 1254 | > 1255 | > ​ /* place-items: center; */ 1256 | 1257 | 13.设置内容位置(居中)(容器内容 包括间隙) 1258 | 1259 | > ​ justify-content: center; 1260 | > 1261 | > ​ align-content: center; 1262 | > 1263 | > ​ /* place-content: center; */ 1264 | 1265 | **二、项目属性(写在项目样式中)** 1266 | 1267 | 1.用网格线定义单元格列数开始的位置和结束的位置 1268 | 1269 | 举例:网格线 列 从第一根开始 第三根结束 1270 | 1271 | > ​ grid-column-start: 1; 1272 | > 1273 | > ​ grid-column-end: 3; 1274 | > 1275 | > ​ /* grid-column: 1 / 3; */ 合并写法 1276 | > 1277 | > /* 合并:grid-area:行的开始/列的开始/行的结束/列的结束 */ 1278 | 1279 | 2. 将该单元格的内容移动到名字为j 的单元格 其他的往前移 1280 | 1281 | > grid-area: j; 1282 | 1283 | 3.该项目自己的对齐方式:水平居中 垂直居中 1284 | 1285 | > ​ justify-self: center; 1286 | > 1287 | > ​ align-self: center; 1288 | -------------------------------------------------------------------------------- /HTML+CSS面试题/代码/0.5px的线.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 35 | 36 | 37 | 38 |
39 |
40 | 41 | 42 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /HTML+CSS面试题/代码/css怎么实现三角形.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 41 | 42 | 43 | 44 |
45 |
46 |
47 |
48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /HTML+CSS面试题/代码/三栏布局的实现.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 9 | 17 | 18 | 19 | 20 | 25 | 26 | 31 | 32 | 37 | 38 |
39 |
40 |
content
41 |
42 |
43 |
44 |
45 | 46 | 107 | 108 | 109 | -------------------------------------------------------------------------------- /HTML+CSS面试题/代码/两栏布局的实现.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 9 | 17 | 18 | 19 |
20 |
21 |
22 |
23 | 24 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /HTML+CSS面试题/代码/实现一个宽高自适应的正方形.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 34 | 35 | 36 | 37 |
38 |
39 |
40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /HTML+CSS面试题/代码/实现一个扇形.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 9 | 18 | 19 | 20 |
21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /HTML+CSS面试题/代码/宫格布局.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 37 | 38 | 39 | 40 |
41 |
1
42 |
2
43 |
3
44 |
4
45 |
5
46 |
6
47 |
7
48 |
8
49 |
9
50 |
51 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /HTML+CSS面试题/代码/懒加载的实现原理.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /HTML+CSS面试题/代码/水平垂直居中的实现.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 9 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /JS详解面试题/1.7手写.js: -------------------------------------------------------------------------------- 1 | // 手写call 2 | 3 | Function.prototype.myCall = function (ctx, ...args) { 4 | if (typeof this !== "function") return 5 | ctx = ctx || window 6 | const fn = Symbol() 7 | ctx[fn] = this 8 | const result = ctx[fn](...args) 9 | delete ctx[fn] 10 | return result 11 | } 12 | Function.prototype.myApply = function (ctx, args) { 13 | if (typeof this !== "function") return 14 | ctx = ctx || window 15 | const fn = Symbol() 16 | ctx[fn] = this 17 | ctx[fn](...args) 18 | delete ctx[fn] 19 | } 20 | Function.prototype.myBind = function (ctx, ...args1) { 21 | if (typeof this !== "function") return 22 | const fn = this 23 | return function (...args2) { 24 | const allArgs = [...args1, ...args2] 25 | if (new.target) { 26 | // this instanceof fn 判断是否是new 构造函数调用 27 | return new fn(...allArgs) 28 | } else { 29 | return fn.apply(ctx, allArgs) 30 | } 31 | } 32 | } 33 | function greet(greeting, punctuation) { 34 | return `${greeting} ${this.name}${punctuation}` 35 | } 36 | 37 | const person2 = { name: "Alice" } 38 | 39 | const boundFunc = greet.myBind(person2, "Hello") 40 | console.log(boundFunc("!")) // 输出:'Hello Alice!' 41 | 42 | const newObj = new boundFunc("!!!") 43 | console.log(newObj) // 44 | const newObj1 = new (greet.bind(person2, "Hello"))("111") 45 | console.log(newObj1) 46 | 47 | // 1.原型链继承 2.构造函数继承 3.组合继承 4.原型式继承 5.寄生式继承 6.寄生式组合继承 7.class的extends 48 | function Person(name) { 49 | this.name = name 50 | } 51 | Person.prototype.sayName = function () { 52 | console.log("this.name", this.name) 53 | } 54 | 55 | function Child(name, age) { 56 | Person.call(this, name) 57 | this.age = age 58 | } 59 | 60 | Child.prototype = Object.create(Person.prototype) 61 | Child.prototype.constructor = Child 62 | 63 | Child.prototype.sayAge = function () { 64 | console.log("this.age", this.age) 65 | } 66 | 67 | const child1 = new Child("Tom", 18) 68 | child1.sayAge() 69 | child1.sayName() 70 | 71 | function myInstanceof(left, right) { 72 | let proto = Object.getPrototypeOf(left) 73 | let prototype = right.prototype 74 | while (true) { 75 | if (!proto) return false 76 | if (proto === prototype) return true 77 | proto = Object.getPrototypeOf(proto) 78 | } 79 | } 80 | 81 | function myNew(ctx, ...args) { 82 | if (typeof ctx !== "function") return 83 | let obj = {} 84 | obj.prototype = Object.create(ctx.prototype) 85 | const res = ctx.apply(obj, args) 86 | if (res && (typeof res !== "object" || typeof res === "function")) return res 87 | return obj 88 | } 89 | 90 | function memorize(fn) { 91 | const cache = {} 92 | function foo(args) { 93 | const key = JSON.stringify(args) 94 | let result = cache[key] 95 | if (!result) { 96 | cache[key] = args 97 | result = fn(args) 98 | } 99 | return { cache, result } 100 | } 101 | foo.cache = cache 102 | return foo 103 | } 104 | 105 | function add(a) { 106 | return a + 1 107 | } 108 | 109 | const added = memorize(add) 110 | console.log("", added(1)) 111 | console.log("", added(2)) 112 | console.log("", added(3)) 113 | console.log("", added(4)) 114 | 115 | function debounce(fn, wait) { 116 | let timer = null 117 | return function (...args) { 118 | let ctx = this 119 | if (timer) { 120 | clearTimeout(timer) 121 | timer = null 122 | } 123 | timer = setTimeout(() => { 124 | fn.apply(ctx, args) 125 | }, wait) 126 | } 127 | } 128 | 129 | function throttle(fn, wait) { 130 | let timer = null 131 | return function (...args) { 132 | let ctx = this 133 | if (!timer) { 134 | timer = setTimeout(() => { 135 | fn.apply(ctx, args) 136 | timer = null 137 | }, wait) 138 | } 139 | } 140 | } 141 | 142 | function throttle(fn, wait) { 143 | let curTime = Date.now() 144 | return function (...args) { 145 | let ctx = this 146 | const nowTime = Date.now() 147 | if (nowTime - curTime >= wait) { 148 | curTime = Date.now() 149 | fn.apply(ctx, args) 150 | } 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /JS详解面试题/JS执行.js: -------------------------------------------------------------------------------- 1 | // 2、实现寄生组合继承 2 | // 定义一个父类 3 | function Parent(name) { 4 | this.name = name 5 | } 6 | Parent.prototype.sayName = function () { 7 | console.log(this.name) 8 | } 9 | 10 | // 定义一个子类,通过调用其父类的构造函数,实现属性的继承 11 | function Child(name, age) { 12 | Parent.call(this, name) 13 | this.age = age 14 | } 15 | // 这一行创建了一个新的对象,该对象的原型指向了父类 Parent 的原型对象。这样做的目的是让子类 Child 的原型链能够访问到父类 Parent 的原型上的属性和方法 16 | Child.prototype = Object.create(Parent.prototype) 17 | Child.prototype.constructor = Child // 这一行将子类 Child 的原型对象的构造函数指向了子类自身,这样在创建子类实例时就能正确地调用子类的构造函数。 18 | 19 | // 在子类的原型上添加子类自己的方法; 20 | Child.prototype.sayAge = function () { 21 | console.log(this.age) 22 | } 23 | // 测试 24 | var child1 = new Child("Tom", 18) 25 | child1.sayAge() 26 | child1.sayName() 27 | 28 | // 3.instanceof的原理 29 | function myInstanceOf(left, right) { 30 | let proto = Object.getPrototypeOf(left) // 获取左边对象的原型 31 | let prototype = right.prototype 32 | while (true) { 33 | if (!proto) return false 34 | if (proto === prototype) return true 35 | proto = Object.getPrototypeOf(proto) 36 | } 37 | } 38 | // 4.new操作符的原理 39 | 40 | function myNew() { 41 | const constructor = [...arguments][0] 42 | if (typeof constructor !== "function") return 43 | const obj = Object.create(constructor.prototype) 44 | const res = constructor.apply(obj, [...arguments].slice(1)) 45 | if (res && (typeof res !== "object" || typeof res === "function")) return res 46 | return obj 47 | } 48 | function Fn(obj) { 49 | this.obj = obj 50 | } 51 | let obj = myNew(Fn, "222") 52 | console.log(obj) 53 | 54 | // this指向问题 55 | const obj1 = { 56 | text: 1, 57 | fn: function () { 58 | return this.text 59 | }, 60 | } 61 | 62 | const obj2 = { 63 | text: 2, 64 | fn: function () { 65 | return obj1.fn() 66 | }, 67 | } 68 | 69 | const obj3 = { 70 | text: 3, 71 | fn: function () { 72 | var fn = obj1.fn 73 | return fn() 74 | }, 75 | // fn:obj1.fn 这样就会打印3 76 | } 77 | 78 | console.log(obj1.fn()) 79 | console.log(obj2.fn()) 80 | console.log(obj3.fn()) 81 | 82 | let a = {} 83 | let fn = function () { 84 | console.log(this) 85 | } 86 | fn.bind().bind(a)() 87 | 88 | // 缓存函数(备忘模式) 89 | function memorize(fn) { 90 | const cache = {} 91 | function memorized(...args) { 92 | const key = JSON.stringify(args) 93 | if (cache[key] !== undefined) { 94 | return { result: cache[key], cache } 95 | } else { 96 | const result = fn.apply(fn, args) 97 | cache[key] = result 98 | return { result, cache } 99 | } 100 | } 101 | memorized.cache = cache // 将缓存对象作为属性添加到返回的函数上 102 | return memorized 103 | } 104 | 105 | function add(a) { 106 | return a + 1 107 | } 108 | 109 | const adder = memorize(add) 110 | 111 | console.log(adder(1)) // 输出: { result: 2, cache: { '[1]': 2 } } 112 | console.log(adder(1)) // 输出: { result: 2, cache: { '[1]': 2 } } 113 | console.log(adder(2)) // 输出: { result: 3, cache: { '[1]': 2, '[2]': 3 } } 114 | console.log(adder(3)) // 输出: { result: 4, cache: { '[1]': 2, '[2]': 3, '[3]': 4 } } 115 | 116 | // call的实现原理 117 | Function.prototype.myCall = function (context, ...args) { 118 | if (typeof this !== "function") return 119 | context = context || window 120 | const fn = Symbol("fn") 121 | context[fn] = this 122 | const result = context[fn](...args) 123 | delete context[fn] 124 | return result 125 | } 126 | // 测试代码 127 | function greet(name) { 128 | return `Hello, ${name}! I'm ${this.role}.` 129 | } 130 | const person = { 131 | role: "developer", 132 | } 133 | const result = greet.myCall(person, "Alice") 134 | console.log(result) // 期望输出: "Hello, Alice! I'm developer." 135 | // apply的实现原理 136 | Function.prototype.myApply = function (context, args) { 137 | if (typeof this !== "function") return 138 | context = context || window 139 | const fn = Symbol("fn") 140 | context[fn] = this 141 | const result = context[fn](...args) 142 | delete context[fn] 143 | return result 144 | } 145 | function greet(...args) { 146 | return `Hello, ${args[0]}! I'm ${this.role}.` 147 | } 148 | const person1 = { 149 | role: "developer", 150 | } 151 | const result1 = greet.myApply(person1, ["Alice"]) 152 | console.log(result1) // 期望输出: "Hello, Alice! I'm developer." 153 | 154 | // bind 的实现原理 155 | 156 | Function.prototype.myBind = function (context, ...args1) { 157 | if (typeof this !== "function") return 158 | const fn = this 159 | return function (...args2) { 160 | const allArgs = [...args1, ...args2] 161 | if (new.target) { 162 | return new fn(...allArgs) 163 | } else { 164 | return fn.apply(context, allArgs) 165 | } 166 | } 167 | } 168 | function greet(greeting, punctuation) { 169 | return `${greeting} ${this.name}${punctuation}` 170 | } 171 | 172 | const person2 = { name: "Alice" } 173 | 174 | const boundFunc = greet.myBind(person2, "Hello") 175 | console.log(boundFunc("!")) // 输出:'Hello Alice!' 176 | 177 | const newObj = new boundFunc("!!!") 178 | console.log(newObj) // 输出:{ name: 'Alice' } 179 | const newObj1 = new (greet.bind(person2, "Hello"))("111") 180 | console.log(newObj1) 181 | 182 | // 浅拷贝 183 | 184 | let aaa1 = 1 185 | let ccc = 3 186 | let bbb1 = aaa1 187 | aaa1 = ccc 188 | 189 | console.log("bbb1", bbb1) 190 | 191 | // 手写浅拷贝 192 | 193 | function shallowCopy(obj) { 194 | if (!obj || typeof obj !== "object") return 195 | const newObj = Array.isArray(obj) ? [] : {} 196 | for (const key in obj) { 197 | if (Object.hasOwnProperty.call(obj, key)) { 198 | newObj[key] = object[key] 199 | } 200 | } 201 | return newObj 202 | } 203 | 204 | // 手写深拷贝 205 | 206 | function deepCopy(obj) { 207 | if (!obj || typeof obj !== "object") return 208 | const newObj = Array.isArray(obj) ? [] : {} 209 | 210 | for (const key in obj) { 211 | if (Object.hasOwnProperty.call(obj, key)) { 212 | const el = object[key] 213 | newObj[key] = typeof el === "object" ? deepCopy(el) : el 214 | } 215 | } 216 | return newObj 217 | } 218 | 219 | // 完美深拷贝 220 | 221 | function cloneForce(x) { 222 | const uniqueList = [] 223 | let root = {} 224 | const loopList = [ 225 | { 226 | parent: root, 227 | key: undefined, 228 | data: x, 229 | }, 230 | ] 231 | const find = (arr, item) => { 232 | for (let i = 0; i < arr.length; i++) { 233 | if (arr[i].source === item) { 234 | return arr[i] 235 | } 236 | } 237 | return null 238 | } 239 | while (loopList.length) { 240 | const node = loopList.pop() 241 | const parent = node.parent 242 | const key = node.key 243 | const data = node.data 244 | let res = parent 245 | // 初始化赋值目标,key为undefined则拷贝到父元素,否则拷贝到子元素 246 | if (typeof key !== "undefined") { 247 | res = parent[key] = {} 248 | } 249 | let uniqueData = find(uniqueList, data) 250 | 251 | if (uniqueData) { 252 | parent[key] = uniqueData.target 253 | continue 254 | } 255 | uniqueList.push({ 256 | source: data, 257 | target: res, 258 | }) 259 | for (let k in data) { 260 | if (data.hasOwnProperty(k)) { 261 | if (typeof data[k] === "object") { 262 | loopList.push({ 263 | parent: res, 264 | key: k, 265 | data: data[k], 266 | }) 267 | } else { 268 | res[k] = data[k] 269 | } 270 | } 271 | } 272 | } 273 | return root 274 | } 275 | 276 | // 防抖的实现 277 | 278 | function debounce(fn, wait) { 279 | let timer = null 280 | return function (...args) { 281 | let ctx = this 282 | if (timer) { 283 | clearTimeout(timer) 284 | timer = null 285 | } 286 | timer = setTimeout(() => { 287 | fn.apply(ctx, args) 288 | }, wait) 289 | } 290 | } 291 | 292 | // 测试用例 293 | function handleInput(text, text2) { 294 | console.log("Input changed:", text, text2) 295 | } 296 | 297 | const debouncedHandleInput = debounce(handleInput, 300) 298 | 299 | // 模拟输入事件 300 | debouncedHandleInput("First input", "我是测试文本") 301 | debouncedHandleInput("Second input", "我是测试文本") 302 | debouncedHandleInput("third input", "我是测试文本") 303 | // 300 毫秒后只会输出 'Input changed: Second input' 304 | 305 | // 节流的实现 306 | 307 | function throttle(fn, wait) { 308 | let timer = null 309 | return function (...args) { 310 | const ctx = this 311 | if (!timer) { 312 | timer = setTimeout(() => { 313 | fn.apply(ctx, args) 314 | timer = null 315 | }, wait) 316 | } 317 | } 318 | } 319 | function throttle(fn, wait) { 320 | let curTime = Date.now() 321 | return function (...args) { 322 | let ctx = this 323 | let nowTime = Date.now() 324 | // 如果两次时间间隔超过了指定时间,则执行函数。 325 | if (nowTime - curTime >= wait) { 326 | curTime = Date.now() 327 | return fn.apply(ctx, args) 328 | } 329 | } 330 | } 331 | // 测试用例 332 | function handleScroll() { 333 | console.log("Scrolled") 334 | } 335 | 336 | const throttledHandleScroll = throttle(handleScroll, 1000) 337 | 338 | // 模拟滚动事件 339 | // 在 1000 毫秒内多次触发滚动事件,但只会在每隔 1000 毫秒输出一次 'Scrolled' 340 | setInterval(() => { 341 | throttledHandleScroll() 342 | }, 200) 343 | -------------------------------------------------------------------------------- /JS详解面试题/image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oldTimer98/interviewModel/6f4ba9d1e28a3a72169867df2e5b900b02569ad4/JS详解面试题/image.png -------------------------------------------------------------------------------- /JS详解面试题/tempCodeRunnerFile.js: -------------------------------------------------------------------------------- 1 | arrayLike.forEach(item=>{ 2 | console.log('item',item); 3 | }) -------------------------------------------------------------------------------- /JS详解面试题/手写js.js: -------------------------------------------------------------------------------- 1 | // 1、寄生组合式继承 2 | function Person(name) { 3 | this.name = name 4 | } 5 | Person.prototype.getName = function () { 6 | console.log("this.name", this.name) 7 | } 8 | 9 | function Child(name, age) { 10 | Person.call(this, name) 11 | this.age = age 12 | } 13 | 14 | Child.prototype = Object.create(Person.prototype) 15 | Child.prototype.constructor = Child 16 | 17 | Child.prototype.getAge = function () { 18 | console.log("this.age", this.age) 19 | } 20 | 21 | const child1 = new Child("Tom", 18) 22 | child1.getAge() 23 | child1.getName() 24 | // 2、instanceof的原理 25 | 26 | function myInstanceof(left, right) { 27 | // 1、left 是对象,right是构造函数 '1' myInstanceOf String 28 | let prototype = Object.getPrototypeOf(left) 29 | let proto = right.prototype 30 | while (true) { 31 | if (!proto) return false 32 | if (prototype === proto) return true 33 | proto = Object.getPrototypeOf(proto) 34 | } 35 | } 36 | 37 | // 3、new 的原理 fn,... 38 | 39 | function myNew(...args) { 40 | const constructor = [...arguments][0] 41 | if (typeof constructor !== "function") return 42 | const obj = {} 43 | obj.prototype = constructor.prototype 44 | const res = constructor.apply(this, [...arguments].slice(1)) 45 | if (res && (typeof res === "object" || typeof res === "function")) return res 46 | return obj 47 | } 48 | 49 | // 4、备忘模式(缓存函数) 50 | 51 | function memorize(fn) { 52 | let cache = {} // [1]:2, [2]:3, [3]:4 53 | function fn1(...args) { 54 | // 1,2,3 55 | const key = JSON.stringify(args) 56 | let result 57 | if (!cache[key]) { 58 | result = fn.apply(this, args) 59 | cache[key] = result 60 | return { result, cache } 61 | } else { 62 | return { result, cache } 63 | } 64 | } 65 | fn1.cache = cache 66 | 67 | return fn1 68 | } 69 | 70 | function add(a) { 71 | return a + 1 72 | } 73 | 74 | const added = new memorize(add) 75 | 76 | console.log("added(", added(1)) 77 | console.log("added(", added(2)) 78 | console.log("added(", added(3)) 79 | 80 | function throttle(fn, wait) { 81 | let curTime = Date.now() 82 | return function (...args) { 83 | let ctx = this 84 | let nowTime = Date.now() 85 | if (nowTime - curTime >= wait) { 86 | fn.apply(ctx, args) 87 | curTime = Date.now() 88 | } 89 | } 90 | } 91 | function throttle(fn, wait) { 92 | let timer 93 | return function (...args) { 94 | let ctx = this 95 | if (!timer) { 96 | timer = setTimeout(() => { 97 | fn.apply(ctx, args) 98 | timer = null 99 | }, wait) 100 | } 101 | } 102 | } 103 | function debounce(fn, wait) { 104 | let timer 105 | return function (...args) { 106 | let ctx = this 107 | if (timer) { 108 | clearTimeout(timer) 109 | timer = null 110 | } 111 | timer = setTimeout(() => { 112 | fn.apply(ctx, args) 113 | }, wait) 114 | } 115 | } 116 | 117 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # interviewModel 2 | 面试题默写、手写版本,包含html,css,js,vue,react以及计算机网络,浏览器原理、性能优化 3 | -------------------------------------------------------------------------------- /TS全部面试题/TS面试题.md: -------------------------------------------------------------------------------- 1 | * ## 前言 2 | 3 | - 本文将简要介绍一些工具泛型使用及其实现, 这些泛型接口定义大多数是语法糖(简写), 你可以在 typescript 包中的 lib.es5.d.ts 中找到它的定义, 我们项目的版本 "typescript": "^3.9.7", 4 | 5 | ## 关键字 6 | 7 | > 在了解这这些内置帮助类型之前,我们先聊一聊一些关键字,有助于了解,因为这些关键字和js中的意识还是有出入的,我当时就一脸懵逼 8 | 9 | ### extends 10 | 11 | - 可以用来继承一个class,interface,还可以用来判断有条件类型(很多时候在ts看到extends,并不是继承的意识) 12 | - 示例: 13 | 14 | ```ts 15 | T extends U ? X : Y; 16 | ``` 17 | 18 | - 上面的类型意思是,若 T 能够赋值给 U,那么类型是 X,否则为 Y。 原理是令 T' 和 U' 分别为 T 和 U 的实例,并将所有类型参数替换为 any,如果 T' 能赋值给 U',则将有条件的类型解析成 X,否则为Y。 上面的官方解释有点绕,下面举个栗子: 19 | 20 | 21 | ```ts 22 | type Words = 'a'|'b'|"c"; 23 | 24 | type W = T extends Words ? true : false; 25 | 26 | ``` 27 | 28 | ```ts 29 | type WA = W<'a'>; // -> true 30 | 31 | type WD = W<'d'>; // -> false 32 | ``` 33 | ``` 34 | 35 | - a 可以赋值给 Words 类型,所以 WA 为 true,而 d 不能赋值给 Words 类型,所以 WD 为 false。 36 | 37 | ### infer 38 | 39 | - 表示在extends条件语句中待推断得类型变量(可结合后面的returnType) 40 | 41 | ```ts 42 | type Union = T extends Array ? U: never 43 | ``` 44 | 45 | - 如果泛型参数T满足约束条件Array 那么就返回这个类型变量U 46 | - 有点懵逼再来一个 47 | 48 | ```ts 49 | type ParamType = T extends (param: infer P) => any ? P: T; 50 | // 解析如果T能赋值给(param: infer P) => any 类型,就返回P,否则就返回T 51 | 52 | interface IDog { 53 | name: string; 54 | age:number; 55 | } 56 | 57 | type Func = (dog:IDog) => void; 58 | 59 | type Param = ParamType; // IDog 60 | type TypeString = ParamType // string 61 | ``` 62 | 63 | ### keyof 64 | 65 | - keyof 可以用来取得一个对象接口的所有 key 值: 66 | - 示例: 67 | 68 | ```ts 69 | interface IDog { 70 | name: string; 71 | age: number; 72 | sex?: string; 73 | } 74 | 75 | type K1 = keyof Person; // "name" | "age" | "sex" 76 | type K2 = keyof Person[]; // "length" | "push" | "pop" ... 77 | type K3 = keyof { [x: string]: Person }; // string | number 78 | ``` 79 | 80 | ### typeof 81 | 82 | - 在 JS 中 typeof 可以判断数据类型,在 TS 中,它还有一个作用,就是获取一个变量的声明类型,如果不存在,则获取该类型的推论类型。 83 | - 示例: 84 | 85 | ```ts 86 | interface IDog { 87 | name: string; 88 | age: number; 89 | sex?: string; 90 | } 91 | 92 | const jack: IDog = { name: 'jack', age: 100 }; 93 | type Jack = typeof jack; // -> IDog 94 | 95 | function foo(x: number): Array { 96 | return [x]; 97 | } 98 | 99 | type F = typeof foo; // -> (x: number) => number[] 100 | - Jack 这个类型别名实际上就是 jack 的类型 Person,而 F 的类型就是 TS 自己推导出来的 foo 的类型 (x: number) => number[]。 101 | ``` 102 | 103 | ## 内置帮助类型 104 | 105 | ### Partial 106 | 107 | ```ts 108 | /** 109 | * Make all properties in T optional 110 | * 让T中的所有属性都是可选的 111 | */ 112 | type Partial = { 113 | [P in keyof T]?: T[P]; 114 | }; 115 | ``` 116 | 117 | - 在某些情况下,我们希望类型中的所有属性都不是必需的,只有在某些条件下才存在,我们就可以使用Partial来将已声明的类型中的所有属性标识为可选的。 118 | - 示例: 119 | 120 | ```ts 121 | interface Dog { 122 | age: number; 123 | name: string; 124 | price: number; 125 | } 126 | 127 | type PartialDog = Partial; 128 | // 等价于 129 | type PartialDog = { 130 | age?: number; 131 | name?: string; 132 | price?: number; 133 | } 134 | 135 | let dog: PartialDog = { 136 | age: 2, 137 | name: 'xiaobai' 138 | }; 139 | ``` 140 | 141 | - 在上述示例中由于我们使用Partial将所有属性标识为可选的,因此最终dog对象中虽然只包含age和name属性,但是编译器依旧没有报错,当我们不能明确地确定对象中包含哪些属性时,我们就可以通过Partial来声明。 142 | 143 | ### Required 144 | 145 | ```ts 146 | /** 147 | * Make all properties in T required 148 | * 使T中的所有属性都是必需的 149 | */ 150 | type Required = { 151 | [P in keyof T]-?: T[P]; 152 | }; 153 | ``` 154 | 155 | - Required 的作用刚好跟 Partial 相反,Partial 是将所有属性改成可选项,Required 则是将所有类型改成必选项: 156 | - 其中 -? 是代表移除 ? 这个 modifier 的标识。 157 | - 与之对应的还有个 +? , 这个含义自然与 -? 之前相反, 它是用来把属性变成可选项的,+ 可省略,见 Partial。 158 | - 示例: 159 | 160 | ```ts 161 | interface Dog { 162 | age: number; 163 | name: string; 164 | price: number; 165 | } 166 | 167 | type RequiredDog = Required; 168 | // 等价于 169 | type RequiredDog = { 170 | age: number; 171 | name: string; 172 | price: number; 173 | } 174 | 175 | let dog: RequiredDog = { 176 | age?: 2, 177 | name?: 'xiaobai' 178 | }; 179 | ``` 180 | 181 | ### Readonly 182 | 183 | ```ts 184 | /** 185 | * Make all properties in T readonly 186 | * 将所有属性设置为只读 187 | */ 188 | type Readonly = { 189 | readonly [P in keyof T]: T[P]; 190 | }; 191 | ``` 192 | 193 | - 给子属性添加 readonly 的标识,如果将上面的 readonly 改成 -readonly, 就是移除子属性的 readonly 标识。 194 | - 示例: 195 | 196 | ```ts 197 | interface IDog{ 198 | name: string; 199 | age: number; 200 | } 201 | type TDog = Readonly; 202 | class TestDog { 203 | run() { 204 | let dog: IDog = { 205 | name: 'dd', 206 | age: 1 207 | }; 208 | person.name = 'cc'; 209 | let dog1: TDog = { 210 | name: 'read', 211 | age: 1 212 | }; 213 | // person2.age = 3; 报错,不能赋值 214 | } 215 | } 216 | ``` 217 | 218 | ### Pick 219 | 220 | ```ts 221 | /** 222 | * From T, pick a set of properties whose keys are in the union K 223 | * 从T中,选择一组键在并集K中的属性 224 | */ 225 | type Pick = { 226 | [P in K]: T[P]; 227 | }; 228 | ``` 229 | 230 | - 从源码可以看到 K 必须是 T 的 key,然后用 in 进行遍历, 将值赋给 P, 最后 T[P] 取得相应属性的值。 231 | - 示例: 232 | 233 | ```ts 234 | interface IDog { 235 | name: string; 236 | age: number; 237 | height: number; 238 | weight: number; 239 | } 240 | 241 | type PickDog = Pick; 242 | // 等价于 243 | type PickDog = { 244 | name: string; 245 | age: number; 246 | height: number; 247 | }; 248 | 249 | let dog: PickDog = { 250 | name: 'wangcai', 251 | age: 3, 252 | height: 70 253 | }; 254 | ``` 255 | 256 | - 在上述示例中,由于我们只关心IDog对象中的name,age和height是否存在,因此我们就可以使用Pick从IDog接口中拣选出我们关心的属性而忽略其他属性的编译检查。 257 | 258 | ### Record 259 | 260 | ```ts 261 | /** 262 | * Construct a type with a set of properties K of type T 263 | * 构造一个具有一组属性K(类型T)的类型 264 | */ 265 | 266 | type Record = { 267 | [P in K]: T; 268 | }; 269 | ``` 270 | 271 | - 可以根据 K 中的所有可能值来设置 key,以及 value 的类型 272 | - 示例: 273 | 274 | ```ts 275 | let dog = Record; // -> string | number | undefined 276 | ``` 277 | 278 | 该类型可以将 K 中所有的属性的值转化为 T 类型,并将返回的新类型返回给dog,K可以是联合类型、对象、枚举… 279 | 280 | - 示例: 281 | 282 | ```ts 283 | type petsGroup = 'dog' | 'cat'; 284 | interface IPetInfo { 285 | name:string, 286 | age:number, 287 | } 288 | 289 | type IPets = Record; 290 | 291 | const animalsInfo:IPets = { 292 | dog:{ 293 | name:'wangcai', 294 | age:2 295 | }, 296 | cat:{ 297 | name:'xiaobai', 298 | age:3 299 | }, 300 | } 301 | ``` 302 | 303 | ### Exclude 304 | 305 | ```ts 306 | /** 307 | * Exclude from T those types that are assignable to U 308 | * 从T中排除那些可分配给U的类型 309 | */ 310 | type Exclude = T extends U ? never : T; 311 | ``` 312 | 313 | - 与Pick相反,Pick用于拣选出我们需要关心的属性,而Exclude用于排除掉我们不需要关心的属性 314 | - 示例: 315 | 316 | ```ts 317 | interface IDog { 318 | name: string; 319 | age: number; 320 | height: number; 321 | weight: number; 322 | sex: string; 323 | } 324 | 325 | type keys = keyof IDog; // -> "name" | "age" | "height" | "weight" | "sex" 326 | 327 | type ExcludeDog = Exclude; 328 | // 等价于 329 | type ExcludeDog = "height" | "weight" | "sex"; 330 | ``` 331 | 332 | - 在上述示例中我们通过在ExcludeDog中传入我们只关心的height、weight、sex属性,Exclude会帮助我们将不需要的属性进行剔除。留下的属性id,name和gender即为我们需要关心的属性。 333 | - 示例: 334 | 335 | ```ts 336 | type T = Exclude<1 | 2, 1 | 3> // -> 2 337 | ``` 338 | 339 | - 很轻松地得出结果 2根据代码和示例我们可以推断出 Exclude 的作用是从 T 中找出 U 中没有的元素, 换种更加贴近语义的说法其实就是从T 中排除 U 340 | - 一般来说,Exclude很少单独使用,可以与其他类型配合实现更复杂更有用的功能。 341 | 342 | ### Extract 343 | 344 | ```ts 345 | /** 346 | * Extract from T those types that are assignable to U 347 | * 从T中提取可分配给U的类型 348 | */ 349 | type Extract = T extends U ? T : never; 350 | ``` 351 | 352 | - Extract 的作用是提取出 T 包含在 U 中的元素,换种更加贴近语义的说法就是从 T 中提取出 U 353 | - 以上语句的意思就是 如果 T 能赋值给 U 类型的话,那么就会返回 T 类型,否则返回 never,最终结果是将 T 和 U 中共有的属性提取出来 354 | - 示例: 355 | 356 | ```ts 357 | type test = Extract<'a' | 'b' | 'c' | 'd', 'a' | 'c' | 'f'|'g'>; // -> 'a' | 'c' 358 | ``` 359 | 360 | - 可以看到 T 是 'a' | 'b' | 'c' | 'd' ,然后 U 是 'a' | 'c' | 'f'|'g' ,返回的新类型就可以将 T 和 U 中共有的属性提取出来,也就是 'a' | 'c' 了。 361 | 362 | ### Omit 363 | 364 | ```ts 365 | /** 366 | * Construct a type with the properties of T except for those in type K. 367 | * 构造一个除类型K之外的T属性的类型 368 | */ 369 | type Omit = Pick>; 370 | ``` 371 | 372 | - 在上一个用法中,我们使用Exclude来排除掉其他不需要的属性,但是在上述示例中的写法耦合度较高,当有其他类型也需要这样处理时,就必须再实现一遍相同的逻辑,使用Omit可以避免这些问题,老版本ts未内置,TypeScript 3.5已经内置: 373 | - 示例: 374 | 375 | ```ts 376 | interface IDog { 377 | name: string; 378 | age: number; 379 | height: number; 380 | weight: number; 381 | sex: string; 382 | } 383 | 384 | // 表示忽略掉User接口中的name和age属性 385 | type OmitDog = Omit; 386 | // 等价于 387 | type OmitDog = { 388 | height: number; 389 | weight: number; 390 | sex: string; 391 | }; 392 | 393 | let dog: OmitDog = { 394 | height: 1, 395 | weight: 'wangcai', 396 | sex: 'boy' 397 | }; 398 | ``` 399 | 400 | - 在上述示例中,我们需要忽略掉IDog接口中的name和age属性,则只需要将接口名和属性传入Omit即可,对于其他类型也是如此,大大提高了类型的可扩展能力,方便复用 401 | 402 | ### NonNullable 403 | 404 | ```ts 405 | /** 406 | * Exclude null and undefined from T 407 | * 从T中排除null和undefined 408 | */ 409 | type NonNullable = T extends null | undefined ? never : T; 410 | ``` 411 | 412 | - 这个类型可以用来过滤类型中的 null 及 undefined 类型。 413 | - 示例: 414 | 415 | ```ts 416 | type test = string | number | null; 417 | type test1 = NonNullable; // -> string | number; 418 | ``` 419 | 420 | ### Parameters 421 | 422 | ```ts 423 | /** 424 | * Obtain the parameters of a function type in a tuple 425 | * 在元组中获取构造函数类型的参数 426 | */ 427 | type Parameters any> = T extends (...args: infer P) => any ? P : never; 428 | ``` 429 | 430 | - 该类型可以获得函数的参数类型组成的元组类型。 431 | - 示例: 432 | 433 | ```ts 434 | function foo(x: number): Array { 435 | return [x]; 436 | } 437 | 438 | type P = Parameters; // -> [number] 439 | ``` 440 | 441 | - 此时 P 的真实类型就是 foo 的参数组成的元组类型 [number]。 442 | 443 | ### ConstructorParameters 444 | 445 | ```ts 446 | /** 447 | * Obtain the parameters of a constructor function type in a tuple 448 | * 在元组中获取构造函数类型的参数 449 | */ 450 | type ConstructorParameters any> = T extends new (...args: infer P) => any ? P : never; 451 | ``` 452 | 453 | - 该类型的作用是获得类的参数类型组成的元组类型 454 | - 示例: 455 | 456 | ```ts 457 | class Person { 458 | private firstName: string; 459 | private lastName: string; 460 | 461 | constructor(firstName: string, lastName: string) { 462 | this.firstName = firstName; 463 | this.lastName = lastName; 464 | } 465 | } 466 | 467 | type P = ConstructorParameters; // -> [string, string] 468 | ``` 469 | 470 | - 此时 P 就是 Person 中 constructor 的参数 firstName 和 lastName 的类型所组成的元组类型 [string, string]。 471 | 472 | ### ReturnType 473 | 474 | ```ts 475 | /** 476 | * Obtain the return type of a function type 477 | * 获取函数类型的返回类型 478 | */ 479 | type ReturnType any> = T extends (...args: any) => infer R ? R : any; 480 | ``` 481 | 482 | - 该类型的作用是获取函数的返回类型。 483 | - 其实这里的 infer R 就是声明一个变量来承载传入函数签名的返回值类型, 简单说就是用它取到函数返回值的类型方便之后使用 484 | - 实际使用的话,就可以通过 ReturnType 拿到函数的返回类型 485 | - 示例: 486 | 487 | ```ts 488 | function foo(x: number): Array { 489 | return [x]; 490 | } 491 | 492 | type fn = ReturnType; // -> number[] 493 | ``` 494 | 495 | ### InstanceType 496 | 497 | ```ts 498 | /** 499 | * Obtain the return type of a constructor function type 500 | * 获取构造函数类型的返回类型 501 | */ 502 | 503 | type InstanceType any> = T extends new (...args: any) => infer R ? R : any; 504 | ``` 505 | 506 | - 该类型的作用是获取构造函数类型的实例类型。 507 | 508 | ```ts 509 | class ConstructorType { 510 | x = 0; 511 | y = 0; 512 | } 513 | type test1 = InstanceType; // ConstructorType 514 | 515 | type test1 = InstanceType; // any 516 | ``` 517 | 518 | ### ThisType 519 | 520 | ```ts 521 | /** 522 | * Marker for contextual 'this' type 523 | * 上下文“this”类型的标记 524 | */ 525 | interface ThisType { } 526 | ``` 527 | 528 | - 这个类型是用于指定上下文对象类型的。 529 | - 这类型怎么用呢,举个例子: 530 | 531 | ```ts 532 | interface Cat { 533 | name: string; 534 | age: number; 535 | } 536 | const obj: ThisType = { 537 | mimi() { 538 | this.name // string 539 | } 540 | } 541 | ``` 542 | 543 | - 这样的话,就可以指定 obj 里的所有方法里的上下文对象改成 Person 这个类型了。 544 | 545 | ```ts 546 | // 没有ThisType情况下 547 | const dog = { 548 | wang() { 549 | console.log(this.age); // error,在dog中只有wang一个函数,不存在a 550 | } 551 | } 552 | // 使用ThisType 553 | const dog: { wang: any } & ThisType<{ age: number }> = { 554 | wang() { 555 | console.log(this.wang) // error,因为没有在ThisType中定义 556 | console.log(this.age); // ok 557 | } 558 | } 559 | dog.wang // ok 正常调用 560 | dog.age // error,在外面的话,就跟ThisType没有关系了,这里就是没有定义age了 561 | ``` 562 | 563 | - 从上面的代码中可以看到,ThisType的作用是:提示其下所定义的函数,在函数body中,其调用者的类型是什么。 564 | 565 | ### 参考 566 | 567 | - [segmentfault.com/a/119000001…](https://link.juejin.cn/?target=https%3A%2F%2Fsegmentfault.com%2Fa%2F1190000018514540%3Futm_source%3Dtag-newest) 568 | - 深入理解typescript 569 | -------------------------------------------------------------------------------- /代码输出题/代码输出题.md: -------------------------------------------------------------------------------- 1 | ### 前言: 2 | 3 | **代码输出结果**也是面试中常考的题目,一段代码中可能涉及到很多的知识点,这就考察到了应聘者的基础能力。在前端面试中,常考的代码输出问题主要涉及到以下知识点:**异步编程、事件循环、this指向、作用域、变量提升、闭包、原型、继承**等,这些知识点往往不是单独出现的,而是在同一段代码中包含多个知识点。所以,笔者将这些问题大致分为四类进行讨论。这里不会系统的阐述基础知识,而是通过面试例题的形式,来讲述每个题目的知识点以及代码的执行过程。如果会了这些例题,在前端面试中多数代码输出问题就可以轻而易举的解决了。 4 | 5 | ## 一、异步&事件循环 6 | 7 | ------ 8 | 9 | ### 1. 代码输出结果 10 | 11 | ```js 12 | const promise = new Promise((resolve, reject) => { 13 | console.log(1); 14 | console.log(2); 15 | }); 16 | promise.then(() => { 17 | console.log(3); 18 | }); 19 | console.log(4); 20 | ``` 21 | 22 | 输出结果如下: 23 | 24 | ```js 25 | 1 26 | 2 27 | 4 28 | ``` 29 | 30 | promise.then 是微任务,它会在所有的宏任务执行完之后才会执行,同时需要promise内部的状态发生变化,因为这里内部没有发生变化,一直处于pending状态,所以不输出3。 31 | 32 | ### 2. 代码输出结果 33 | 34 | ```js 35 | const promise1 = new Promise((resolve, reject) => { 36 | console.log('promise1') 37 | resolve('resolve1') 38 | }) 39 | const promise2 = promise1.then(res => { 40 | console.log(res) 41 | }) 42 | console.log('1', promise1); 43 | console.log('2', promise2); 44 | ``` 45 | 46 | 输出结果如下: 47 | 48 | ```js 49 | promise1 50 | 1 Promise{: resolve1} 51 | 2 Promise{} 52 | resolve1 53 | ``` 54 | 55 | 需要注意的是,直接打印promise1,会打印出它的状态值和参数。 56 | 57 | 代码执行过程如下: 58 | 59 | 1. script是一个宏任务,按照顺序执行这些代码; 60 | 2. 首先进入Promise,执行该构造函数中的代码,打印 `promise1`; 61 | 3. 碰到 `resolve`函数, 将 `promise1`的状态改变为 `resolved`, 并将结果保存下来; 62 | 4. 碰到 `promise1.then`这个微任务,将它放入微任务队列; 63 | 5. `promise2`是一个新的状态为 `pending`的 `Promise`; 64 | 6. 执行同步代码1, 同时打印出 `promise1`的状态是 `resolved`; 65 | 7. 执行同步代码2,同时打印出 `promise2`的状态是 `pending`; 66 | 8. 宏任务执行完毕,查找微任务队列,发现 `promise1.then`这个微任务且状态为 `resolved`,执行它。 67 | 68 | ### 3. 代码输出结果 69 | 70 | ```js 71 | const promise = new Promise((resolve, reject) => { 72 | console.log(1); 73 | setTimeout(() => { 74 | console.log("timerStart"); 75 | resolve("success"); 76 | console.log("timerEnd"); 77 | }, 0); 78 | console.log(2); 79 | }); 80 | promise.then((res) => { 81 | console.log(res); 82 | }); 83 | console.log(4); 84 | ``` 85 | 86 | 输出结果如下: 87 | 88 | ```js 89 | 1 90 | 2 91 | 4 92 | timerStart 93 | timerEnd 94 | success 95 | ``` 96 | 97 | 代码执行过程如下: 98 | 99 | - 首先遇到Promise构造函数,会先执行里面的内容,打印 1; 100 | - 遇到定时器 `steTimeout`,它是一个宏任务,放入宏任务队列; 101 | - 继续向下执行,打印出2; 102 | - 由于 `Promise`的状态此时还是 `pending`,所以 `promise.then`先不执行; 103 | - 继续执行下面的同步任务,打印出4; 104 | - 此时微任务队列没有任务,继续执行下一轮宏任务,执行 `steTimeout`; 105 | - 首先执行 `timerStart`,然后遇到了 `resolve`,将 `promise`的状态改为 `resolved`且保存结果并将之前的 `promise.then`推入微任务队列,再执行 `timerEnd`; 106 | - 执行完这个宏任务,就去执行微任务 `promise.then`,打印出 `resolve`的结果。 107 | 108 | ### 4. 代码输出结果 109 | 110 | ```js 111 | Promise.resolve().then(() => { 112 | console.log('promise1'); 113 | const timer2 = setTimeout(() => { 114 | console.log('timer2') 115 | }, 0) 116 | }); 117 | const timer1 = setTimeout(() => { 118 | console.log('timer1') 119 | Promise.resolve().then(() => { 120 | console.log('promise2') 121 | }) 122 | }, 0) 123 | console.log('start'); 124 | ``` 125 | 126 | 输出结果如下: 127 | 128 | ```js 129 | start 130 | promise1 131 | timer1 132 | promise2 133 | timer2 134 | ``` 135 | 136 | 代码执行过程如下: 137 | 138 | 1. 首先,`Promise.resolve().then`是一个微任务,加入微任务队列 139 | 2. 执行timer1,它是一个宏任务,加入宏任务队列 140 | 3. 继续执行下面的同步代码,打印出 `start` 141 | 4. 这样第一轮宏任务就执行完了,开始执行微任务 `Promise.resolve().then`,打印出 `promise1` 142 | 5. 遇到 `timer2`,它是一个宏任务,将其加入宏任务队列,此时宏任务队列有两个任务,分别是 `timer1`、`timer2`; 143 | 6. 这样第一轮微任务就执行完了,开始执行第二轮宏任务,首先执行定时器 `timer1`,打印 `timer1`; 144 | 7. 遇到 `Promise.resolve().then`,它是一个微任务,加入微任务队列 145 | 8. 开始执行微任务队列中的任务,打印 `promise2`; 146 | 9. 最后执行宏任务 `timer2`定时器,打印出 `timer2`; 147 | 148 | ### 5. 代码输出结果 149 | 150 | ```js 151 | const promise = new Promise((resolve, reject) => { 152 | resolve('success1'); 153 | reject('error'); 154 | resolve('success2'); 155 | }); 156 | promise.then((res) => { 157 | console.log('then:', res); 158 | }).catch((err) => { 159 | console.log('catch:', err); 160 | }) 161 | ``` 162 | 163 | 输出结果如下: 164 | 165 | ```js 166 | then:success1 167 | ``` 168 | 169 | 这个题目考察的就是**Promise的状态在发生变化之后,就不会再发生变化**。开始状态由 `pending`变为 `resolve`,说明已经变为已完成状态,下面的两个状态的就不会再执行,同时下面的catch也不会捕获到错误。 170 | 171 | ### 6. 代码输出结果 172 | 173 | ```js 174 | Promise.resolve(1) 175 | .then(2) 176 | .then(Promise.resolve(3)) 177 | .then(console.log) 178 | ``` 179 | 180 | 输出结果如下: 181 | 182 | ```js 183 | 1 184 | Promise {: undefined} 185 | ``` 186 | 187 | Promise.resolve方法的参数如果是一个原始值,或者是一个不具有then方法的对象,则Promise.resolve方法返回一个新的Promise对象,状态为resolved,Promise.resolve方法的参数,会同时传给回调函数。 188 | 189 | then方法接受的参数是函数,而如果传递的并非是一个函数,它实际上会将其解释为then(null),这就会导致前一个Promise的结果会传递下面。 190 | 191 | ### 7. 代码输出结果 192 | 193 | ```js 194 | const promise1 = new Promise((resolve, reject) => { 195 | setTimeout(() => { 196 | resolve('success') 197 | }, 1000) 198 | }) 199 | const promise2 = promise1.then(() => { 200 | throw new Error('error!!!') 201 | }) 202 | console.log('promise1', promise1) 203 | console.log('promise2', promise2) 204 | setTimeout(() => { 205 | console.log('promise1', promise1) 206 | console.log('promise2', promise2) 207 | }, 2000) 208 | ``` 209 | 210 | 输出结果如下: 211 | 212 | ```js 213 | promise1 Promise {} 214 | promise2 Promise {} 215 | 216 | Uncaught (in promise) Error: error!!! 217 | promise1 Promise {: "success"} 218 | promise2 Promise {: Error: error!!} 219 | ``` 220 | 221 | ### 8. 代码输出结果 222 | 223 | ```js 224 | Promise.resolve(1) 225 | .then(res => { 226 | console.log(res); 227 | return 2; 228 | }) 229 | .catch(err => { 230 | return 3; 231 | }) 232 | .then(res => { 233 | console.log(res); 234 | }); 235 | ``` 236 | 237 | 输出结果如下: 238 | 239 | ```js 240 | 1 241 | 2 242 | ``` 243 | 244 | Promise是可以链式调用的,由于每次调用 `.then` 或者 `.catch` 都会返回一个新的 promise,从而实现了链式调用, 它并不像一般任务的链式调用一样return this。 245 | 246 | 上面的输出结果之所以依次打印出1和2,是因为 `resolve(1)`之后走的是第一个then方法,并没有进catch里,所以第二个then中的res得到的实际上是第一个then的返回值。并且return 2会被包装成 `resolve(2)`,被最后的then打印输出2。 247 | 248 | ### 9. 代码输出结果 249 | 250 | ```js 251 | Promise.resolve().then(() => { 252 | return new Error('error!!!') 253 | }).then(res => { 254 | console.log("then: ", res) 255 | }).catch(err => { 256 | console.log("catch: ", err) 257 | }) 258 | ``` 259 | 260 | 输出结果如下: 261 | 262 | ```js 263 | "then: " "Error: error!!!" 264 | ``` 265 | 266 | 返回任意一个非 promise 的值都会被包裹成 promise 对象,因此这里的 `return new Error('error!!!')`也被包裹成了 `return Promise.resolve(new Error('error!!!'))`,因此它会被then捕获而不是catch。 267 | 268 | ### 10. 代码输出结果 269 | 270 | ```js 271 | const promise = Promise.resolve().then(() => { 272 | return promise; 273 | }) 274 | promise.catch(console.err) 275 | ``` 276 | 277 | 输出结果如下: 278 | 279 | ```js 280 | Uncaught (in promise) TypeError: Chaining cycle detected for promise # 281 | ``` 282 | 283 | 这里其实是一个坑,`.then` 或 `.catch` 返回的值不能是 promise 本身,否则会造成死循环。 284 | 285 | ### 11. 代码输出结果 286 | 287 | ```js 288 | Promise.resolve(1) 289 | .then(2) 290 | .then(Promise.resolve(3)) 291 | .then(console.log) 292 | ``` 293 | 294 | 输出结果如下: 295 | 296 | ```js 297 | 1 298 | ``` 299 | 300 | 看到这个题目,好多的then,实际上只需要记住一个原则:`.then` 或 `.catch` 的参数期望是函数,传入非函数则会发生**值传透**。 301 | 302 | 第一个then和第二个then中传入的都不是函数,一个是数字,一个是对象,因此发生了透传,将 `resolve(1)` 的值直接传到最后一个then里,直接打印出1。 303 | 304 | ### 12. 代码输出结果 305 | 306 | ```js 307 | Promise.reject('err!!!') 308 | .then((res) => { 309 | console.log('success', res) 310 | }, (err) => { 311 | console.log('error', err) 312 | }).catch(err => { 313 | console.log('catch', err) 314 | }) 315 | ``` 316 | 317 | 输出结果如下: 318 | 319 | ```js 320 | error err!!! 321 | ``` 322 | 323 | 我们知道,`.then`函数中的两个参数: 324 | 325 | - 第一个参数是用来处理Promise成功的函数 326 | - 第二个则是处理失败的函数 327 | 328 | 也就是说 `Promise.resolve('1')`的值会进入成功的函数,`Promise.reject('2')`的值会进入失败的函数。 329 | 330 | 在这道题中,错误直接被 `then`的第二个参数捕获了,所以就不会被 `catch`捕获了,输出结果为:`error err!!!'` 331 | 332 | 但是,如果是像下面这样: 333 | 334 | ```js 335 | Promise.resolve() 336 | .then(function success (res) { 337 | throw new Error('error!!!') 338 | }, function fail1 (err) { 339 | console.log('fail1', err) 340 | }).catch(function fail2 (err) { 341 | console.log('fail2', err) 342 | }) 343 | ``` 344 | 345 | 在 `then`的第一参数中抛出了错误,那么他就不会被第二个参数不活了,而是被后面的 `catch`捕获到。 346 | 347 | ### 13. 代码输出结果 348 | 349 | ```js 350 | Promise.resolve('1') 351 | .then(res => { 352 | console.log(res) 353 | }) 354 | .finally(() => { 355 | console.log('finally') 356 | }) 357 | Promise.resolve('2') 358 | .finally(() => { 359 | console.log('finally2') 360 | return '我是finally2返回的值' 361 | }) 362 | .then(res => { 363 | console.log('finally2后面的then函数', res) 364 | }) 365 | ``` 366 | 367 | 输出结果如下: 368 | 369 | ```js 370 | 1 371 | finally2 372 | finally 373 | finally2后面的then函数 2 374 | ``` 375 | 376 | `.finally()`一般用的很少,只要记住以下几点就可以了: 377 | 378 | - `.finally()`方法不管Promise对象最后的状态如何都会执行 379 | - `.finally()`方法的回调函数不接受任何的参数,也就是说你在 `.finally()`函数中是无法知道Promise最终的状态是 `resolved`还是 `rejected`的 380 | - 它最终返回的默认会是一个上一次的Promise对象值,不过如果抛出的是一个异常则返回异常的Promise对象。 381 | - finally本质上是then方法的特例 382 | 383 | `.finally()`的错误捕获: 384 | 385 | ```js 386 | Promise.resolve('1') 387 | .finally(() => { 388 | console.log('finally1') 389 | throw new Error('我是finally中抛出的异常') 390 | }) 391 | .then(res => { 392 | console.log('finally后面的then函数', res) 393 | }) 394 | .catch(err => { 395 | console.log('捕获错误', err) 396 | }) 397 | ``` 398 | 399 | 输出结果为: 400 | 401 | ```js 402 | 'finally1' 403 | '捕获错误' Error: 我是finally中抛出的异常 404 | ``` 405 | 406 | ### 14. 代码输出结果 407 | 408 | ```js 409 | function runAsync (x) { 410 | const p = new Promise(r => setTimeout(() => r(x, console.log(x)), 1000)) 411 | return p 412 | } 413 | 414 | Promise.all([runAsync(1), runAsync(2), runAsync(3)]).then(res => console.log(res)) 415 | ``` 416 | 417 | 输出结果如下: 418 | 419 | ```js 420 | 1 421 | 2 422 | 3 423 | [1, 2, 3] 424 | ``` 425 | 426 | 首先,定义了一个Promise,来异步执行函数runAsync,该函数传入一个值x,然后间隔一秒后打印出这个x。 427 | 428 | 之后再使用 `Promise.all`来执行这个函数,执行的时候,看到一秒之后输出了1,2,3,同时输出了数组[1, 2, 3],三个函数是同步执行的,并且在一个回调函数中返回了所有的结果。并且结果和函数的执行顺序是一致的。 429 | 430 | ### 15. 代码输出结果 431 | 432 | ```js 433 | function runAsync (x) { 434 | const p = new Promise(r => setTimeout(() => r(x, console.log(x)), 1000)) 435 | return p 436 | } 437 | function runReject (x) { 438 | const p = new Promise((res, rej) => setTimeout(() => rej(`Error: ${x}`, console.log(x)), 1000 * x)) 439 | return p 440 | } 441 | Promise.all([runAsync(1), runReject(4), runAsync(3), runReject(2)]) 442 | .then(res => console.log(res)) 443 | .catch(err => console.log(err)) 444 | ``` 445 | 446 | 输出结果如下: 447 | 448 | ```js 449 | // 1s后输出 450 | 1 451 | 3 452 | // 2s后输出 453 | 2 454 | Error: 2 455 | // 4s后输出 456 | 4 457 | ``` 458 | 459 | 可以看到。catch捕获到了第一个错误,在这道题目中最先的错误就是 `runReject(2)`的结果。如果一组异步操作中有一个异常都不会进入 `.then()`的第一个回调函数参数中。会被 `.then()`的第二个回调函数捕获。 460 | 461 | ### 16. 代码输出结果 462 | 463 | ```js 464 | function runAsync (x) { 465 | const p = new Promise(r => setTimeout(() => r(x, console.log(x)), 1000)) 466 | return p 467 | } 468 | Promise.race([runAsync(1), runAsync(2), runAsync(3)]) 469 | .then(res => console.log('result: ', res)) 470 | .catch(err => console.log(err)) 471 | ``` 472 | 473 | 输出结果如下: 474 | 475 | ```js 476 | 1 477 | 'result: ' 1 478 | 2 479 | 3 480 | ``` 481 | 482 | then只会捕获第一个成功的方法,其他的函数虽然还会继续执行,但是不是被then捕获了。 483 | 484 | ### 17. 代码输出结果 485 | 486 | ```js 487 | function runAsync(x) { 488 | const p = new Promise(r => 489 | setTimeout(() => r(x, console.log(x)), 1000) 490 | ); 491 | return p; 492 | } 493 | function runReject(x) { 494 | const p = new Promise((res, rej) => 495 | setTimeout(() => rej(`Error: ${x}`, console.log(x)), 1000 * x) 496 | ); 497 | return p; 498 | } 499 | Promise.race([runReject(0), runAsync(1), runAsync(2), runAsync(3)]) 500 | .then(res => console.log("result: ", res),rej=>console.log("reject",rej)) 501 | .catch(err => console.log("catch",err)); 502 | ``` 503 | 504 | 输出结果如下: 505 | 506 | ```js 507 | 0 508 | Error: 0 509 | 1 510 | 2 511 | 3 512 | ``` 513 | 514 | 可以看到在catch捕获到第一个错误之后,后面的代码还不执行,不过不会再被捕获了。 515 | 516 | 注意:`all`和 `race`传入的数组中如果有会抛出异常的异步任务,那么只有最先抛出的错误会被捕获,并且是被then的第二个参数或者后面的catch捕获;但并不会影响数组中其它的异步任务的执行。 517 | 518 | ### 18. 代码输出结果 519 | 520 | ```js 521 | async function async1() { 522 | console.log("async1 start"); 523 | await async2(); 524 | console.log("async1 end"); 525 | } 526 | async function async2() { 527 | console.log("async2"); 528 | } 529 | async1(); 530 | console.log('start') 531 | ``` 532 | 533 | 输出结果如下: 534 | 535 | ```js 536 | async1 start 537 | async2 538 | start 539 | async1 end 540 | ``` 541 | 542 | 代码的执行过程如下: 543 | 544 | 1. 首先执行函数中的同步代码 `async1 start`,之后遇到了 `await`,它会阻塞 `async1`后面代码的执行,因此会先去执行 `async2`中的同步代码 `async2`,然后跳出 `async1`; 545 | 2. 跳出 `async1`函数后,执行同步代码 `start`; 546 | 3. 在一轮宏任务全部执行完之后,再来执行 `await`后面的内容 `async1 end`。 547 | 548 | 这里可以理解为await后面的语句相当于放到了new Promise中,下一行及之后的语句相当于放在Promise.then中。 549 | 550 | ### 19. 代码输出结果 551 | 552 | ```js 553 | async function async1() { 554 | console.log("async1 start"); 555 | await async2(); 556 | console.log("async1 end"); 557 | setTimeout(() => { 558 | console.log('timer1') 559 | }, 0) 560 | } 561 | async function async2() { 562 | setTimeout(() => { 563 | console.log('timer2') 564 | }, 0) 565 | console.log("async2"); 566 | } 567 | async1(); 568 | setTimeout(() => { 569 | console.log('timer3') 570 | }, 0) 571 | console.log("start") 572 | ``` 573 | 574 | 输出结果如下: 575 | 576 | ```js 577 | async1 start 578 | async2 579 | start 580 | async1 end 581 | timer2 582 | timer3 583 | timer1 584 | ``` 585 | 586 | 代码的执行过程如下: 587 | 588 | 1. 首先进入 `async1`,打印出 `async1 start`; 589 | 2. 之后遇到 `async2`,进入 `async2`,遇到定时器 `timer2`,加入宏任务队列,之后打印 `async2`; 590 | 3. 由于 `async2`阻塞了后面代码的执行,所以执行后面的定时器 `timer3`,将其加入宏任务队列,之后打印 `start`; 591 | 4. 然后执行`async2`后面的代码,打印出 `async1 end`,遇到定时器`timer1`,将其加入宏任务队列; 592 | 5. 最后,宏任务队列有三个任务,先后顺序为 `timer2`,`timer3`,`timer1`,没有微任务,所以直接所有的宏任务按照先进先出的原则执行。 593 | 594 | ### 20. 代码输出结果 595 | 596 | ```js 597 | async function async1 () { 598 | console.log('async1 start'); 599 | await new Promise(resolve => { 600 | console.log('promise1') 601 | }) 602 | console.log('async1 success'); 603 | return 'async1 end' 604 | } 605 | console.log('srcipt start') 606 | async1().then(res => console.log(res)) 607 | console.log('srcipt end') 608 | ``` 609 | 610 | 输出结果如下: 611 | 612 | ```js 613 | script start 614 | async1 start 615 | promise1 616 | script end 617 | ``` 618 | 619 | 这里需要注意的是在 `async1`中 `await`后面的Promise是没有返回值的,也就是它的状态始终是 `pending`状态,所以在 `await`之后的内容是不会执行的,包括 `async1`后面的 `.then`。 620 | 621 | ### 21. 代码输出结果 622 | 623 | ```js 624 | async function async1 () { 625 | console.log('async1 start'); 626 | await new Promise(resolve => { 627 | console.log('promise1') 628 | resolve('promise1 resolve') 629 | }).then(res => console.log(res)) 630 | console.log('async1 success'); 631 | return 'async1 end' 632 | } 633 | console.log('srcipt start') 634 | async1().then(res => console.log(res)) 635 | console.log('srcipt end') 636 | ``` 637 | 638 | 这里是对上面一题进行了改造,加上了resolve。 639 | 640 | 输出结果如下: 641 | 642 | ```js 643 | script start 644 | async1 start 645 | promise1 646 | script end 647 | promise1 resolve 648 | async1 success 649 | async1 end 650 | ``` 651 | 652 | ### 22. 代码输出结果 653 | 654 | ```js 655 | async function async1() { 656 | console.log("async1 start"); 657 | await async2(); 658 | console.log("async1 end"); 659 | } 660 | 661 | async function async2() { 662 | console.log("async2"); 663 | } 664 | 665 | console.log("script start"); 666 | 667 | setTimeout(function() { 668 | console.log("setTimeout"); 669 | }, 0); 670 | 671 | async1(); 672 | 673 | new Promise(resolve => { 674 | console.log("promise1"); 675 | resolve(); 676 | }).then(function() { 677 | console.log("promise2"); 678 | }); 679 | console.log('script end') 680 | ``` 681 | 682 | 输出结果如下: 683 | 684 | ```js 685 | script start 686 | async1 start 687 | async2 688 | promise1 689 | script end 690 | async1 end 691 | promise2 692 | setTimeout 693 | ``` 694 | 695 | 代码执行过程如下: 696 | 697 | 1. 开头定义了async1和async2两个函数,但是并未执行,执行script中的代码,所以打印出script start; 698 | 2. 遇到定时器Settimeout,它是一个宏任务,将其加入到宏任务队列; 699 | 3. 之后执行函数async1,首先打印出async1 start; 700 | 4. 遇到await,执行async2,打印出async2,并阻断后面代码的执行,将后面的代码加入到微任务队列; 701 | 5. 然后跳出async1和async2,遇到Promise,打印出promise1; 702 | 6. 遇到resolve,将其加入到微任务队列,然后执行后面的script代码,打印出script end; 703 | 7. 之后就该执行微任务队列了,首先打印出async1 end,然后打印出promise2; 704 | 8. 执行完微任务队列,就开始执行宏任务队列中的定时器,打印出setTimeout。 705 | 706 | ### 23. 代码输出结果 707 | 708 | ```js 709 | async function async1 () { 710 | await async2(); 711 | console.log('async1'); 712 | return 'async1 success' 713 | } 714 | async function async2 () { 715 | return new Promise((resolve, reject) => { 716 | console.log('async2') 717 | reject('error') 718 | }) 719 | } 720 | async1().then(res => console.log(res)) 721 | ``` 722 | 723 | 输出结果如下: 724 | 725 | ```js 726 | async2 727 | Uncaught (in promise) error 728 | ``` 729 | 730 | 可以看到,如果async函数中抛出了错误,就会终止错误结果,不会继续向下执行。 731 | 732 | 如果想要让错误不足之处后面的代码执行,可以使用catch来捕获: 733 | 734 | ```js 735 | async function async1 () { 736 | await Promise.reject('error!!!').catch(e => console.log(e)) 737 | console.log('async1'); 738 | return Promise.resolve('async1 success') 739 | } 740 | async1().then(res => console.log(res)) 741 | console.log('script start') 742 | ``` 743 | 744 | 这样的输出结果就是: 745 | 746 | ```js 747 | script start 748 | error!!! 749 | async1 750 | async1 success 751 | ``` 752 | 753 | ### 24. 代码输出结果 754 | 755 | ```js 756 | const first = () => (new Promise((resolve, reject) => { 757 | console.log(3); 758 | let p = new Promise((resolve, reject) => { 759 | console.log(7); 760 | setTimeout(() => { 761 | console.log(5); 762 | resolve(6); 763 | console.log(p) 764 | }, 0) 765 | resolve(1); 766 | }); 767 | resolve(2); 768 | p.then((arg) => { 769 | console.log(arg); 770 | }); 771 | })); 772 | first().then((arg) => { 773 | console.log(arg); 774 | }); 775 | console.log(4); 776 | ``` 777 | 778 | 输出结果如下: 779 | 780 | ```js 781 | 3 782 | 7 783 | 4 784 | 1 785 | 2 786 | 5 787 | Promise{: 1} 788 | ``` 789 | 790 | 代码的执行过程如下: 791 | 792 | 1. 首先会进入Promise,打印出3,之后进入下面的Promise,打印出7; 793 | 2. 遇到了定时器,将其加入宏任务队列; 794 | 3. 执行Promise p中的resolve,状态变为resolved,返回值为1; 795 | 4. 执行Promise first中的resolve,状态变为resolved,返回值为2; 796 | 5. 遇到p.then,将其加入微任务队列,遇到first().then,将其加入任务队列; 797 | 6. 执行外面的代码,打印出4; 798 | 7. 这样第一轮宏任务就执行完了,开始执行微任务队列中的任务,先后打印出1和2; 799 | 8. 这样微任务就执行完了,开始执行下一轮宏任务,宏任务队列中有一个定时器,执行它,打印出5,由于执行已经变为resolved状态,所以 `resolve(6)`不会再执行; 800 | 9. 最后 `console.log(p)`打印出 `Promise{: 1}`; 801 | 802 | ### 25. 代码输出结果 803 | 804 | ```js 805 | const async1 = async () => { 806 | console.log('async1'); 807 | setTimeout(() => { 808 | console.log('timer1') 809 | }, 2000) 810 | await new Promise(resolve => { 811 | console.log('promise1') 812 | }) 813 | console.log('async1 end') 814 | return 'async1 success' 815 | } 816 | console.log('script start'); 817 | async1().then(res => console.log(res)); 818 | console.log('script end'); 819 | Promise.resolve(1) 820 | .then(2) 821 | .then(Promise.resolve(3)) 822 | .catch(4) 823 | .then(res => console.log(res)) 824 | setTimeout(() => { 825 | console.log('timer2') 826 | }, 1000) 827 | ``` 828 | 829 | 输出结果如下: 830 | 831 | ```js 832 | script start 833 | async1 834 | promise1 835 | script end 836 | 1 837 | timer2 838 | timer1 839 | ``` 840 | 841 | 代码的执行过程如下: 842 | 843 | 1. 首先执行同步带吗,打印出script start; 844 | 2. 遇到定时器timer1将其加入宏任务队列; 845 | 3. 之后是执行Promise,打印出promise1,由于Promise没有返回值,所以后面的代码不会执行; 846 | 4. 然后执行同步代码,打印出script end; 847 | 5. 继续执行下面的Promise,.then和.catch期望参数是一个函数,这里传入的是一个数字,因此就会发生值渗透,将resolve(1)的值传到最后一个then,直接打印出1; 848 | 6. 遇到第二个定时器,将其加入到微任务队列,执行微任务队列,按顺序依次执行两个定时器,但是由于定时器时间的原因,会在两秒后先打印出timer2,在四秒后打印出timer1。 849 | 850 | ### 26. 代码输出结果 851 | 852 | ```js 853 | const p1 = new Promise((resolve) => { 854 | setTimeout(() => { 855 | resolve('resolve3'); 856 | console.log('timer1') 857 | }, 0) 858 | resolve('resovle1'); 859 | resolve('resolve2'); 860 | }).then(res => { 861 | console.log(res) // resolve1 862 | setTimeout(() => { 863 | console.log(p1) 864 | }, 1000) 865 | }).finally(res => { 866 | console.log('finally', res) 867 | }) 868 | ``` 869 | 870 | 执行结果为如下: 871 | 872 | ```js 873 | resolve1 874 | finally undefined 875 | timer1 876 | Promise{: undefined} 877 | ``` 878 | 879 | ### 27. 代码输出结果 880 | 881 | ```js 882 | console.log('1'); 883 | 884 | setTimeout(function() { 885 | console.log('2'); 886 | process.nextTick(function() { 887 | console.log('3'); 888 | }) 889 | new Promise(function(resolve) { 890 | console.log('4'); 891 | resolve(); 892 | }).then(function() { 893 | console.log('5') 894 | }) 895 | }) 896 | process.nextTick(function() { 897 | console.log('6'); 898 | }) 899 | new Promise(function(resolve) { 900 | console.log('7'); 901 | resolve(); 902 | }).then(function() { 903 | console.log('8') 904 | }) 905 | 906 | setTimeout(function() { 907 | console.log('9'); 908 | process.nextTick(function() { 909 | console.log('10'); 910 | }) 911 | new Promise(function(resolve) { 912 | console.log('11'); 913 | resolve(); 914 | }).then(function() { 915 | console.log('12') 916 | }) 917 | }) 918 | ``` 919 | 920 | 输出结果如下: 921 | 922 | ```js 923 | 1 924 | 7 925 | 6 926 | 8 927 | 2 928 | 4 929 | 3 930 | 5 931 | 9 932 | 11 933 | 10 934 | 12 935 | ``` 936 | 937 | **(1)第一轮事件循环流程分析如下:** 938 | 939 | - 整体script作为第一个宏任务进入主线程,遇到 `console.log`,输出1。 940 | - 遇到 `setTimeout`,其回调函数被分发到宏任务Event Queue中。暂且记为 `setTimeout1`。 941 | - 遇到 `process.nextTick()`,其回调函数被分发到微任务Event Queue中。记为 `process1`。 942 | - 遇到 `Promise`,`new Promise`直接执行,输出7。`then`被分发到微任务Event Queue中。记为 `then1`。 943 | - 又遇到了 `setTimeout`,其回调函数被分发到宏任务Event Queue中,记为 `setTimeout2`。 944 | 945 | | 宏任务Event Queue | 微任务Event Queue | 946 | | :---------------- | :---------------- | 947 | | setTimeout1 | process1 | 948 | | setTimeout2 | then1 | 949 | 950 | 上表是第一轮事件循环宏任务结束时各Event Queue的情况,此时已经输出了1和7。发现了 `process1`和 `then1`两个微任务: 951 | 952 | - 执行 `process1`,输出6。 953 | - 执行 `then1`,输出8。 954 | 955 | 第一轮事件循环正式结束,这一轮的结果是输出1,7,6,8。 956 | 957 | **(2)第二轮时间循环从** `setTimeout1`**宏任务开始:** 958 | 959 | - 首先输出2。接下来遇到了 `process.nextTick()`,同样将其分发到微任务Event Queue中,记为 `process2`。 960 | - `new Promise`立即执行输出4,`then`也分发到微任务Event Queue中,记为 `then2`。 961 | 962 | | 宏任务Event Queue | 微任务Event Queue | 963 | | :---------------- | :---------------- | 964 | | setTimeout2 | process2 | 965 | | | then2 | 966 | 967 | 第二轮事件循环宏任务结束,发现有 `process2`和 `then2`两个微任务可以执行: 968 | 969 | - 输出3。 970 | - 输出5。 971 | 972 | 第二轮事件循环结束,第二轮输出2,4,3,5。 973 | 974 | **(3)第三轮事件循环开始,此时只剩setTimeout2了,执行。** 975 | 976 | - 直接输出9。 977 | - 将 `process.nextTick()`分发到微任务Event Queue中。记为 `process3`。 978 | - 直接执行 `new Promise`,输出11。 979 | - 将 `then`分发到微任务Event Queue中,记为 `then3`。 980 | 981 | | 宏任务Event Queue | 微任务Event Queue | 982 | | :---------------- | :---------------- | 983 | | | process3 | 984 | | | then3 | 985 | 986 | 第三轮事件循环宏任务执行结束,执行两个微任务 `process3`和 `then3`: 987 | 988 | - 输出10。 989 | - 输出12。 990 | 991 | 第三轮事件循环结束,第三轮输出9,11,10,12。 992 | 993 | 整段代码,共进行了三次事件循环,完整的输出为1,7,6,8,2,4,3,5,9,11,10,12。 994 | 995 | ### 28. 代码输出结果 996 | 997 | ```js 998 | console.log(1) 999 | 1000 | setTimeout(() => { 1001 | console.log(2) 1002 | }) 1003 | 1004 | new Promise(resolve => { 1005 | console.log(3) 1006 | resolve(4) 1007 | }).then(d => console.log(d)) 1008 | 1009 | setTimeout(() => { 1010 | console.log(5) 1011 | new Promise(resolve => { 1012 | resolve(6) 1013 | }).then(d => console.log(d)) 1014 | }) 1015 | 1016 | setTimeout(() => { 1017 | console.log(7) 1018 | }) 1019 | 1020 | console.log(8) 1021 | ``` 1022 | 1023 | 输出结果如下: 1024 | 1025 | ```js 1026 | 1 1027 | 3 1028 | 8 1029 | 4 1030 | 2 1031 | 5 1032 | 6 1033 | 7 1034 | ``` 1035 | 1036 | 代码执行过程如下: 1037 | 1038 | 1. 首先执行script代码,打印出1; 1039 | 2. 遇到第一个定时器,加入到宏任务队列; 1040 | 3. 遇到Promise,执行代码,打印出3,遇到resolve,将其加入到微任务队列; 1041 | 4. 遇到第二个定时器,加入到宏任务队列; 1042 | 5. 遇到第三个定时器,加入到宏任务队列; 1043 | 6. 继续执行script代码,打印出8,第一轮执行结束; 1044 | 7. 执行微任务队列,打印出第一个Promise的resolve结果:4; 1045 | 8. 开始执行宏任务队列,执行第一个定时器,打印出2; 1046 | 9. 此时没有微任务,继续执行宏任务中的第二个定时器,首先打印出5,遇到Promise,首选打印出6,遇到resolve,将其加入到微任务队列; 1047 | 10. 执行微任务队列,打印出6; 1048 | 11. 执行宏任务队列中的最后一个定时器,打印出7。 1049 | 1050 | ### 29. 代码输出结果 1051 | 1052 | ```js 1053 | console.log(1); 1054 | 1055 | setTimeout(() => { 1056 | console.log(2); 1057 | Promise.resolve().then(() => { 1058 | console.log(3) 1059 | }); 1060 | }); 1061 | 1062 | new Promise((resolve, reject) => { 1063 | console.log(4) 1064 | resolve(5) 1065 | }).then((data) => { 1066 | console.log(data); 1067 | }) 1068 | 1069 | setTimeout(() => { 1070 | console.log(6); 1071 | }) 1072 | 1073 | console.log(7); 1074 | ``` 1075 | 1076 | 代码输出结果如下: 1077 | 1078 | ```js 1079 | 1 1080 | 4 1081 | 7 1082 | 5 1083 | 2 1084 | 3 1085 | 6 1086 | ``` 1087 | 1088 | 代码执行过程如下: 1089 | 1090 | 1. 首先执行scrip代码,打印出1; 1091 | 2. 遇到第一个定时器setTimeout,将其加入到宏任务队列; 1092 | 3. 遇到Promise,执行里面的同步代码,打印出4,遇到resolve,将其加入到微任务队列; 1093 | 4. 遇到第二个定时器setTimeout,将其加入到红任务队列; 1094 | 5. 执行script代码,打印出7,至此第一轮执行完成; 1095 | 6. 指定微任务队列中的代码,打印出resolve的结果:5; 1096 | 7. 执行宏任务中的第一个定时器setTimeout,首先打印出2,然后遇到 Promise.resolve().then(),将其加入到微任务队列; 1097 | 8. 执行完这个宏任务,就开始执行微任务队列,打印出3; 1098 | 9. 继续执行宏任务队列中的第二个定时器,打印出6。 1099 | 1100 | ### 30. 代码输出结果 1101 | 1102 | ```js 1103 | Promise.resolve().then(() => { 1104 | console.log('1'); 1105 | throw 'Error'; 1106 | }).then(() => { 1107 | console.log('2'); 1108 | }).catch(() => { 1109 | console.log('3'); 1110 | throw 'Error'; 1111 | }).then(() => { 1112 | console.log('4'); 1113 | }).catch(() => { 1114 | console.log('5'); 1115 | }).then(() => { 1116 | console.log('6'); 1117 | }); 1118 | ``` 1119 | 1120 | 执行结果如下: 1121 | 1122 | ```js 1123 | 1 1124 | 3 1125 | 5 1126 | 6 1127 | ``` 1128 | 1129 | 在这道题目中,我们需要知道,无论是thne还是catch中,只要throw 抛出了错误,就会被catch捕获,如果没有throw出错误,就被继续执行后面的then。 1130 | 1131 | ### 31. 代码输出结果 1132 | 1133 | ```js 1134 | setTimeout(function () { 1135 | console.log(1); 1136 | }, 100); 1137 | 1138 | new Promise(function (resolve) { 1139 | console.log(2); 1140 | resolve(); 1141 | console.log(3); 1142 | }).then(function () { 1143 | console.log(4); 1144 | new Promise((resove, reject) => { 1145 | console.log(5); 1146 | setTimeout(() => { 1147 | console.log(6); 1148 | }, 10); 1149 | }) 1150 | }); 1151 | console.log(7); 1152 | console.log(8); 1153 | ``` 1154 | 1155 | 输出结果为: 1156 | 1157 | ```js 1158 | 2 1159 | 3 1160 | 7 1161 | 8 1162 | 4 1163 | 5 1164 | 6 1165 | 1 1166 | ``` 1167 | 1168 | 代码执行过程如下: 1169 | 1170 | 1. 首先遇到定时器,将其加入到宏任务队列; 1171 | 2. 遇到Promise,首先执行里面的同步代码,打印出2,遇到resolve,将其加入到微任务队列,执行后面同步代码,打印出3; 1172 | 3. 继续执行script中的代码,打印出7和8,至此第一轮代码执行完成; 1173 | 4. 执行微任务队列中的代码,首先打印出4,如遇到Promise,执行其中的同步代码,打印出5,遇到定时器,将其加入到宏任务队列中,此时宏任务队列中有两个定时器; 1174 | 5. 执行宏任务队列中的代码,这里我们需要注意是的第一个定时器的时间为100ms,第二个定时器的时间为10ms,所以先执行第二个定时器,打印出6; 1175 | 6. 此时微任务队列为空,继续执行宏任务队列,打印出1。 1176 | 1177 | 做完这道题目,我们就需要格外注意,每个定时器的时间,并不是所有定时器的时间都为0哦。 1178 | 1179 | ## 二、this 1180 | 1181 | ------ 1182 | 1183 | ### 1. 代码输出结果 1184 | 1185 | ```js 1186 | function foo() { 1187 | console.log( this.a ); 1188 | } 1189 | 1190 | function doFoo() { 1191 | foo(); 1192 | } 1193 | 1194 | var obj = { 1195 | a: 1, 1196 | doFoo: doFoo 1197 | }; 1198 | 1199 | var a = 2; 1200 | obj.doFoo() 1201 | ``` 1202 | 1203 | 输出结果:2 1204 | 1205 | 在Javascript中,this指向函数执行时的当前对象。在执行foo的时候,执行环境就是doFoo函数,执行环境为全局。所以,foo中的this是指向window的,所以会打印出2。 1206 | 1207 | ### 2. 代码输出结果 1208 | 1209 | ```js 1210 | var a = 10 1211 | var obj = { 1212 | a: 20, 1213 | say: () => { 1214 | console.log(this.a) 1215 | } 1216 | } 1217 | obj.say() 1218 | 1219 | var anotherObj = { a: 30 } 1220 | obj.say.apply(anotherObj) 1221 | ``` 1222 | 1223 | 输出结果:10 10 1224 | 1225 | 我么知道,箭头函数时不绑定this的,它的this来自原其父级所处的上下文,所以首先会打印全局中的 a 的值10。后面虽然让say方法指向了另外一个对象,但是仍不能改变箭头函数的特性,它的this仍然是指向全局的,所以依旧会输出10。 1226 | 1227 | 但是,如果是普通函数,那么就会有完全不一样的结果: 1228 | 1229 | ```js 1230 | var a = 10 1231 | var obj = { 1232 | a: 20, 1233 | say(){ 1234 | console.log(this.a) 1235 | } 1236 | } 1237 | obj.say() 1238 | var anotherObj={a:30} 1239 | obj.say.apply(anotherObj) 1240 | ``` 1241 | 1242 | 输出结果:20 30 1243 | 1244 | 这时,say方法中的this就会指向他所在的对象,输出其中的a的值。 1245 | 1246 | ### 3. 代码输出结果 1247 | 1248 | ```js 1249 | function a() { 1250 | console.log(this); 1251 | } 1252 | a.call(null); 1253 | ``` 1254 | 1255 | 打印结果:window对象 1256 | 1257 | 根据ECMAScript262规范规定:如果第一个参数传入的对象调用者是null或者undefined,call方法将把全局对象(浏览器上是window对象)作为this的值。所以,不管传入null 还是 undefined,其this都是全局对象window。所以,在浏览器上答案是输出 window 对象。 1258 | 1259 | 要注意的是,在严格模式中,null 就是 null,undefined 就是 undefined: 1260 | 1261 | ```js 1262 | 'use strict'; 1263 | 1264 | function a() { 1265 | console.log(this); 1266 | } 1267 | a.call(null); // null 1268 | a.call(undefined); // undefined 1269 | ``` 1270 | 1271 | ### 4. 代码输出结果 1272 | 1273 | ```js 1274 | var obj = { 1275 | name: 'cuggz', 1276 | fun: function(){ 1277 | console.log(this.name); 1278 | } 1279 | } 1280 | obj.fun() // cuggz 1281 | new obj.fun() // undefined 1282 | ``` 1283 | 1284 | ### 6. 代码输出结果 1285 | 1286 | ```js 1287 | var obj = { 1288 | say: function() { 1289 | var f1 = () => { 1290 | console.log("1111", this); 1291 | } 1292 | f1(); 1293 | }, 1294 | pro: { 1295 | getPro:() => { 1296 | console.log(this); 1297 | } 1298 | } 1299 | } 1300 | var o = obj.say; 1301 | o(); 1302 | obj.say(); 1303 | obj.pro.getPro(); 1304 | ``` 1305 | 1306 | 输出结果: 1307 | 1308 | ```js 1309 | 1111 window对象 1310 | 1111 obj对象 1311 | window对象 1312 | ``` 1313 | 1314 | **解析:** 1315 | 1316 | 1. o(),o是在全局执行的,而f1是箭头函数,它是没有绑定this的,它的this指向其父级的this,其父级say方法的this指向的是全局作用域,所以会打印出window; 1317 | 2. obj.say(),谁调用say,say 的this就指向谁,所以此时this指向的是obj对象; 1318 | 3. obj.pro.getPro(),我们知道,箭头函数时不绑定this的,getPro处于pro中,而对象不构成单独的作用域,所以箭头的函数的this就指向了全局作用域window。 1319 | 1320 | ### 7. 代码输出结果 1321 | 1322 | ```js 1323 | var myObject = { 1324 | foo: "bar", 1325 | func: function() { 1326 | var self = this; 1327 | console.log(this.foo); 1328 | console.log(self.foo); 1329 | (function() { 1330 | console.log(this.foo); 1331 | console.log(self.foo); 1332 | }()); 1333 | } 1334 | }; 1335 | myObject.func(); 1336 | ``` 1337 | 1338 | 输出结果:bar bar undefined bar 1339 | 1340 | **解析:** 1341 | 1342 | 1. 首先func是由myObject调用的,this指向myObject。又因为var self = this;所以self指向myObject。 1343 | 2. 这个立即执行匿名函数表达式是由window调用的,this指向window 。立即执行匿名函数的作用域处于myObject.func的作用域中,在这个作用域找不到self变量,沿着作用域链向上查找self变量,找到了指向 myObject对象的self。 1344 | 1345 | ### 8. 代码输出问题 1346 | 1347 | ```js 1348 | window.number = 2; 1349 | var obj = { 1350 | number: 3, 1351 | db1: (function(){ 1352 | console.log(this); 1353 | this.number *= 4; 1354 | return function(){ 1355 | console.log(this); 1356 | this.number *= 5; 1357 | } 1358 | })() 1359 | } 1360 | var db1 = obj.db1; 1361 | db1(); 1362 | obj.db1(); 1363 | console.log(obj.number); 1364 | console.log(window.number); 1365 | ``` 1366 | 1367 | 这道题目看清起来有点乱,但是实际上是考察this指向的: 1368 | 1369 | 先执行obj的自执行函数,相当于 1370 | 1371 | ​ window.number = 8 1372 | 1373 | ​ obj = { 1374 | 1375 | ​ number :3, 1376 | 1377 | ​ db1:function(){ 1378 | 1379 | ​ .... 1380 | 1381 | ​ } 1382 | 1383 | ​ } 1384 | 1385 | 1. 执行db1()时,this指向全局作用域,所以window.number * 4 = 8,然后执行匿名函数, 所以window.number * 5 = 40; 1386 | 2. 执行obj.db1();时,this指向obj对象,执行匿名函数,所以obj.numer * 5 = 15。 1387 | 1388 | ### 9. 代码输出结果 1389 | 1390 | ```js 1391 | var length = 10; 1392 | function fn() { 1393 | console.log(this.length); 1394 | } 1395 | 1396 | var obj = { 1397 | length: 5, 1398 | method: function(fn) { 1399 | fn(); 1400 | arguments[0](); 1401 | } 1402 | }; 1403 | 1404 | obj.method(fn, 1); 1405 | ``` 1406 | 1407 | 输出结果: 10 2 1408 | 1409 | **解析:** 1410 | 1411 | 因为arguments[0],相当于obj.fn() 1412 | 1413 | 1. 第一次执行fn(),this指向window对象,输出10。 1414 | 2. 第二次执行arguments0,相当于arguments调用方法,this指向arguments,而这里传了两个参数,故输出arguments长度为2。 1415 | 1416 | ### 10. 代码输出结果 1417 | 1418 | ```js 1419 | var a = 1; 1420 | function printA(){ 1421 | console.log(this.a); 1422 | } 1423 | var obj={ 1424 | a:2, 1425 | foo:printA, 1426 | bar:function(){ 1427 | printA(); 1428 | } 1429 | } 1430 | 1431 | obj.foo(); 1432 | obj.bar(); 1433 | var foo = obj.foo; 1434 | foo(); // 1 1435 | ``` 1436 | 1437 | 输出结果: 2 1 1 1438 | 1439 | **解析:** 1440 | 1441 | 1. obj.foo(),foo 的this指向obj对象,所以a会输出2; 1442 | 2. obj.bar(),printA在bar方法中执行,所以此时printA的this指向的是window,所以会输出1; 1443 | 3. foo(),foo是在全局对象中执行的,所以其this指向的是window,所以会输出1; 1444 | 1445 | ### 11. 代码输出结果 1446 | 1447 | ```js 1448 | var x = 3; 1449 | var y = 4; 1450 | var obj = { 1451 | x: 1, 1452 | y: 6, 1453 | getX: function() { 1454 | var x = 5; 1455 | return function() { 1456 | return this.x; 1457 | }(); 1458 | }, 1459 | getY: function() { 1460 | var y = 7; 1461 | return this.y; 1462 | } 1463 | } 1464 | console.log(obj.getX()) // 3 1465 | console.log(obj.getY()) // 6 1466 | ``` 1467 | 1468 | 输出结果:3 6 1469 | 1470 | **解析:** 1471 | 1472 | 1. 我们知道,匿名函数的this是指向全局对象的,所以this指向window,会打印出3; 1473 | 2. getY是由obj调用的,所以其this指向的是obj对象,会打印出6。 1474 | 1475 | ### 12. 代码输出结果 1476 | 1477 | ```js 1478 | var a = 10; 1479 | var obt = { 1480 | a: 20, 1481 | fn: function(){ 1482 | var a = 30; 1483 | console.log(this.a) 1484 | } 1485 | } 1486 | obt.fn(); // 20 1487 | obt.fn.call(); // 10 1488 | (obt.fn)(); // 20 1489 | ``` 1490 | 1491 | 输出结果: 20 10 20 1492 | 1493 | **解析:** 1494 | 1495 | 1. obt.fn(),fn是由obt调用的,所以其this指向obt对象,会打印出20; 1496 | 2. obt.fn.call(),这里call的参数啥都没写,就表示null,我们知道如果call的参数为undefined或null,那么this就会指向全局对象this,所以会打印出 10; 1497 | 3. (obt.fn)(), 这里给表达式加了括号,而括号的作用是改变表达式的运算顺序,而在这里加与不加括号并无影响;相当于 obt.fn(),所以会打印出 20; 1498 | 1499 | ### 13. 代码输出结果 1500 | 1501 | ```js 1502 | function a(xx){ 1503 | this.x = xx; 1504 | return this 1505 | }; 1506 | var x = a(5); 1507 | y = null 1508 | 1509 | console.log(x.x) 1510 | console.log(y.x) 1511 | ``` 1512 | 1513 | 输出结果: undefined 6 1514 | 1515 | **解析:** 1516 | 1517 | 1. 最关键的就是var x = a(5),函数a是在全局作用域调用,所以函数内部的this指向window对象。**所以 this.x = 5 就相当于:window.x = 5。**之后 return this,也就是说 var x = a(5) 中的x变量的值是window,这里的x将函数内部的x的值覆盖了。然后执行console.log(x.x), 也就是console.log(window.x),而window对象中没有x属性,所以会输出undefined。 1518 | 2. 当指向y.x时,会给全局变量中的x赋值为6,所以会打印出6。 1519 | 1520 | ### 14. 代码输出结果 1521 | 1522 | ```js 1523 | function foo(something){ 1524 | this.a = something 1525 | } 1526 | 1527 | var obj1 = { 1528 | foo: foo 1529 | } 1530 | 1531 | var obj2 = {} 1532 | 1533 | obj1.foo(2); 1534 | console.log(obj1.a); 1535 | 1536 | obj1.foo.call(obj2, 3); 1537 | console.log(obj2.a); 1538 | 1539 | var bar = new obj1.foo(4) 1540 | console.log(obj1.a); 1541 | console.log(bar.a); 1542 | ``` 1543 | 1544 | 输出结果: 2 3 2 4 1545 | 1546 | **解析:** 1547 | 1548 | 1. 首先执行obj1.foo(2); 会在obj中添加a属性,其值为2。之后执行obj1.a,a是右obj1调用的,所以this指向obj,打印出2; 1549 | 2. 执行 obj1.foo.call(obj2, 3) 时,会将foo的this指向obj2,后面就和上面一样了,所以会打印出3; 1550 | 3. obj1.a会打印出2; 1551 | 4. 最后就是考察this绑定的优先级了,new 绑定是比隐式绑定优先级高,所以会输出4。 1552 | 1553 | ### 15. 代码输出结果 1554 | 1555 | ```js 1556 | function foo(something){ 1557 | this.a = something 1558 | } 1559 | 1560 | var obj1 = {} 1561 | 1562 | var bar = foo.bind(obj1); 1563 | bar(2); 1564 | console.log(obj1.a); 1565 | 1566 | var baz = new bar(3); 1567 | console.log(obj1.a); 1568 | console.log(baz.a); 1569 | ``` 1570 | 1571 | 输出结果: 2 2 3 1572 | 1573 | 这道题目和上面题目差不多,主要都是考察this绑定的优先级。记住以下结论即可:**this绑定的优先级:****new绑定 > 显式绑定 > 隐式绑定 > 默认绑定。** 1574 | 1575 | ## 三、作用域&变量提升&闭包 1576 | 1577 | ------ 1578 | 1579 | ### 1. 代码输出结果 1580 | 1581 | ```js 1582 | (function(){ 1583 | var x = y = 1; 1584 | })(); 1585 | var z; 1586 | 1587 | console.log(y); // 1 1588 | console.log(z); // undefined 1589 | console.log(x); // Uncaught ReferenceError: x is not defined 1590 | ``` 1591 | 1592 | 这段代码的关键在于:var x = y = 1; 实际上这里是从右往左执行的,首先执行y = 1, 因为y没有使用var声明,所以它是一个全局变量,然后第二步是将y赋值给x,讲一个全局变量赋值给了一个局部变量,最终,x是一个局部变量,y是一个全局变量,所以打印x是报错。 1593 | 1594 | ### 2. 代码输出结果 1595 | 1596 | ```js 1597 | var a, b 1598 | (function () { 1599 | console.log(a); 1600 | console.log(b); 1601 | var a = (b = 3); 1602 | console.log(a); 1603 | console.log(b); 1604 | })() 1605 | console.log(a); 1606 | console.log(b); 1607 | ``` 1608 | 1609 | 输出结果: 1610 | 1611 | ```js 1612 | undefined 1613 | undefined 1614 | 3 1615 | 3 1616 | undefined 1617 | 3 1618 | ``` 1619 | 1620 | 这个题目和上面题目考察的知识点类似,b赋值为3,b此时是一个全局变量,而将3赋值给a,a是一个局部变量,所以最后打印的时候,a仍旧是undefined。 1621 | 1622 | ### 3. 代码输出结果 1623 | 1624 | ```js 1625 | var friendName = 'World'; 1626 | (function() { 1627 | if (typeof friendName === 'undefined') { 1628 | var friendName = 'Jack'; 1629 | console.log('Goodbye ' + friendName); 1630 | } else { 1631 | console.log('Hello ' + friendName); 1632 | } 1633 | })(); 1634 | ``` 1635 | 1636 | 输出结果:Goodbye Jack 1637 | 1638 | 我们知道,在 JavaScript中, Function 和 var 都会被提升(变量提升),所以上面的代码就相当于: 1639 | 1640 | ```js 1641 | var name = 'World!'; 1642 | (function () { 1643 | var name; 1644 | if (typeof name === 'undefined') { 1645 | name = 'Jack'; 1646 | console.log('Goodbye ' + name); 1647 | } else { 1648 | console.log('Hello ' + name); 1649 | } 1650 | })(); 1651 | ``` 1652 | 1653 | 这样,答案就一目了然了。 1654 | 1655 | ### 4. 代码输出结果 1656 | 1657 | ```js 1658 | function fn1(){ 1659 | console.log('fn1') 1660 | } 1661 | var fn2 1662 | 1663 | fn1() 1664 | fn2() 1665 | 1666 | fn2 = function() { 1667 | console.log('fn2') 1668 | } 1669 | 1670 | fn2() 1671 | ``` 1672 | 1673 | 输出结果: 1674 | 1675 | ```js 1676 | fn1 1677 | Uncaught TypeError: fn2 is not a function 1678 | fn2 1679 | ``` 1680 | 1681 | 这里也是在考察变量提升,关键在于第一个fn2(),这时fn2仍是一个undefined的变量,所以会报错fn2不是一个函数。 1682 | 1683 | ### 5. 代码输出结果 1684 | 1685 | ```js 1686 | function a() { 1687 | var temp = 10; 1688 | function b() { 1689 | console.log(temp); // 10 1690 | } 1691 | b(); 1692 | } 1693 | a(); 1694 | 1695 | function a() { 1696 | var temp = 10; 1697 | b(); 1698 | } 1699 | function b() { 1700 | console.log(temp); // 报错 Uncaught ReferenceError: temp is not defined 1701 | } 1702 | a(); 1703 | ``` 1704 | 1705 | 在上面的两段代码中,第一段是可以正常输出,这个应该没啥问题,关键在于第二段代码,它会报错Uncaught ReferenceError: temp is not defined。这时因为在b方法执行时,temp 的值为undefined。 1706 | 1707 | ### 6. 代码输出结果 1708 | 1709 | ```js 1710 | var a=3; 1711 | function c(){ 1712 | alert(a); 1713 | } 1714 | (function(){ 1715 | var a=4; 1716 | c(); 1717 | })(); 1718 | ``` 1719 | 1720 | js中变量的作用域链与定义时的环境有关,与执行时无关。执行环境只会改变this、传递的参数、全局变量等 1721 | 1722 | ### 7. 代码输出问题 1723 | 1724 | ```js 1725 | function fun(n, o) { 1726 | console.log(o) 1727 | return { 1728 | fun: function(m){ 1729 | return fun(m, n); 1730 | } 1731 | }; 1732 | } 1733 | var a = fun(0); a.fun(1); a.fun(2); a.fun(3); 1734 | var b = fun(0).fun(1).fun(2).fun(3); 1735 | var c = fun(0).fun(1); c.fun(2); c.fun(3); 1736 | ``` 1737 | 1738 | 输出结果: 1739 | 1740 | ```js 1741 | undefined 0 0 0 1742 | undefined 0 1 2 1743 | undefined 0 1 1 1744 | ``` 1745 | 1746 | 这是一道关于闭包的题目,对于fun方法,调用之后返回的是一个对象。我们知道,当调用函数的时候传入的实参比函数声明时指定的形参个数要少,剩下的形参都将设置为undefined值。所以 `console.log(o);` 会输出undefined。而a就是是fun(0)返回的那个对象。也就是说,函数fun中参数 n 的值是0,而返回的那个对象中,需要一个参数n,而这个对象的作用域中没有n,它就继续沿着作用域向上一级的作用域中寻找n,最后在函数fun中找到了n,n的值是0。了解了这一点,其他运算就很简单了,以此类推。 1747 | 1748 | ### 8. 代码输出结果 1749 | 1750 | ```js 1751 | f = function() {return true;}; 1752 | g = function() {return false;}; 1753 | (function() { 1754 | if (g() && [] == ![]) { 1755 | f = function f() {return false;}; 1756 | function g() {return true;} //在匿名函数内部发生函数提升 因此if判断中的g()返回true 1757 | } 1758 | })(); 1759 | console.log(f()); 1760 | ``` 1761 | 1762 | 输出结果: false 1763 | 1764 | 这里首先定义了两个变量f和g,我们知道变量是可以重新赋值的。后面是一个匿名自执行函数,在 if 条件中调用了函数 g(),由于在匿名函数中,又重新定义了函数g,就覆盖了外部定义的变量g,所以,这里调用的是内部函数 g 方法,返回为 true。第一个条件通过,进入第二个条件。 1765 | 1766 | 第二个条件是[] == ![],先看 ![] ,在 JavaScript 中,当用于布尔运算时,比如在这里,对象的非空引用被视为 true,空引用 null 则被视为 false。由于这里不是一个 null, 而是一个没有元素的数组,所以 [] 被视为 true, 而 ![] 的结果就是 false 了。当一个布尔值参与到条件运算的时候,true 会被看作 1, 而 false 会被看作 0。现在条件变成了 [] == 0 的问题了,当一个对象参与条件比较的时候,它会被求值,求值的结果是数组成为一个字符串,[] 的结果就是 '' ,而 '' 会被当作 0 ,所以,条件成立。 1767 | 1768 | 两个条件都成立,所以会执行条件中的代码, f 在定义是没有使用var,所以他是一个全局变量。因此,这里会通过闭包访问到外部的变量 f, 重新赋值,现在执行 f 函数返回值已经成为 false 了。而 g 则不会有这个问题,这里是一个函数内定义的 g,不会影响到外部的 g 函数。所以最后的结果就是 false。 1769 | 1770 | ## 四、原型&继承 1771 | 1772 | ------ 1773 | 1774 | ### 1. 代码输出结果 1775 | 1776 | ```js 1777 | function Person(name) { 1778 | this.name = name 1779 | } 1780 | var p2 = new Person('king'); 1781 | console.log(p2.__proto__) //Person.prototype 1782 | console.log(p2.__proto__.__proto__) //Object.prototype 1783 | console.log(p2.__proto__.__proto__.__proto__) // null 1784 | console.log(p2.__proto__.__proto__.__proto__.__proto__)//null后面没有了,报错 1785 | console.log(p2.__proto__.__proto__.__proto__.__proto__.__proto__)//null后面没有了,报错 1786 | console.log(p2.constructor)//Person 1787 | console.log(p2.prototype)//undefined p2是实例,没有prototype属性 1788 | console.log(Person.constructor)//Function 一个空函数 1789 | console.log(Person.prototype)//打印出Person.prototype这个对象里所有的方法和属性 1790 | console.log(Person.prototype.constructor)//Person 1791 | console.log(Person.prototype.__proto__)// Object.prototype 1792 | console.log(Person.__proto__) //Function.prototype 1793 | console.log(Function.prototype.__proto__)//Object.prototype 1794 | console.log(Function.__proto__)//Function.prototype 1795 | console.log(Object.__proto__)//Function.prototype 1796 | console.log(Object.prototype.__proto__)//null 1797 | ``` 1798 | 1799 | 这道义题目考察原型、原型链的基础,记住就可以了。 1800 | 1801 | ### 2. 代码输出结果 1802 | 1803 | ```js 1804 | // a 1805 | function Foo () { 1806 | getName = function () { 1807 | console.log(1); 1808 | } 1809 | return this; 1810 | } 1811 | // b 1812 | Foo.getName = function () { 1813 | console.log(2); 1814 | } 1815 | // c 1816 | Foo.prototype.getName = function () { 1817 | console.log(3); 1818 | } 1819 | // d 1820 | var getName = function () { 1821 | console.log(4); 1822 | } 1823 | // e 1824 | function getName () { 1825 | console.log(5); 1826 | } 1827 | 1828 | Foo.getName(); // 2 1829 | getName(); // 4 1830 | Foo().getName(); // 1 1831 | getName(); // 1 1832 | new Foo.getName(); // 2 1833 | new Foo().getName(); // 3 1834 | new new Foo().getName(); // 3 1835 | ``` 1836 | 1837 | 输出结果:2 4 1 1 2 3 3 1838 | 1839 | **解析:** 1840 | 1841 | 1. **Foo.getName()**,Foo为一个函数对象,对象都可以有属性,b 处定义Foo的getName属性为函数,输出2; 1842 | 2. **getName()**,这里看d、e处,d为函数表达式,e为函数声明,两者区别在于变量提升,函数声明的 5 会被后边函数表达式的 4 覆盖; 1843 | 3. **Foo().getName()**,这里要看a处,在Foo内部将全局的getName重新赋值为 console.log(1) 的函数,执行Foo()返回 this,这个this指向window,Foo().getName() 即为window.getName(),输出 1; 1844 | 4. **getName()**,上面3中,全局的getName已经被重新赋值,所以这里依然输出 1; 1845 | 5. **new Foo.getName()**,这里等价于 new (Foo.getName()),先执行 Foo.getName(),输出 2,然后new一个实例; 1846 | 6. **new Foo().getName()**,这里等价于 (new Foo()).getName(), 先new一个Foo的实例,再执行这个实例的getName方法,但是这个实例本身没有这个方法,所以去原型链__protot__上边找,实例.protot === Foo.prototype,所以输出 3; 1847 | 7. **new new Foo().getName()**,这里等价于new (new Foo().getName()),如上述6,先输出 3,然后new 一个 new Foo().getName() 的实例。 1848 | 1849 | ### 3. 代码输出结果 1850 | 1851 | ```js 1852 | var F = function() {}; 1853 | Object.prototype.a = function() { 1854 | console.log('a'); 1855 | }; 1856 | Function.prototype.b = function() { 1857 | console.log('b'); 1858 | } 1859 | var f = new F(); 1860 | f.a(); 1861 | f.b(); 1862 | F.a(); 1863 | F.b() 1864 | ``` 1865 | 1866 | 输出结果: 1867 | 1868 | ```js 1869 | a 1870 | Uncaught TypeError: f.b is not a function 1871 | a 1872 | b 1873 | ``` 1874 | 1875 | **解析:** 1876 | 1877 | 1. f 并不是 Function 的实例,因为它本来就不是构造函数,调用的是 Function 原型链上的相关属性和方法,只能访问到 Object 原型链。所以 f.a() 输出 a ,而 f.b() 就报错了。 1878 | 2. F 是个构造函数,而 F 是构造函数 Function 的一个实例。因为 F instanceof Object === true,F instanceof Function === true,由此可以得出结论:F 是 Object 和 Function 两个的实例,即 F 能访问到 a, 也能访问到 b。所以 F.a() 输出 a ,F.b() 输出 b。 1879 | 1880 | ### 4. 代码输出结果 1881 | 1882 | ```js 1883 | function Foo(){ 1884 | Foo.a = function(){ 1885 | console.log(1); 1886 | } 1887 | this.a = function(){ 1888 | console.log(2) 1889 | } 1890 | } 1891 | 1892 | Foo.prototype.a = function(){ 1893 | console.log(3); 1894 | } 1895 | 1896 | Foo.a = function(){ 1897 | console.log(4); 1898 | } 1899 | 1900 | Foo.a(); 1901 | let obj = new Foo(); 1902 | obj.a(); 1903 | Foo.a(); 1904 | ``` 1905 | 1906 | 输出结果:4 2 1 1907 | 1908 | **解析:** 1909 | 1910 | 1. Foo.a() 这个是调用 Foo 函数的静态方法 a,虽然 Foo 中有优先级更高的属性方法 a,但 Foo 此时没有被调用,所以此时输出 Foo 的静态方法 a 的结果:4 1911 | 2. let obj = new Foo(); 使用了 new 方法调用了函数,返回了函数实例对象,此时 Foo 函数内部的属性方法初始化,原型链建立。 1912 | 3. obj.a() ; 调用 obj 实例上的方法 a,该实例上目前有两个 a 方法:一个是内部属性方法,另一个是原型上的方法。当这两者都存在时,首先查找 ownProperty ,如果没有才去原型链上找,所以调用实例上的 a 输出:2 1913 | 4. Foo.a() ; 根据第2步可知 Foo 函数内部的属性方法已初始化,覆盖了同名的静态方法,所以输出:1 1914 | 1915 | ### 5. 代码输出结果 1916 | 1917 | ```js 1918 | function Dog() { 1919 | this.name = 'puppy' 1920 | } 1921 | Dog.prototype.bark = () => { 1922 | console.log('woof!woof!') 1923 | } 1924 | const dog = new Dog() 1925 | console.log(Dog.prototype.constructor === Dog && dog.constructor === Dog && dog instanceof Dog) 1926 | ``` 1927 | 1928 | 输出结果:true 1929 | 1930 | **解析:** 1931 | 1932 | 因为constructor是prototype上的属性,所以dog.constructor实际上就是指向Dog.prototype.constructor;constructor属性指向构造函数。instanceof而实际检测的是类型是否在实例的原型链上。 1933 | 1934 | constructor是prototype上的属性,这一点很容易被忽略掉。constructor和instanceof 的作用是不同的,感性地来说,constructor的限制比较严格,它只能严格对比对象的构造函数是不是指定的值;而instanceof比较松散,只要检测的类型在原型链上,就会返回true。 1935 | 1936 | ### 6. 代码输出结果 1937 | 1938 | ```js 1939 | var A = {n: 4399}; 1940 | var B = function(){this.n = 9999}; 1941 | var C = function(){var n = 8888}; 1942 | B.prototype = A; 1943 | C.prototype = A; 1944 | var b = new B(); 1945 | var c = new C(); 1946 | A.n++ 1947 | console.log(b.n); 1948 | console.log(c.n); 1949 | ``` 1950 | 1951 | 输出结果:9999 4400 1952 | 1953 | **解析:** 1954 | 1955 | 1. console.log(b.n),在查找b.n是首先查找 b 对象自身有没有 n 属性,如果没有会去原型(prototype)上查找,当执行var b = new B()时,函数内部this.n=9999(此时this指向 b) 返回b对象,b对象有自身的n属性,所以返回 9999。 1956 | 2. console.log(c.n),同理,当执行var c = new C()时,c对象没有自身的n属性,向上查找,找到原型 (prototype)上的 n 属性,因为 A.n++(此时对象A中的n为4400), 所以返回4400。 1957 | 1958 | ### 7. 代码输出问题 1959 | 1960 | ```js 1961 | function A(){ 1962 | } 1963 | function B(a){ 1964 |   this.a = a; 1965 | } 1966 | function C(a){ 1967 |   if(a){ 1968 | this.a = a; 1969 |   } 1970 | } 1971 | A.prototype.a = 1; 1972 | B.prototype.a = 1; 1973 | C.prototype.a = 1; 1974 | 1975 | console.log(new A().a); 1976 | console.log(new B().a); 1977 | console.log(new C(2).a); 1978 | ``` 1979 | 1980 | 输出结果:1 undefined 2 1981 | 1982 | **解析:** 1983 | 1984 | 1. console.log(new A().a),new A()为构造函数创建的对象,本身没有a属性,所以向它的原型去找,发现原型的a属性的属性值为1,故该输出值为1; 1985 | 2. console.log(new B().a),ew B()为构造函数创建的对象,该构造函数有参数a,但该对象没有传参,故该输出值为undefined; 1986 | 3. console.log(new C(2).a),new C()为构造函数创建的对象,该构造函数有参数a,且传的实参为2,执行函数内部,发现if为真,执行this.a = 2,故属性a的值为2。 1987 | 1988 | ### 8 代码输出问题 1989 | 1990 | ```js 1991 | function Parent() { 1992 | this.a = 1; 1993 | this.b = [1, 2, this.a]; 1994 | this.c = { demo: 5 }; 1995 | this.show = function () { 1996 | console.log(this.a , this.b , this.c.demo ); 1997 | } 1998 | } 1999 | 2000 | function Child() { 2001 | this.a = 2; 2002 | this.change = function () { 2003 | this.b.push(this.a); 2004 | this.a = this.b.length; 2005 | this.c.demo = this.a++; 2006 | } 2007 | } 2008 | 2009 | Child.prototype = new Parent(); 2010 | var parent = new Parent(); 2011 | var child1 = new Child(); 2012 | var child2 = new Child(); 2013 | child1.a = 11; 2014 | child2.a = 12; 2015 | parent.show(); 2016 | child1.show(); 2017 | child2.show(); 2018 | child1.change(); 2019 | child2.change(); 2020 | parent.show(); 2021 | child1.show(); 2022 | child2.show(); 2023 | ``` 2024 | 2025 | 输出结果: 2026 | 2027 | ```js 2028 | parent.show(); // 1 [1,2,1] 5 2029 | 2030 | child1.show(); // 11 [1,2,1] 5 2031 | child2.show(); // 12 [1,2,1] 5 2032 | 2033 | parent.show(); // 1 [1,2,1] 5 2034 | 2035 | child1.show(); // 5 [1,2,1,11,12] 5 2036 | 2037 | child2.show(); // 6 [1,2,1,11,12] 5 2038 | ``` 2039 | 2040 | 这道题目值得审题,他涉及到的知识点很多,例如**this的指向、原型、原型链、类的继承、数据类型**等。 2041 | 2042 | **解析**: 2043 | 2044 | 1. parent.show(),可以直接获得所需的值,没啥好说的; 2045 | 2046 | 2. child1.show(),`Child`的构造函数原本是指向 `Child`的,题目显式将 `Child`类的原型对象指向了 `Parent`类的一个实例,需要注意 `Child.prototype`指向的是 `Parent`的实例 `parent`,而不是指向 `Parent`这个类。 2047 | 2048 | 3. child2.show(),这个也没啥好说的; 2049 | 2050 | 4. parent.show(),`parent`是一个 `Parent`类的实例,`Child.prorotype`指向的是 `Parent`类的另一个实例,两者在堆内存中互不影响,所以上述操作不影响 `parent`实例,所以输出结果不变; 2051 | 2052 | 5. child1.show(),`child1`执行了 `change()`方法后,发生了怎样的变化呢? 2053 | 2054 | 6. - this.b.push(this.a),由于this的动态指向特性,this.b会指向 `Child.prototype`上的b数组,this.a会指向 `child1`的a属性,所以 `Child.prototype.b`变成了**[1,2,1,11]**; 2055 | - this.a = this.b.length,这条语句中 `this.a`和 `this.b`的指向与上一句一致,故结果为 `child1.a`变为4; 2056 | - this.c.demo = this.a++,由于 `child1`自身属性并没有c这个属性,所以此处的 `this.c`会指向 `Child.prototype.c`,`this.a`值为4,为原始类型,故赋值操作时会直接赋值,`Child.prototype.c.demo`的结果为4,而 `this.a`随后自增为5(4 + 1 = 5)。 2057 | 2058 | 7. `child2`执行了 `change()`方法, 而 `child2`和 `child1`均是 `Child`类的实例,所以他们的原型链指向同一个原型对象 `Child.prototype`,也就是同一个 `parent`实例,所以 `child2.change()`中所有影响到原型对象的语句都会影响 `child1`的最终输出结果。 2059 | 2060 | 8. - this.b.push(this.a),由于this的动态指向特性,this.b会指向 `Child.prototype`上的b数组,this.a会指向 `child2`的a属性,所以 `Child.prototype.b`变成了**[1,2,1,11,12]**; 2061 | - this.a = this.b.length,这条语句中 `this.a`和 `this.b`的指向与上一句一致,故结果为 `child2.a`变为5; 2062 | - this.c.demo = this.a++,由于 `child2`自身属性并没有c这个属性,所以此处的 `this.c`会指向 `Child.prototype.c`,故执行结果为 `Child.prototype.c.demo`的值变为 `child2.a`的值5,而 `child2.a`最终自增为6(5 + 1 = 6)。 2063 | 2064 | ### 9. 代码输出结果 2065 | 2066 | ```js 2067 | function SuperType(){ 2068 | this.property = true; 2069 | } 2070 | 2071 | SuperType.prototype.getSuperValue = function(){ 2072 | return this.property; 2073 | }; 2074 | 2075 | function SubType(){ 2076 | this.subproperty = false; 2077 | } 2078 | 2079 | SubType.prototype = new SuperType(); 2080 | SubType.prototype.getSubValue = function (){ 2081 | return this.subproperty; 2082 | }; 2083 | 2084 | var instance = new SubType(); 2085 | console.log(instance.getSuperValue()); 2086 | ``` 2087 | 2088 | 输出结果:true 2089 | 2090 | 实际上,这段代码就是在实现原型链继承,SubType继承了SuperType,本质是重写了SubType的原型对象,代之以一个新类型的实例。SubType的原型被重写了,所以instance.constructor指向的是SuperType。具体如下: 2091 | 2092 | ![img](https://gitee.com/nest-of-old-time/picture/raw/master/typora/202401161015586.png) 2093 | -------------------------------------------------------------------------------- /小程序面试题/小程序面试题.md: -------------------------------------------------------------------------------- 1 | ## 1.小程序开发中常见的问题有哪些? 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | ## 2.小程序开发过程中遇到过什么比较困难的问题?怎么解决的 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | ## 3.uni-app开发的优势?常见的兼容性问题? 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | ## 4.小程序中有哪些性能优化方法 26 | 27 | 28 | 29 | 30 | 31 | ## 5.bindtap和catchtap的区别 32 | 33 | 34 | 35 | 36 | 37 | ## 6.小程序页面之间如何传递数据? 38 | 39 | 40 | 41 | 42 | 43 | ## 7.小程序中,App 与 Page 分别有哪些生命周期,常用的有哪些? 44 | 45 | 46 | 47 | 48 | 49 | ## 8.小程序的支付做过吗?大致流程是怎么样的? 50 | 51 | 52 | 53 | 54 | 55 | ## 9.小程序的热启动与冷启动分别是什么? 56 | 57 | 58 | 59 | 60 | 61 | ## 10.在小程序中,你通常会关注那些性能指标? 62 | 63 | 64 | 65 | 66 | 67 | ## 11.为什么我们能使用 Vue/React DSL 编写小程序? 68 | 69 | 70 | 71 | 72 | 73 | ## 12.聊聊 jsbridge 的基本原理 -------------------------------------------------------------------------------- /常见手写题/js基础手写/1.16手写.js: -------------------------------------------------------------------------------- 1 | class MyPromise { 2 | constructor(callback) { 3 | this.status = "pending" 4 | this.value = undefined 5 | this.reason = undefined 6 | this.onResolveCallback = [] 7 | this.onRejectCallback = [] 8 | const resolve = value => { 9 | if (value instanceof MyPromise) { 10 | return this.then(resolve, reject) 11 | } 12 | if (this.status === "pending") { 13 | this.status = "resolved" 14 | this.value = value 15 | this.onResolveCallback.forEach(fn => fn()) 16 | } 17 | } 18 | const reject = reason => { 19 | if (this.status === "pending") { 20 | this.status = "rejected" 21 | this.reason = reason 22 | this.onRejectCallback.forEach(fn => fn()) 23 | } 24 | } 25 | try { 26 | callback(resolve, reject) 27 | } catch (error) { 28 | reject(error) 29 | } 30 | } 31 | then(onResolved, onRejected) { 32 | onResolved = typeof onResolved === "function" ? onResolved : value => value 33 | onRejected = 34 | typeof onRejected === "function" 35 | ? onRejected 36 | : reason => { 37 | throw reason 38 | } 39 | const promise2 = new MyPromise((resolve, reject) => { 40 | if (this.status === "resolved") { 41 | try { 42 | const x = onResolved(this.value) 43 | resolve(x) 44 | } catch (error) { 45 | reject(error) 46 | } 47 | } 48 | if (this.status === "rejected") { 49 | try { 50 | const x = onRejected(this.reason) 51 | resolve(x) 52 | } catch (error) { 53 | reject(error) 54 | } 55 | } 56 | 57 | if (this.status === "pending") { 58 | this.onResolveCallback.push(() => { 59 | try { 60 | const x = onResolved(this.value) 61 | resolve(x) 62 | } catch (error) { 63 | reject(error) 64 | } 65 | }) 66 | this.onRejectCallback.push(() => { 67 | try { 68 | const x = onRejected(this.reason) 69 | resolve(x) 70 | } catch (error) { 71 | reject(error) 72 | } 73 | }) 74 | } else { 75 | this.onResolveCallback = [] 76 | this.onRejectCallback = [] 77 | } 78 | }) 79 | 80 | return promise2 81 | } 82 | 83 | resolve(value) { 84 | return new MyPromise((resolve, reject) => { 85 | resolve(value) 86 | }) 87 | } 88 | 89 | reject(reason) { 90 | return new MyPromise((resolve, reject) => { 91 | reject(reason) 92 | }) 93 | } 94 | 95 | catch(callback) { 96 | return this.then(null, callback) 97 | } 98 | 99 | finally(callback) { 100 | return this.then( 101 | value => { 102 | return MyPromise.resolve(callback()).then(() => value) 103 | }, 104 | reason => { 105 | return MyPromise.resolve(callback()).then(() => { 106 | throw reason 107 | }) 108 | } 109 | ) 110 | } 111 | 112 | all(promises) { 113 | if (!Array.isArray(promises)) { 114 | throw new TypeError("promises must be an array") 115 | } 116 | return new MyPromise((resolve, reject) => { 117 | const result = [] 118 | promises.forEach((promise, index) => { 119 | MyPromise.resolve(promise) 120 | .then(value => { 121 | result[index] = value 122 | }) 123 | .catch(error => { 124 | reject(error) 125 | }) 126 | .finally(() => { 127 | if (result.length === promises.length) { 128 | resolve(result) 129 | } 130 | }) 131 | }) 132 | }) 133 | } 134 | allsettled(promises) { 135 | if (!Array.isArray(promises)) { 136 | throw new TypeError("promises must be an array") 137 | } 138 | return new MyPromise((resolve, reject) => { 139 | const result = [] 140 | promises.forEach((promise, index) => { 141 | MyPromise.resolve(promise) 142 | .then(value => { 143 | result[index] = { 144 | status: "resolved", 145 | value, 146 | } 147 | }) 148 | .catch(reason => { 149 | result[index] = { 150 | status: "rejected", 151 | reason, 152 | } 153 | }) 154 | .finally(() => { 155 | if (result.length === promises.length) { 156 | resolve(result) 157 | } 158 | }) 159 | }) 160 | }) 161 | } 162 | race(promises) { 163 | if (!Array.isArray(promises)) { 164 | throw new TypeError("promises must be an array") 165 | } 166 | return new MyPromise((resolve, reject) => { 167 | promises.forEach(promise => { 168 | MyPromise.resolve(promise) 169 | .then(value => { 170 | resolve(value) 171 | }) 172 | .catch(reason => { 173 | reject(reason) 174 | }) 175 | }) 176 | }) 177 | } 178 | } 179 | 180 | const promise = new MyPromise((resolve, reject) => { 181 | setTimeout(() => { 182 | resolve("成功") 183 | }, 1000) 184 | }) 185 | // promise.then(1).then(value => { 186 | // console.log("value", value) 187 | // }) 188 | promise 189 | .then(value => { 190 | console.log("2", value) 191 | return "第一次" 192 | }) 193 | .then(value => { 194 | console.log("3", value) 195 | return new MyPromise((resolve, reject) => { 196 | setTimeout(() => { 197 | resolve("第二次处理结果") 198 | }, 1000) 199 | }).then(result => { 200 | console.log("result", result) 201 | return result // 返回新的MyPromise实例的结果 202 | }) 203 | }) 204 | .then(value => { 205 | console.log(value) 206 | throw new Error("抛出异常") 207 | }) 208 | .catch(error => { 209 | console.log(error) 210 | }) 211 | 212 | let url = "xxx.com" 213 | let xhr = new XMLHttpRequest() 214 | xhr.open("GET", url, true) 215 | xhr.onreadystatechange = function () { 216 | if (this.readyState !== 4) return 217 | if (this.status === 200) { 218 | handle(this.responseText) 219 | } else { 220 | console.error(this.statusText) 221 | } 222 | } 223 | xhr.onerror = function () { 224 | console.error(this.statusText) 225 | } 226 | xhr.responseType = "json" 227 | xhr.setRequestHeader("Accept", "application/json") 228 | xhr.send(null) 229 | 230 | function sendJson() { 231 | return new Promise((resolve, reject) => { 232 | let url = "xxx.com" 233 | let xhr = new XMLHttpRequest() 234 | xhr.open("GET", url, true) 235 | xhr.onreadystatechange = function () { 236 | if (this.readyState !== 4) return 237 | if (this.status === 200) { 238 | resolve(this.responseText) 239 | } else { 240 | reject(this.statusText) 241 | } 242 | } 243 | xhr.onerror = function () { 244 | reject(this.statusText) 245 | } 246 | xhr.responseType = "json" 247 | xhr.setRequestHeader("Accept", "application/json") 248 | xhr.send(null) 249 | }) 250 | } 251 | 252 | function dateFormat(dateInput, format) { 253 | var date = new Date(dateInput) 254 | let day = date.getDay() 255 | let month = /[\u4E00-\u9FFF]/.test(format) ? date.getMonth() + 1 : (date.getMonth() + 1).toString().padStart(2, "0") 256 | let year = date.getFullYear() 257 | format = format.replace(/yyyy/, year) 258 | format = format.replace(/MM/, month) 259 | format = format.replace(/dd/, day) 260 | console.log("", format) 261 | } 262 | 263 | dateFormat(new Date("2020-12-01"), "yyyy/MM/dd") // 2020/12/01 264 | dateFormat(new Date("2020-04-01"), "yyyy/MM/dd") // 2020/04/01 265 | dateFormat(new Date("2020-04-01"), "yyyy年MM月dd日") // 2020年04月01日 266 | 267 | a = a + b 268 | b = a - b // b = a 269 | a = a - b // a+b-a = b 270 | 271 | // var arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] 272 | // for (let i = 0; i < arr.length; i++) { 273 | // const randomIndex = Math.floor(Math.random() * (arr.length - 1) - i) + i 274 | // ;[arr[i], arr[randomIndex]] = [arr[randomIndex], arr[i]] 275 | // } 276 | 277 | // const sum = arr.reduce((total, i) => (total += i), 0) 278 | // console.log("sum", sum) 279 | // 实现数组的乱序排列 280 | // function randomNum(arr){ 281 | // for (let i = 0; i < arr.length; i++) { 282 | // const randomIndex= Math.round(Math.random() * (arr.length - 1 -i)+i); 283 | // [arr[randomIndex], arr[i]] =[arr[i],arr[randomIndex]] 284 | 285 | // } 286 | // console.log('arr',arr); 287 | // } 288 | // function randomNum(arr) { 289 | // let length = arr.length, 290 | // randomIndex, 291 | // temp 292 | // while (length) { 293 | // randomIndex = Math.floor(Math.random() * length--) 294 | // temp = arr[length] 295 | // arr[length] = arr[randomIndex] 296 | // arr[randomIndex] = temp 297 | // } 298 | // console.log("arr", arr) 299 | // } 300 | // console.log("", randomNum([1, 2, 3, 4, 5, 6, 7, 8, 9, 10])) 301 | // let arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] 302 | // let sum = arr.reduce((sum, cur) => sum + cur, 0) 303 | // let arr=[1,2,3,[[4,5],6],7,8,9] 304 | // arr = arr.toString().split(',').reduce((sum, cur) => sum + Number(cur), 0) 305 | // let arr = [1, 2, 3, 4, 5, 6] 306 | // function add(arr){ 307 | // if(arr.length===1) return arr[0] 308 | // return arr[0] + add(arr.slice(1)) 309 | // } 310 | // console.log(add(arr)); 311 | 312 | // 数组扁平化 313 | 314 | let arr = [1, [2, [3, 4, 5]]] 315 | // function flatten(arr) { 316 | // let result = [] 317 | // for (let i = 0; i < arr.length; i++) { 318 | // if (Array.isArray(arr[i])) { 319 | // result = result.concat(flatten(arr[i])) 320 | // } else { 321 | // result.push(arr[i]) 322 | // } 323 | // } 324 | // return result 325 | // } 326 | 327 | // function flatten(arr) { 328 | // return arr.reduce((pre, cur) => { 329 | // return pre.concat(Array.isArray(cur) ? flatten(cur) : cur) 330 | // }, []) 331 | // } 332 | // function flatten(arr) { 333 | // console.log('arr',arr); 334 | // return arr.map(item => (Array.isArray(item) ? flatten(item) : item)).reduce((pre, cur) => pre.concat(cur), []) 335 | // } 336 | function flatten(arr) { 337 | while (arr.some(item => Array.isArray(item))) { 338 | arr = [].concat(...arr) 339 | } 340 | return arr 341 | } 342 | function flatten(arr) { 343 | return arr.toString().split(",").map(Number) 344 | } 345 | function flatten(arr) { 346 | return arr.flat(Infinity) 347 | } 348 | function flatten(arr) { 349 | let str = JSON.stringify(arr) 350 | str = str.replace(/\[|\]/g, "") 351 | return str.split(",").map(Number) 352 | } 353 | console.log("", flatten([1, [2, [3, 4, 5]]])) 354 | // 去重 355 | console.log("", new Set([1, 2, 3, 5, 1, 5, 9, 1, 2, 8])) 356 | 357 | // function uniqueArray(arr) { 358 | // let map = {} 359 | // let res = [] 360 | // for (let i = 0; i < arr.length; i++) { 361 | // if (!map.hasOwnProperty(arr[i])) { 362 | // map[arr[i]] = 1 363 | // res.push(arr[i]) 364 | // } 365 | // } 366 | // console.log("res,map", res, map) 367 | // return res 368 | // } 369 | console.log("", uniqueArray([1, 2, 3, 5, 1, 5, 9, 1, 2, 8])) 370 | 371 | function _flat(arr, depth) { 372 | if (!Array.isArray(arr) || depth <= 0) { 373 | return arr 374 | } 375 | return arr.reduce((prev, cur) => { 376 | if (Array.isArray(cur)) { 377 | return prev.concat(_flat(cur, depth - 1)) 378 | } else { 379 | return prev.concat(cur) 380 | } 381 | }, []) 382 | } 383 | // push 384 | Array.prototype.myPush = function () { 385 | for (let i = 0; i < arguments.length; i++) { 386 | this[this.length] = arguments[i] 387 | } 388 | return this.length 389 | } 390 | // filter 391 | 392 | Array.prototype.myFilter = function (fn) { 393 | if (typeof fn !== "function") { 394 | throw Error("参数必须是一个函数") 395 | } 396 | const res = [] 397 | for (let i = 0; i < this.length; i++) { 398 | fn(this[i]) && res.push(this[i]) 399 | } 400 | return res 401 | } 402 | 403 | // map 404 | Array.prototype.myFilter = function (fn) { 405 | if (typeof fn !== "function") { 406 | throw Error("参数必须是一个函数") 407 | } 408 | const res = [] 409 | for (let i = 0; i < this.length; i++) { 410 | res.push(this[i]) 411 | } 412 | return res 413 | } 414 | 415 | function debounce(fn, delay) { 416 | let timer = null 417 | return function (...args) { 418 | const ctx = this 419 | 420 | if (timer) { 421 | clearTimeout(timer) 422 | timer = null 423 | } 424 | 425 | timer = setTimeout(() => { 426 | fn.apply(ctx, args) 427 | }, delay) 428 | } 429 | } 430 | 431 | function throttle(fn, delay) { 432 | let timer = null 433 | return function (...args) { 434 | const ctx = this 435 | if (!timer) { 436 | timer = setTimeout(() => { 437 | fn.apply(ctx, args) 438 | timer = null 439 | }, delay) 440 | } 441 | } 442 | } 443 | 444 | function throttle(fn, delay) { 445 | let curTime = Date.now() 446 | return function (...args) { 447 | const noTime = Date.now() 448 | const ctx = this 449 | if (delay <= noTime - curTime) { 450 | curTime = Date.now() 451 | return fn.apply(ctx, args) 452 | } 453 | } 454 | } 455 | 456 | Function.prototype.myCall = function (ctx, ...args) { 457 | if (typeof this !== "function") return 458 | ctx = ctx || this 459 | const fn = Symbol() 460 | ctx[fn] = this 461 | const res = ctx[fn](...args) 462 | delete ctx[fn] 463 | return res 464 | } 465 | Function.prototype.myApply = function (ctx, args) { 466 | if (typeof this !== "function") return 467 | ctx = ctx || this 468 | const fn = Symbol() 469 | ctx[fn] = this 470 | const res = ctx[fn](...args) 471 | delete ctx[fn] 472 | return res 473 | } 474 | Function.prototype.myBind = function (ctx, args1) { 475 | if (typeof this !== "function") return 476 | const fn = this 477 | return function (...args2) { 478 | const allArgs = [...args1, ...args2] 479 | if (new.target) { 480 | // this instanceof fn 481 | return new fn(...allArgs) 482 | } else { 483 | return fn.apply(ctx, allArgs) 484 | } 485 | } 486 | } 487 | -------------------------------------------------------------------------------- /常见手写题/js基础手写/2.22手写.js: -------------------------------------------------------------------------------- 1 | // instanceof 2 | function MyInstanceof(left, right) { 3 | if (typeof right !== "function") { 4 | throw new Error("error") 5 | } 6 | let prototype = right.prototype 7 | let proto = Object.getPrototypeOf(left) 8 | while (true) { 9 | if (!proto) return false 10 | if (proto === prototype) return true 11 | proto = Object.getPrototypeOf(proto) 12 | } 13 | } 14 | 15 | function unique(arr) { 16 | // let result = [] 17 | // arr.forEach(i => { 18 | // // if (result.indexOf(i) === -1) { 19 | // // result.push(i) 20 | // // } 21 | // }) 22 | // return result 23 | return [...new Set(arr)] 24 | } 25 | 26 | const arr = [1, 1, 2, 2, 3, 5, 4, 4] 27 | 28 | // console.log(unique(arr)) 29 | 30 | // arr.reduce((pre,cur)=>{ 31 | // return pre += cur 32 | // }) 33 | console.log( 34 | arr.reduce((pre, cur, index, arr) => { 35 | return (pre += cur) 36 | }) 37 | ) 38 | Array.prototype.MyReduce = function (callback, params) { 39 | const arr = this 40 | for (let i = 0; i < arr.length; i++) { 41 | params = callback.apply(this, [params ? params : 0, arr[i], i, arr]) 42 | } 43 | return params 44 | } 45 | console.log( 46 | [1, 1, 2, 2, 3, 5, 4, 4].MyReduce((pre, cur, index, arr) => { 47 | return (pre += cur) 48 | }) 49 | ) 50 | // 1、冒泡排序---前一个和后一个进行比较,交换位置 51 | function bubbleSort(arr) { 52 | let len = arr.length 53 | for (let i = 0; i < len - 1; i++) { 54 | for (let j = 0; j < len - 1 - i; j++) { 55 | if (arr[j] > arr[j + 1]) { 56 | let temp = arr[j + 1] 57 | arr[j + 1] = arr[j] 58 | arr[j] = temp 59 | } 60 | } 61 | } 62 | return arr 63 | } 64 | let arr1 = [64, 34, 25, 12, 22, 11, 90] 65 | console.log(bubbleSort(arr1)) // [11, 12, 22, 25, 34, 64, 90] 66 | 67 | // 2、快速排序---取出中间值,进行递归 68 | function quickSort(arr) { 69 | if (arr.length <= 1) return arr 70 | let mainIndex = parseInt(arr.length / 2) 71 | let mainItem = arr.splice(mainIndex, 1)[0] 72 | let left = [] 73 | let right = [] 74 | arr.forEach(item => { 75 | if (item > mainItem) { 76 | right.push(item) 77 | } else { 78 | left.push(item) 79 | } 80 | }) 81 | return quickSort(left).concat([mainItem], quickSort(right)) 82 | } 83 | const arr2 = [64, 34, 25, 12, 22, 11, 90] 84 | console.log(quickSort(arr2)) 85 | // 3、插入排序---前一个和后一个进行比较,交换位置 86 | function insertionSort(arr) { 87 | let len = arr.length 88 | for (let i = 0; i < len; i++) { 89 | let key = arr[i] 90 | let j = i - 1 91 | while (j >= 0 && arr[j] > key) { 92 | arr[j + 1] = arr[j] 93 | j = j - 1 94 | } 95 | arr[j + 1] = key 96 | } 97 | return arr 98 | } 99 | var arr3 = [64, 34, 25, 12, 22, 11, 90]; 100 | console.log(insertionSort(arr3)); 101 | -------------------------------------------------------------------------------- /常见手写题/js基础手写/index.js: -------------------------------------------------------------------------------- 1 | // 1. 手写 Object.create 2 | function myCreate(obj) { 3 | function F() {} 4 | F.prototype = obj 5 | return new F() 6 | } 7 | // 2. 手写 instanceof 方法 8 | function myInstanceof(left, right) { 9 | if (typeof right !== "function") return false 10 | let proto = Object.getPrototypeOf(left) 11 | let prototype = right.prototype 12 | while (true) { 13 | if (!proto) return false 14 | if (proto === prototype) return true 15 | proto = Object.getPrototypeOf(proto) 16 | } 17 | } 18 | // 3. 手写 new 操作符 19 | function myNew(ctx, ...args) { 20 | if (typeof ctx !== "function") return 21 | let obj = {} 22 | obj.prototype = Object.create(ctx.prototype) 23 | const res = ctx.apply(this, args) 24 | if (res && (typeof res !== "object" || typeof res !== "function")) return res 25 | return obj 26 | } 27 | // 4. 手写 Promise 28 | class MyPromise { 29 | constructor(callback) { 30 | this.status = "pending" 31 | // 成功的值 32 | this.value = undefined 33 | // 失败的值 34 | this.reason = undefined 35 | // 成功的函数 36 | this.onResolveCallbacks = [] 37 | // 失败的函数 38 | this.onRejectCallbacks = [] 39 | 40 | const resolve = value => { 41 | if (value instanceof MyPromise) { 42 | return value.then(resolve, reject) 43 | } 44 | if (this.status === "pending") { 45 | this.status = "resolved" 46 | this.value = value 47 | this.onResolveCallbacks.forEach(cb => cb()) 48 | } 49 | } 50 | const reject = reason => { 51 | if (this.status === "pending") { 52 | this.status = "rejected" 53 | this.reason = reason 54 | this.onRejectCallbacks.forEach(cb => cb()) 55 | } 56 | } 57 | callback(resolve, reject) 58 | } 59 | then(OnResolved, OnRejected) { 60 | OnResolved = typeof OnResolved === "function" ? OnResolved : value => value 61 | OnRejected = 62 | typeof OnRejected === "function" 63 | ? OnRejected 64 | : reason => { 65 | throw reason 66 | } 67 | console.log("", OnResolved, OnRejected) 68 | const promise2 = new MyPromise((resolve, reject) => { 69 | if (this.status === "resolved") { 70 | try { 71 | const x = OnResolved(this.value) 72 | resolve(x) 73 | } catch (error) { 74 | reject(error) 75 | } 76 | } else if (this.status === "rejected") { 77 | try { 78 | const x = OnRejected(this.reason) 79 | resolve(x) 80 | } catch (error) { 81 | reject(error) 82 | } 83 | } 84 | if (this.status === "pending") { 85 | this.onResolveCallbacks.push(() => { 86 | try { 87 | const x = OnResolved(this.value) 88 | resolve(x) 89 | } catch (error) { 90 | reject(error) 91 | } 92 | }) 93 | this.onRejectCallbacks.push(() => { 94 | try { 95 | const x = OnRejected(this.reason) 96 | resolve(x) 97 | } catch (error) { 98 | reject(error) 99 | } 100 | }) 101 | } else { 102 | // 执行完所有回调函数之后,清空回调数组 103 | this.onResolvedCallbacks = [] 104 | this.onRejectedCallbacks = [] 105 | } 106 | }) 107 | return promise2 108 | } 109 | catch(OnRejected) { 110 | console.log("OnRejected", OnRejected) 111 | return this.then(null, OnRejected) 112 | } 113 | resolve(value) { 114 | return new MyPromise((resolve, reject) => { 115 | resolve(value) 116 | }) 117 | } 118 | reject(reason) { 119 | return new MyPromise((resolve, reject) => { 120 | reject(reason) 121 | }) 122 | } 123 | finally(callback) { 124 | return this.then( 125 | value => { 126 | return MyPromise.resolve(callback()).then(() => value) 127 | }, 128 | reason => { 129 | return MyPromise.resolve(callback()).then(() => { 130 | throw reason 131 | }) 132 | } 133 | ) 134 | } 135 | all(promises) { 136 | return new MyPromise((resolve, reject) => { 137 | if (!Array.isArray(promises)) { 138 | throw new TypeError(`argument must be a array`) 139 | } 140 | let arr = [] 141 | promises.forEach((promise, index) => { 142 | MyPromise.resolve(promise) 143 | .then(res => { 144 | arr[index] = res 145 | // 只有都请求成功了,才算成功 146 | if (Object.keys(arr).length === promises.length) { 147 | resolve(arr) 148 | } 149 | }) 150 | .catch(err => { 151 | // 一个失败就算失败 152 | reject(err) 153 | }) 154 | }) 155 | }) 156 | } 157 | allSettled(promises) { 158 | return new MyPromise((resolve, reject) => { 159 | let arr = [] 160 | promises.forEach((promise, index) => { 161 | MyPromise.resolve(promise) 162 | .then(res => { 163 | // 成功的 164 | arr[index] = { 165 | status: "fulfilled", 166 | value: res, 167 | } 168 | }) 169 | .catch(err => { 170 | // 失败的 171 | arr[index] = { 172 | status: "rejected", 173 | reason: err, 174 | } 175 | }) 176 | .finally(() => { 177 | // 所有请求都完成,那么resolve 178 | if (Object.keys(arr).length === promises.length) { 179 | resolve(arr) 180 | } 181 | }) 182 | }) 183 | }) 184 | } 185 | race(promises) { 186 | return new MyPromise((resolve, reject) => { 187 | promises.forEach(promise => { 188 | MyPromise.resolve(promise) 189 | .then(res => { 190 | // 成功的立马返回 191 | resolve(res) 192 | }) 193 | .catch(err => { 194 | // 失败的立马返回 195 | reject(err) 196 | }) 197 | }) 198 | }) 199 | } 200 | } 201 | 202 | // 测试代码 203 | const promise = new MyPromise((resolve, reject) => { 204 | setTimeout(() => { 205 | resolve("成功") 206 | }, 1000) 207 | }) 208 | promise.then(1).then(value => { 209 | console.log("value", value) 210 | }) 211 | // promise 212 | // .then(value => { 213 | // console.log("2", value) 214 | // return "第一次" 215 | // }) 216 | // .then(value => { 217 | // console.log("3", value) 218 | // return new MyPromise((resolve, reject) => { 219 | // setTimeout(() => { 220 | // resolve("第二次处理结果") 221 | // }, 1000) 222 | // }).then(result => { 223 | // console.log("result", result) 224 | // return result // 返回新的MyPromise实例的结果 225 | // }) 226 | // }) 227 | // .then(value => { 228 | // console.log(value) 229 | // throw new Error("抛出异常") 230 | // }) 231 | // .catch(error => { 232 | // console.log(error) 233 | // }) 234 | 235 | // 对AJAX的理解,实现一个AJAX请求 236 | const url = "xxx.com" 237 | let xhr = new XMLHttpRequest() 238 | xhr.open("GET", url, true) 239 | xhr.onreadystatechange = function () { 240 | if (this.readyState !== 4) return 241 | if (this.status === 200) { 242 | handle(this.response) 243 | } else { 244 | console.error(this.statusText) 245 | } 246 | } 247 | xhr.onerror = function () { 248 | console.error(this.statusText) 249 | } 250 | 251 | xhr.responseType = "json" 252 | xhr.setRequestHeader("Accept", "application/json") 253 | xhr.send(null) 254 | // promise 封装实现: 255 | function getJSON(url) { 256 | // 创建一个 promise 对象 257 | let promise = new Promise(function (resolve, reject) { 258 | let xhr = new XMLHttpRequest() 259 | xhr.open("GET", url, true) 260 | xhr.onreadystatechange = function () { 261 | if (this.readyState !== 4) return 262 | // 当请求成功或失败时,改变 promise 的状态 263 | if (this.status === 200) { 264 | resolve(this.response) 265 | } else { 266 | reject(new Error(this.statusText)) 267 | } 268 | } 269 | xhr.onerror = function () { 270 | reject(new Error(this.statusText)) 271 | } 272 | xhr.responseType = "json" 273 | xhr.setRequestHeader("Accept", "application/json") 274 | xhr.send(null) 275 | }) 276 | return promise 277 | } 278 | let obj = { 279 | a: 1, 280 | b: 2, 281 | c: 3, 282 | } 283 | 284 | obj[Symbol.iterator] = function () { 285 | const keys = Object.keys(obj) 286 | let count = 0 287 | return { 288 | next() { 289 | if (keys.length < count) { 290 | return { value: obj[keys[count++]], done: false } 291 | } else { 292 | return { value: undefined, done: true } 293 | } 294 | }, 295 | } 296 | } 297 | obj[Symbol.iterator] = function* () { 298 | const keys = Object.keys(obj) 299 | for (const key of keys) { 300 | yield [key, obj[key]] 301 | } 302 | } 303 | 304 | // 8. 手写防抖函数 305 | function debounce(fn, wait) { 306 | let timer = null 307 | return function (...args) { 308 | let context = this 309 | if (timer) { 310 | clearTimeout(timer) 311 | timer = null 312 | } 313 | timer = setTimeout(() => { 314 | fn.apply(context, args) 315 | }, wait) 316 | } 317 | } 318 | // 9. 手写节流函数 319 | function throttle(fn, wait) { 320 | let timer = null 321 | return function (...args) { 322 | let context = this 323 | if (!timer) { 324 | timer = setTimeout(() => { 325 | fn.apply(context, args) 326 | timer = null 327 | }, wait) 328 | } 329 | } 330 | } 331 | 332 | function throttle(fn, wait) { 333 | let curTime = Date.now() 334 | return function (...args) { 335 | let nowTime = Date.now() 336 | if (nowTime - curTime >= wait) { 337 | curTime = Date.now() 338 | return fn.apply(this, args) 339 | } 340 | } 341 | } 342 | 343 | // 11. 手写 call 函数js 344 | function myCall(ctx, ...args) { 345 | if (typeof this !== "function") return 346 | let ctx = ctx || window 347 | const fn = Symbol() 348 | ctx[fn] = this 349 | const result = ctx[fn](...args) 350 | delete ctx[fn] 351 | return result 352 | } 353 | 354 | function myApply(ctx, args) { 355 | if (typeof this !== "function") return 356 | let ctx = ctx || window 357 | const fn = Symbol() 358 | ctx[fn] = this 359 | const result = ctx[fn](...args) 360 | delete ctx[fn] 361 | return result 362 | } 363 | 364 | function myBind(ctx, ...args1) { 365 | if (typeof this !== "function") return 366 | let fn = this 367 | return function (...args2) { 368 | let allArgs = [...args1, ...args2] 369 | if (new.target) { 370 | return new fn(...allArgs) 371 | } else { 372 | return fn.apply(ctx, ...allArgs) 373 | } 374 | } 375 | } 376 | // 14. 函数柯里化的实现 377 | function currying(fn) { 378 | let args = [] 379 | return function temp(...newArgs) { 380 | if (newArgs.length) { 381 | args = [...args, ...newArgs] 382 | return temp 383 | } else { 384 | const result = fn.apply(this, args) 385 | args = [] 386 | return result 387 | } 388 | } 389 | } 390 | 391 | function currying(fn, ...args) { 392 | return fn.length <= args.length ? fn(...args) : currying.bind(null, fn, ...args) 393 | } 394 | 395 | function add(a, b, c) { 396 | return a + b + c 397 | } 398 | 399 | let curriedAdd = currying(add) 400 | console.log(curriedAdd(2)(3)(4)()) // 输出 9 401 | 402 | class AsyncQueue { 403 | constructor(count) { 404 | this.count = count 405 | this.running = 0 406 | this.queue = [] 407 | } 408 | addTak(task) { 409 | this.queue.push(task) 410 | this.runTasks() 411 | } 412 | async runTasks() { 413 | while (this.running < this.count && this.queue.length > 0) { 414 | const task = this.queue.shift() 415 | this.running++ 416 | try { 417 | await task() 418 | } catch (error) { 419 | console.log("task error", error) 420 | } 421 | this.running-- 422 | } 423 | } 424 | } 425 | 426 | // 事件中心 427 | 428 | class EventEmitter { 429 | constructor() { 430 | this.events = {} 431 | } 432 | // 订阅事件 433 | on(eventName, callback) { 434 | if (!this.events[eventName]) { 435 | this.events[eventName] = [] 436 | } 437 | this.events[eventName].push(callback) 438 | } 439 | // 发布事件 440 | emit(eventName, ...args) { 441 | const callbacks = this.events[eventName] 442 | if (callbacks) { 443 | callbacks.forEach(v => v(...args)) 444 | } 445 | } 446 | // 取消事件 447 | off(eventName, callback) { 448 | const callbacks = this.events[eventName] 449 | if (callbacks) { 450 | this.events[eventName] = callbacks.filter(i => i !== callback) 451 | } 452 | } 453 | // 加入之后只执行一次 454 | once(eventName, callback) { 455 | const onceCallback = (...args) => { 456 | callback(...args) 457 | this.off(eventName, onceCallback) 458 | } 459 | this.on(eventName, onceCallback) 460 | } 461 | } 462 | 463 | // 判断对象是否存在循环引用 464 | 465 | const isCycleObject = (obj, parent) => { 466 | const parentArr = parent || [obj] 467 | for (const i in obj) { 468 | if (Object.hasOwnProperty.call(obj, i)) { 469 | const el = obj[i] 470 | if (typeof el === "object") { 471 | let flag = false 472 | parentArr.forEach(v => { 473 | if (v === obj[v]) { 474 | flag = true 475 | } 476 | }) 477 | if (flag) return true 478 | flag = isCycleObject(obj[i], [...parentArr, obj[i]]) 479 | if (flag) return true 480 | } 481 | } 482 | } 483 | return false 484 | } 485 | // 使用 setTimeout 实现 setInterval 486 | 487 | function mySetInterval(fn, timeout) { 488 | const timer = { 489 | flag: true, 490 | } 491 | function interval() { 492 | if (timer.flag) { 493 | fn() 494 | setTimeout(interval, timeout) 495 | } 496 | } 497 | setTimeout(interval, timeout) 498 | return timer 499 | } 500 | 501 | const myTimer = mySetInterval(() => { 502 | console.log("Hello, World!"); 503 | }, 1000); 504 | 505 | // 运行 5 秒后停止定时器 506 | setTimeout(() => { 507 | myTimer.flag = false; // 停止定时器 508 | console.log("定时器已停止"); 509 | }, 5000); 510 | 511 | function parseUrlParams(url) { 512 | const params = {}; 513 | const urlParams = new URLSearchParams(url); 514 | 515 | for (const [key, value] of urlParams) { 516 | params[key] = value; 517 | } 518 | 519 | return params; 520 | } 521 | -------------------------------------------------------------------------------- /常见手写题/js基础手写/tempCodeRunnerFile.js: -------------------------------------------------------------------------------- 1 | function insertionSort(arr) { 2 | let len = arr.length 3 | for (let i = 0; i < len; i++) { 4 | let key = arr[i] 5 | let j = i - 1 6 | while (j >= 0 && arr[j] > key) { 7 | arr[j + 1] = arr[j] 8 | j = j - 1 9 | } 10 | arr[j + 1] = key 11 | } 12 | return arr 13 | } 14 | var arr3 = [64, 34, 25, 12, 22, 11, 90]; 15 | console.log(insertionSort(arr3)); -------------------------------------------------------------------------------- /常见算法题/常见算法题.md: -------------------------------------------------------------------------------- 1 | ![刷穿LeetCode.png](https://cdn.nlark.com/yuque/0/2022/png/1500604/1647684251333-2f30743e-4823-47be-b76d-eff466c9411f.png?x-oss-process=image%2Fwatermark%2Ctype_d3F5LW1pY3JvaGVp%2Csize_83%2Ctext_5pyI5ZOl55qE6Z2i6K-V6K6t57uD6JCl%2Ccolor_FFFFFF%2Cshadow_50%2Ct_80%2Cg_se%2Cx_10%2Cy_10) 2 | 3 | 图 4 | 5 | [133.克隆图](https://leetcode-cn.com/problems/clone-graph) 6 | 7 | [207.课程表](https://leetcode-cn.com/problems/course-schedule) 8 | 9 | [210.课程表 II](https://leetcode-cn.com/problems/course-schedule-ii) 10 | 11 | [399.除法求值](https://leetcode-cn.com/problems/evaluate-division) 12 | 13 | [547.省份数量](https://leetcode-cn.com/problems/number-of-provinces) 14 | 15 | [684.冗余连接](https://leetcode-cn.com/problems/redundant-connection) 16 | 17 | [743.网络延迟时间](https://leetcode-cn.com/problems/network-delay-time) 18 | 19 | [785.判断二分图](https://leetcode-cn.com/problems/is-graph-bipartite) 20 | 21 | 堆 22 | 23 | [215.数组中的第K个最大元素](https://leetcode-cn.com/problems/kth-largest-element-in-an-array) 24 | 25 | [295.数据流的中位数](https://leetcode-cn.com/problems/find-median-from-data-stream) 26 | 27 | [264.丑数 II](https://leetcode-cn.com/problems/ugly-number-ii) 28 | 29 | [347.前 K 个高频元素](https://leetcode-cn.com/problems/top-k-frequent-elements) 30 | 31 | [378.有序矩阵中第 K 小的元素](https://leetcode-cn.com/problems/kth-smallest-element-in-a-sorted-matrix) 32 | 33 | [703.数据流中的第 K 大元素](https://leetcode-cn.com/problems/kth-largest-element-in-a-stream) 34 | 35 | [767.重构字符串](https://leetcode-cn.com/problems/reorganize-string) 36 | 37 | [剑指 Offer 41.数据流中的中位数](https://leetcode-cn.com/problems/shu-ju-liu-zhong-de-zhong-wei-shu-lcof/) 38 | 39 | 栈 40 | 41 | [20.有效的括号](https://leetcode-cn.com/problems/valid-parentheses) 42 | 43 | [71.简化路径](https://leetcode-cn.com/problems/simplify-path) 44 | 45 | [84.柱状图中最大的矩形](https://leetcode-cn.com/problems/largest-rectangle-in-histogram) 46 | 47 | [85.最大矩形](https://leetcode-cn.com/problems/maximal-rectangle) 48 | 49 | [155.最小栈](https://leetcode-cn.com/problems/min-stack) 50 | 51 | [224.基本计算器](https://leetcode-cn.com/problems/basic-calculator) 52 | 53 | [227.基本计算器 II](https://leetcode-cn.com/problems/basic-calculator-ii) 54 | 55 | [394.字符串解码](https://leetcode-cn.com/problems/decode-string) 56 | 57 | [402.移掉 K 位数字](https://leetcode-cn.com/problems/remove-k-digits) 58 | 59 | [503.下一个更大元素 II](https://leetcode-cn.com/problems/next-greater-element-ii) 60 | 61 | [739.每日温度](https://leetcode-cn.com/problems/daily-temperatures) 62 | 63 | [1047.删除字符串中的所有相邻重复项](https://leetcode-cn.com/problems/remove-all-adjacent-duplicates-in-string) 64 | 65 | [剑指 Offer 06.从尾到头打印链表](https://leetcode-cn.com/problems/cong-wei-dao-tou-da-yin-lian-biao-lcof/) 66 | 67 | [剑指 Offer 30.包含min函数的栈](https://leetcode-cn.com/problems/bao-han-minhan-shu-de-zhan-lcof/) 68 | 69 | [剑指 Offer 31.栈的压入、弹出序列](https://leetcode-cn.com/problems/zhan-de-ya-ru-dan-chu-xu-lie-lcof/) 70 | 71 | 队列 72 | 73 | [225.用队列实现栈](https://leetcode-cn.com/problems/implement-stack-using-queues) 74 | 75 | [232.用栈实现队列](https://leetcode-cn.com/problems/implement-queue-using-stacks) 76 | 77 | [239.滑动窗口最大值](https://leetcode-cn.com/problems/sliding-window-maximum) 78 | 79 | [387.字符串中的第一个唯一字符](https://leetcode-cn.com/problems/first-unique-character-in-a-string) 80 | 81 | [622.设计循环队列](https://leetcode-cn.com/problems/design-circular-queue) 82 | 83 | [862.和至少为 K 的最短子数组](https://leetcode-cn.com/problems/shortest-subarray-with-sum-at-least-k) 84 | 85 | [933.最近的请求次数](https://leetcode-cn.com/problems/number-of-recent-calls) 86 | 87 | [剑指 Offer 09.用两个栈实现队列](https://leetcode-cn.com/problems/yong-liang-ge-zhan-shi-xian-dui-lie-lcof) 88 | 89 | [剑指 Offer 59 - I.滑动窗口的最大值](https://leetcode-cn.com/problems/hua-dong-chuang-kou-de-zui-da-zhi-lcof/) 90 | 91 | [剑指 Offer 59 - II.队列的最大值](https://leetcode-cn.com/problems/dui-lie-de-zui-da-zhi-lcof/) 92 | 93 | 链表 94 | 95 | [2.两数相加](https://leetcode-cn.com/problems/add-two-numbers) 96 | 97 | [25.K 个一组翻转链表](https://leetcode-cn.com/problems/reverse-nodes-in-k-group) 98 | 99 | [82.删除排序链表中的重复元素 II](https://leetcode-cn.com/problems/remove-duplicates-from-sorted-list-ii) 100 | 101 | [83.删除排序链表中的重复元素](https://leetcode-cn.com/problems/remove-duplicates-from-sorted-list) 102 | 103 | [86.分隔链表](https://leetcode-cn.com/problems/partition-list) 104 | 105 | [92.反转链表 II](https://leetcode-cn.com/problems/reverse-linked-list-ii) 106 | 107 | [138.复制带随机指针的链表](https://leetcode-cn.com/problems/copy-list-with-random-pointer) 108 | 109 | [143.重排链表](https://leetcode-cn.com/problems/reorder-list) 110 | 111 | [160.相交链表](https://leetcode-cn.com/problems/intersection-of-two-linked-lists) 112 | 113 | [328.奇偶链表](https://leetcode-cn.com/problems/odd-even-linked-list) 114 | 115 | [445.两数相加 II](https://leetcode-cn.com/problems/add-two-numbers-ii) 116 | 117 | [876.链表的中间结点](https://leetcode-cn.com/problems/middle-of-the-linked-list) 118 | 119 | [剑指 Offer 22.链表中倒数第k个节点](https://leetcode-cn.com/problems/lian-biao-zhong-dao-shu-di-kge-jie-dian-lcof) 120 | 121 | [剑指 Offer 24.反转链表](https://leetcode-cn.com/problems/fan-zhuan-lian-biao-lcof) 122 | 123 | [剑指 Offer 52.两个链表的第一个公共节点](https://leetcode-cn.com/problems/liang-ge-lian-biao-de-di-yi-ge-gong-gong-jie-dian-lcof/) 124 | 125 | 数组 126 | 127 | [1.两数之和](https://leetcode-cn.com/problems/two-sum) 128 | 129 | [31.下一个排列](https://leetcode-cn.com/problems/next-permutation) 130 | 131 | [33.搜索旋转排序数组](https://leetcode-cn.com/problems/search-in-rotated-sorted-array) 132 | 133 | [41.缺失的第一个正数](https://leetcode-cn.com/problems/first-missing-positive) 134 | 135 | [56.合并区间](https://leetcode-cn.com/problems/merge-intervals) 136 | 137 | [162.寻找峰值](https://leetcode-cn.com/problems/find-peak-element) 138 | 139 | [189.轮转数组](https://leetcode-cn.com/problems/rotate-array) 140 | 141 | [560.和为 K 的子数组](https://leetcode-cn.com/problems/subarray-sum-equals-k) 142 | 143 | [695.岛屿的最大面积](https://leetcode-cn.com/problems/max-area-of-island) 144 | 145 | [剑指 Offer 66.构建乘积数组](https://leetcode-cn.com/problems/gou-jian-cheng-ji-shu-zu-lcof/) 146 | 147 | 矩阵 148 | 149 | [36.有效的数独](https://leetcode-cn.com/problems/valid-sudoku) 150 | 151 | [37.解数独](https://leetcode-cn.com/problems/sudoku-solver) 152 | 153 | [48.旋转图像](https://leetcode-cn.com/problems/rotate-image) 154 | 155 | [54.螺旋矩阵](https://leetcode-cn.com/problems/spiral-matrix) 156 | 157 | [59.螺旋矩阵 II](https://leetcode-cn.com/problems/spiral-matrix-ii) 158 | 159 | [73.矩阵置零](https://leetcode-cn.com/problems/set-matrix-zeroes) 160 | 161 | [74.搜索二维矩阵](https://leetcode-cn.com/problems/search-a-2d-matrix) 162 | 163 | [240.搜索二维矩阵 II](https://leetcode-cn.com/problems/search-a-2d-matrix-ii) 164 | 165 | [733.图像渲染](https://leetcode-cn.com/problems/flood-fill) 166 | 167 | [剑指 Offer 04.二维数组中的查找](https://leetcode-cn.com/problems/er-wei-shu-zu-zhong-de-cha-zhao-lcof/) 168 | 169 | [剑指 Offer 29.顺时针打印矩阵](https://leetcode-cn.com/problems/shun-shi-zhen-da-yin-ju-zhen-lcof/) 170 | 171 | 字符串 172 | 173 | [6.Z 字形变换](https://leetcode-cn.com/problems/zigzag-conversion) 174 | 175 | [12.整数转罗马数字](https://leetcode-cn.com/problems/integer-to-roman) 176 | 177 | [14.最长公共前缀](https://leetcode-cn.com/problems/longest-common-prefix) 178 | 179 | [28.实现 strStr()](https://leetcode-cn.com/problems/implement-strstr) 180 | 181 | [58.最后一个单词的长度](https://leetcode-cn.com/problems/length-of-last-word) 182 | 183 | [115.不同的子序列](https://leetcode-cn.com/problems/distinct-subsequences) 184 | 185 | [151.颠倒字符串中的单词](https://leetcode-cn.com/problems/reverse-words-in-a-string) 186 | 187 | [344.反转字符串](https://leetcode-cn.com/problems/reverse-string) 188 | 189 | [443.压缩字符串](https://leetcode-cn.com/problems/string-compression) 190 | 191 | [459.重复的子字符串](https://leetcode-cn.com/problems/repeated-substring-pattern) 192 | 193 | [556.下一个更大元素 III](https://leetcode-cn.com/problems/next-greater-element-iii) 194 | 195 | [557.反转字符串中的单词 III](https://leetcode-cn.com/problems/reverse-words-in-a-string-iii) 196 | 197 | [647.回文子串](https://leetcode-cn.com/problems/palindromic-substrings) 198 | 199 | [678.有效的括号字符串](https://leetcode-cn.com/problems/valid-parenthesis-string) 200 | 201 | [680.验证回文字符串 Ⅱ](https://leetcode-cn.com/problems/valid-palindrome-ii) 202 | 203 | [剑指 Offer 05.替换空格](https://leetcode-cn.com/problems/ti-huan-kong-ge-lcof/) 204 | 205 | [剑指 Offer 20.表示数值的字符串](https://leetcode-cn.com/problems/biao-shi-shu-zhi-de-zi-fu-chuan-lcof/) 206 | 207 | [剑指 Offer 67.把字符串转换成整数](https://leetcode-cn.com/problems/ba-zi-fu-chuan-zhuan-huan-cheng-zheng-shu-lcof/) 208 | 209 | 哈希表 210 | 211 | [49.字母异位词分组](https://leetcode-cn.com/problems/group-anagrams) 212 | 213 | [202.快乐数](https://leetcode-cn.com/problems/happy-number) 214 | 215 | [204.计数质数](https://leetcode-cn.com/problems/count-primes) 216 | 217 | [692.前K个高频单词](https://leetcode-cn.com/problems/top-k-frequent-words) 218 | 219 | [706.设计哈希映射](https://leetcode-cn.com/problems/design-hashmap) 220 | 221 | [895.最大频率栈](https://leetcode-cn.com/problems/maximum-frequency-stack) 222 | 223 | [974.和可被 K 整除的子数组](https://leetcode-cn.com/problems/subarray-sums-divisible-by-k) 224 | 225 | [1044.最长重复子串](https://leetcode-cn.com/problems/longest-duplicate-substring) 226 | 227 | [剑指 Offer 03.数组中重复的数字](https://leetcode-cn.com/problems/shu-zu-zhong-zhong-fu-de-shu-zi-lcof/) 228 | 229 | [剑指 Offer 35.复杂链表的复制](https://leetcode-cn.com/problems/fu-za-lian-biao-de-fu-zhi-lcof/) 230 | 231 | [面试题50.第一个只出现一次的字符](https://leetcode-cn.com/problems/di-yi-ge-zhi-chu-xian-yi-ci-de-zi-fu-lcof) 232 | 233 | 二叉树 234 | 235 | [94.二叉树的中序遍历](https://leetcode-cn.com/problems/binary-tree-inorder-traversal) 236 | 237 | [100.相同的树](https://leetcode-cn.com/problems/same-tree) 238 | 239 | [108.将有序数组转换为二叉搜索树](https://leetcode-cn.com/problems/convert-sorted-array-to-binary-search-tree) 240 | 241 | [116.填充每个节点的下一个右侧节点指针](https://leetcode-cn.com/problems/populating-next-right-pointers-in-each-node) 242 | 243 | [117.填充每个节点的下一个右侧节点指针 II](https://leetcode-cn.com/problems/populating-next-right-pointers-in-each-node-ii) 244 | 245 | [144.二叉树的前序遍历](https://leetcode-cn.com/problems/binary-tree-preorder-traversal) 246 | 247 | [144.二叉树的前序遍历](https://leetcode-cn.com/problems/binary-tree-preorder-traversal) 248 | 249 | [222.完全二叉树的节点个数](https://leetcode-cn.com/problems/count-complete-tree-nodes) 250 | 251 | [226.翻转二叉树](https://leetcode-cn.com/problems/invert-binary-tree) 252 | 253 | [236.二叉树的最近公共祖先](https://leetcode-cn.com/problems/lowest-common-ancestor-of-a-binary-tree) 254 | 255 | [297.二叉树的序列化与反序列化](https://leetcode-cn.com/problems/serialize-and-deserialize-binary-tree) 256 | 257 | [404.左叶子之和](https://leetcode-cn.com/problems/sum-of-left-leaves) 258 | 259 | [450.删除二叉搜索树中的节点](https://leetcode-cn.com/problems/delete-node-in-a-bst) 260 | 261 | [501.二叉搜索树中的众数](https://leetcode-cn.com/problems/find-mode-in-binary-search-tree) 262 | 263 | [508.出现次数最多的子树元素和](https://leetcode-cn.com/problems/most-frequent-subtree-sum) 264 | 265 | [530.二叉搜索树的最小绝对差](https://leetcode-cn.com/problems/minimum-absolute-difference-in-bst) 266 | 267 | [538.把二叉搜索树转换为累加树](https://leetcode-cn.com/problems/convert-bst-to-greater-tree) 268 | 269 | [543.二叉树的直径](https://leetcode-cn.com/problems/diameter-of-binary-tree) 270 | 271 | [617.合并二叉树](https://leetcode-cn.com/problems/merge-two-binary-trees) 272 | 273 | [637.二叉树的层平均值](https://leetcode-cn.com/problems/average-of-levels-in-binary-tree) 274 | 275 | [654.最大二叉树](https://leetcode-cn.com/problems/maximum-binary-tree) 276 | 277 | [662.二叉树最大宽度](https://leetcode-cn.com/problems/maximum-width-of-binary-tree) 278 | 279 | [669.修剪二叉搜索树](https://leetcode-cn.com/problems/trim-a-binary-search-tree) 280 | 281 | [700.二叉搜索树中的搜索](https://leetcode-cn.com/problems/search-in-a-binary-search-tree) 282 | 283 | [701.二叉搜索树中的插入操作](https://leetcode-cn.com/problems/insert-into-a-binary-search-tree) 284 | 285 | [889.根据前序和后序遍历构造二叉树](https://leetcode-cn.com/problems/construct-binary-tree-from-preorder-and-postorder-traversal) 286 | 287 | [958.二叉树的完全性检验](https://leetcode-cn.com/problems/check-completeness-of-a-binary-tree) 288 | 289 | [剑指 Offer 27.二叉树的镜像](https://leetcode-cn.com/problems/er-cha-shu-de-jing-xiang-lcof) 290 | 291 | [剑指 Offer 54.二叉搜索树的第k大节点](https://leetcode-cn.com/problems/er-cha-sou-suo-shu-de-di-kda-jie-dian-lcof/) 292 | 293 | [剑指 Offer 68 - I.二叉搜索树的最近公共祖先](https://leetcode-cn.com/problems/er-cha-sou-suo-shu-de-zui-jin-gong-gong-zu-xian-lcof/) 294 | 295 | [剑指 Offer 68 - II.二叉树的最近公共祖先](https://leetcode-cn.com/problems/er-cha-shu-de-zui-jin-gong-gong-zu-xian-lcof/) 296 | 297 | 广度优先搜索 298 | 299 | [102.二叉树的层序遍历](https://leetcode-cn.com/problems/binary-tree-level-order-traversal) 300 | 301 | [103.二叉树的锯齿形层序遍历](https://leetcode-cn.com/problems/binary-tree-zigzag-level-order-traversal) 302 | 303 | [107.二叉树的层序遍历 II](https://leetcode-cn.com/problems/binary-tree-level-order-traversal-ii) 304 | 305 | [111.二叉树的最小深度](https://leetcode-cn.com/problems/minimum-depth-of-binary-tree) 306 | 307 | [310.最小高度树](https://leetcode-cn.com/problems/minimum-height-trees) 308 | 309 | [513.找树左下角的值](https://leetcode-cn.com/problems/find-bottom-left-tree-value) 310 | 311 | [剑指 Offer 32 - II.从上到下打印二叉树 II](https://leetcode-cn.com/problems/cong-shang-dao-xia-da-yin-er-cha-shu-ii-lcof) 312 | 313 | [剑指 Offer 32 - III.从上到下打印二叉树 III](https://leetcode-cn.com/problems/cong-shang-dao-xia-da-yin-er-cha-shu-iii-lcof) 314 | 315 | [剑指 Offer 37.序列化二叉树](https://leetcode-cn.com/problems/xu-lie-hua-er-cha-shu-lcof/) 316 | 317 | 深度优先搜索 318 | 319 | [99.恢复二叉搜索树](https://leetcode-cn.com/problems/recover-binary-search-tree) 320 | 321 | [101.对称二叉树](https://leetcode-cn.com/problems/symmetric-tree) 322 | 323 | [105.从前序与中序遍历序列构造二叉树](https://leetcode-cn.com/problems/construct-binary-tree-from-preorder-and-inorder-traversal) 324 | 325 | [106.从中序与后序遍历序列构造二叉树](https://leetcode-cn.com/problems/construct-binary-tree-from-inorder-and-postorder-traversal) 326 | 327 | [109.有序链表转换二叉搜索树](https://leetcode-cn.com/problems/convert-sorted-list-to-binary-search-tree) 328 | 329 | [114.二叉树展开为链表](https://leetcode-cn.com/problems/flatten-binary-tree-to-linked-list) 330 | 331 | [257.二叉树的所有路径](https://leetcode-cn.com/problems/binary-tree-paths) 332 | 333 | [301.删除无效的括号](https://leetcode-cn.com/problems/remove-invalid-parentheses) 334 | 335 | [剑指 Offer 12.矩阵中的路径](https://leetcode-cn.com/problems/ju-zhen-zhong-de-lu-jing-lcof) 336 | 337 | [剑指 Offer 17.打印从1到最大的n位数](https://leetcode-cn.com/problems/da-yin-cong-1dao-zui-da-de-nwei-shu-lcof/) 338 | 339 | [剑指 Offer 34.二叉树中和为某一值的路径](https://leetcode-cn.com/problems/er-cha-shu-zhong-he-wei-mou-yi-zhi-de-lu-jing-lcof) 340 | 341 | [剑指 Offer 55 - I.二叉树的深度](https://leetcode-cn.com/problems/er-cha-shu-de-shen-du-lcof/) 342 | 343 | [剑指 Offer 55 - II.平衡二叉树](https://leetcode-cn.com/problems/ping-heng-er-cha-shu-lcof/) 344 | 345 | 滑动窗口 346 | 347 | [3.无重复字符的最长子串](https://leetcode-cn.com/problems/longest-substring-without-repeating-characters) 348 | 349 | [187.重复的DNA序列](https://leetcode-cn.com/problems/repeated-dna-sequences) 350 | 351 | [219.存在重复元素 II](https://leetcode-cn.com/problems/contains-duplicate-ii) 352 | 353 | [220.存在重复元素 III](https://leetcode-cn.com/problems/contains-duplicate-iii) 354 | 355 | [1004.最大连续1的个数 III](https://leetcode-cn.com/problems/max-consecutive-ones-iii) 356 | 357 | [剑指 Offer 48.最长不含重复字符的子字符串](https://leetcode-cn.com/problems/zui-chang-bu-han-zhong-fu-zi-fu-de-zi-zi-fu-chuan-lcof/) 358 | 359 | [剑指 Offer 57 - II.和为s的连续正数序列](https://leetcode-cn.com/problems/he-wei-sde-lian-xu-zheng-shu-xu-lie-lcof/) 360 | 361 | [剑指 Offer 58 - I.翻转单词顺序](https://leetcode-cn.com/problems/fan-zhuan-dan-ci-shun-xu-lcof/) 362 | 363 | 动态规划 364 | 365 | [5.最长回文子串](https://leetcode-cn.com/problems/longest-palindromic-substring) 366 | 367 | [32.最长有效括号](https://leetcode-cn.com/problems/longest-valid-parentheses) 368 | 369 | [62.不同路径](https://leetcode-cn.com/problems/unique-paths) 370 | 371 | [63.不同路径 II](https://leetcode-cn.com/problems/unique-paths-ii) 372 | 373 | [64.最小路径和](https://leetcode-cn.com/problems/minimum-path-sum) 374 | 375 | [70.爬楼梯](https://leetcode-cn.com/problems/climbing-stairs) 376 | 377 | [72.编辑距离](https://leetcode-cn.com/problems/edit-distance) 378 | 379 | [91.解码方法](https://leetcode-cn.com/problems/decode-ways) 380 | 381 | [96.不同的二叉搜索树](https://leetcode-cn.com/problems/unique-binary-search-trees) 382 | 383 | [97.交错字符串](https://leetcode-cn.com/problems/interleaving-string) 384 | 385 | [120.三角形最小路径和](https://leetcode-cn.com/problems/triangle) 386 | 387 | [121.买卖股票的最佳时机](https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock) 388 | 389 | [122.买卖股票的最佳时机 II](https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock-ii) 390 | 391 | [123.买卖股票的最佳时机 III](https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock-iii) 392 | 393 | [139.单词拆分](https://leetcode-cn.com/problems/word-break) 394 | 395 | [152.乘积最大子数组](https://leetcode-cn.com/problems/maximum-product-subarray) 396 | 397 | [198.打家劫舍](https://leetcode-cn.com/problems/house-robber) 398 | 399 | [213.打家劫舍 II](https://leetcode-cn.com/problems/house-robber-ii) 400 | 401 | [221.最大正方形](https://leetcode-cn.com/problems/maximal-square) 402 | 403 | [279.完全平方数](https://leetcode-cn.com/problems/perfect-squares) 404 | 405 | [300.最长递增子序列](https://leetcode-cn.com/problems/longest-increasing-subsequence) 406 | 407 | [322.零钱兑换](https://leetcode-cn.com/problems/coin-change) 408 | 409 | [337.打家劫舍 III](https://leetcode-cn.com/problems/house-robber-iii) 410 | 411 | [343.整数拆分](https://leetcode-cn.com/problems/integer-break) 412 | 413 | [410.分割数组的最大值](https://leetcode-cn.com/problems/split-array-largest-sum) 414 | 415 | [494.目标和](https://leetcode-cn.com/problems/target-sum) 416 | 417 | [516.最长回文子序列](https://leetcode-cn.com/problems/longest-palindromic-subsequence) 418 | 419 | [518.零钱兑换 II](https://leetcode-cn.com/problems/coin-change-2) 420 | 421 | [673.最长递增子序列的个数](https://leetcode-cn.com/problems/number-of-longest-increasing-subsequence) 422 | 423 | [718.最长重复子数组](https://leetcode-cn.com/problems/maximum-length-of-repeated-subarray) 424 | 425 | [1143.最长公共子序列](https://leetcode-cn.com/problems/longest-common-subsequence) 426 | 427 | [剑指 Offer 14- I.剪绳子](https://leetcode-cn.com/problems/jian-sheng-zi-lcof/) 428 | 429 | [剑指 Offer 14- II.剪绳子 II](https://leetcode-cn.com/problems/jian-sheng-zi-ii-lcof/) 430 | 431 | [剑指 Offer 46.把数字翻译成字符串](https://leetcode-cn.com/problems/ba-shu-zi-fan-yi-cheng-zi-fu-chuan-lcof/) 432 | 433 | [剑指 Offer 47.礼物的最大价值](https://leetcode-cn.com/problems/li-wu-de-zui-da-jie-zhi-lcof/) 434 | 435 | [剑指 Offer 49.丑数](https://leetcode-cn.com/problems/chou-shu-lcof/) 436 | 437 | [剑指 Offer 60.n个骰子的点数](https://leetcode-cn.com/problems/nge-tou-zi-de-dian-shu-lcof/) 438 | 439 | [剑指 Offer 63.股票的最大利润](https://leetcode-cn.com/problems/gu-piao-de-zui-da-li-run-lcof/) 440 | 441 | 二分查找 442 | 443 | [4.寻找两个正序数组的中位数](https://leetcode-cn.com/problems/median-of-two-sorted-arrays) 444 | 445 | [33.搜索旋转排序数组](https://leetcode-cn.com/problems/search-in-rotated-sorted-array) 446 | 447 | [34.在排序数组中查找元素的第一个和最后一个位置](https://leetcode-cn.com/problems/find-first-and-last-position-of-element-in-sorted-array) 448 | 449 | [50.Pow(x, n)](https://leetcode-cn.com/problems/powx-n) 450 | 451 | [69.x 的平方根](https://leetcode-cn.com/problems/sqrtx) 452 | 453 | [153.寻找旋转排序数组中的最小值](https://leetcode-cn.com/problems/find-minimum-in-rotated-sorted-array) 454 | 455 | [162.寻找峰值](https://leetcode-cn.com/problems/find-peak-element) 456 | 457 | [230.二叉搜索树中第K小的元素](https://leetcode-cn.com/problems/kth-smallest-element-in-a-bst) 458 | 459 | [287.寻找重复数](https://leetcode-cn.com/problems/find-the-duplicate-number) 460 | 461 | [349.两个数组的交集](https://leetcode-cn.com/problems/intersection-of-two-arrays) 462 | 463 | [704.二分查找](https://leetcode-cn.com/problems/binary-search) 464 | 465 | [剑指 Offer 11.旋转数组的最小数字](https://leetcode-cn.com/problems/xuan-zhuan-shu-zu-de-zui-xiao-shu-zi-lcof/) 466 | 467 | [剑指 Offer 53 - I.在排序数组中查找数字 I](https://leetcode-cn.com/problems/zai-pai-xu-shu-zu-zhong-cha-zhao-shu-zi-lcof/) 468 | 469 | [剑指 Offer 53 - II.0~n-1中缺失的数字](https://leetcode-cn.com/problems/que-shi-de-shu-zi-lcof/) 470 | 471 | 双指针 472 | 473 | [3.无重复字符的最长子串](https://leetcode-cn.com/problems/longest-substring-without-repeating-characters) 474 | 475 | [11.盛最多水的容器](https://leetcode-cn.com/problems/container-with-most-water) 476 | 477 | [15.三数之和](https://leetcode-cn.com/problems/3sum) 478 | 479 | [19.删除链表的倒数第 N 个结点](https://leetcode-cn.com/problems/remove-nth-node-from-end-of-list) 480 | 481 | [26.删除有序数组中的重复项](https://leetcode-cn.com/problems/remove-duplicates-from-sorted-array) 482 | 483 | [42.接雨水](https://leetcode-cn.com/problems/trapping-rain-water) 484 | 485 | [61.旋转链表](https://leetcode-cn.com/problems/rotate-list) 486 | 487 | [75.颜色分类](https://leetcode-cn.com/problems/sort-colors) 488 | 489 | [76.最小覆盖子串](https://leetcode-cn.com/problems/minimum-window-substring) 490 | 491 | [88.合并两个有序数组](https://leetcode-cn.com/problems/merge-sorted-array) 492 | 493 | [125.验证回文串](https://leetcode-cn.com/problems/valid-palindrome) 494 | 495 | [141.环形链表](https://leetcode-cn.com/problems/linked-list-cycle) 496 | 497 | [142.环形链表 II](https://leetcode-cn.com/problems/linked-list-cycle-ii) 498 | 499 | [209.长度最小的子数组](https://leetcode-cn.com/problems/minimum-size-subarray-sum) 500 | 501 | [234.回文链表](https://leetcode-cn.com/problems/palindrome-linked-list) 502 | 503 | [283.移动零](https://leetcode-cn.com/problems/move-zeroes) 504 | 505 | [剑指 Offer 04.二维数组中的查找](https://leetcode-cn.com/problems/er-wei-shu-zu-zhong-de-cha-zhao-lcof) 506 | 507 | [剑指 Offer 18.删除链表的节点](https://leetcode-cn.com/problems/shan-chu-lian-biao-de-jie-dian-lcof/) 508 | 509 | [剑指 Offer 21.调整数组顺序使奇数位于偶数前面](https://leetcode-cn.com/problems/diao-zheng-shu-zu-shun-xu-shi-qi-shu-wei-yu-ou-shu-qian-mian-lcof/) 510 | 511 | [剑指 Offer 22.链表中倒数第k个节点](https://leetcode-cn.com/problems/lian-biao-zhong-dao-shu-di-kge-jie-dian-lcof) 512 | 513 | [剑指 Offer 58 - I.翻转单词顺序](https://leetcode-cn.com/problems/fan-zhuan-dan-ci-shun-xu-lcof/) 514 | 515 | 并查集 516 | 517 | [128.最长连续序列](https://leetcode-cn.com/problems/longest-consecutive-sequence) 518 | 519 | [130.被围绕的区域](https://leetcode-cn.com/problems/surrounded-regions) 520 | 521 | [200.岛屿数量](https://leetcode-cn.com/problems/number-of-islands) 522 | 523 | 位运算 524 | 525 | [136.只出现一次的数字](https://leetcode-cn.com/problems/single-number) 526 | 527 | [137.只出现一次的数字 II](https://leetcode-cn.com/problems/single-number-ii) 528 | 529 | [191.位1的个数](https://leetcode-cn.com/problems/number-of-1-bits) 530 | 531 | [231.2 的幂](https://leetcode-cn.com/problems/power-of-two) 532 | 533 | [260.只出现一次的数字 III](https://leetcode-cn.com/problems/single-number-iii) 534 | 535 | [268.丢失的数字](https://leetcode-cn.com/problems/missing-number) 536 | 537 | [405.数字转换为十六进制数](https://leetcode-cn.com/problems/convert-a-number-to-hexadecimal) 538 | 539 | [剑指 Offer 15.二进制中1的个数](https://leetcode-cn.com/problems/er-jin-zhi-zhong-1de-ge-shu-lcof/) 540 | 541 | [剑指 Offer 56 - I.数组中数字出现的次数](https://leetcode-cn.com/problems/shu-zu-zhong-shu-zi-chu-xian-de-ci-shu-lcof/) 542 | 543 | [剑指 Offer 56 - II.数组中数字出现的次数 II](https://leetcode-cn.com/problems/shu-zu-zhong-shu-zi-chu-xian-de-ci-shu-ii-lcof/) 544 | 545 | [剑指 Offer 65.不用加减乘除做加法](https://leetcode-cn.com/problems/bu-yong-jia-jian-cheng-chu-zuo-jia-fa-lcof/) 546 | 547 | 分治 548 | 549 | [23.合并K个升序链表](https://leetcode-cn.com/problems/merge-k-sorted-lists) 550 | 551 | [53.最大子数组和](https://leetcode-cn.com/problems/maximum-subarray) 552 | 553 | [169.多数元素](https://leetcode-cn.com/problems/majority-element) 554 | 555 | [剑指 Offer 42.连续子数组的最大和](https://leetcode-cn.com/problems/lian-xu-zi-shu-zu-de-zui-da-he-lcof) 556 | 557 | [剑指 Offer 36.二叉搜索树与双向链表](https://leetcode-cn.com/problems/er-cha-sou-suo-shu-yu-shuang-xiang-lian-biao-lcof) 558 | 559 | [剑指 Offer 39.数组中出现次数超过一半的数字](https://leetcode-cn.com/problems/shu-zu-zhong-chu-xian-ci-shu-chao-guo-yi-ban-de-shu-zi-lcof) 560 | 561 | 回溯 562 | 563 | [10.正则表达式匹配](https://leetcode-cn.com/problems/regular-expression-matching) 564 | 565 | [17.电话号码的字母组合](https://leetcode-cn.com/problems/letter-combinations-of-a-phone-number) 566 | 567 | [22.括号生成](https://leetcode-cn.com/problems/generate-parentheses) 568 | 569 | [39.组合总和](https://leetcode-cn.com/problems/combination-sum) 570 | 571 | [40.组合总和 II](https://leetcode-cn.com/problems/combination-sum-ii) 572 | 573 | [46.全排列](https://leetcode-cn.com/problems/permutations) 574 | 575 | [47.全排列 II](https://leetcode-cn.com/problems/permutations-ii) 576 | 577 | [51.N 皇后](https://leetcode-cn.com/problems/n-queens) 578 | 579 | [60.排列序列](https://leetcode-cn.com/problems/permutation-sequence) 580 | 581 | [77.组合](https://leetcode-cn.com/problems/combinations) 582 | 583 | [78.子集](https://leetcode-cn.com/problems/subsets) 584 | 585 | [79.单词搜索](https://leetcode-cn.com/problems/word-search) 586 | 587 | [89.格雷编码](https://leetcode-cn.com/problems/gray-code) 588 | 589 | [93.复原 IP 地址](https://leetcode-cn.com/problems/restore-ip-addresses) 590 | 591 | [140.单词拆分 II](https://leetcode-cn.com/problems/word-break-ii) 592 | 593 | [306.累加数](https://leetcode-cn.com/problems/additive-number) 594 | 595 | [剑指 Offer 13.机器人的运动范围](https://leetcode-cn.com/problems/ji-qi-ren-de-yun-dong-fan-wei-lcof/) 596 | 597 | [剑指 Offer 38.字符串的排列](https://leetcode-cn.com/problems/zi-fu-chuan-de-pai-lie-lcof) 598 | 599 | 贪心 600 | 601 | [44.通配符匹配](https://leetcode-cn.com/problems/wildcard-matching) 602 | 603 | [45.跳跃游戏 II](https://leetcode-cn.com/problems/jump-game-ii) 604 | 605 | [55.跳跃游戏](https://leetcode-cn.com/problems/jump-game) 606 | 607 | [134.加油站](https://leetcode-cn.com/problems/gas-station) 608 | 609 | [135.分发糖果](https://leetcode-cn.com/problems/candy) 610 | 611 | [316.去除重复字母](https://leetcode-cn.com/problems/remove-duplicate-letters) 612 | 613 | [763.划分字母区间](https://leetcode-cn.com/problems/partition-labels) 614 | 615 | 排序 616 | 617 | [148.排序链表](https://leetcode-cn.com/problems/sort-list) 618 | 619 | [164.最大间距](https://leetcode-cn.com/problems/maximum-gap) 620 | 621 | [179.最大数](https://leetcode-cn.com/problems/largest-number) 622 | 623 | [242.有效的字母异位词](https://leetcode-cn.com/problems/valid-anagram) 624 | 625 | [315.计算右侧小于当前元素的个数](https://leetcode-cn.com/problems/count-of-smaller-numbers-after-self) 626 | 627 | [剑指 Offer 40.最小的k个数](https://leetcode-cn.com/problems/zui-xiao-de-kge-shu-lcof/) 628 | 629 | [剑指 Offer 45.把数组排成最小的数](https://leetcode-cn.com/problems/ba-shu-zu-pai-cheng-zui-xiao-de-shu-lcof) 630 | 631 | [剑指 Offer 51.数组中的逆序对](https://leetcode-cn.com/problems/shu-zu-zhong-de-ni-xu-dui-lcof/) 632 | 633 | [剑指 Offer 61.扑克牌中的顺子](https://leetcode-cn.com/problems/bu-ke-pai-zhong-de-shun-zi-lcof/) 634 | 635 | 递归 636 | 637 | [2.两数相加](https://leetcode-cn.com/problems/add-two-numbers) 638 | 639 | [21.合并两个有序链表](https://leetcode-cn.com/problems/merge-two-sorted-lists) 640 | 641 | [24.两两交换链表中的节点](https://leetcode-cn.com/problems/swap-nodes-in-pairs) 642 | 643 | [98.验证二叉搜索树](https://leetcode-cn.com/problems/validate-binary-search-tree) 644 | 645 | [104.二叉树的最大深度](https://leetcode-cn.com/problems/maximum-depth-of-binary-tree) 646 | 647 | [110.平衡二叉树](https://leetcode-cn.com/problems/balanced-binary-tree) 648 | 649 | [124.二叉树中的最大路径和](https://leetcode-cn.com/problems/binary-tree-maximum-path-sum) 650 | 651 | [199.二叉树的右视图](https://leetcode-cn.com/problems/binary-tree-right-side-view) 652 | 653 | [剑指 Offer 07.重建二叉树](https://leetcode-cn.com/problems/zhong-jian-er-cha-shu-lcof) 654 | 655 | [剑指 Offer 10- I.斐波那契数列](https://leetcode-cn.com/problems/fei-bo-na-qi-shu-lie-lcof) 656 | 657 | [剑指 Offer 10- II.青蛙跳台阶问题](https://leetcode-cn.com/problems/qing-wa-tiao-tai-jie-wen-ti-lcof) 658 | 659 | [剑指 Offer 26.树的子结构](https://leetcode-cn.com/problems/shu-de-zi-jie-gou-lcof/) 660 | 661 | [剑指 Offer 28.对称的二叉树](https://leetcode-cn.com/problems/dui-cheng-de-er-cha-shu-lcof/) 662 | 663 | [剑指 Offer 64.求1+2+…+n](https://leetcode-cn.com/problems/qiu-12n-lcof/) 664 | 665 | 数学 666 | 667 | 668 | 669 | [7.整数反转](https://leetcode-cn.com/problems/reverse-integer) 670 | 671 | [8.字符串转换整数 (atoi)](https://leetcode-cn.com/problems/string-to-integer-atoi) 672 | 673 | [9.回文数](https://leetcode-cn.com/problems/palindrome-number) 674 | 675 | [43.字符串相乘](https://leetcode-cn.com/problems/multiply-strings) 676 | 677 | [166.分数到小数](https://leetcode-cn.com/problems/fraction-to-recurring-decimal) 678 | 679 | [168.Excel表列名称](https://leetcode-cn.com/problems/excel-sheet-column-title) 680 | 681 | [171.Excel 表列序号](https://leetcode-cn.com/problems/excel-sheet-column-number) 682 | 683 | [400.第 N 位数字](https://leetcode-cn.com/problems/nth-digit) 684 | 685 | [670.最大交换](https://leetcode-cn.com/problems/maximum-swap) 686 | 687 | [剑指 Offer 16.数值的整数次方](https://leetcode-cn.com/problems/shu-zhi-de-zheng-shu-ci-fang-lcof/) 688 | 689 | [剑指 Offer 43.1~n 整数中 1 出现的次数](https://leetcode-cn.com/problems/1nzheng-shu-zhong-1chu-xian-de-ci-shu-lcof/) 690 | 691 | [剑指 Offer 44.数字序列中某一位的数字](https://leetcode-cn.com/problems/shu-zi-xu-lie-zhong-mou-yi-wei-de-shu-zi-lcof/) 692 | 693 | [剑指 Offer 62.圆圈中最后剩下的数字](https://leetcode-cn.com/problems/yuan-quan-zhong-zui-hou-sheng-xia-de-shu-zi-lcof/) 694 | 695 | 696 | 697 | ![image.png](https://cdn.nlark.com/yuque/0/2021/png/1500604/1616337697571-91fbfc95-155b-4c03-973e-992a9542f27c.png?x-oss-process=image%2Fwatermark%2Ctype_d3F5LW1pY3JvaGVp%2Csize_76%2Ctext_5pyI5ZOl55qE6Z2i6K-V6K6t57uD6JCl%2Ccolor_FFFFFF%2Cshadow_50%2Ct_80%2Cg_se%2Cx_10%2Cy_10%2Fresize%2Cw_893%2Climit_0) 698 | 699 | ![链表.png](https://cdn.nlark.com/yuque/0/2021/png/1500604/1616547710292-e621c072-fb76-4574-a199-73f5210e9540.png?x-oss-process=image%2Fwatermark%2Ctype_d3F5LW1pY3JvaGVp%2Csize_43%2Ctext_5pyI5ZOl55qE6Z2i6K-V6K6t57uD6JCl%2Ccolor_FFFFFF%2Cshadow_50%2Ct_80%2Cg_se%2Cx_10%2Cy_10%2Fresize%2Cw_750%2Climit_0) 700 | 701 | ![动态规划.png](https://cdn.nlark.com/yuque/0/2021/png/1500604/1629541988228-c0f94f7a-f2b5-41da-b795-0c135bd3bd2f.png?x-oss-process=image%2Fwatermark%2Ctype_d3F5LW1pY3JvaGVp%2Csize_59%2Ctext_5pyI5ZOl55qE6Z2i6K-V6K6t57uD6JCl%2Ccolor_FFFFFF%2Cshadow_50%2Ct_80%2Cg_se%2Cx_10%2Cy_10%2Fresize%2Cw_750%2Climit_0) -------------------------------------------------------------------------------- /计算机网络面试题/计算机网络面试题42题.md: -------------------------------------------------------------------------------- 1 | ## 1.GET和POST的请求的区别 2 | 3 | Post 和 Get 是 HTTP 请求的两种方法,其区别如下: 4 | 5 | - **应用场景:**GET 请求是一个幂等的请求,一般 Get 请求用于对服务器资源不会产生影响的场景,比如说请求一个网页的资源。而 Post 不是一个幂等的请求,一般用于对服务器资源会产生影响的情景,比如注册用户这一类的操作。 6 | - **是否缓存:**因为两者应用场景不同,浏览器一般会对 Get 请求缓存,但很少对 Post 请求缓存。 7 | - **发送的报文格式:**Get 请求的报文中实体部分为空,Post 请求的报文中实体部分一般为向服务器发送的数据。 8 | - **安全性:**Get 请求可以将请求的参数放入 url 中向服务器发送,这样的做法相对于 Post 请求来说是不太安全的,因为请求的 url 会被保留在历史记录中。 9 | - **请求长度:**浏览器由于对 url 长度的限制,所以会影响 get 请求发送数据时的长度。这个限制是浏览器规定的,并不是 RFC 规定的。 10 | - **参数类型:**post 的参数传递支持更多的数据类型。 11 | 12 | ## 2.POST和PUT请求的区别 13 | 14 | - PUT请求是向服务器端发送数据,从而修改数据的内容,但是不会增加数据的种类等,也就是说无论进行多少次PUT操作,其结果并没有不同。(可以理解为是**更新数据**) 15 | - POST请求是向服务器端发送数据,该请求会改变数据的种类等资源,它会创建新的内容。(可以理解为是**创建数据**) 16 | 17 | ## 3.常见的HTTP请求头和响应头 18 | 19 | **HTTP Request Header 常见的请求头:** 20 | 21 | - Accept:浏览器能够处理的内容类型 22 | - Accept-Charset:浏览器能够显示的字符集 23 | - Accept-Encoding:浏览器能够处理的压缩编码 24 | - Accept-Language:浏览器当前设置的语言 25 | - Connection:浏览器与服务器之间连接的类型 26 | - Cookie:当前页面设置的任何Cookie 27 | - Host:发出请求的页面所在的域 28 | - Referer:发出请求的页面的URL 29 | - User-Agent:浏览器的用户代理字符串 30 | 31 | **HTTP Responses Header 常见的响应头:** 32 | 33 | - Date:表示消息发送的时间,时间的描述格式由rfc822定义 34 | - server:服务器名称 35 | - Connection:浏览器与服务器之间连接的类型 36 | - Cache-Control:控制HTTP缓存 37 | - content-type:表示后面的文档属于什么MIME类型 38 | 39 | 常见的 Content-Type 属性值有以下四种: 40 | 41 | (1)application/x-www-form-urlencoded:浏览器的原生 form 表单,如果不设置 enctype 属性,那么最终就会以 application/x-www-form-urlencoded 方式提交数据。该种方式提交的数据放在 body 里面,数据按照 key1=val1&key2=val2 的方式进行编码,key 和 val 都进行了 URL转码。 42 | 43 | (2)multipart/form-data:该种方式也是一个常见的 POST 提交方式,通常表单上传文件时使用该种方式。 44 | 45 | (3)application/json:服务器消息主体是序列化后的 JSON 字符串。 46 | 47 | (4)text/xml:该种方式主要用来提交 XML 格式的数据。 48 | 49 | 服务器向客户端发送数据格式类型:XML、HTML、JSON 50 | 51 | ## 4.HTTP状态码304是多好还是少好 52 | 53 | 服务器为了提高网站访问速度,对之前访问的部分页面指定缓存机制,当客户端在此对这些页面进行请求,服务器会根据缓存内容判断页面与之前是否相同,若相同便直接返回304,此时客户端调用缓存内容,不必进行二次下载。 54 | 55 | 状态码304不应该认为是一种错误,而是对客户端**有缓存情况下**服务端的一种响应。 56 | 57 | 搜索引擎蜘蛛会更加青睐内容源更新频繁的网站。通过特定时间内对网站抓取返回的状态码来调节对该网站的抓取频次。若网站在一定时间内一直处于304的状态,那么蜘蛛可能会降低对网站的抓取次数。相反,若网站变化的频率非常之快,每次抓取都能获取新内容,那么日积月累,的回访率也会提高。 58 | 59 | **产生较多304状态码的原因:** 60 | 61 | - 页面更新周期长或不更新 62 | - 纯静态页面或强制生成静态html 63 | 64 | **304状态码出现过多会造成以下问题:** 65 | 66 | - 网站快照停止; 67 | - 收录减少; 68 | - 权重下降。 69 | 70 | ## 5.常见的HTTP请求方法 71 | 72 | - GET: 向服务器获取数据; 73 | - POST:将实体提交到指定的资源,通常会造成服务器资源的修改; 74 | - PUT:上传文件,更新数据; 75 | - DELETE:删除服务器上的对象; 76 | - HEAD:获取报文首部,与GET相比,不返回报文主体部分; 77 | - OPTIONS:询问支持的请求方法,用来跨域请求; 78 | - CONNECT:要求在与代理服务器通信时建立隧道,使用隧道进行TCP通信; 79 | - TRACE: 回显服务器收到的请求,主要⽤于测试或诊断。 80 | 81 | ## 6. HTTP 1.0 和 HTTP 1.1 之间有哪些区别? 82 | 83 | **HTTP 1.0和 HTTP 1.1** **有以下区别**: 84 | 85 | - **连接方面**,http1.0 默认使用非持久连接,而 http1.1 默认使用持久连接。http1.1 通过使用持久连接来使多个 http 请求复用同一个 TCP 连接,以此来避免使用非持久连接时每次需要建立连接的时延。 86 | - **资源请求方面**,在 http1.0 中,存在一些浪费带宽的现象,例如客户端只是需要某个对象的一部分,而服务器却将整个对象送过来了,并且不支持断点续传功能,http1.1 则在请求头引入了 range 头域,它允许只请求资源的某个部分,即返回码是 206(Partial Content),这样就方便了开发者自由的选择以便于充分利用带宽和连接。 87 | - **缓存方面**,在 http1.0 中主要使用 header 里的 If-Modified-Since、Expires 来做为缓存判断的标准,http1.1 则引入了更多的缓存控制策略,例如 Etag、If-Unmodified-Since、If-Match、If-None-Match 等更多可供选择的缓存头来控制缓存策略。 88 | - http1.1 中**新增了 host 字段**,用来指定服务器的域名。http1.0 中认为每台服务器都绑定一个唯一的 IP 地址,因此,请求消息中的 URL 并没有传递主机名(hostname)。但随着虚拟主机技术的发展,在一台物理服务器上可以存在多个虚拟主机,并且它们共享一个IP地址。因此有了 host 字段,这样就可以将请求发往到同一台服务器上的不同网站。 89 | - http1.1 相对于 http1.0 还新增了很多**请求方法**,如 PUT、HEAD、OPTIONS 等。 90 | 91 | ## 7. HTTP 1.1 和 HTTP 2.0 的区别 92 | 93 | - **二进制协议:**HTTP/2 是一个二进制协议。在 HTTP/1.1 版中,报文的头信息必须是文本(ASCII 编码),数据体可以是文本,也可以是二进制。HTTP/2 则是一个彻底的二进制协议,头信息和数据体都是二进制,并且统称为"帧",可以分为头信息帧和数据帧。 帧的概念是它实现多路复用的基础。 94 | - **多路复用:**HTTP/2 实现了多路复用,HTTP/2 仍然复用 TCP 连接,但是在一个连接里,客户端和服务器都可以同时发送多个请求或回应,而且不用按照顺序一一发送,这样就避免了"队头堵塞"【1】的问题。 95 | - **数据流:**HTTP/2 使用了数据流的概念,因为 HTTP/2 的数据包是不按顺序发送的,同一个连接里面连续的数据包,可能属于不同的请求。因此,必须要对数据包做标记,指出它属于哪个请求。HTTP/2 将每个请求或回应的所有数据包,称为一个数据流。每个数据流都有一个独一无二的编号。数据包发送时,都必须标记数据流 ID ,用来区分它属于哪个数据流。 96 | - **头信息压缩:**HTTP/2 实现了头信息压缩,由于 HTTP 1.1 协议不带状态,每次请求都必须附上所有信息。所以,请求的很多字段都是重复的,比如 Cookie 和 User Agent ,一模一样的内容,每次请求都必须附带,这会浪费很多带宽,也影响速度。HTTP/2 对这一点做了优化,引入了头信息压缩机制。一方面,头信息使用 gzip 或 compress 压缩后再发送;另一方面,客户端和服务器同时维护一张头信息表,所有字段都会存入这个表,生成一个索引号,以后就不发送同样字段了,只发送索引号,这样就能提高速度了。 97 | - **服务器推送:**HTTP/2 允许服务器未经请求,主动向客户端发送资源,这叫做服务器推送。使用服务器推送提前给客户端推送必要的资源,这样就可以相对减少一些延迟时间。这里需要注意的是 http2 下服务器主动推送的是静态资源,和 WebSocket 以及使用 SSE 等方式向客户端发送即时数据的推送是不同的。 98 | 99 | **【1】队头堵塞:** 100 | 101 | > 队头阻塞是由 HTTP 基本的“请求 - 应答”模型所导致的。HTTP 规定报文必须是“一发一收”,这就形成了一个先进先出的“串行”队列。队列里的请求是没有优先级的,只有入队的先后顺序,排在最前面的请求会被最优先处理。如果队首的请求因为处理的太慢耽误了时间,那么队列里后面的所有请求也不得不跟着一起等待,结果就是其他的请求承担了不应有的时间成本,造成了队头堵塞的现象。 102 | 103 | ## 8. HTTP和HTTPS协议的区别 104 | 105 | HTTP和HTTPS协议的主要区别如下: 106 | 107 | - HTTPS协议需要CA证书,费用较高;而HTTP协议不需要; 108 | - HTTP协议是超文本传输协议,信息是明文传输的,HTTPS则是具有安全性的SSL加密传输协议; 109 | - 使用不同的连接方式,端口也不同,HTTP协议端口是80,HTTPS协议端口是443; 110 | - HTTP协议连接很简单,是无状态的;HTTPS协议是有SSL和HTTP协议构建的可进行加密传输、身份认证的网络协议,比HTTP更加安全。 111 | 112 | ## 9. 当在浏览器中输入 Google.com 并且按下回车之后发生了什么? 113 | 114 | **(1)解析URL:**首先会对 URL 进行解析,分析所需要使用的传输协议和请求的资源的路径。如果输入的 URL 中的协议或者主机名不合法,将会把地址栏中输入的内容传递给搜索引擎。如果没有问题,浏览器会检查 URL 中是否出现了非法字符,如果存在非法字符,则对非法字符进行转义后再进行下一过程。 115 | 116 | **(2)缓存判断:**浏览器会判断所请求的资源是否在缓存里,如果请求的资源在缓存里并且没有失效,那么就直接使用,否则向服务器发起新的请求。 117 | 118 | **(3)DNS解析:**下一步首先需要获取的是输入的 URL 中的域名的 IP 地址,首先会判断本地是否有该域名的 IP 地址的缓存,如果有则使用,如果没有则向本地 DNS 服务器发起请求。本地 DNS 服务器也会先检查是否存在缓存,如果没有就会先向根域名服务器发起请求,获得负责的顶级域名服务器的地址后,再向顶级域名服务器请求,然后获得负责的权威域名服务器的地址后,再向权威域名服务器发起请求,最终获得域名的 IP 地址后,本地 DNS 服务器再将这个 IP 地址返回给请求的用户。用户向本地 DNS 服务器发起请求属于递归请求,本地 DNS 服务器向各级域名服务器发起请求属于迭代请求。 119 | 120 | **(4)获取MAC地址:**当浏览器得到 IP 地址后,数据传输还需要知道目的主机 MAC 地址,因为应用层下发数据给传输层,TCP 协议会指定源端口号和目的端口号,然后下发给网络层。网络层会将本机地址作为源地址,获取的 IP 地址作为目的地址。然后将下发给数据链路层,数据链路层的发送需要加入通信双方的 MAC 地址,本机的 MAC 地址作为源 MAC 地址,目的 MAC 地址需要分情况处理。通过将 IP 地址与本机的子网掩码相与,可以判断是否与请求主机在同一个子网里,如果在同一个子网里,可以使用 ARP 协议获取到目的主机的 MAC 地址,如果不在一个子网里,那么请求应该转发给网关,由它代为转发,此时同样可以通过 ARP 协议来获取网关的 MAC 地址,此时目的主机的 MAC 地址应该为网关的地址。 121 | 122 | **(5)TCP三次握手:**下面是 TCP 建立连接的三次握手的过程,首先客户端向服务器发送一个 SYN 连接请求报文段和一个随机序号,服务端接收到请求后向服务器端发送一个 SYN ACK报文段,确认连接请求,并且也向客户端发送一个随机序号。客户端接收服务器的确认应答后,进入连接建立的状态,同时向服务器也发送一个ACK 确认报文段,服务器端接收到确认后,也进入连接建立状态,此时双方的连接就建立起来了。 123 | 124 | **(6)HTTPS四次握手:**如果使用的是 HTTPS 协议,在通信前还存在 TLS 的一个四次握手的过程。首先由客户端向服务器端发送使用的协议的版本号、一个随机数和可以使用的加密方法。服务器端收到后,确认加密的方法,也向客户端发送一个随机数和自己的数字证书。客户端收到后,首先检查数字证书是否有效,如果有效,则再生成一个随机数,并使用证书中的公钥对随机数加密,然后发送给服务器端,并且还会提供一个前面所有内容的 hash 值供服务器端检验。服务器端接收后,使用自己的私钥对数据解密,同时向客户端发送一个前面所有内容的 hash 值供客户端检验。这个时候双方都有了三个随机数,按照之前所约定的加密方法,使用这三个随机数生成一把秘钥,以后双方通信前,就使用这个秘钥对数据进行加密后再传输。 125 | 126 | **(7)返回数据:**当页面请求发送到服务器端后,服务器端会返回一个 html 文件作为响应,浏览器接收到响应后,开始对 html 文件进行解析,开始页面的渲染过程。 127 | 128 | **(8)页面渲染:**浏览器首先会根据 html 文件构建 DOM 树,根据解析到的 css 文件构建 CSSOM 树,如果遇到 script 标签,则判端是否含有 defer 或者 async 属性,要不然 script 的加载和执行会造成页面的渲染的阻塞。当 DOM 树和 CSSOM 树建立好后,根据它们来构建渲染树。渲染树构建好后,会根据渲染树来进行布局。布局完成后,最后使用浏览器的 UI 接口对页面进行绘制。这个时候整个页面就显示出来了。 129 | 130 | **(9)TCP四次挥手:**最后一步是 TCP 断开连接的四次挥手过程。若客户端认为数据发送完成,则它需要向服务端发送连接释放请求。服务端收到连接释放请求后,会告诉应用层要释放 TCP 链接。然后会发送 ACK 包,并进入 CLOSE_WAIT 状态,此时表明客户端到服务端的连接已经释放,不再接收客户端发的数据了。但是因为 TCP 连接是双向的,所以服务端仍旧可以发送数据给客户端。服务端如果此时还有没发完的数据会继续发送,完毕后会向客户端发送连接释放请求,然后服务端便进入 LAST-ACK 状态。客户端收到释放请求后,向服务端发送确认应答,此时客户端进入 TIME-WAIT 状态。该状态会持续 2MSL(最大段生存期,指报文段在网络中生存的时间,超时会被抛弃) 时间,若该时间段内没有服务端的重发请求的话,就进入 CLOSED 状态。当服务端收到确认应答后,也便进入 CLOSED 状态。 131 | 132 | 133 | 134 | 简洁版 135 | 136 | 1、url的解析:协议和主机名合不合法,不合法就跳转到搜索引擎,然后在验证是否有非法字符,如果有的话,会进行转义, 137 | 138 | 2、缓存判断: 139 | 140 | 3、dns解析: 141 | 142 | 4、获取mac地址 : 通过ip地址于子网掩码相与,判断请求是否在请求主机是否在同一个子网里,如果在一个子网里的话,就通过ARP协议去获取mac地址,如果不是,那就需要网关转发,然后在通过ARP协议去获取 143 | 144 | 5、tcp的三次握手 145 | 146 | ​ 客户端-服务端 发送一个SYN连接请求报文段和一个随机序号,服务端接受后,也发送一个SYN ack报文段和一个随机序号,确认连接请求,客户端收到应答后,进入连接状态,并向服务器也发送一个ack确认报文段,服务器收到后,也进入连接状态 147 | 148 | 6、https的四次握手 149 | 150 | ​ 1、客户端-服务端 情求中携带 协议号、加密的方法、以及一个随机数 151 | 152 | ​ 2、服务端-客户单 返回加密的方法,证书、和随机数 153 | 154 | ​ 3、客户端验证证书的有效性,并使用证书的公钥去加密生成的一个随机数,发给服务端,还会提供一个前面所有内容的hash值,用来供服务器校验 155 | 156 | ​ 4、服务端用私钥去解密随机数,然后向客户端返回前面所有内容的hash值供客户端校验 157 | 158 | ​ 5、客户端用加密方法去加密三个随机数,生成密钥,以后双方通信就使用这个密钥去通信了 159 | 160 | 7、返回数据、页面渲染 161 | 162 | 8、TCP的四次挥手 163 | 164 | ​ 1、客户端-服务端 发送连接释放请求 165 | 166 | ​ 2、服务端收到后,就会释放tcp连接,然后发送ack包,并进入closewait状态,此时表明不在接收客户端的数据了,但是因为tcp是双向的,所以服务端还可以发送数据给客户端,发送完毕后,向客户端发送连接释放请求,并进入last-ack状态 167 | 168 | ​ 3、客户端收到连接释放请求后,向服务端确认应答,此时会进入time-wait状态,该状态会持续2MSL最大生存期,若该时间段没有收到服务端的重发请求的话,便进入closed状态 169 | 170 | ​ 4、服务端接受到确认应答后,也进入closed状态 171 | 172 | ## 10. 对keep-alive的理解 173 | 174 | HTTP1.0 中默认是在每次请求/应答,客户端和服务器都要新建一个连接,完成之后立即断开连接,这就是**短连接**。当使用Keep-Alive模式时,Keep-Alive功能使客户端到服务器端的连接持续有效,当出现对服务器的后继请求时,Keep-Alive功能避免了建立或者重新建立连接,这就是**长连接**。其使用方法如下: 175 | 176 | - HTTP1.0版本是默认没有Keep-alive的(也就是默认会发送keep-alive),所以要想连接得到保持,必须手动配置发送 `Connection: keep-alive`字段。若想断开keep-alive连接,需发送 `Connection:close`字段; 177 | - HTTP1.1规定了默认保持长连接,数据传输完成了保持TCP连接不断开,等待在同域名下继续用这个通道传输数据。如果需要关闭,需要客户端发送 `Connection:close`首部字段。 178 | 179 | Keep-Alive的**建立过程**: 180 | 181 | - 客户端向服务器在发送请求报文同时在首部添加发送Connection字段 182 | - 服务器收到请求并处理 Connection字段 183 | - 服务器回送Connection:Keep-Alive字段给客户端 184 | - 客户端接收到Connection字段 185 | - Keep-Alive连接建立成功 186 | 187 | **服务端自动断开过程(也就是没有keep-alive)**: 188 | 189 | - 客户端向服务器只是发送内容报文(不包含Connection字段) 190 | - 服务器收到请求并处理 191 | - 服务器返回客户端请求的资源并关闭连接 192 | - 客户端接收资源,发现没有Connection字段,断开连接 193 | 194 | **客户端请求断开连接过程**: 195 | 196 | - 客户端向服务器发送Connection:close字段 197 | - 服务器收到请求并处理connection字段 198 | - 服务器回送响应资源并断开连接 199 | - 客户端接收资源并断开连接 200 | 201 | 开启Keep-Alive的**优点:** 202 | 203 | - 较少的CPU和内存的使⽤(由于同时打开的连接的减少了); 204 | - 允许请求和应答的HTTP管线化; 205 | - 降低拥塞控制 (TCP连接减少了); 206 | - 减少了后续请求的延迟(⽆需再进⾏握⼿); 207 | - 报告错误⽆需关闭TCP连; 208 | 209 | 开启Keep-Alive的**缺点**: 210 | 211 | - 长时间的Tcp连接容易导致系统资源无效占用,浪费系统资源。 212 | 213 | ## 11. 页面有多张图片,HTTP是怎样的加载表现? 214 | 215 | - 在 **HTTP 1**下,浏览器对一个域名下最大TCP连接数为6,所以会请求多次。可以用**多域名部署**解决。这样可以提高同时请求的数目,加快页面图片的获取速度。 216 | - 在 **HTTP 2**下,可以一瞬间加载出来很多资源,因为,HTTP2支持多路复用,可以在一个TCP连接中发送多个HTTP请求。 217 | 218 | ## 12. HTTP2的头部压缩算法是怎样的? 219 | 220 | HTTP2的头部压缩是HPACK算法。在客户端和服务器两端建立“字典”,用索引号表示重复的字符串,采用哈夫曼编码来压缩整数和字符串,可以达到50%~90%的高压缩率。 221 | 222 | 具体来说: 223 | 224 | - 在客户端和服务器端使用“首部表”来跟踪和存储之前发送的键值对,对于相同的数据,不再通过每次请求和响应发送; 225 | - 首部表在HTTP/2的连接存续期内始终存在,由客户端和服务器共同渐进地更新; 226 | - 每个新的首部键值对要么被追加到当前表的末尾,要么替换表中之前的值。 227 | 228 | 例如下图中的两个请求, 请求一发送了所有的头部字段,第二个请求则只需要发送差异数据,这样可以减少冗余数据,降低开销。 229 | 230 | ![img](https://gitee.com/nest-of-old-time/picture/raw/master/typora/202401161009378.png) 231 | 232 | ## 13. HTTP请求报文的是什么样的? 233 | 234 | 请求报⽂有4部分组成: 235 | 236 | - 请求⾏ 237 | - 请求头部 238 | - 空⾏ 239 | - 请求体 240 | 241 | ![image.png](https://gitee.com/nest-of-old-time/picture/raw/master/typora/202401161009482.png) 242 | 243 | **其中:** 244 | 245 | (1)请求⾏包括:请求⽅法字段、URL字段、HTTP协议版本字段。它们⽤空格分隔。例如,GET /index.html HTTP/1.1。 246 | 247 | (2)请求头部:请求头部由关键字/值对组成,每⾏⼀对,关键字和值⽤英⽂冒号“:”分隔 248 | 249 | - User-Agent:产⽣请求的浏览器类型。 250 | - Accept:客户端可识别的内容类型列表。 251 | - Host:请求的主机名,允许多个域名同处⼀个IP地址,即虚拟主机。 252 | 253 | (3)请求体: post put等请求携带的数据 254 | 255 | ![image.png](https://gitee.com/nest-of-old-time/picture/raw/master/typora/202401161009873.png) 256 | 257 | ## 14. HTTP响应报文的是什么样的? 258 | 259 | 请求报⽂有4部分组成: 260 | 261 | - 响应⾏ 262 | - 响应头 263 | - 空⾏ 264 | - 响应体 265 | 266 | ![image.png](https://gitee.com/nest-of-old-time/picture/raw/master/typora/202401161009374.png) 267 | 268 | - 响应⾏:由网络协议版本,状态码和状态码的原因短语组成,例如 HTTP/1.1 200 OK 。 269 | - 响应头:响应部⾸组成 270 | - 响应体:服务器响应的数据 271 | 272 | ## 15. HTTP协议的优点和缺点 273 | 274 | HTTP 是超文本传输协议,它定义了客户端和服务器之间交换报文的格式和方式,默认使用 80 端口。它使用 TCP 作为传输层协议,保证了数据传输的可靠性。 275 | 276 | HTTP协议具有以下**优点**: 277 | 278 | - 支持客户端/服务器模式 279 | - **简单快速**:客户向服务器请求服务时,只需传送请求方法和路径。由于 HTTP 协议简单,使得 HTTP 服务器的程序规模小,因而通信速度很快。 280 | - **无连接**:无连接就是限制每次连接只处理一个请求。服务器处理完客户的请求,并收到客户的应答后,即断开连接,采用这种方式可以节省传输时间。 281 | - **无状态**:HTTP 协议是无状态协议,这里的状态是指通信过程的上下文信息。缺少状态意味着如果后续处理需要前面的信息,则它必须重传,这样可能会导致每次连接传送的数据量增大。另一方面,在服务器不需要先前信息时它的应答就比较快。 282 | - **灵活**:HTTP 允许传输任意类型的数据对象。正在传输的类型由 Content-Type 加以标记。 283 | 284 | HTTP协议具有以下**缺点**: 285 | 286 | - **无状态**:HTTP 是一个无状态的协议,HTTP 服务器不会保存关于客户的任何信息。 287 | - **明文传输**:协议中的报文使用的是文本形式,这就直接暴露给外界,不安全。 288 | - **不安全** 289 | 290 | (1)通信使用明文(不加密),内容可能会被窃听; 291 | 292 | (2)不验证通信方的身份,因此有可能遭遇伪装; 293 | 294 | (3)无法证明报文的完整性,所以有可能已遭篡改; 295 | 296 | ## 16. HTTP协议的性能怎么样 297 | 298 | HTTP 协议是基于 TCP/IP,并且使用了**请求-应答**的通信模式,所以性能的关键就在这两点里。 299 | 300 | - **长连接** 301 | 302 | HTTP协议有两种连接模式,一种是持续连接,一种非持续连接。 303 | 304 | (1)非持续连接指的是服务器必须为每一个请求的对象建立和维护一个全新的连接。 305 | 306 | (2)持续连接下,TCP 连接默认不关闭,可以被多个请求复用。采用持续连接的好处是可以避免每次建立 TCP 连接三次握手时所花费的时间。 307 | 308 | 对于不同版本的采用不同的连接方式: 309 | 310 | - 在HTTP/1.0 每发起一个请求,都要新建一次 TCP 连接(三次握手),而且是串行请求,做了无畏的 TCP 连接建立和断开,增加了通信开销。该版本使用的非持续的连接,但是可以在请求时,加上 Connection: keep-a live 来要求服务器不要关闭 TCP 连接。 311 | - 在HTTP/1.1 提出了**长连接**的通信方式,也叫持久连接。这种方式的好处在于减少了 TCP 连接的重复建立和断开所造成的额外开销,减轻了服务器端的负载。该版本及以后版本默认采用的是持续的连接。目前对于同一个域,大多数浏览器支持同时建立 6 个持久连接。 312 | 313 | ![image](https://gitee.com/nest-of-old-time/picture/raw/master/typora/202401161009389.png) 314 | 315 | - **管道网络传输**--多路复用的一种方式 316 | 317 | HTTP/1.1 采用了长连接的方式,这使得管道(pipeline)网络传输成为了可能。 318 | 319 | 管道(pipeline)网络传输是指:可以在同一个 TCP 连接里面,客户端可以发起多个请求,只要第一个请求发出去了,不必等其回来,就可以发第二个请求出去,可以减少整体的响应时间。但是服务器还是按照顺序回应请求。如果前面的回应特别慢,后面就会有许多请求排队等着。这称为队头堵塞。 320 | 321 | - **队头堵塞** 322 | 323 | HTTP 传输的报文必须是一发一收,但是,里面的任务被放在一个任务队列中串行执行,一旦队首的请求处理太慢,就会阻塞后面请求的处理。这就是HTTP队头阻塞问题。 324 | 325 | **队头阻塞的解决方案:** 326 | 327 | (1)并发连接:对于一个域名允许分配多个长连接,那么相当于增加了任务队列,不至于一个队伍的任务阻塞其它所有任务。 328 | 329 | (2)域名分片:将域名分出很多二级域名,它们都指向同样的一台服务器,能够并发的长连接数变多,解决了队头阻塞的问题。 330 | 331 | ## 17. URL有哪些组成部分 332 | 333 | 以下面的URL为例:**http://www.aspxfans.com:8080/news/index.asp?boardID=5&ID=24618&page=1#name** 334 | 335 | 从上面的URL可以看出,一个完整的URL包括以下几部分: 336 | 337 | - **协议部分**:该URL的协议部分为“http:”,这代表网页使用的是HTTP协议。在Internet中可以使用多种协议,如HTTP,FTP等等本例中使用的是HTTP协议。在"HTTP"后面的“//”为分隔符; 338 | - **域名部分**:该URL的域名部分为“www.aspxfans.com”。一个URL中,也可以使用IP地址作为域名使用 339 | - **端口部分**:跟在域名后面的是端口,域名和端口之间使用“:”作为分隔符。端口不是一个URL必须的部分,如果省略端口部分,将采用默认端口(HTTP协议默认端口是80,HTTPS协议默认端口是443); 340 | - **虚拟目录部分**:从域名后的第一个“/”开始到最后一个“/”为止,是虚拟目录部分。虚拟目录也不是一个URL必须的部分。本例中的虚拟目录是“/news/”; 341 | - **文件名部分**:从域名后的最后一个“/”开始到“?”为止,是文件名部分,如果没有“?”,则是从域名后的最后一个“/”开始到“#”为止,是文件部分,如果没有“?”和“#”,那么从域名后的最后一个“/”开始到结束,都是文件名部分。本例中的文件名是“index.asp”。文件名部分也不是一个URL必须的部分,如果省略该部分,则使用默认的文件名; 342 | - **锚部分**:从“#”开始到最后,都是锚部分。本例中的锚部分是“name”。锚部分也不是一个URL必须的部分; 343 | - **参数部分**:从“?”开始到“#”为止之间的部分为参数部分,又称搜索部分、查询部分。本例中的参数部分为“boardID=5&ID=24618&page=1”。参数可以允许有多个参数,参数与参数之间用“&”作为分隔符。 344 | 345 | ## 18. 与缓存相关的HTTP请求头有哪些 346 | 347 | 强缓存: 348 | 349 | - Expires 350 | - Cache-Control 351 | 352 | 协商缓存: 353 | 354 | - Etag、If-None-Match 355 | - Last-Modified、If-Modified-Since 356 | 357 | ## 19. 什么是HTTPS协议? 358 | 359 | 超文本传输安全协议(Hypertext Transfer Protocol Secure,简称:HTTPS)是一种通过计算机网络进行安全通信的传输协议。HTTPS经由HTTP进行通信,利用SSL/TLS来加密数据包。HTTPS的主要目的是提供对网站服务器的身份认证,保护交换数据的隐私与完整性。 360 | 361 | 362 | 363 | HTTP协议采用**明文传输**信息,存在**信息窃听**、**信息篡改**和**信息劫持**的风险,而协议TLS/SSL具有**身份验证**、**信息加密**和**完整性校验**的功能,可以避免此类问题发生。 364 | 365 | 安全层的主要职责就是**对发起的HTTP请求的数据进行加密操作** 和 **对接收到的HTTP的内容进行解密操作**。 366 | 367 | ## 20. TLS/SSL的工作原理 368 | 369 | **TLS/SSL**全称**安全传输层协议**(Transport Layer Security), 是介于TCP和HTTP之间的一层安全协议,不影响原有的TCP协议和HTTP协议,所以使用HTTPS基本上不需要对HTTP页面进行太多的改造。 370 | 371 | TLS/SSL的功能实现主要依赖三类基本算法:**散列函数hash**、**对称加密**、**非对称加密**。这三类算法的作用如下: 372 | 373 | - 基于散列函数验证信息的完整性 374 | - 对称加密算法采用协商的秘钥对数据加密 375 | - 非对称加密实现身份认证和秘钥协商 376 | 377 | ![image](https://gitee.com/nest-of-old-time/picture/raw/master/typora/1063868747470733a2f2f63646e2e6e6c61726b2e636f6d2f79757175652f302f323032302f706e672f313530303630342f313630333936353638353736392d36336139316461652d393336642d343264332d383537312d3537376365666131316533332e706e67.png) 378 | 379 | #### (1)散列函数hash 380 | 381 | 常见的散列函数有MD5、SHA1、SHA256。该函数的特点是单向不可逆,对输入数据非常敏感,输出的长度固定,任何数据的修改都会改变散列函数的结果,可以用于防止信息篡改并验证数据的完整性。 382 | 383 | **特点**:在信息传输过程中,散列函数不能三都实现信息防篡改,由于传输是明文传输,中间人可以修改信息后重新计算信息的摘要,所以需要对传输的信息和信息摘要进行加密。 384 | 385 | #### (2)对称加密 386 | 387 | 对称加密的方法是,双方使用同一个秘钥对数据进行加密和解密。但是对称加密的存在一个问题,就是如何保证秘钥传输的安全性,因为秘钥还是会通过网络传输的,一旦秘钥被其他人获取到,那么整个加密过程就毫无作用了。 这就要用到非对称加密的方法。 388 | 389 | 常见的对称加密算法有AES-CBC、DES、3DES、AES-GCM等。相同的秘钥可以用于信息的加密和解密。掌握秘钥才能获取信息,防止信息窃听,其通讯方式是一对一。 390 | 391 | **特点**:对称加密的优势就是信息传输使用一对一,需要共享相同的密码,密码的安全是保证信息安全的基础,服务器和N个客户端通信,需要维持N个密码记录且不能修改密码。 392 | 393 | #### (3)非对称加密 394 | 395 | 非对称加密的方法是,我们拥有两个秘钥,一个是公钥,一个是私钥。公钥是公开的,私钥是保密的。用私钥加密的数据,只有对应的公钥才能解密,用公钥加密的数据,只有对应的私钥才能解密。我们可以将公钥公布出去,任何想和我们通信的客户, 都可以使用我们提供的公钥对数据进行加密,这样我们就可以使用私钥进行解密,这样就能保证数据的安全了。但是非对称加密有一个缺点就是加密的过程很慢,因此如果每次通信都使用非对称加密的方式的话,反而会造成等待时间过长的问题。 396 | 397 | 常见的非对称加密算法有RSA、ECC、DH等。秘钥成对出现,一般称为公钥(公开)和私钥(保密)。公钥加密的信息只有私钥可以解开,私钥加密的信息只能公钥解开,因此掌握公钥的不同客户端之间不能相互解密信息,只能和服务器进行加密通信,服务器可以实现一对多的的通信,客户端也可以用来验证掌握私钥的服务器的身份。 398 | 399 | **特点**:非对称加密的特点就是信息一对多,服务器只需要维持一个私钥就可以和多个客户端进行通信,但服务器发出的信息能够被所有的客户端解密,且该算法的计算复杂,加密的速度慢。 400 | 401 | 综合上述算法特点,TLS/SSL的工作方式就是客户端使用非对称加密与服务器进行通信,实现身份的验证并协商对称加密使用的秘钥。对称加密算法采用协商秘钥对信息以及信息摘要进行加密通信,不同节点之间采用的对称秘钥不同,从而保证信息只能通信双方获取。这样就解决了两个方法各自存在的问题。 402 | 403 | ## 21. 数字证书是什么? 404 | 405 | 现在的方法也不一定是安全的,因为没有办法确定得到的公钥就一定是安全的公钥。可能存在一个中间人,截取了对方发给我们的公钥,然后将他自己的公钥发送给我们,当我们使用他的公钥加密后发送的信息,就可以被他用自己的私钥解密。然后他伪装成我们以同样的方法向对方发送信息,这样我们的信息就被窃取了,然而自己还不知道。为了解决这样的问题,可以使用数字证书。 406 | 407 | 首先使用一种 Hash 算法来对公钥和其他信息进行加密,生成一个信息摘要,然后让有公信力的认证中心(简称 CA )用它的私钥对消息摘要加密,形成签名。最后将原始的信息和签名合在一起,称为数字证书。当接收方收到数字证书的时候,先根据原始信息使用同样的 Hash 算法生成一个摘要,然后使用公证处的公钥来对数字证书中的摘要进行解密,最后将解密的摘要和生成的摘要进行对比,就能发现得到的信息是否被更改了。 408 | 409 | 这个方法最要的是认证中心的可靠性,一般浏览器里会内置一些顶层的认证中心的证书,相当于我们自动信任了他们,只有这样才能保证数据的安全。 410 | 411 | ![image](https://gitee.com/nest-of-old-time/picture/raw/master/typora/202401161010686.png) 412 | 413 | ## 22. HTTPS通信(握手)过程 414 | 415 | HTTPS的通信过程如下: 416 | 417 | 1. 客户端向服务器发起请求,请求中包含使用的协议版本号、生成的一个随机数、以及客户端支持的加密方法。 418 | 2. 服务器端接收到请求后,确认双方使用的加密方法、并给出服务器的证书、以及一个服务器生成的随机数。 419 | 3. 客户端确认服务器证书有效后,生成一个新的随机数,并使用数字证书中的公钥,加密这个随机数,然后发给服务器。并且还会提供一个前面所有内容的 hash 的值,用来供服务器检验。 420 | 4. 服务器使用自己的私钥,来解密客户端发送过来的随机数。并提供前面所有内容的 hash 值来供客户端检验。 421 | 5. 客户端和服务器端根据约定的加密方法使用前面的三个随机数,生成对话秘钥,以后的对话过程都使用这个秘钥来加密信息。 422 | 423 | ## 23. HTTPS的特点 424 | 425 | HTTPS的**优点**如下: 426 | 427 | - 使用HTTPS协议可以认证用户和服务器,确保数据发送到正确的客户端和服务器; 428 | - 使用HTTPS协议可以进行加密传输、身份认证,通信更加安全,防止数据在传输过程中被窃取、修改,确保数据安全性; 429 | - HTTPS是现行架构下最安全的解决方案,虽然不是绝对的安全,但是大幅增加了中间人攻击的成本; 430 | 431 | HTTPS的**缺点**如下: 432 | 433 | - HTTPS需要做服务器和客户端双方的加密个解密处理,耗费更多服务器资源,过程复杂; 434 | - HTTPS协议握手阶段比较费时,增加页面的加载时间; 435 | - SSL证书是收费的,功能越强大的证书费用越高; 436 | - HTTPS连接服务器端资源占用高很多,支持访客稍多的网站需要投入更大的成本; 437 | - SSL证书需要绑定IP,不能再同一个IP上绑定多个域名。 438 | 439 | ## 24、HTTPS是如何保证安全的?数字签名 440 | 441 | 先理解两个概念: 442 | 443 | - 对称加密:即通信的双⽅都使⽤同⼀个秘钥进⾏加解密,对称加密虽然很简单性能也好,但是⽆法解决⾸次把秘钥发给对⽅的问题,很容易被⿊客拦截秘钥。 444 | - ⾮对称加密: 445 | 446 | \1. 私钥 + 公钥= 密钥对 447 | 448 | \2. 即⽤私钥加密的数据,只有对应的公钥才能解密,⽤公钥加密的数据,只有对应的私钥才能解密 449 | 450 | \3. 因为通信双⽅的⼿⾥都有⼀套⾃⼰的密钥对,通信之前双⽅会先把⾃⼰的公钥都先发给对⽅ 451 | 452 | \4. 然后对⽅再拿着这个公钥来加密数据响应给对⽅,等到到了对⽅那⾥,对⽅再⽤⾃⼰的私钥进⾏解密 453 | 454 | ⾮对称加密虽然安全性更⾼,但是带来的问题就是速度很慢,影响性能。 455 | 456 | **解决⽅案:** 457 | 458 | 结合两种加密⽅式,将对称加密的密钥使⽤⾮对称加密的公钥进⾏加密,然后发送出去,接收⽅使⽤私钥进⾏解密得到对称加密的密钥,然后双⽅可以使⽤对称加密来进⾏沟通。 459 | 460 | 此时⼜带来⼀个问题,中间⼈问题: 461 | 462 | 如果此时在客户端和服务器之间存在⼀个中间⼈,这个中间⼈只需要把原本双⽅通信互发的公钥,换成⾃⼰的公钥,这样中间⼈就可以轻松解密通信双⽅所发送的所有数据。 463 | 464 | 所以这个时候需要⼀个安全的第三⽅颁发证书(CA),证明身份的身份,防⽌被中间⼈攻击。 证书中包括:签发者、证书⽤途、使⽤者公钥、使⽤者私钥、使⽤者的HASH算法、证书到期时间等。 465 | 466 | 但是问题来了,如果中间⼈篡改了证书,那么身份证明是不是就⽆效了?这个证明就⽩买了,这个时候需要⼀个新的技术,数字签名。 467 | 468 | 数字签名就是⽤CA⾃带的HASH算法对证书的内容进⾏HASH得到⼀个摘要,再⽤CA的私钥加密,最终组成数字签名。当别⼈把他的证书发过来的时候,我再⽤同样的Hash算法,再次⽣成消息摘要,然后⽤CA的公钥对数字签名解密,得到CA创建的消息摘要,两者⼀⽐,就知道中间有没有被⼈篡改了。这个时候就能最⼤程度保证通信的安全了。 469 | 470 | ## 25. HTTP状态码 471 | 472 | **(1)2XX 成功** 473 | 474 | - 200 OK,表示从客户端发来的请求在服务器端被正确处理 475 | - 204 No content,表示请求成功,但响应报文不含实体的主体部分 476 | - 205 Reset Content,表示请求成功,但响应报文不含实体的主体部分,但是与 204 响应不同在于要求请求方重置内容 477 | - 206 Partial Content,进行范围请求 478 | 479 | **(2)3XX 重定向** 480 | 481 | - 301 moved permanently,永久性重定向,表示资源已被分配了新的 URL 482 | - 302 found,临时性重定向,表示资源临时被分配了新的 URL 483 | - 303 see other,表示资源存在着另一个 URL,应使用 GET 方法获取资源 484 | - 304 not modified,命中协商缓存 485 | - 307 temporary redirect,临时重定向,和302含义类似,但是期望客户端保持请求方法不变向新的地址发出请求 486 | 487 | **(3)4XX 客户端错误** 488 | 489 | - 400 bad request,请求报文存在语法错误 490 | - 401 unauthorized,表示发送的请求需要有通过 HTTP 认证的认证信息 491 | - 403 forbidden,表示对请求资源的访问被服务器拒绝 492 | - 404 not found,表示在服务器上没有找到请求的资源 493 | 494 | **(4)5XX 服务器错误** 495 | 496 | - 500 internal sever error,表示服务器端在执行请求时发生了错误 497 | - 502 Not Implemented,服务器重启 498 | - 503 service unavailable,表明服务器暂时处于超负载或正在停机维护,无法处理请求 499 | 500 | ## 26. DNS 协议是什么 501 | 502 | **概念**: DNS 是域名系统 (Domain Name System) 的缩写,提供的是一种主机名到 IP 地址的转换服务,就是我们常说的域名系统。它是一个由分层的 DNS 服务器组成的分布式数据库,是定义了主机如何查询这个分布式数据库的方式的应用层协议。能够使人更方便的访问互联网,而不用去记住能够被机器直接读取的IP数串。 503 | 504 | **作用**: 将域名解析为IP地址,客户端向DNS服务器(DNS服务器有自己的IP地址)发送域名查询请求,DNS服务器告知客户机Web服务器的 IP 地址。 505 | 506 | ## 27. DNS同时使用TCP和UDP协议? 507 | 508 | **DNS占用53号端口,同时使用TCP和UDP协议。** 509 | 510 | (1)在区域传输的时候使用TCP协议 511 | 512 | - 辅域名服务器会定时(一般3小时)向主域名服务器进行查询以便了解数据是否有变动。如有变动,会执行一次区域传送,进行数据同步。区域传送使用TCP而不是UDP,因为数据同步传送的数据量比一个请求应答的数据量要多得多。 513 | - TCP是一种可靠连接,保证了数据的准确性。 514 | 515 | (2)在域名解析的时候使用UDP协议 516 | 517 | - 客户端向DNS服务器查询域名,一般返回的内容都不超过512字节,用UDP传输即可。不用经过三次握手,这样DNS服务器负载更低,响应更快。理论上说,客户端也可以指定向DNS服务器查询时用TCP,但事实上,很多DNS服务器进行配置的时候,仅支持UDP查询包。 518 | 519 | ## 28. DNS完整的查询过程 520 | 521 | DNS服务器解析域名的过程: 522 | 523 | - 首先会在**浏览器的缓存**中查找对应的IP地址,如果查找到直接返回,若找不到继续下一步 524 | - 将请求发送给**本地DNS服务器**,在本地域名服务器缓存中查询,如果查找到,就直接将查找结果返回,若找不到继续下一步 525 | - 本地DNS服务器向**根域名服务器**发送请求,根域名服务器会返回一个所查询域的顶级域名服务器地址 526 | - 本地DNS服务器向**顶级域名服务器**发送请求,接受请求的服务器查询自己的缓存,如果有记录,就返回查询结果,如果没有就返回相关的下一级的权威域名服务器的地址 527 | - 本地DNS服务器向**权威域名服务器**发送请求,域名服务器返回对应的结果 528 | - 本地DNS服务器将返回结果保存在缓存中,便于下次使用 529 | - 本地DNS服务器将返回结果返回给浏览器 530 | 531 | 比如要查询 **www.baidu.com** 的 IP 地址,首先会在浏览器的缓存中查找是否有该域名的缓存,如果不存在就将请求发送到本地的 DNS 服务器中,本地DNS服务器会判断是否存在该域名的缓存,如果不存在,则向根域名服务器发送一个请求,根域名服务器返回负责 .com 的顶级域名服务器的 IP 地址的列表。然后本地 DNS 服务器再向其中一个负责 .com 的顶级域名服务器发送一个请求,负责 .com 的顶级域名服务器返回负责 .baidu 的权威域名服务器的 IP 地址列表。然后本地 DNS 服务器再向其中一个权威域名服务器发送一个请求,最后权威域名服务器返回一个对应的主机名的 IP 地址列表。 532 | 533 | ## 29. 迭代查询与递归查询 534 | 535 | 实际上,DNS解析是一个包含迭代查询和递归查询的过程。 536 | 537 | - **递归查询**指的是查询请求发出后,域名服务器代为向下一级域名服务器发出请求,最后向用户返回查询的最终结果。使用递归 查询,用户只需要发出一次查询请求。 538 | - **迭代查询**指的是查询请求后,域名服务器返回单次查询的结果。下一级的查询由用户自己请求。使用迭代查询,用户需要发出 多次的查询请求。 539 | 540 | 一般我们向本地 DNS 服务器发送请求的方式就是递归查询,因为我们只需要发出一次请求,然后本地 DNS 服务器返回给我 们最终的请求结果。而本地 DNS 服务器向其他域名服务器请求的过程是迭代查询的过程,因为每一次域名服务器只返回单次 查询的结果,下一级的查询由本地 DNS 服务器自己进行。 541 | 542 | ## 30. OSI七层模型 543 | 544 | `ISO`为了更好的使网络应用更为普及,推出了 `OSI`参考模型。 545 | 546 | #### (1)应用层 547 | 548 | `OSI`参考模型中最靠近用户的一层,是为计算机用户提供应用接口,也为用户直接提供各种网络服务。我们常见应用层的网络服务协议有:`HTTP`,`HTTPS`,`FTP`,`POP3`、`SMTP`等。 549 | 550 | - 在客户端与服务器中经常会有数据的请求,这个时候就是会用到 `http(hyper text transfer protocol)(超文本传输协议)`或者 `https`在后端设计数据接口时,我们常常使用到这个协议。 551 | - `FTP`是文件传输协议,在开发过程中,个人并没有涉及到,但是我想,在一些资源网站,比如 **百度网盘 迅雷**应该是基于此协议的。 552 | - `SMTP`是 `simple mail transfer protocol(简单邮件传输协议)`。在一个项目中,在用户邮箱验证码登录的功能时,使用到了这个协议。 553 | 554 | #### (2)表示层 555 | 556 | 表示层提供各种用于应用层数据的编码和转换功能,确保一个系统的应用层发送的数据能被另一个系统的应用层识别。如果必要,该层可提供一种标准表示形式,用于将计算机内部的多种数据格式转换成通信中采用的标准表示形式。数据压缩和加密也是表示层可提供的转换功能之一。 557 | 558 | 在项目开发中,为了方便数据传输,可以使用 `base64`对数据进行编解码。如果按功能来划分,`base64`应该是工作在表示层。 559 | 560 | #### (3)会话层 561 | 562 | 会话层就是负责建立、管理和终止表示层实体之间的通信会话。该层的通信由不同设备中的应用程序之间的服务请求和响应组成。 563 | 564 | #### (4)传输层 565 | 566 | 传输层建立了主机端到端的链接,传输层的作用是为上层协议提供端到端的可靠和透明的数据传输服务,包括处理差错控制和流量控制等问题。该层向高层屏蔽了下层数据通信的细节,使高层用户看到的只是在两个传输实体间的一条主机到主机的、可由用户控制和设定的、可靠的数据通路。我们通常说的,`TCP` `UDP`就是在这一层。端口号既是这里的“端”。 567 | 568 | #### (5)网络层 569 | 570 | 本层通过 `IP`寻址来建立两个节点之间的连接,为源端的运输层送来的分组,选择合适的路由和交换节点,正确无误地按照地址传送给目的端的运输层。就是通常说的 `IP`层。这一层就是我们经常说的 `IP`协议层。`IP`协议是 `Internet`的基础。我们可以这样理解,网络层规定了数据包的传输路线,而传输层则规定了数据包的传输方式。 571 | 572 | #### (6)数据链路层 573 | 574 | 将比特组合成字节,再将字节组合成帧,使用链路层地址 (以太网使用MAC地址)来访问介质,并进行差错检测。 575 | 576 | 网络层与数据链路层的对比,通过上面的描述,我们或许可以这样理解,网络层是规划了数据包的传输路线,而数据链路层就是传输路线。不过,在数据链路层上还增加了差错控制的功能。 577 | 578 | #### (7)物理层 579 | 580 | 实际最终信号的传输是通过物理层实现的。通过物理介质传输比特流。规定了电平、速度和电缆针脚。常用设备有(各种物理设备)集线器、中继器、调制解调器、网线、双绞线、同轴电缆。这些都是物理层的传输介质。 581 | 582 | **OSI七层模型通信特点:对等通信** 583 | 584 | 对等通信,为了使数据分组从源传送到目的地,源端OSI模型的每一层都必须与目的端的对等层进行通信,这种通信方式称为对等层通信。在每一层通信过程中,使用本层自己协议进行通信。 585 | 586 | 587 | 588 | 物理层:相当于网线-----传输比特流 589 | 590 | 数据链路层:将比特转化成字节,再转化成帧,使用链路层的地址也就是MAC地址来访问数据,相当于传输路线 591 | 592 | 网络层:通过IP地址来建立节点间的联系,相当于规划数据的传输路线 593 | 594 | 传输层:主机端到端的连接,提供了端到端的数据传输服务,也就是端口号,通常就是TCP和UDP 595 | 596 | 会话层:负责建立和管理终止通信会话 597 | 598 | 表示层:提供给应用层的数据编码和转化功能,确保能被应用层识别 599 | 600 | 应用层:为计算机用户提供接口,也就是为用户提供网络服务,例如网络服务协议http,https,ftp等 601 | 602 | ## 31.TCP/IP五层协议 603 | 604 | `TCP/IP`五层协议和 `OSI`的七层协议对应关系如下: 605 | 606 | ![image](https://gitee.com/nest-of-old-time/picture/raw/master/typora/202401161012584.png) 607 | 608 | - **应用层 (application layer)**:直接为应用进程提供服务。应用层协议定义的是应用进程间通讯和交互的规则,不同的应用有着不同的应用层协议,如 HTTP协议(万维网服务)、FTP协议(文件传输)、SMTP协议(电子邮件)、DNS(域名查询)等。 609 | 610 | - **传输层 (transport layer)**:有时也译为运输层,它负责为两台主机中的进程提供通信服务。该层主要有以下两种协议: 611 | 612 | - - **传输控制协议 (Transmission Control Protocol,TCP)**:提供面向连接的、可靠的数据传输服务,数据传输的基本单位是报文段(segment); 613 | - **用户数据报协议 (User Datagram Protocol,UDP)**:提供无连接的、尽最大努力的数据传输服务,但不保证数据传输的可靠性,数据传输的基本单位是用户数据报。 614 | 615 | - **网络层 (internet layer)**:有时也译为网际层,它负责为两台主机提供通信服务,并通过选择合适的路由将数据传递到目标主机。 616 | 617 | - **数据链路层 (data link layer)**:负责将网络层交下来的 IP 数据报封装成帧,并在链路的两个相邻节点间传送帧,每一帧都包含数据和必要的控制信息(如同步信息、地址信息、差错控制等)。 618 | 619 | - **物理层 (physical Layer)**:确保数据可以在各种物理媒介上进行传输,为数据的传输提供可靠的环境。 620 | 621 | 从上图中可以看出,`TCP/IP`模型比 `OSI`模型更加简洁,它把 `应用层/表示层/会话层`全部整合为了 `应用层`。 622 | 623 | 在每一层都工作着不同的设备,比如我们常用的交换机就工作在数据链路层的,一般的路由器是工作在网络层的。 624 | 625 | 在每一层实现的协议也各不同,即每一层的服务也不同,下图列出了每层主要的传输协议: 626 | 627 | 同样,`TCP/IP`五层协议的通信方式也是对等通信: 628 | 629 | ![image.png](https://gitee.com/nest-of-old-time/picture/raw/master/typora/202401161012438.png) 630 | 631 | ## 32. TCP 和 UDP的概念及特点 632 | 633 | TCP 和 UDP都是传输层协议,他们都属于TCP/IP协议族: 634 | 635 | **(1)UDP** 636 | 637 | UDP的全称是**用户数据报协议**,在网络中它与TCP协议一样用于处理数据包,是一种无连接的协议。在OSI模型中,在传输层,处于IP协议的上一层。UDP有不提供数据包分组、组装和不能对数据包进行排序的缺点,也就是说,当报文发送之后,是无法得知其是否安全完整到达的。 638 | 639 | 它的特点如下: 640 | 641 | **1)面向无连接** 642 | 643 | 首先 UDP 是不需要和 TCP一样在发送数据前进行三次握手建立连接的,想发数据就可以开始发送了。并且也只是数据报文的搬运工,不会对数据报文进行任何拆分和拼接操作。 644 | 645 | 具体来说就是: 646 | 647 | - 在发送端,应用层将数据传递给传输层的 UDP 协议,UDP 只会给数据增加一个 UDP 头标识下是 UDP 协议,然后就传递给网络层了 648 | - 在接收端,网络层将数据传递给传输层,UDP 只去除 IP 报文头就传递给应用层,不会任何拼接操作 649 | 650 | **2)有单播,多播,广播的功能** 651 | 652 | UDP 不止支持一对一的传输方式,同样支持一对多,多对多,多对一的方式,也就是说 UDP 提供了单播,多播,广播的功能。 653 | 654 | **3)面向报文** 655 | 656 | 发送方的UDP对应用程序交下来的报文,在添加首部后就向下交付IP层。UDP对应用层交下来的报文,既不合并,也不拆分,而是保留这些报文的边界。因此,应用程序必须选择合适大小的报文 657 | 658 | **4)不可靠性** 659 | 660 | 首先不可靠性体现在无连接上,通信都不需要建立连接,想发就发,这样的情况肯定不可靠。 661 | 662 | 并且收到什么数据就传递什么数据,并且也不会备份数据,发送数据也不会关心对方是否已经正确接收到数据了。 663 | 664 | 再者网络环境时好时坏,但是 UDP 因为没有拥塞控制,一直会以恒定的速度发送数据。即使网络条件不好,也不会对发送速率进行调整。这样实现的弊端就是在网络条件不好的情况下可能会导致丢包,但是优点也很明显,在某些实时性要求高的场景(比如电话会议)就需要使用 UDP 而不是 TCP。 665 | 666 | **5)头部开销小,传输数据报文时是很高效的。** 667 | 668 | ![image](https://gitee.com/nest-of-old-time/picture/raw/master/typora/202401161012398.png) 669 | 670 | UDP 头部包含了以下几个数据: 671 | 672 | - 两个十六位的端口号,分别为源端口(可选字段)和目标端口 673 | - 整个数据报文的长度 674 | - 整个数据报文的检验和(IPv4 可选字段),该字段用于发现头部信息和数据中的错误 675 | 676 | 因此 UDP 的头部开销小,只有8字节,相比 TCP 的至少20字节要少得多,在传输数据报文时是很高效的。 677 | 678 | **(2)TCP** 679 | 680 | TCP的全称是传输控制协议是一种面向连接的、可靠的、基于字节流的传输层通信协议。TCP 是面向连接的、可靠的流协议(流就是指不间断的数据结构)。 681 | 682 | 它有以下几个特点: 683 | 684 | **1)面向连接** 685 | 686 | 面向连接,是指发送数据之前必须在两端建立连接。建立连接的方法是“三次握手”,这样能建立可靠的连接。建立连接,是为数据的可靠传输打下了基础。 687 | 688 | **2)仅支持单播传输** 689 | 690 | 每条TCP传输连接只能有两个端点,只能进行点对点的数据传输,不支持多播和广播传输方式。 691 | 692 | **3)面向字节流** 693 | 694 | TCP不像UDP一样那样一个个报文独立地传输,而是在不保留报文边界的情况下以字节流方式进行传输。 695 | 696 | **4)可靠传输** 697 | 698 | 对于可靠传输,判断丢包、误码靠的是TCP的段编号以及确认号。TCP为了保证报文传输的可靠,就给每个包一个序号,同时序号也保证了传送到接收端实体的包的按序接收。然后接收端实体对已成功收到的字节发回一个相应的确认(ACK);如果发送端实体在合理的往返时延(RTT)内未收到确认,那么对应的数据(假设丢失了)将会被重传。 699 | 700 | **5)提供拥塞控制** 701 | 702 | 当网络出现拥塞的时候,TCP能够减小向网络注入数据的速率和数量,缓解拥塞。 703 | 704 | **6)提供全双工通信** 705 | 706 | TCP允许通信双方的应用程序在任何时候都能发送数据,因为TCP连接的两端都设有缓存,用来临时存放双向通信的数据。当然,TCP可以立即发送一个数据段,也可以缓存一段时间以便一次发送更多的数据段(最大的数据段大小取决于MSS) 707 | 708 | ## 33.TCP和UDP的区别 709 | 710 | | | UDP | TCP | 711 | | :----------- | :----------------------------------------- | :--------------------------------------------------- | 712 | | 是否连接 | 无连接 | 面向连接 | 713 | | 是否可靠 | 不可靠传输,不使用流量控制和拥塞控制 | 可靠传输(数据顺序和正确性),使用流量控制和拥塞控制 | 714 | | 连接对象个数 | 支持一对一,一对多,多对一和多对多交互通信 | 只能是一对一通信 | 715 | | 传输方式 | 面向报文 | 面向字节流 | 716 | | 首部开销 | 首部开销小,仅8字节 | 首部最小20字节,最大60字节 | 717 | | 适用场景 | 适用于实时应用,例如视频会议、直播 | 适用于要求可靠传输的应用,例如文件传输 | 718 | 719 | ## 34.TCP和UDP的使用场景 720 | 721 | - **TCP应用场景**: 效率要求相对低,但对准确性要求相对高的场景。因为传输中需要对数据确认、重发、排序等操作,相比之下效率没有UDP高。例如:文件传输(准确高要求高、但是速度可以相对慢)、接受邮件、远程登录。 722 | - **UDP应用场景**: 效率要求相对高,对准确性要求相对低的场景。例如:QQ聊天、在线视频、网络语音电话(即时通讯,速度要求高,但是出现偶尔断续不是太大问题,并且此处完全不可以使用重发机制)、广播通信(广播、多播)。 723 | 724 | ## 35.UDP协议为什么不可靠? 725 | 726 | UDP在传输数据之前不需要先建立连接,远地主机的运输层在接收到UDP报文后,不需要确认,提供不可靠交付。总结就以下四点: 727 | 728 | - 不保证消息交付:不确认,不重传,无超时 729 | - 不保证交付顺序:不设置包序号,不重排,不会发生队首阻塞 730 | - 不跟踪连接状态:不必建立连接或重启状态机 731 | - 不进行拥塞控制:不内置客户端或网络反馈机制 732 | 733 | ## 36. TCP的重传机制 734 | 735 | 由于TCP的下层网络(网络层)可能出现**丢失、重复或失序**的情况,TCP协议提供可靠数据传输服务。为保证数据传输的正确性,TCP会重传其认为已丢失(包括报文中的比特错误)的包。TCP使用两套独立的机制来完成重传,一是**基于时间**,二是**基于确认信息**。 736 | 737 | TCP在发送一个数据之后,就开启一个定时器,若是在这个时间内没有收到发送数据的ACK确认报文,则对该报文进行重传,在达到一定次数还没有成功时放弃并发送一个复位信号。 738 | 739 | ## 37.TCP的拥塞控制机制 740 | 741 | TCP的拥塞控制机制主要是以下四种机制: 742 | 743 | - 慢启动(慢开始) 744 | - 拥塞避免 745 | - 快速重传 746 | - 快速恢复 747 | 748 | **(1)慢启动(慢开始)** 749 | 750 | - 在开始发送的时候设置cwnd = 1(cwnd指的是拥塞窗口) 751 | 752 | - 思路:开始的时候不要发送大量数据,而是先测试一下网络的拥塞程度,由小到大增加拥塞窗口的大小。 753 | 754 | - 为了防止cwnd增长过大引起网络拥塞,设置一个慢开始门限(ssthresh 状态变量) 755 | 756 | - - 当cnwd < ssthresh,使用慢开始算法 757 | - 当cnwd = ssthresh,既可使用慢开始算法,也可以使用拥塞避免算法 758 | - 当cnwd > ssthresh,使用拥塞避免算法 759 | 760 | **(2)拥塞避免** 761 | 762 | - 拥塞避免未必能够完全避免拥塞,是说在拥塞避免阶段将拥塞窗口控制为按线性增长,使网络不容易出现阻塞。 763 | - 思路: 让拥塞窗口cwnd缓慢的增大,即每经过一个返回时间RTT就把发送方的拥塞控制窗口加一 764 | - 无论是在慢开始阶段还是在拥塞避免阶段,只要发送方判断网络出现拥塞,就把慢开始门限设置为出现拥塞时的发送窗口大小的一半。然后把拥塞窗口设置为1,执行慢开始算法。如图所示: 765 | 766 | ![image](https://gitee.com/nest-of-old-time/picture/raw/master/typora/202401161013873.png) 767 | 768 | - 其中,判断网络出现拥塞的根据就是没有收到确认,虽然没有收到确认可能是其他原因的分组丢失,但是因为无法判定,所以都当做拥塞来处理。 769 | 770 | **(3)快速重传** 771 | 772 | - 快重传要求接收方在收到一个失序的报文段后就立即发出重复确认(为的是使发送方及早知道有报文段没有到达对方)。发送方只要连续收到三个重复确认就立即重传对方尚未收到的报文段,而不必继续等待设置的重传计时器时间到期。 773 | - 由于不需要等待设置的重传计时器到期,能尽早重传未被确认的报文段,能提高整个网络的吞吐量 774 | 775 | **(4)快速恢复** 776 | 777 | - 当发送方连续收到三个重复确认时,就执行“乘法减小”算法,把ssthresh门限减半。但是接下去并不执行慢开始算法。 778 | - 考虑到如果网络出现拥塞的话就不会收到好几个重复的确认,所以发送方现在认为网络可能没有出现拥塞。所以此时不执行慢开始算法,而是将cwnd设置为ssthresh的大小,然后执行拥塞避免算法。 779 | 780 | ## 38.TCP的流量控制机制 781 | 782 | 一般来说,流量控制就是为了让发送方发送数据的速度不要太快,要让接收方来得及接收。TCP采用大小可变的**滑动窗口**进行流量控制,窗口大小的单位是字节。这里说的窗口大小其实就是每次传输的数据大小。 783 | 784 | - 当一个连接建立时,连接的每一端分配一个缓冲区来保存输入的数据,并将缓冲区的大小发送给另一端。 785 | - 当数据到达时,接收方发送确认,其中包含了自己剩余的缓冲区大小。(剩余的缓冲区空间的大小被称为窗口,指出窗口大小的通知称为窗口通告 。接收方在发送的每一确认中都含有一个窗口通告。) 786 | - 如果接收方应用程序读数据的速度能够与数据到达的速度一样快,接收方将在每一确认中发送一个正的窗口通告。 787 | - 如果发送方操作的速度快于接收方,接收到的数据最终将充满接收方的缓冲区,导致接收方通告一个零窗口 。发送方收到一个零窗口通告时,必须停止发送,直到接收方重新通告一个正的窗口。 788 | 789 | ## 39.TCP的可靠传输机制 790 | 791 | TCP 的可靠传输机制是基于连续 ARQ 协议和滑动窗口协议的。 792 | 793 | TCP 协议在发送方维持了一个发送窗口,发送窗口以前的报文段是已经发送并确认了的报文段,发送窗口中包含了已经发送但 未确认的报文段和允许发送但还未发送的报文段,发送窗口以后的报文段是缓存中还不允许发送的报文段。当发送方向接收方发 送报文时,会依次发送窗口内的所有报文段,并且设置一个定时器,这个定时器可以理解为是最早发送但未收到确认的报文段。 如果在定时器的时间内收到某一个报文段的确认回答,则滑动窗口,将窗口的首部向后滑动到确认报文段的后一个位置,此时如 果还有已发送但没有确认的报文段,则重新设置定时器,如果没有了则关闭定时器。如果定时器超时,则重新发送所有已经发送 但还未收到确认的报文段,并将超时的间隔设置为以前的两倍。当发送方收到接收方的三个冗余的确认应答后,这是一种指示, 说明该报文段以后的报文段很有可能发生丢失了,那么发送方会启用快速重传的机制,就是当前定时器结束前,发送所有的已发 送但确认的报文段。 794 | 795 | 接收方使用的是累计确认的机制,对于所有按序到达的报文段,接收方返回一个报文段的肯定回答。如果收到了一个乱序的报文 段,那么接方会直接丢弃,并返回一个最近的按序到达的报文段的肯定回答。使用累计确认保证了返回的确认号之前的报文段都 已经按序到达了,所以发送窗口可以移动到已确认报文段的后面。 796 | 797 | 发送窗口的大小是变化的,它是由接收窗口剩余大小和网络中拥塞程度来决定的,TCP 就是通过控制发送窗口的长度来控制报文 段的发送速率。 798 | 799 | 但是 TCP 协议并不完全和滑动窗口协议相同,因为许多的 TCP 实现会将失序的报文段给缓存起来,并且发生重传时,只会重 传一个报文段,因此 TCP 协议的可靠传输机制更像是窗口滑动协议和选择重传协议的一个混合体。 800 | 801 | ## 40.TCP的三次握手和四次挥手 802 | 803 | #### (1)三次握手 804 | 805 | ![image](https://gitee.com/nest-of-old-time/picture/raw/master/typora/1697268747470733a2f2f63646e2e6e6c61726b2e636f6d2f79757175652f302f323032302f706e672f313530303630342f313630343032333636333235362d35656236646364662d666462362d346236372d613364612d6461313563316433393666622e706e67.png) 806 | 807 | 三次握手(Three-way Handshake)其实就是指建立一个TCP连接时,需要客户端和服务器总共发送3个包。进行三次握手的主要作用就是为了确认双方的接收能力和发送能力是否正常、指定自己的初始化序列号为后面的可靠性传送做准备。实质上其实就是连接服务器指定端口,建立TCP连接,并同步连接双方的序列号和确认号,交换TCP窗口大小信息。 808 | 809 | 刚开始客户端处于 Closed 的状态,服务端处于 Listen 状态。 810 | 811 | - 第一次握手:客户端给服务端发一个 SYN 报文,并指明客户端的初始化序列号 ISN,此时客户端处于 SYN_SEND 状态。 812 | 813 | > 首部的同步位SYN=1,初始序号seq=x,SYN=1的报文段不能携带数据,但要消耗掉一个序号。 814 | 815 | - 第二次握手:服务器收到客户端的 SYN 报文之后,会以自己的 SYN 报文作为应答,并且也是指定了自己的初始化序列号 ISN。同时会把客户端的 ISN + 1 作为ACK 的值,表示自己已经收到了客户端的 SYN,此时服务器处于 SYN_REVD 的状态。 816 | 817 | > 在确认报文段中SYN=1,ACK=1,确认号ack=x+1,初始序号seq=y 818 | 819 | - 第三次握手:客户端收到 SYN 报文之后,会发送一个 ACK 报文,当然,也是一样把服务器的 ISN + 1 作为 ACK 的值,表示已经收到了服务端的 SYN 报文,此时客户端处于 ESTABLISHED 状态。服务器收到 ACK 报文之后,也处于 ESTABLISHED 状态,此时,双方已建立起了连接。 820 | 821 | > 确认报文段ACK=1,确认号ack=y+1,序号seq=x+1(初始为seq=x,第二个报文段所以要+1),ACK报文段可以携带数据,不携带数据则不消耗序号。 822 | 823 | **那为什么要三次握手呢?两次不行吗?** 824 | 825 | - 为了确认双方的接收能力和发送能力都正常 826 | - 如果是用两次握手,则会出现下面这种情况: 827 | 828 | > 如客户端发出连接请求,但因连接请求报文丢失而未收到确认,于是客户端再重传一次连接请求。后来收到了确认,建立了连接。数据传输完毕后,就释放了连接,客户端共发出了两个连接请求报文段,其中第一个丢失,第二个到达了服务端,但是第一个丢失的报文段只是在某些网络结点长时间滞留了,延误到连接释放以后的某个时间才到达服务端,此时服务端误认为客户端又发出一次新的连接请求,于是就向客户端发出确认报文段,同意建立连接,不采用三次握手,只要服务端发出确认,就建立新的连接了,此时客户端忽略服务端发来的确认,也不发送数据,则服务端一致等待客户端发送数据,浪费资源。 829 | 830 | **简单来说就是以下三步:** 831 | 832 | - **第一次握手**:客户端向服务端发送连接请求报文段。该报文段中包含自身的数据通讯初始序号。请求发送后,客户端便进入 SYN-SENT 状态。 833 | - **第二次握手**:服务端收到连接请求报文段后,如果同意连接,则会发送一个应答,该应答中也会包含自身的数据通讯初始序号,发送完成后便进入 SYN-RECEIVED 状态。 834 | - **第三次握手**:当客户端收到连接同意的应答后,还要向服务端发送一个确认报文。客户端发完这个报文段后便进入 ESTABLISHED 状态,服务端收到这个应答后也进入 ESTABLISHED 状态,此时连接建立成功。 835 | 836 | TCP 三次握手的建立连接的过程就是相互确认初始序号的过程,告诉对方,什么样序号的报文段能够被正确接收。 第三次握手的作用是客户端对服务器端的初始序号的确认。如果只使用两次握手,那么服务器就没有办法知道自己的序号是否 已被确认。同时这样也是为了防止失效的请求报文段被服务器接收,而出现错误的情况。 837 | 838 | #### (2)四次挥手 839 | 840 | ![image](https://gitee.com/nest-of-old-time/picture/raw/master/typora/202401161013992.png) 841 | 842 | 刚开始双方都处于 ESTABLISHED 状态,假如是客户端先发起关闭请求。四次挥手的过程如下: 843 | 844 | - 第一次挥手: 客户端会发送一个 FIN 报文,报文中会指定一个序列号。此时客户端处于 FIN_WAIT1 状态。 845 | 846 | > 即发出连接释放报文段(FIN=1,序号seq=u),并停止再发送数据,主动关闭TCP连接,进入FIN_WAIT1(终止等待1)状态,等待服务端的确认。 847 | 848 | - 第二次挥手:服务端收到 FIN 之后,会发送 ACK 报文,且把客户端的序列号值 +1 作为 ACK 报文的序列号值,表明已经收到客户端的报文了,此时服务端处于 CLOSE_WAIT 状态。 849 | 850 | > 即服务端收到连接释放报文段后即发出确认报文段(ACK=1,确认号ack=u+1,序号seq=v),服务端进入CLOSE_WAIT(关闭等待)状态,此时的TCP处于半关闭状态,客户端到服务端的连接释放。客户端收到服务端的确认后,进入FIN_WAIT2(终止等待2)状态,等待服务端发出的连接释放报文段。 851 | 852 | - 第三次挥手:如果服务端也想断开连接了,和客户端的第一次挥手一样,发给 FIN 报文,且指定一个序列号。此时服务端处于 LAST_ACK 的状态。 853 | 854 | > 即服务端没有要向客户端发出的数据,服务端发出连接释放报文段(FIN=1,ACK=1,序号seq=w,确认号ack=u+1),服务端进入LAST_ACK(最后确认)状态,等待客户端的确认。 855 | 856 | - 第四次挥手:客户端收到 FIN 之后,一样发送一个 ACK 报文作为应答,且把服务端的序列号值 +1 作为自己 ACK 报文的序列号值,此时客户端处于 TIME_WAIT 状态。需要过一阵子以确保服务端收到自己的 ACK 报文之后才会进入 CLOSED 状态,服务端收到 ACK 报文之后,就处于关闭连接了,处于 CLOSED 状态。 857 | 858 | > 即客户端收到服务端的连接释放报文段后,对此发出确认报文段(ACK=1,seq=u+1,ack=w+1),客户端进入TIME_WAIT(时间等待)状态。此时TCP未释放掉,需要经过时间等待计时器设置的时间2MSL后,客户端才进入CLOSED状态。 859 | 860 | 那为什么需要四次挥手呢? 861 | 862 | > 因为当服务端收到客户端的SYN连接请求报文后,可以直接发送SYN+ACK报文。其中ACK报文是用来应答的,SYN报文是用来同步的。但是关闭连接时,当服务端收到FIN报文时,很可能并不会立即关闭SOCKET,所以只能先回复一个ACK报文,告诉客户端,“你发的FIN报文我收到了”。只有等到我服务端所有的报文都发送完了,我才能发送FIN报文,因此不能一起发送,故需要四次挥手。 863 | 864 | 简单来说就是以下四步: 865 | 866 | - **第一次挥手**:若客户端认为数据发送完成,则它需要向服务端发送连接释放请求。 867 | - **第二次挥手**:服务端收到连接释放请求后,会告诉应用层要释放 TCP 链接。然后会发送 ACK 包,并进入 CLOSE_WAIT 状态,此时表明客户端到服务端的连接已经释放,不再接收客户端发的数据了。但是因为 TCP 连接是双向的,所以服务端仍旧可以发送数据给客户端。 868 | - **第三次挥手**:服务端如果此时还有没发完的数据会继续发送,完毕后会向客户端发送连接释放请求,然后服务端便进入 LAST-ACK 状态。 869 | - **第四次挥手**:客户端收到释放请求后,向服务端发送确认应答,此时客户端进入 TIME-WAIT 状态。该状态会持续 2MSL(最大段生存期,指报文段在网络中生存的时间,超时会被抛弃) 时间,若该时间段内没有服务端的重发请求的话,就进入 CLOSED 状态。当服务端收到确认应答后,也便进入 CLOSED 状态。 870 | 871 | TCP 使用四次挥手的原因是因为 TCP 的连接是全双工的,所以需要双方分别释放到对方的连接,单独一方的连接释放,只代 表不能再向对方发送数据,连接处于的是半释放的状态。 872 | 873 | 最后一次挥手中,客户端会等待一段时间再关闭的原因,是为了防止发送给服务器的确认报文段丢失或者出错,从而导致服务器 端不能正常关闭。 874 | 875 | ## 41. 对 WebSocket 的理解 876 | 877 | WebSocket是HTML5提供的一种浏览器与服务器进行**全双工通讯**的网络技术,属于应用层协议。它基于TCP传输协议,并复用HTTP的握手通道。浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接, 并进行双向数据传输。 878 | 879 | WebSocket 的出现就解决了半双工通信的弊端。它最大的特点是:**服务器可以向客户端主动推动消息,客户端也可以主动向服务器推送消息。** 880 | 881 | **WebSocket原理**:客户端向 WebSocket 服务器通知(notify)一个带有所有接收者ID(recipients IDs)的事件(event),服务器接收后立即通知所有活跃的(active)客户端,只有ID在接收者ID序列中的客户端才会处理这个事件。 882 | 883 | **WebSocket 特点的如下:** 884 | 885 | - 支持双向通信,实时性更强 886 | - 可以发送文本,也可以发送二进制数据‘’ 887 | - 建立在TCP协议之上,服务端的实现比较容易 888 | - 数据格式比较轻量,性能开销小,通信高效 889 | - 没有同源限制,客户端可以与任意服务器通信 890 | - 协议标识符是ws(如果加密,则为wss),服务器网址就是 URL 891 | - 与 HTTP 协议有着良好的兼容性。默认端口也是80和443,并且握手阶段采用 HTTP 协议,因此握手时不容易屏蔽,能通过各种 HTTP 代理服务器。 892 | 893 | **Websocket的使用方法如下:** 894 | 895 | 在客户端中: 896 | 897 | ```js 898 | // 在index.html中直接写WebSocket,设置服务端的端口号为 9999 899 | let ws = new WebSocket('ws://localhost:9999'); 900 | // 在客户端与服务端建立连接后触发 901 | ws.onopen = function() { 902 | console.log("Connection open."); 903 | ws.send('hello'); 904 | }; 905 | // 在服务端给客户端发来消息的时候触发 906 | ws.onmessage = function(res) { 907 | console.log(res); // 打印的是MessageEvent对象 908 | console.log(res.data); // 打印的是收到的消息 909 | }; 910 | // 在客户端与服务端建立关闭后触发 911 | ws.onclose = function(evt) { 912 | console.log("Connection closed."); 913 | }; 914 | ``` 915 | 916 | ## 42.即时通讯的实现:短轮询、长轮询、SSE 和 WebSocket 间的区别? 917 | 918 | 短轮询和长轮询的目的都是用于实现客户端和服务器端的一个即时通讯。 919 | 920 | **短轮询的基本思路**:浏览器每隔一段时间向浏览器发送 http 请求,服务器端在收到请求后,不论是否有数据更新,都直接进行响应。这种方式实现的即时通信,本质上还是浏览器发送请求,服务器接受请求的一个过程,通过让客户端不断的进行请求,使得客户端能够模拟实时地收到服务器端的数据的变化。这种方式的优点是比较简单,易于理解。缺点是这种方式由于需要不断的建立 http 连接,严重浪费了服务器端和客户端的资源。当用户增加时,服务器端的压力就会变大,这是很不合理的。 921 | 922 | **长轮询的基本思路**:首先由客户端向服务器发起请求,当服务器收到客户端发来的请求后,服务器端不会直接进行响应,而是先将这个请求挂起,然后判断服务器端数据是否有更新。如果有更新,则进行响应,如果一直没有数据,则到达一定的时间限制才返回。客户端 JavaScript 响应处理函数会在处理完服务器返回的信息后,再次发出请求,重新建立连接。长轮询和短轮询比起来,它的优点是明显减少了很多不必要的 http 请求次数,相比之下节约了资源。长轮询的缺点在于,连接挂起也会导致资源的浪费。 923 | 924 | **SSE 的基本思想**:服务器使用流信息向客户端推送信息。严格地说,http 协议无法做到服务器主动推送信息。但是,有一种变通方法,就是服务器向客户端声明,接下来要发送的是流信息。也就是说,发送的不是一次性的数据包,而是一个数据流,会连续不断地发送过来。这时,客户端不会关闭连接,会一直等着服务器发过来的新的数据流,视频播放就是这样的例子。SSE 就是利用这种机制,使用流信息向浏览器推送信息。它基于 http 协议,目前除了 IE/Edge,其他浏览器都支持。它相对于前面两种方式来说,不需要建立过多的 http 请求,相比之下节约了资源。 925 | 926 | WebSocket 是 HTML5 定义的一个新协议议,与传统的 http 协议不同,该协议允许由服务器主动的向客户端推送信息。使用 WebSocket 协议的缺点是在服务器端的配置比较复杂。WebSocket 是一个全双工的协议,也就是通信双方是平等的,可以相互发送消息,而 SSE 的方式是单向通信的,只能由服务器端向客户端推送信息,如果客户端需要发送信息就是属于下一个 http 请求了。 927 | 928 | **上面的四个通信协议,前三个都是基于HTTP协议的。** 929 | 930 | 对于这四种即使通信协议,从性能的角度来看: 931 | 932 | **WebSocket > 长连接(SEE) > 长轮询 > 短轮询** 933 | 934 | 但是,我们如果考虑浏览器的兼容性问题,顺序就恰恰相反了: 935 | 936 | **短轮询 > 长轮询 > 长连接(SEE) > WebSocket** 937 | 938 | 所以,还是要根据具体的使用场景来判断使用哪种方式。 939 | 940 | ## 42.说一下HTTP 3.0? 941 | 942 | HTTP/3基于UDP协议实现了类似于TCP的多路复用数据流、传输可靠性等功能,这套功能被称为QUIC协议 943 | 944 | 1.流量控制、传输可靠性功能:QUIC在UDP的基础上增加了一层来保证数据传输可靠性,它提供了数据包重传、拥塞控制、以及其他一些TCP中的特性。 945 | 946 | 2.集成TLS加密功能:目前QUIC使用TLS1.3,减少了握手所花费的RTT数。 947 | 948 | 3.多路复用:同一物理连接上可以有多个独立的逻辑数据流,实现了数据流的单独传输,解决了TCP的队头阻塞问题。 949 | 950 | 4.快速握手:由于基于UDP,可以实现使用0 ~ 1个RTT来建立连接 --------------------------------------------------------------------------------