├── README.md ├── assets ├── js_1.webp ├── js_2.webp ├── html_1.webp ├── html_2.webp ├── js7.1.webp ├── js7.2.webp ├── js7.3.webp ├── js7.4.webp ├── js7.5.webp ├── js7.6.webp ├── js7.7.webp ├── js8-1.webp ├── vue │ ├── 1.webp │ ├── 2.webp │ ├── 3.webp │ ├── 4.webp │ ├── 5.webp │ ├── 6.webp │ ├── 7.webp │ ├── 2_1.webp │ ├── 2_10.webp │ ├── 2_11.webp │ ├── 2_12.webp │ ├── 2_13.webp │ ├── 2_14.webp │ ├── 2_15.webp │ ├── 2_16.webp │ ├── 2_17.webp │ ├── 2_18.webp │ ├── 2_19.webp │ ├── 2_2.webp │ ├── 2_3.webp │ ├── 2_4.webp │ ├── 2_5.webp │ ├── 2_6.webp │ ├── 2_7.webp │ ├── 2_8.webp │ ├── 2_9.webp │ ├── 3_1.webp │ ├── 3_2.webp │ ├── 3_3.webp │ ├── 3_4.webp │ ├── 3_5.webp │ ├── 3_6.webp │ ├── 3_7.webp │ ├── 3_8.webp │ ├── 5_1.webp │ ├── 5_2.webp │ ├── 5_3.webp │ ├── 5_4.webp │ ├── 5_5.webp │ ├── 6_1.webp │ ├── 6_2.webp │ ├── 6_3.webp │ ├── 6_4.webp │ ├── 6_5.webp │ └── 6_6.webp ├── browser │ ├── 1.webp │ ├── 2.webp │ ├── 3.webp │ ├── 4.webp │ └── 5.webp └── writeCode │ ├── writeCode4_1.webp │ └── writeCode4_2.webp ├── .idea ├── .gitignore ├── vcs.xml ├── modules.xml ├── marks.iml └── git_toolbox_prj.xml ├── js面试题八(JS基础).md ├── V8引擎.md ├── nodejsEventLoop.md ├── 论前端工程师核心竞争力.md ├── ES6-->class继承.md ├── js面试题三(JS基础).md ├── eventLoop.md ├── 前端手撕代码面试题(二).md ├── promise2.md ├── js面试题二(JS基础).md ├── 前端手撕代码面试题(三).md ├── 前端手撕代码面试题(四).md ├── 原型.md ├── browser_two.md ├── this指向.md ├── 前端手撕代码面试题(一).md ├── vue面试题四.md ├── vue面试题六.md ├── vue面试题二.md ├── vue面试题三.md ├── vue面试题五.md ├── vue面试题一.md ├── 迭代器:可迭代对象:生成器.md ├── browser_three.md ├── js面试题四(JS基础).md └── js面试题七(JS基础).md /README.md: -------------------------------------------------------------------------------- 1 | # marks 2 | 日常笔记分享 3 | -------------------------------------------------------------------------------- /assets/js_1.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/summmer-is-hot/marks/HEAD/assets/js_1.webp -------------------------------------------------------------------------------- /assets/js_2.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/summmer-is-hot/marks/HEAD/assets/js_2.webp -------------------------------------------------------------------------------- /assets/html_1.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/summmer-is-hot/marks/HEAD/assets/html_1.webp -------------------------------------------------------------------------------- /assets/html_2.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/summmer-is-hot/marks/HEAD/assets/html_2.webp -------------------------------------------------------------------------------- /assets/js7.1.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/summmer-is-hot/marks/HEAD/assets/js7.1.webp -------------------------------------------------------------------------------- /assets/js7.2.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/summmer-is-hot/marks/HEAD/assets/js7.2.webp -------------------------------------------------------------------------------- /assets/js7.3.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/summmer-is-hot/marks/HEAD/assets/js7.3.webp -------------------------------------------------------------------------------- /assets/js7.4.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/summmer-is-hot/marks/HEAD/assets/js7.4.webp -------------------------------------------------------------------------------- /assets/js7.5.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/summmer-is-hot/marks/HEAD/assets/js7.5.webp -------------------------------------------------------------------------------- /assets/js7.6.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/summmer-is-hot/marks/HEAD/assets/js7.6.webp -------------------------------------------------------------------------------- /assets/js7.7.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/summmer-is-hot/marks/HEAD/assets/js7.7.webp -------------------------------------------------------------------------------- /assets/js8-1.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/summmer-is-hot/marks/HEAD/assets/js8-1.webp -------------------------------------------------------------------------------- /assets/vue/1.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/summmer-is-hot/marks/HEAD/assets/vue/1.webp -------------------------------------------------------------------------------- /assets/vue/2.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/summmer-is-hot/marks/HEAD/assets/vue/2.webp -------------------------------------------------------------------------------- /assets/vue/3.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/summmer-is-hot/marks/HEAD/assets/vue/3.webp -------------------------------------------------------------------------------- /assets/vue/4.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/summmer-is-hot/marks/HEAD/assets/vue/4.webp -------------------------------------------------------------------------------- /assets/vue/5.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/summmer-is-hot/marks/HEAD/assets/vue/5.webp -------------------------------------------------------------------------------- /assets/vue/6.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/summmer-is-hot/marks/HEAD/assets/vue/6.webp -------------------------------------------------------------------------------- /assets/vue/7.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/summmer-is-hot/marks/HEAD/assets/vue/7.webp -------------------------------------------------------------------------------- /assets/browser/1.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/summmer-is-hot/marks/HEAD/assets/browser/1.webp -------------------------------------------------------------------------------- /assets/browser/2.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/summmer-is-hot/marks/HEAD/assets/browser/2.webp -------------------------------------------------------------------------------- /assets/browser/3.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/summmer-is-hot/marks/HEAD/assets/browser/3.webp -------------------------------------------------------------------------------- /assets/browser/4.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/summmer-is-hot/marks/HEAD/assets/browser/4.webp -------------------------------------------------------------------------------- /assets/browser/5.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/summmer-is-hot/marks/HEAD/assets/browser/5.webp -------------------------------------------------------------------------------- /assets/vue/2_1.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/summmer-is-hot/marks/HEAD/assets/vue/2_1.webp -------------------------------------------------------------------------------- /assets/vue/2_10.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/summmer-is-hot/marks/HEAD/assets/vue/2_10.webp -------------------------------------------------------------------------------- /assets/vue/2_11.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/summmer-is-hot/marks/HEAD/assets/vue/2_11.webp -------------------------------------------------------------------------------- /assets/vue/2_12.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/summmer-is-hot/marks/HEAD/assets/vue/2_12.webp -------------------------------------------------------------------------------- /assets/vue/2_13.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/summmer-is-hot/marks/HEAD/assets/vue/2_13.webp -------------------------------------------------------------------------------- /assets/vue/2_14.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/summmer-is-hot/marks/HEAD/assets/vue/2_14.webp -------------------------------------------------------------------------------- /assets/vue/2_15.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/summmer-is-hot/marks/HEAD/assets/vue/2_15.webp -------------------------------------------------------------------------------- /assets/vue/2_16.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/summmer-is-hot/marks/HEAD/assets/vue/2_16.webp -------------------------------------------------------------------------------- /assets/vue/2_17.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/summmer-is-hot/marks/HEAD/assets/vue/2_17.webp -------------------------------------------------------------------------------- /assets/vue/2_18.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/summmer-is-hot/marks/HEAD/assets/vue/2_18.webp -------------------------------------------------------------------------------- /assets/vue/2_19.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/summmer-is-hot/marks/HEAD/assets/vue/2_19.webp -------------------------------------------------------------------------------- /assets/vue/2_2.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/summmer-is-hot/marks/HEAD/assets/vue/2_2.webp -------------------------------------------------------------------------------- /assets/vue/2_3.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/summmer-is-hot/marks/HEAD/assets/vue/2_3.webp -------------------------------------------------------------------------------- /assets/vue/2_4.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/summmer-is-hot/marks/HEAD/assets/vue/2_4.webp -------------------------------------------------------------------------------- /assets/vue/2_5.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/summmer-is-hot/marks/HEAD/assets/vue/2_5.webp -------------------------------------------------------------------------------- /assets/vue/2_6.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/summmer-is-hot/marks/HEAD/assets/vue/2_6.webp -------------------------------------------------------------------------------- /assets/vue/2_7.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/summmer-is-hot/marks/HEAD/assets/vue/2_7.webp -------------------------------------------------------------------------------- /assets/vue/2_8.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/summmer-is-hot/marks/HEAD/assets/vue/2_8.webp -------------------------------------------------------------------------------- /assets/vue/2_9.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/summmer-is-hot/marks/HEAD/assets/vue/2_9.webp -------------------------------------------------------------------------------- /assets/vue/3_1.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/summmer-is-hot/marks/HEAD/assets/vue/3_1.webp -------------------------------------------------------------------------------- /assets/vue/3_2.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/summmer-is-hot/marks/HEAD/assets/vue/3_2.webp -------------------------------------------------------------------------------- /assets/vue/3_3.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/summmer-is-hot/marks/HEAD/assets/vue/3_3.webp -------------------------------------------------------------------------------- /assets/vue/3_4.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/summmer-is-hot/marks/HEAD/assets/vue/3_4.webp -------------------------------------------------------------------------------- /assets/vue/3_5.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/summmer-is-hot/marks/HEAD/assets/vue/3_5.webp -------------------------------------------------------------------------------- /assets/vue/3_6.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/summmer-is-hot/marks/HEAD/assets/vue/3_6.webp -------------------------------------------------------------------------------- /assets/vue/3_7.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/summmer-is-hot/marks/HEAD/assets/vue/3_7.webp -------------------------------------------------------------------------------- /assets/vue/3_8.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/summmer-is-hot/marks/HEAD/assets/vue/3_8.webp -------------------------------------------------------------------------------- /assets/vue/5_1.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/summmer-is-hot/marks/HEAD/assets/vue/5_1.webp -------------------------------------------------------------------------------- /assets/vue/5_2.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/summmer-is-hot/marks/HEAD/assets/vue/5_2.webp -------------------------------------------------------------------------------- /assets/vue/5_3.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/summmer-is-hot/marks/HEAD/assets/vue/5_3.webp -------------------------------------------------------------------------------- /assets/vue/5_4.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/summmer-is-hot/marks/HEAD/assets/vue/5_4.webp -------------------------------------------------------------------------------- /assets/vue/5_5.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/summmer-is-hot/marks/HEAD/assets/vue/5_5.webp -------------------------------------------------------------------------------- /assets/vue/6_1.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/summmer-is-hot/marks/HEAD/assets/vue/6_1.webp -------------------------------------------------------------------------------- /assets/vue/6_2.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/summmer-is-hot/marks/HEAD/assets/vue/6_2.webp -------------------------------------------------------------------------------- /assets/vue/6_3.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/summmer-is-hot/marks/HEAD/assets/vue/6_3.webp -------------------------------------------------------------------------------- /assets/vue/6_4.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/summmer-is-hot/marks/HEAD/assets/vue/6_4.webp -------------------------------------------------------------------------------- /assets/vue/6_5.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/summmer-is-hot/marks/HEAD/assets/vue/6_5.webp -------------------------------------------------------------------------------- /assets/vue/6_6.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/summmer-is-hot/marks/HEAD/assets/vue/6_6.webp -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # 默认忽略的文件 2 | /shelf/ 3 | /workspace.xml 4 | # 基于编辑器的 HTTP 客户端请求 5 | /httpRequests/ 6 | -------------------------------------------------------------------------------- /assets/writeCode/writeCode4_1.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/summmer-is-hot/marks/HEAD/assets/writeCode/writeCode4_1.webp -------------------------------------------------------------------------------- /assets/writeCode/writeCode4_2.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/summmer-is-hot/marks/HEAD/assets/writeCode/writeCode4_2.webp -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/marks.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /.idea/git_toolbox_prj.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 14 | 15 | -------------------------------------------------------------------------------- /js面试题八(JS基础).md: -------------------------------------------------------------------------------- 1 | ## 前端常见面试题(JS篇)八 2 | 3 | ## 八、其他类型问题补充 4 | 5 | ## 8.1 简单说说你对观察者模式的理解 6 | 7 | 观察者模式定义了对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都将得到通知,并自动更新 8 | 9 | 观察者模式属于行为型模式,行为型模式关注的是对象之间的通讯,观察者模式就是观察者和被观察者之间的通讯 10 | 11 | ![img](https://raw.githubusercontent.com/summmer-is-hot/marks/main/assets/js8-1.webp?) 12 | 13 | 例如生活中,我们可以用报纸期刊的订阅来形象的说明,当你订阅了一份报纸,每天都会有一份最新的报纸送到你手上,有多少人订阅报纸,报社就会发多少份报纸 14 | 15 | 报社和订报纸的客户就形成了一对多的依赖关系 16 | 17 | ## 8.2 简单说说你对发布订阅模式的理解 18 | 19 | 发布-订阅是一种消息范式,消息的发送者(称为发布者)不会将消息直接发送给特定的接收者(称为订阅者)。而是将发布的消息分为不同的类别,无需了解哪些订阅者(如果有的话)可能存在 20 | 21 | 同样的,订阅者可以表达对一个或多个类别的兴趣,只接收感兴趣的消息,无需了解哪些发布者存在 22 | 23 | ## 8.3 观察者模式与发布订阅的区别 24 | 25 | - 在观察者模式中,观察者是知道Subject的,Subject一直保持对观察者进行记录。然而,在发布订阅模式中,发布者和订阅者不知道对方的存在。它们只有通过消息代理进行通信。 26 | - 在发布订阅模式中,组件是松散耦合的,正好和观察者模式相反。 27 | - 观察者模式大多数时候是同步的,比如当事件触发,Subject就会去调用观察者的方法。而发布-订阅模式大多数时候是异步的(使用消息队列) 28 | 29 | ## 8.4 说说你对正则表达式的理解 30 | 31 | 正则表达式是一种用来匹配字符串的强有力的武器 32 | 33 | 它的设计思想是用一种描述性的语言定义一个规则,凡是符合规则的字符串,我们就认为它“匹配”了,否则,该字符串就是不合法的 34 | 35 | 在 `JavaScript`中,正则表达式也是对象,构建正则表达式有两种方式: 36 | 37 | 1. 字面量创建,其由包含在斜杠之间的模式组成 38 | 39 | ```js 40 | const re = /\d+/g; 41 | ``` 42 | 43 | 1. 调用`RegExp`对象的构造函数 44 | 45 | ```js 46 | const re = new RegExp("\d+","g"); 47 | 48 | const rul = "\d+" 49 | const re1 = new RegExp(rul,"g"); 50 | ``` 51 | 52 | 使用构建函数创建,第一个参数可以是一个变量,遇到特殊字符``需要使用`\`进行转义 53 | 54 | ## 8.5 如何判断当前的Js代码是否在浏览器环境中运行 55 | 56 | 如果Javascript在浏览器环境中运行,则会有一个全局对象:`window`。因此,可以通过以下方式判断环境: 57 | 58 | ```javascript 59 | typeof window.self !== "undefined"; 60 | // 在web worker或者sevice worker下是无法获取到windows全局变量, 所以需要通过self变量判断 61 | ``` 62 | 63 | ## 结语 64 | 65 | >   今天的面试题的分享就到这边啦,明天继续给大家分享~ 66 | 67 | ## 作者 68 | 69 | | Coder | 小红书ID | 创建时间 | 70 | | :---------- | :-------- | :--------- | 71 | | 落寞的前端👣 | 121450513 | 2022.10.18 | 72 | -------------------------------------------------------------------------------- /V8引擎.md: -------------------------------------------------------------------------------- 1 | ## 大多数前端工程师不了解V8引擎 2 | 3 | ## 一.浏览器的工作原理 4 | 5 | 1. 首先,我们的静态资源是存放在服务器上,当我们在浏览器输入地址时(域名或者ip地址),当我们输入域名时,会通过DNS解析域名到所在的IP地址上。 6 | 2. 开始先解析到Index.html文件,当遇到Link标签开始下载Css文件 7 | 3. 遇到Script标签开始下载JavaScript文件 8 | 9 | ## 二.认识浏览器的内核 10 | 11 | 不同的浏览器有不同的内核组成 12 | 13 | 1. Gecko:早期被Netscape和Mozilla Firefox浏览器浏览器使用; 14 | 2. Trident:微软开发,被IE4~IE11浏览器使用,但是Edge浏览器已经转向Blink; 15 | 3. Webkit:苹果基于KHTML开发、开源的,用于Safari,Google Chrome之前也在使用; 16 | 4. Blink:是Webkit的一个分支,Google开发,目前应用于Google Chrome、Edge、Opera 17 | 18 | ## 三.浏览器渲染过程 19 | 20 | ![v8_process.png](https://raw.githubusercontent.com/summmer-is-hot/picture-store/main/marks/v8_process.webp?)
21 | HTML解析的时候遇到了JavaScript标签,应该怎么办呢?
22 | 会停止解析HTML,而去加载和执行JavaScript代码;
23 | Js代码由js引擎来执行 24 | 25 | ## 四.认识JavaScript引擎 26 | 27 | 为什么要使用js引擎? 28 | ​ 高级的编程语言都是需要转成最终的机器指令来执行的,事实上我们编写的JavaScript无论你交给浏览器或者Node执行,最后都是需要被CPU执行的,但是CPU只认识自己的指令集,实际上是机器语言,才能被CPU所执行,所以我们需要js引擎来帮我们解析成CPU认识的指令集,才能被cpu执行,** 29 | 所以js引擎帮助我们把代码翻译成CPU所认识的指令,最后交给CPU来执行。** 30 | 31 | ## 五.浏览器内核和JS引擎的关系 32 | 33 | 我们拿主流的WebKit内核为例,事实上他们由两部分组合
34 | **WebCore**:负责HTML解析、布局、渲染等等相关的工作;
35 | **JavaScriptCore**:解析、执行JavaScript代码;
36 | 37 | ## 六.V8引擎的原理 38 | 39 | ​ V8是用C ++编写的Google开源高性能JavaScript和WebAssembly引擎,它用于Chrome和Node.js等。 V8可以独立运行,也可以嵌入到任何C ++应用程序中。
40 | ​ V8引擎本身的源码非常复杂,大概有超过100w行C++代码,通过了解它的架构,我们可以知道它是如何对JavaScript执行的。
41 | ​ Parse模块会将JavaScript代码转换成AST(抽象语法树),这是因为解释器并不直接认识JavaScript代码; 如果函数没有被调用,那么是不会被转换成AST的;
42 | ​ Parse的V8官方文档:[v8.dev/blog/scanner](https://v8.dev/blog/scanner)
43 | ​ Ignition是一个解释器,会将AST转换成ByteCode(字节码) 同时会收集TurboFan优化所需要的信息(比如函数参数的类型信息,有了类型才能进行真实的运算); 44 | 如果函数只调用一次,Ignition会执行解释执行ByteCode; 45 | Ignition的V8官方文档:[https://v8.dev/blog/ignition-interpreter](https://v8.dev/blog/ignition-interpreter) 46 | TurboFan是一个编译器,可以将字节码编译为CPU可以直接执行的机器码;
47 | ​ 如果一个函数被多次调用,那么就会被标记为热点函数,那么就会经过TurboFan转换成优化的机器码,提高代码的执行性能;
48 | ​ 但是,机器码实际上也会被还原为ByteCode,这是因为如果后续执行函数的过程中,类型发生了变化(比如sum函数原来执行的是number类型,后来执行变成了string类型),之前优化的机器码并不能正确的处理运算,就会逆向的转换成字节码;
49 | 50 | ## 七.V8引擎的解析图 51 | 52 | ![v8_progress.png](https://raw.githubusercontent.com/summmer-is-hot/picture-store/main/marks/v8_progress.webp?) 53 | 54 | ## 八.V8执行的细节 55 | 56 | ​ **Blink将源码交给V8引擎,Stream获取到源码并且进行编码转换;**
57 | ​ Scanner会进行词法分析(lexical analysis), 词法分析会将代码转换成tokens;
58 | ​ 接下来tokens会被转换成AST树,经过Parser和PreParser;
59 | ​ Parser就是直接将tokens转成AST树架构;
60 | ​ PreParser称之为预解析,为什么需要预解析呢?
61 | ​ 这是因为并不是所有的JavaScript代码,在一开始时就会被执行。那么对所有的JavaScript代码进行解析,必然会影响网页的运行效率;
62 | ​ 所以V8引擎就实现了Lazy Parsing(延迟解析)的方案,它的作用是将不必要的函数进行预解析,也就是只解析时需要的内容,而对函数的全量解析是在函数被调用时才会进行;
63 | ​ 比如我们在一个函数outer内部定义了另外一个函数inner,那么inner函数就会进行预解析; 生成AST树后,会被Ignition转成字节码(bytecode),之后的过程就是代码的执行过程。
64 | 65 | ## 作者 66 | 67 | | Coder | 小红书ID | 创建时间 | 68 | | :---------- | :-------- | :--------- | 69 | | 落寞的前端👣 | 121450513 | 2022.10.04 | 70 | -------------------------------------------------------------------------------- /nodejsEventLoop.md: -------------------------------------------------------------------------------- 1 | # 彻底搞懂Node中的事件循环 2 | 3 | ## 前文再续,接上一回 4 | 5 | >   昨天给大家介绍了,[什么是浏览器的事件循环](https://github.com/summmer-is-hot/marks/blob/main/eventLoop.md, 这回给大家介绍浏览器中的事件循环以及常见的面试题。 6 | 7 | ## 1.Node的事件循环 8 | 9 | 浏览器中的EventLoop是根据HTML5定义的规范来实现的,不同的浏览器可能会有不同的实现,而Node中是由libuv实现的。 下面我们来查看Node的架构图: 10 | 11 | - 我们会发现libuv中主要维护了一个EventLoop和workerthreads 12 | - EventLoop主要是调用一些文件IO、Network等 13 | - libuv是一个多平台的专注于异步IO的库,它最初是为Node开发的,但是现在也被使用到Luvit、Julia、pyuv等其他地方 14 | 15 | ![nodejsEventLoop_1.jpg](https://raw.githubusercontent.com/summmer-is-hot/picture-store/main/marks/nodejsEventLoop_1.webp?) 16 | 17 | ## 2.Node事件循环的阶段 18 | 19 | `事件循环就像是一个桥梁,连接着应用程序的JavaScript和系统调用之间的通道` 20 | 21 | - 无论是我们对文件的IO操作、数据库操作,都会有对应的结果和回调函数放到事件循环队列中 22 | - 事件循环会不断从任务队列中取出对应的回调函数然后进行执行。 一次完整的事件循环可以称之为一次Tick(时钟的滴答类似) 分为多个阶段: 23 | 24 | 1. 定时器(Timers):本阶段执行已经被 setTimeout() 和 setInterval() 的调度回调函数。 25 | 2. 待定回调(pading callback):对某些操作系统(如Tcp)的执行回调 26 | 3. idle,perpase:仅系统内部使用 27 | 4. 轮询(Poll):探索检测新的IO事件、执行IO相关的回调 28 | 5. setImmediate()回调函数在这里执行 29 | 6. 关闭的回调函数:一些关闭的回调函数,如:socket.on('close', ...)。 30 | 31 | ## 3.Node事件循环的阶段图解 32 | 33 | ![nodejsEventLoop_2.jpg](https://raw.githubusercontent.com/summmer-is-hot/picture-store/main/marks/nodejsEventLoop_2.webp) 34 | 35 | ## 4.Node的宏任务和微任务 36 | 37 | - 我们会发现从一次事件循环的Tick来说,Node的事件循环更复杂,它也分为微任务和宏任务: 38 | - 宏任务(macrotask):setTimeout、setInterval、IO事件、setImmediate、close事件; 39 | - 微任务(microtask):Promise的then回调、process.nextTick、queueMicrotask; 但是,Node中的事件循环不只是 微任务队列和 宏任务队列: 40 | - 微任务队列: 41 | 42 | 1. `next tick queue:process.nextTick;` 43 | 2. `other queue:Promise的then回调、queueMicrotask;` 44 | 45 | - 宏任务队列: 46 | 47 | 1. `timer queue:setTimeout、setInterval` 48 | 2. ` poll queue:IO事件;` 49 | 3. `check queue:setImmediate;` 50 | 4. `close queue:close事件;` 51 | 52 | ## 5. Node事件循环的顺序 53 | 54 | 所以,在每一次事件循环的tick中,会按照如下顺序来执行代码: 55 | 56 | next tick microtask queue other microtask queue timer queue poll queue check queue close queue 57 | 58 | ## 6. 面试题 59 | 60 | ```js 61 | async function async1() { 62 | console.log("async1 start"); 63 | await async2(); 64 | console.log("async1 end"); 65 | } 66 | 67 | async function async2() { 68 | console.log("async2"); 69 | } 70 | 71 | console.log("script start"); 72 | 73 | setTimeout(function () { 74 | console.log("setTimeout0"); 75 | }, 0); 76 | 77 | setTimeout(function () { 78 | console.log("setTimeout2"); 79 | }, 300); 80 | 81 | setImmediate(() => console.log("setImmediate")); 82 | 83 | process.nextTick(() => console.log("nextTick1")); 84 | 85 | async1(); 86 | 87 | process.nextTick(() => console.log("nextTick2")); 88 | 89 | new Promise(function (resolve) { 90 | console.log("promise1"); 91 | resolve(); 92 | console.log("promise2"); 93 | }).then(function () { 94 | console.log("promise3"); 95 | }); 96 | 97 | console.log("script end"); 98 | 99 | //script start 100 | //async1 start 101 | // async2 promise1 promise2 scriptend nextTick1 nextTick2 102 | // async1 end promise3 settimeout0 setImmediate settimeout2 103 | 104 | ``` 105 | 106 | ## 结语 107 | 108 | 最后浏览器与Node中的事件循环就介绍到这里了,期待我们的下次见面~ 109 | 110 | ## 作者 111 | 112 | | Coder | 小红书ID | 创建时间 | 113 | | :---------- | :-------- | :--------- | 114 | | 落寞的前端👣 | 121450513 | 2022.10.06 | 115 | 116 | -------------------------------------------------------------------------------- /论前端工程师核心竞争力.md: -------------------------------------------------------------------------------- 1 | # 论前端工程师核心竞争力 2 | 3 | ## 若你已做多年前端(5年+),你觉得你目前的核心竞争力在哪? 4 | 5 | ### 前言 6 | 7 | ``` 8 | 前端是个入门门槛很低,深度挖掘又非常复杂的一门职业;现在涌现出很多前端的培训机构,和很多学生时代就完全入门前端的应届生, 9 | 还有工作一年以后能力不错的全栈,他们的产出和能力都不赖;那么,当你做了很多年前端后,开发、测试、部署; 10 | 从前端到后端,从桌面到移动,从嵌入式到物联网,你觉得有什么优势或者竞争力呢? 11 | * 喜欢写代码,知道自己的瓶颈,并接受它 12 | * 做越来越多的事就是挑战自己原来的想法,作总结和交流 13 | * 能写出比较复杂的代码 14 | * 业务理的顺,有一些产品思维 15 | * 还不错的审美 16 | * 沟通和协调的能力,可以组织很多人一起写一些比较复杂的代码 17 | * ppt excel用的比一般程序员要好一些,也挺会写邮件的 18 | * 对新知识有热情,学东西较快,是一个擅长解决问题的人 19 | ``` 20 | 21 | --- 22 | 23 | ### 挑战 24 | 25 | ``` 26 | * 每天工作会积累很多经验,久而久之,你会很熟悉你的工作内容,熟悉配合的人,越来越像机械一样在工作。那么,你会从这份工作中已经慢慢地失去成长的动力。可能你需要有一些转变,不论是工作内容或是岗位。 27 | * 与不同岗位人的沟通,你会得到他们的问题与角度。接触不熟悉的技术,你会遇到它们的问题与角度。 28 | 这些都是让自己走出舒服区,让自己从无限的重复中获得新的成长。 29 | ``` 30 | 31 | --- 32 | 33 | ### 总结 34 | 35 | ``` 36 | * 我们日复一日在做coding,隔三岔五在修复bug,解决各种技术难题是家常便饭。这些问题日复一日,我们是否有过思考,他们的本质是什么,他们有没有共性。 37 | * 从总结中思考,从解决问题到定义问题。定义问题就是从点到面,让自己有布局解决一类问题的能力。这是我们职业生涯中一直需要锻炼的。 38 | ``` 39 | 40 | ### 交流 41 | 42 | ``` 43 | * 我们写文章并不是为了写而写,而是把自己的成果,自己的想法写下来,可以说写文章也是为了交流。 44 | * 交流,就是去碰撞,碰撞你那些不坚定的想法,说服别人,或被说服,过程中帮助你进步,帮助你理解其它人的经验,完善你自己的经验世界。 45 | * 对我来说最大的竞争力除了还可以写点复杂的代码,更多的是这些软性的东西。 46 | * 我会运维,新项目给个服务器权限就够了,Docker 快速部署 47 | * 我会写 Java,NodeJS, 48 | * 我会撸各种后台框架,Spring,Express,Koa,egg之类的 49 | * 我会撸各种前端框架,新框架看看 API 就搞定了,觉得不爽就自己改源码,造新轮子。 50 | * 我能解决各种问题,不用等着别人教 51 | * 我知道产品要的是啥,虽然他们说出来的不知道是个什么鬼, 52 | * 我知道怎么跟测试说我的需求 53 | * 我也知道怎么背锅 54 | * 我还知道怎么带人 55 | * 我知道各种行业架构,运行流程,好像产品经理的活干着也挺顺的。 56 | * 不断学习,不断否定之前的自己,重新认识自己,有自知之明 57 | * 接触了很多前端周边的技术,如果说前端是自己的一套房产,那么我的这套房产的周边配套相对来讲还算比较齐全,所以虽然房子烂点,但不太影响价值 58 | * 前后端(vue,react,java,shell,mysql)都能搞起来,可以自己完成一个项目,就算没工作了也不会太担心 59 | * 自信很重要,永保自信 60 | * 基础扎实,js的一些基本原理、浏览器机制,vue react 的封装思路不求很透彻,起码得了解,知道个大概其。http机制,接口的实现思路,sql怎么写,数据库表结构 这些可以不会,但是得了解。最后就是前端工程化,脚手架那么多,不需要你手撸脚手架,但是得会用,会修改 61 | * 亮点:比如 webgl、大屏可视化、移动端app、跨平台的小程序、C端sass系统,这里面总得有一两个自己很熟的吧,经常做、有经验,而且积累了一些东西,设计思路、复杂情况的解决方案,通用代码等等,都可以。 62 | ``` 63 | 64 | ### 核心竞争力 65 | 66 | ``` 67 | * 做服务端的能力——我掌握了Java,node.js,还学了一点Go(没有用于生产),虽然在分工中不一定需要我做大量的服务端开发,但一方面我可以不求人的完成很多小项目,一方面我可以更好的换位思考我的合作伙伴们。 68 | * 查文档或文献的能力——不仅是文档,还要有文献,虽然做业务开发很少会需要看论文,不过当我需要做一些新奇的玩意, 69 | 比如以前心血来潮的一些计算几何和多边形算法相关的文献,最近倒腾的MIDI和SoundFont2的技术白皮书,这还帮我保证了一定的英语阅读能力。 70 | *【Make Boss Happy的能力】——这不是贬义的,不仅是理解KPI,更重要的是平衡个人技术追求和团队需求。 71 | * 一些算法基础——知道或验证过大多数技术的性能瓶颈和优劣,对网站不同阶段遇到的问题都有处理经验。 72 | * 做过抽丝剥茧的重构,也做过推到重建的大项目,对于可能出问题的地方有着准确的直觉。 73 | * 能快速识别人,快速上手新技术,有自己在这些事情的一套工作方法。 74 | * 带过各种各样的团队,管理过的各式各样的人,知道各种道道但同样能守住本心。 75 | * 5年以后更多的是软实力,对行业的了解,对上下游的协调,沟通,对组织玩法的了解,对项目的管理能力,对人的管理能力等等 76 | * 做管理的没有我懂技术,能做技术的没我懂管理,差异化才是核心竞争力,复合型人才 77 | *** (最重要)快速学习新知识,信息检索,解决开放问题,工程直觉这是 10 年开发经验带给我最大的收获看起来很虚,但确实存在,并且极为重要编程语言是相通的 78 | * 技术选型:根据业务的规模、复杂程度、团队的配置和现状合理的选型。不盲目在项目中尝试新的技术,但是要跟进对新技术的了解,了解其利弊和使用场景,作为选型时的依据。可以在非核心项目或者小模块里尝试。 79 | * 沟通交流:了解对接职位(通常就是后端、交互、产品)的一些基础知识和职能,方便在需求沟通时期就把潜在的问题暴露出来,PK掉不合理的需求,把复杂的问题简单处理。尤其是在项目的初期,很多需求和方案都是拍脑袋决定的,把这些需求用最简单粗暴的方式处理,不要让他们成为前期快速迭代的绊脚石。从项目的角度考虑合理性问题,不要一下扎进坑里,在不必要非核心的功能上浪费太多时间。 80 | * 价值和定位(可能和现在前端的主流价值观有很大出入):前端作为从web开发者中分离出来的职位,其使命和其他客户端程序员一样,除了实现基础的业务功能,最重要的是提升用户的体验。极端的说,即使没有前端工程师,后端一样可以实现现在的业务,只不过是体验不好,性能不佳。所以我认为,用户体验是前端最核心的价值。然而,在我看来社区里越来越多的前端致力于流程化和工程化的研究和优化,提升了开发者体验(但对于新手而言,可能提升了学习成本),而对于用户而言却没有带来太多实质的价值(个人体验来看,新框架初期匹配的组件体验往往不如同类型的 jquery 插件 -- 主要原因可能是年代够久远,体验优化的够好)。结和技术选型,在一些不是特别需要用户体验 且 业务逻辑复杂 且 项目周期短 且 团队技术基础好 的项目中,是非常合适的。然而这么苛刻的条件决定了他们不适合绝大部分业务。 81 | * 工程化实践经验,多端开发经验,多语言跨平台 82 | ``` 83 | 84 | --- 85 | 86 | #### 作者 87 | 88 | | Coder | 小红书ID | 创建时间 | 89 | | :--- | :---| :--- | 90 | | 落寞的前端👣 | 121450513 |2022.09.29 | 91 | 92 | -------------------------------------------------------------------------------- /ES6-->class继承.md: -------------------------------------------------------------------------------- 1 | ## 大多数前端工程师不了解的ES6 Class继承 2 | 3 | ## 前文再续,接上一回 ES5 继承 4 | 5 | ## 6. 面向对象ES6 -> 继承 6 | 7 | - 我们前面介绍了,通过构造函数的形式来模仿“类”,在ES6的新标准中,使用`class`关键字来定义类 8 | - 其class的本质就是前面构造函数、原型链的那一套东西,就是几个API调用而已 9 | 10 | ### 6.1 定义类 11 | 12 | ```arduino 13 | //1. 类申明 14 | class Person1 {} 15 | 16 | //2. 类表达式 17 | const Person2 = class {} 18 | ``` 19 | 20 | ### 6.2 类与构造函数 21 | 22 | - 其本质是跟构造函数一样 23 | 24 | ```javascript 25 | class Person {} 26 | 27 | const p1 = new Person() 28 | 29 | console.log(Person.prototype) 30 | console.log(Person.prototype.constructor) 31 | console.log(p1.__proto__ === Person.prototype) 32 | 33 | //{} 34 | //[class Person] 35 | //true 36 | ``` 37 | 38 | ### 6.3 类的构造函数 39 | 40 | - 如果我们希望在创建对象的时候给类传递一些参数,这个时候应该如何做呢? 41 | - 每个类都可以有一个自己的构造函数(方法),这个方法的名称是固定的constructor; 42 | - 当我们通过new操作符,操作一个类的时候会调用这个类的构造函数constructor; 43 | - 每个类只能有一个构造函数,如果包含多个构造函数,那么会抛出异常; 44 | 45 | ```javascript 46 | class Person { 47 | constructor(name, age, money) { 48 | this.name = name 49 | this.age = age 50 | this.money = money 51 | } 52 | 53 | running() { 54 | console.log(`${this.name} running!!!`) 55 | } 56 | eating() { 57 | console.log(`${this.name} eating!!!`) 58 | } 59 | } 60 | 61 | const p1 = new Person('ice', 22, 100) 62 | console.log(p1) 63 | p1.running() 64 | p1.eating() 65 | 66 | // Person { name: 'ice', age: 22, money: 100 } 67 | // ice running!!! 68 | // ice eating!!! 69 | ``` 70 | 71 | ### 6.4 类的实例方法 72 | 73 | ```javascript 74 | class Person { 75 | running() { 76 | console.log('running!!!') 77 | } 78 | eating() { 79 | console.log('eating!!!') 80 | } 81 | } 82 | 83 | const p1 = new Person() 84 | 85 | p1.running() 86 | p1.eating() 87 | 88 | console.log(p1.running === Person.prototype.running) 89 | console.log(p1.eating === Person.prototype.eating) 90 | 91 | // running!!! 92 | // eating!!! 93 | // true 94 | // true 95 | ``` 96 | 97 | - 从打印结果我们可以看出,通过类关键字定义的方法,是存放在Person的原型对象上的,这样的话可以给多个实例进行共享 98 | 99 | ### 6.5 类的静态方法(类方法) 100 | 101 | - 静态方法通常用于定义直接使用类来执行的方法,不需要有类的实例,使用static关键字来定义: 102 | 103 | ```javascript 104 | Function.prototype.money = 100 105 | class Person { 106 | static getMoney() { 107 | console.log(this.money) 108 | } 109 | } 110 | 111 | Person.getMoney() //100 112 | ``` 113 | 114 | - 这里给大家留两个问题 115 | - 第一:静态方法中的this指向谁?(this指向这个类本身,即Class Person{}) 116 | - 第二: Person作为对象身份,它的隐式原型是谁? (指向`Function`的`prototype`) 117 | 118 | ### 6.6 类实现继承 119 | 120 | ```scala 121 | class Person {} 122 | 123 | class Student extends Person {} 124 | ``` 125 | 126 | - 对你没有看错,两行代码就实现了以前ES5以前的一系列繁琐的操作,很容易实现了继承。 127 | - 在ES6中新增了使用extends关键字,可以方便的帮助我们实现继承 128 | - 即 `Sub extend Super` 129 | 130 | ### 6.7 类继承初体验 131 | 132 | ```javascript 133 | class Person { 134 | constructor(name, age) { 135 | this.name = name 136 | this.age = age 137 | } 138 | 139 | running() { 140 | console.log('running!!!') 141 | } 142 | } 143 | 144 | class Student extends Person { 145 | constructor(name, age, sno) { 146 | super(name, age) 147 | this.sno = sno 148 | } 149 | 150 | studying() { 151 | console.log('studying!!!') 152 | } 153 | } 154 | 155 | const s1 = new Student('ice', 22, 200010) 156 | 157 | console.log(s1) 158 | s1.running() 159 | s1.studying() 160 | 161 | // Student { name: 'ice', age: 22, sno: 200010 } 162 | // running!!! 163 | // studying!!! 164 | ``` 165 | 166 | - 我们会发现在上面的代码中我使用了一个super关键字,这个super关键字有不同的使用方式: 167 | - 注意:在子(派生)类的构造函数中使用this或者返回默认对象之前,必须先通过super调用父类的构造函数! 168 | - super的使用位置有三个:子类的构造函数、实例方法、静态方法 169 | 170 | ```js 171 | class Person { 172 | constructor(name, age) { 173 | this.name = name 174 | this.age = age 175 | } 176 | 177 | running() { 178 | console.log('person running!!!') 179 | } 180 | } 181 | 182 | 183 | class Student extends Person { 184 | stuRunning() { 185 | //调用父类的方法 186 | super.running() 187 | } 188 | } 189 | 190 | const s1 = new Student() 191 | s1.stuRunning() 192 | 193 | //person running!!! 194 | ``` 195 | 196 | - 静态方法调用就不演示了,想演练的可以自己尝试一下,都比较简单就是API调用而已。 197 | 198 | ### 6.8 继承内置类 199 | 200 | ```js 201 | class ICEArray extends Array { 202 | lastItem() { 203 | return this[this.length - 1] 204 | } 205 | } 206 | 207 | const arr = new ICEArray(1, 2, 3) 208 | console.log(arr.lastItem()) //3 209 | ``` 210 | 211 | - 继承字内置Array类,调用`lastItem`,每次获取最后一个元素。 212 | 213 | ## 结语 214 | >  今天ES6 Class继承的分享就到这边了,原型、继承系列就分享完啦。期待我们的下次见面~ 215 | 216 | ## 作者 217 | 218 | | Coder | 小红书ID | 创建时间 | 219 | | :---------- | :-------- | :--------- | 220 | | 落寞的前端👣 | 121450513 | 2022.10.09 | 221 | -------------------------------------------------------------------------------- /js面试题三(JS基础).md: -------------------------------------------------------------------------------- 1 | ## 前端常见面试题(JS篇)三 2 | 3 | ## 三、 函数与函数式编程 4 | 5 | ### 3.1 什么是函数式编程 6 | 7 | 函数式编程是一种"编程范式"(programming paradigm),一种编写程序的方法论 8 | 9 | 主要的编程范式有三种:命令式编程,声明式编程和函数式编程 10 | 11 | 相比命令式编程,函数式编程更加强调程序执行的结果而非执行的过程,倡导利用若干简单的执行单元让计算结果不断渐进,逐层推导复杂的运算,而非设计一个复杂的执行过程 12 | 13 | ### 3.2 函数式编程的优缺点 14 | 15 | **优点** 16 | 17 | - 更好的管理状态:因为它的宗旨是无状态,或者说更少的状态,能最大化的减少这些未知、优化代码、减少出错情况 18 | - 更简单的复用:固定输入->固定输出,没有其他外部变量影响,并且无副作用。这样代码复用时,完全不需要考虑它的内部实现和外部影响 19 | - 更优雅的组合:往大的说,网页是由各个组件组成的。往小的说,一个函数也可能是由多个小函数组成的。更强的复用性,带来更强大的组合性 20 | - 隐性好处。减少代码量,提高维护性 21 | 22 | **缺点** 23 | 24 | - 性能:函数式编程相对于指令式编程,性能绝对是一个短板,因为它往往会对一个方法进行过度包装,从而产生上下文切换的性能开销 25 | - 资源占用:在 JS 中为了实现对象状态的不可变,往往会创建新的对象,因此,它对垃圾回收所产生的压力远远超过其他编程方式 26 | - 递归陷阱:在函数式编程中,为了实现迭代,通常会采用递归操作 27 | 28 | ### 3.3 什么是纯函数,它有什么优点 29 | 30 | 纯函数是对给定的输入返还相同输出的函数,并且要求你所有的数据都是不可变的,即纯函数=无状态+数据不可变 31 | 32 | 特性: 33 | 34 | - 函数内部传入指定的值,就会返回确定唯一的值 35 | - 不会造成超出作用域的变化,例如修改全局变量或引用传递的参数 36 | 37 | 优势: 38 | 39 | - 使用纯函数,我们可以产生可测试的代码 40 | - 不依赖外部环境计算,不会产生副作用,提高函数的复用性 41 | - 可读性更强 ,函数不管是否是纯函数 都会有一个语义化的名称,更便于阅读 42 | - 可以组装成复杂任务的可能性。符合模块化概念及单一职责原则 43 | 44 | ### 3.4 什么是组合函数 (compose) 45 | 46 | 在函数式编程中,有一个很重要的概念就是函数组合,实际上就是把处理的函数数据像管道一样连接起来,然后让数据穿过管道连接起来,得到最终的结果。 47 | 48 | 组合函数,其实大致思想就是将 多个函数组合成一个函数,c(b(a(a(1)))) 这种写法简写为 compose(c, b, a, a)(x) 49 | 。但是注意这里如果一个函数都没有传入,那就是传入的是什么就返回什么,并且函数的执行顺序是和传入的顺序相反的。 50 | 51 | ```js 52 | var compose = (...funcs) => { 53 | // funcs(数组):记录的是所有的函数 54 | // 这里其实也是利用了柯里化的思想,函数执行,生成一个闭包,预先把一些信息存储,供下级上下文使用 55 | return (x) => { 56 | var len = funcs.length; 57 | // 如果没有函数执行,直接返回结果 58 | if (len === 0) return x; 59 | if (len === 1) funcs[0](x); 60 | return funcs.reduceRight((res, func) => { 61 | return func(res); 62 | }, x); 63 | }; 64 | }; 65 | var resFn = compose(c, b, a, a); 66 | resFn(1); 67 | ``` 68 | 69 | 组合函数的思想,在很多框架中也被使用,例如:redux,实现效果来说是其实和上面的代码等价。 70 | 71 | ### 3.5 什么是惰性函数 72 | 73 | 惰性载入表示函数执行的分支只会在函数第一次掉用的时候执行,在第一次调用过程中,该函数会被覆盖为另一个按照合适方式执行的函数,这样任何对原函数的调用就不用再经过执行的分支了 74 | 75 | **惰性函数相当于有记忆的功能一样,当它已经判断了一遍的话,第二遍就不会再判断了。** 76 | 77 | 比如现在要求写一个test函数,这个函数返回首次调用时的new Date().getTime(),注意是首次,而且不允许有全局变量的污染 78 | 79 | ```javascript 80 | //一般会这样实现 81 | var test = (function () { 82 | var t = null; 83 | return function () { 84 | if (t) { 85 | return t; 86 | } 87 | t = new Date().getTime(); 88 | return t; 89 | } 90 | })(); 91 | // 用惰性函数实现 92 | var test = function () { 93 | var t = new Date().getTime(); 94 | test = function () { 95 | return t; 96 | } 97 | return test(); 98 | } 99 | console.log(test()); 100 | console.log(test()); 101 | console.log(test()); 102 | ``` 103 | 104 | ### 3.6 什么是高阶函数 105 | 106 | 高阶函数是指使用其他函数作为参数、或者返回一个函数作为结果的函数。 107 | 108 | ### 3.7 说说你对函数柯里化的理解 109 | 110 | 柯里化(Currying)是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数且返回结果的新函数的技术。 111 | 112 | 函数柯里化的好处: 113 | 114 | (1)参数复用:需要输入多个参数,最终只需输入一个,其余通过 arguments 来获取 115 | 116 | (2)提前确认:避免重复去判断某一条件是否符合,不符合则 return 不再继续执行下面的操作 117 | 118 | (3)延迟运行:避免重复的去执行程序,等真正需要结果的时候再执行 119 | 120 | ### 3.8 什么是箭头函数,有什么特征 121 | 122 | 使用 "箭头" ( => ) 来定义函数. 箭头函数相当于匿名函数, 并且简化了函数定义 123 | 124 | **箭头函数的特征:** 125 | 126 | - 箭头函数没有this, this指向定义箭头函数所处的外部环境 127 | - 箭头函数的this永远不会变,call、apply、bind也无法改变 128 | - 箭头函数只能声明成**匿名函数**,但可以通过表达式的方式让箭头函数具名 129 | - 箭头函数没有原型prototype 130 | - 箭头函数不能当做一个构造函数 因为 this 的指向问题 131 | - 箭头函数没有 arguments 在箭头函数内部访问这个变量访问的是外部环境的arguments, 可以使用 ...代替 132 | 133 | ### 3.9 说说你对递归函数的理解 134 | 135 | 如果一个函数在内部调用自身本身,这个函数就是递归函数 136 | 137 | 其核心思想是把一个大型复杂的问题层层转化为一个与原问题相似的规模较小的问题来求解 138 | 139 | 一般来说,递归需要有边界条件、递归前进阶段和递归返回阶段。当边界条件不满足时,递归前进;当边界条件满足时,递归返回 140 | 141 | **优点**:结构清晰、可读性强 142 | 143 | **缺点**:效率低、调用栈可能会溢出,其实每一次函数调用会在内存栈中分配空间,而每个进程的栈的容量是有限的,当调用的层次太多时,就会超出栈的容量,从而导致栈溢出。 144 | 145 | ### 3.10 什么是尾递归 146 | 147 | 尾递归,即在函数尾位置调用自身(或是一个尾调用本身的其他函数等等)。 148 | 149 | 在递归调用的过程当中系统为每一层的返回点、局部量等开辟了栈来存储,递归次数过多容易造成栈溢出 150 | 151 | 这时候,我们就可以使用尾递归,即一个函数中所有递归形式的调用都出现在函数的末尾,对于尾递归来说,由于只存在一个调用记录,所以永远不会发生"栈溢出"错误 152 | 153 | ### 3.11 函数传参,传递复杂数据类型和简单数据类型有什么区别 154 | 155 | 传递复杂数据类型传递的是引用的地址,修改会改变 156 | 157 | 简单数据类型传递的是具体的值,不会相互影响 158 | 159 | ```js 160 | let a = 8 161 | 162 | function fn(a) { 163 | a = 9 164 | } 165 | 166 | fn(a) 167 | console.log(a) // 8 */ 168 | 169 | let a = {age: 8} 170 | 171 | function fn(a) { 172 | a.age = 9 173 | } 174 | 175 | fn(a) 176 | console.log(a.age) // 9 177 | ``` 178 | 179 | ### 3.12 函数声明与函数表达式的区别 180 | 181 | **函数声明:** function开头,有函数提升 182 | 183 | **函数表达式**: 不是function开头,没有函数提升 184 | 185 | ### 3.13 什么是函数缓存,如何实现? 186 | 187 | **概念** 188 | 189 | 函数缓存,就是将函数运算过的结果进行缓存 190 | 191 | 本质上就是用空间(缓存存储)换时间(计算过程) 192 | 193 | 常用于缓存数据计算结果和缓存对象 194 | 195 | **如何实现** 196 | 197 | 实现函数缓存主要依靠闭包、柯里化、高阶函数 198 | 199 | **应用场景** 200 | 201 | - 对于昂贵的函数调用,执行复杂计算的函数 202 | - 对于具有有限且高度重复输入范围的函数 203 | - 对于具有重复输入值的递归函数 204 | - 对于纯函数,即每次使用特定输入调用时返回相同输出的函数 205 | 206 | ### 3.14 call、apply、bind三者的异同 207 | 208 | **共同点** : 209 | 210 | - 都可以改变this指向; 211 | - 三者第一个参数都是`this`要指向的对象,如果如果没有这个参数或参数为`undefined`或`null`,则默认指向全局`window` 212 | 213 | **不同点**: 214 | 215 | - call 和 apply 会调用函数, 并且改变函数内部this指向. 216 | - call 和 apply传递的参数不一样,call传递参数使用逗号隔开,apply使用数组传递,且`apply`和`call`是一次性传入参数,而`bind`可以分为多次传入 217 | - `bind`是返回绑定this之后的函数 218 | 219 | **应用场景** 220 | 221 | 1. call 经常做继承. 222 | 2. apply经常跟数组有关系. 比如借助于数学对象实现数组最大值最小值 223 | 3. bind 不调用函数,但是还想改变this指向. 比如改变定时器内部的this指向 224 | 225 | ## 结语 226 | 227 | >   今天的面试题的分享就到这边啦,明天继续给大家分享~ 228 | 229 | ## 作者 230 | 231 | | Coder | 小红书ID | 创建时间 | 232 | | :---------- | :-------- | :--------- | 233 | | 落寞的前端👣 | 121450513 | 2022.10.13 | 234 | -------------------------------------------------------------------------------- /eventLoop.md: -------------------------------------------------------------------------------- 1 | ## 彻底搞懂浏览器 Event Loop 事件循环 2 | 3 | ## 前言 4 | 5 | >   浏览器的事件循环和Node的事件循环是每个前端工程师必须掌握的知识点,我们平常最多的就是跟浏览器打交道。本文先介绍浏览器的事件循环,笔者从以下几个角度去分析问题从而引出到底什么是事件循环? 6 | 7 | 1. 你们了解操作系统中 什么是进程? 什么是线程? 8 | 2. JS是单线程还是多线程的? 9 | 3. 浏览器是多进程还是单进程? 为什么? 10 | 4. 如果JS是单线程的,它是如何实现异步处理的? 11 | 5. 引出事件循环 12 | 13 | ## 1.什么是进程? 什么是线程? 14 | 15 |   首先给大家推荐一本《现代操作系统》这本书,真正的理解了操作系统的本质,许多开发问题都会豁然开朗。`在操作系统中无论是运行系统中的什么应用,最终都是会编译成二进制交CPU给执行。`
16 |   我们在日常使用电脑中,我们可能会一边听歌、一边写代码、一边写博客。这些应用中都会在操作系统中开启一个进程或多个进程,不同的应用开启的进程是不一样的。为了大家好理解就比喻某个应用中只会开启一个进程,而一个进程当中会开启多个线程。如果只有一个线程的话,我们称之为主线程。`结论:一个进程当中包含多个线程或只有一个主线程。`
17 |   **画了一幅图让大家更加直观的了解进程和线程的关系** 18 | 19 | ![eventLoop_1.jpg](https://raw.githubusercontent.com/summmer-is-hot/picture-store/main/marks/eventLoop_1.webp?)
20 |   在这副图中,最外层的就相当于操作系统,我们在会在操作系统下载很多应用,当我们运行VsCode、网易云音乐、某信等...
21 |   他们都会开启一个进程或多个进程(这个跟应用程序本身有关),一个进程里面又包含了多个线程,如果只有一个线程称之为主线程。
22 |   如果当您看到了这里,还是对进程跟线程不是特别了解,请允许我用一个通俗易懂的例子来描述他们之间的关系。
23 |   `操作系统就跟工厂一样,一个工厂会包含多个车间,其中多个车间就好比是我们的应用程序有多个进程,车间里面有工人在工作,就相当于是线程的概念。`
24 | 25 | ## 2.JS是单线程还是多线程的? 26 | 27 |   `答案:JS是单线程。`如果您深究为什么是单线程的呢?
28 |   其实这是与它的用途有关,因为JS是一门浏览器脚本语言,主要用途是进行用户操作和操作DOM,所以它只能是单线程的,否则会带来很多复杂的同步问题。 29 | 30 | ## 3.浏览器是多进程还是单进程 31 | 32 |   `答案:浏览器是多进程的。`为什么说是多进程的? 你说是就是吗? 凭什么呢?
33 |   当我们浏览网页的时候,有的时候是不是会遇到浏览器卡死的情况。如果我们开了多个会话,就假如我们一边刷力扣,一边开发程序,写循环的时候,写了一个死循环,导致了我们开发的这个会话的崩溃,如果浏览器是单进程的情况下,力扣这个时候也会崩溃。
34 |   当然浏览器肯定不会允许这样的事情发生,它就是多进程的,多个会话互相不影响,你要崩溃你崩溃去,跟我可没关系~~~。 35 | 36 | ## 4.JS实现异步处理 37 | 38 |   首先当您看到了这里,其实你们离成功只有一步之遥了,跟着我继续探讨下去,把Event Loop一点一点捣碎了,喂进你们的嘴里。
39 | 40 |   `我们先来看一段同步的JS代码` 41 | 42 | ```js 43 | const foo = 'foo' 44 | 45 | function bar() { 46 | console.log('bar') 47 | } 48 | 49 | console.log(foo) //foo 50 | bar() //bar 51 | 52 | ``` 53 | 54 |   JS的代码执行顺序是`从上至下`进行的,所以答案如注释所示,我们是毫无疑问的 55 | 56 |   现在玩点花样,来一点同步和异步代码给大家看看 57 | 58 | ```js 59 | const foo = 'foo' 60 | 61 | function bar() { 62 | console.log('bar') 63 | } 64 | 65 | queueMicrotask(() => { 66 | console.log('microtask') 67 | }) 68 | 69 | console.log(foo) 70 | 71 | setTimeout(() => { 72 | console.log('setTimeout') 73 | },1000) 74 | 75 | bar() 76 | 77 | //主线程: foo bar 78 | //微任务队列:microtask 79 | //宏任务队列:setTimeout 80 | //执行顺序: foo bar microtask setTimeout(1s过后) 81 | 82 | ``` 83 | 84 |   现在穿插了异步的代码,如果把上面从上至下运行代码的结论用到异步,肯定是错误的! 正确的执行顺序的答案,大家可以先看一下,可能对异步不太了解的人会产生疑惑,不要慌,我们一点一点来看,引出下面的事件循环。 85 | 86 | ## 5.事件循环 87 | 88 |   首先我们又回到 JS 代码是`单线程`开始讲起,当我们编写的`同步`的代码的时候,代码的执行顺序是从上至下的,但是当有`异步`操作或者一些耗时操作的时候,如果还是按照之前的结论从上至下的话,那么一定会堵塞我们主线程的代码 也就是 main script中的代码,这样在js当中肯定是不被允许的。假如我有耗时操作(setTimeout)或者网络请求(ajax),延迟了5s才执行,那我后面的代码都`堵塞`了。 89 |   用文字的形式,还是有点抽象,我们直接上图,到底是一个怎么样的机制,完美解决这一问题。 90 | 91 | ![eventLoop_2.jpg](https://raw.githubusercontent.com/summmer-is-hot/picture-store/main/marks/eventLoop_2.webp?) 92 |   简单描述一下这幅图:首先js代码先执行主线程的代码,也就是同步的代码,从上至下,遇到异步代码交给浏览器,浏览器专门开了一个线程,其中浏览器线程中维护这两个队列,`一个微任务队列,一个宏任务队列`。 93 | 94 | `宏任务队列 Macrotask Queue: ajax、setTimeout、setInterval、Dom监听等 ` ; 95 | 96 | `微任务队列 Microtask Queue: Promise的then回调、 Mutation Observer API、queueMicrotask `; 97 | 98 | `注意:每一次执行宏任务之前,都是要确保我微任务的队列是空的,也就是说从代码执行的顺序来说微任务优先于宏任务。` 99 | 100 | 但是存在插队的情况,也就是说当我微任务执行完了,要开始执行宏任务了(有多个宏任务),宏任务队列当队列中的代码执行了,宏任务队列里面又有微任务代码,又把微任务放入到微任务队列当中。 101 | 102 | 此时特别注意!!!从严格的意义来说,紧接着是先进行编译的宏任务,但是此时微任务里面有任务才去执行的微任务队列,而不是直接去执行的。这些异步的代码交给js执行,`这样三者形成了一个闭环,我们称之为事件循环`。 103 | 104 | ## 6.常见面试题 105 | 106 | ###  1.面试题一 107 | 108 | 看到下面的代码,直接copy到编辑器当中,跟我来分析一波。学会了什么是事件循环? 109 | 110 | ```js 111 | setTimeout(function () { 112 | console.log("setTimeout1"); 113 | new Promise(function (resolve) { 114 | resolve(); 115 | }).then(function () { 116 | new Promise(function (resolve) { 117 | resolve(); 118 | }).then(function () { 119 | console.log("then4"); 120 | }); 121 | console.log("then2"); 122 | }); 123 | }); 124 | 125 | new Promise(function (resolve) { 126 | console.log("promise1"); 127 | resolve(); 128 | }).then(function () { 129 | console.log("then1"); 130 | }); 131 | 132 | setTimeout(function () { 133 | console.log("setTimeout2"); 134 | }); 135 | 136 | console.log(2); 137 | 138 | queueMicrotask(() => { 139 | console.log("queueMicrotask1"); 140 | }); 141 | 142 | new Promise(function (resolve) { 143 | resolve(); 144 | }).then(function () { 145 | console.log("then3"); 146 | }); 147 | 148 | //promise1 2 then1 queueMicrotask1 then3 setTimeout1 then2 then4 setTimeout2 149 | ``` 150 | 151 |   请耐心观看 下面执行顺序图(你想要的都在图里): 152 | 153 | ![eventLoop_3.jpg](https://raw.githubusercontent.com/summmer-is-hot/picture-store/main/marks/eventLoop_3.webp?) 154 | 155 | ###   2.面试题二 156 | 157 |   我们穿插一道简单的吧,首先您要对async await 有一定的了解,如果您不太了解,下方评论,我会尽快更新的,直接告诉您`结论:async函数当中,await后面的代码相当于promise.then的代码,是一个微任务,但是await所在的函数可是会直接执行的,不能混淆了。` 话不多说,我们直接上代码。 158 | 159 | ```js 160 | async function bar() { 161 | console.log("222222"); 162 | return new Promise((resolve, reject) => { 163 | reject(); 164 | }); 165 | } 166 | 167 | async function foo() { 168 | console.log("111111"); 169 | await bar().catch((err) => {}); 170 | console.log("333333"); 171 | } 172 | foo(); 173 | console.log("444444"); 174 | 175 | //111111 222222 444444 333333 176 | 177 | ``` 178 | 179 |   执行顺序分析图: 180 | 181 | ![eventLoop_4.jpg](https://raw.githubusercontent.com/summmer-is-hot/picture-store/main/marks/eventLoop_4.webp?) 182 | 183 | ###   3.面试题三 184 | 185 |   `这是一道字节的面试题,让我们来给它撕烂吧`,废话少说直接上代码 186 | 187 | ```js 188 | //字节面试题 189 | async function async1() { 190 | console.log("async1 start"); 191 | await async2(); 192 | console.log("async1 end"); 193 | } 194 | 195 | async function async2() { 196 | console.log("async2"); 197 | } 198 | 199 | console.log("script start"); 200 | 201 | setTimeout(function () { 202 | console.log("setTimeout"); 203 | }, 0); 204 | 205 | async1(); 206 | 207 | new Promise(function (resolve) { 208 | console.log("promise1"); 209 | resolve(); 210 | }).then(function () { 211 | console.log("promise2"); 212 | }); 213 | 214 | console.log("script end"); 215 | 216 | /* 217 | script start 218 | async1 start 219 | async2 220 | promise1 221 | script end 222 | async1 end 223 | promise2 224 | setTimeout 225 | */ 226 | 227 | ``` 228 | 229 |   请仔细查看分析图: 230 | 231 | ![eventLoop_5.jpg](https://raw.githubusercontent.com/summmer-is-hot/picture-store/main/marks/eventLoop_5.webp?) 232 | 233 | ## 结语 234 | 235 | 看完本期分享,浏览器的事件循环告一段落了,基本可以手撕90%以上的面试题~ 236 | 237 | ## 作者 238 | 239 | | Coder | 小红书ID | 创建时间 | 240 | | :---------- | :-------- | :--------- | 241 | | 落寞的前端👣 | 121450513 | 2022.10.05 | 242 | -------------------------------------------------------------------------------- /前端手撕代码面试题(二).md: -------------------------------------------------------------------------------- 1 | ## 前端手写代码题(二) 2 | 3 | ## 11. 使用Promise封装AJAX请求 4 | 5 | ```javascript 6 | // promise 封装实现: 7 | function getJSON(url) { 8 | // 创建一个 promise 对象 9 | let promise = new Promise(function (resolve, reject) { 10 | let xhr = new XMLHttpRequest(); 11 | // 新建一个 http 请求 12 | xhr.open("GET", url, true); 13 | // 设置状态的监听函数 14 | xhr.onreadystatechange = function () { 15 | if (this.readyState !== 4) return; 16 | // 当请求成功或失败时,改变 promise 的状态 17 | if (this.status === 200) { 18 | resolve(this.response); 19 | } else { 20 | reject(new Error(this.statusText)); 21 | } 22 | }; 23 | // 设置错误监听函数 24 | xhr.onerror = function () { 25 | reject(new Error(this.statusText)); 26 | }; 27 | // 设置响应的数据类型 28 | xhr.responseType = "json"; 29 | // 设置请求头信息 30 | xhr.setRequestHeader("Accept", "application/json"); 31 | // 发送 http 请求 32 | xhr.send(null); 33 | }); 34 | return promise; 35 | } 36 | 37 | ``` 38 | 39 | ## 12. 手写深拷贝 40 | 41 | ```javascript 42 | function fn(obj) { 43 | // 判断数据是否是复杂类型 44 | if (obj instanceof Object) { 45 | //判断数据是否是数组 46 | if (Array.isArray(obj)) { 47 | //声明一个空数组来接收拷贝后的数据 48 | let result = [] 49 | obj.forEach(item => { 50 | // 需要递归深层遍历,否则复制的是地址 51 | result.push(fn(item)) 52 | }) 53 | // 返回输出这个数组,数组拷贝完成 54 | return result 55 | } else { 56 | //如果是对象,就声明一个空对象来接收拷贝后的数据 57 | let result = {} 58 | for (let k in obj) { 59 | // 使用递归深层遍历 60 | result[k] = fn(obj[k]) 61 | } 62 | // 返回输出这个对象,对象拷贝完成 63 | return result 64 | } 65 | } 66 | // 简单数据类型则直接返回输出 67 | return obj 68 | } 69 | 70 | ``` 71 | 72 | ## 13. 手写打乱数组顺序的方法 73 | 74 | 主要的实现思路就是: 75 | 76 | - 取出数组的第一个元素,随机产生一个索引值,将该第一个元素和这个索引对应的元素进行交换。 77 | - 第二次取出数据数组第二个元素,随机产生一个除了索引为1的之外的索引值,并将第二个元素与该索引值对应的元素进行交换 78 | - 按照上面的规律执行,直到遍历完成 79 | 80 | ```js 81 | let arr = [1,2,3,4,5,6,7,8,9,10]; 82 | for (let i = 0; i < arr.length; i++) { 83 | const randomIndex = Math.round(Math.random() * (arr.length - 1 - i)) + i; 84 | [arr[i], arr[randomIndex]] = [arr[randomIndex], arr[i]]; 85 | } 86 | console.log(arr) 87 | 88 | ``` 89 | 90 | ## 14. 实现数组扁平化 91 | 92 | 通过循环递归的方式,一项一项地去遍历,如果每一项还是一个数组,那么就继续往下遍历,利用递归程序的方法,来实现数组的每一项的连接: 93 | 94 | ```js 95 | let arr = [1, [2, [3, 4, 5]]]; 96 | function flatten(arr) { 97 | let result = []; 98 | 99 | for(let i = 0; i < arr.length; i++) { 100 | if(Array.isArray(arr[i])) { 101 | result = result.concat(flatten(arr[i])); 102 | } else { 103 | result.push(arr[i]); 104 | } 105 | } 106 | return result; 107 | } 108 | flatten(arr); // [1, 2, 3, 4,5] 109 | ``` 110 | 111 | ## 15. 实现数组的flat方法 112 | 113 | ```javascript 114 | function _flat(arr, depth) { 115 | if (!Array.isArray(arr) || depth <= 0) { 116 | return arr; 117 | } 118 | return arr.reduce((prev, cur) => { 119 | if (Array.isArray(cur)) { 120 | return prev.concat(_flat(cur, depth - 1)) 121 | } else { 122 | return prev.concat(cur); 123 | } 124 | }, []); 125 | } 126 | 127 | ``` 128 | 129 | ## 16. 实现数组的push方法 130 | 131 | ```js 132 | let arr = []; 133 | Array.prototype.push = function() { 134 | for( let i = 0 ; i < arguments.length ; i++){ 135 | this[this.length] = arguments[i] ; 136 | } 137 | return this.length; 138 | } 139 | 140 | ``` 141 | 142 | ## 17. 实现数组的filter方法 143 | 144 | ```javascript 145 | Array.prototype._filter = function (fn) { 146 | if (typeof fn !== "function") { 147 | throw Error('参数必须是一个函数'); 148 | } 149 | const res = []; 150 | for (let i = 0, len = this.length; i < len; i++) { 151 | fn(this[i]) && res.push(this[i]); 152 | } 153 | return res; 154 | } 155 | ``` 156 | 157 | ## 18. 实现数组的map方法 158 | 159 | ```javascript 160 | Array.prototype._map = function (fn) { 161 | if (typeof fn !== "function") { 162 | throw Error('参数必须是一个函数'); 163 | } 164 | const res = []; 165 | for (let i = 0, len = this.length; i < len; i++) { 166 | res.push(fn(this[i])); 167 | } 168 | return res; 169 | } 170 | ``` 171 | 172 | ## 19. 实现 add(1)(2)(3)(4) 173 | 174 | 可以实现任意数量数字相加,但是需要用+号隐式转换 175 | 176 | ```javascript 177 | function fn() { 178 | let result = []; 179 | 180 | function add(...args) { 181 | // ...args剩余参数,可以获取到传进来的参数 182 | result = [...result, ...args] 183 | return add; 184 | } 185 | ; 186 | // 创建一个取代 valueOf 方法的函数,覆盖自定义对象的 valueOf 方法 187 | add.toString = () => result.reduce((sum, k) => sum + k, 0); 188 | return add; 189 | }; 190 | let add = fn() 191 | console.log(+add(1)(2)(3)(4)) // --->10 192 | // let add2 = fn(); 193 | console.log(+add2(1, 2, 3)(4)) // --->10 194 | ``` 195 | 196 | 参数固定的情况下,不需要用+号,可以根据参数长度来判断返回值 197 | 198 | ```js 199 | function currying(fn, length) { 200 | length = length || fn.length; // 第一次调用,给length赋值fn的长度,后面每次重复调用,length的长度都会减去参数的长度 201 | return function (...args) { 202 | return args.length >= length // 当前传递进来的参数的长度与length长度进行比较 203 | ? fn.apply(this, args) // 把最后一组实参传给为赋值的形参,此时所有形参都已赋值,并调用fn函数 204 | : currying(fn.bind(this, ...args), length - args.length) 205 | // 每一次调用fn.bind,都会把当前的args里的实参依次传给fn的形参,length的长度减去参数的长度 206 | // 相当于fn.bind(this, 1).bind(this, 2, 3),bind的连续调用,来填充fn的参数 207 | // 直到某一次调用,fn的形参即将全部都被赋值时,条件成立,会执行fn.apply,把最后的参数传递过去,并且调用fn 208 | } 209 | } 210 | 211 | function fn(a, b, c, d) { 212 | return a + b + c + d 213 | } 214 | 215 | const add = currying(fn) 216 | add(4)(3)(1)(2) //10 217 | add(1, 3)(4)(2) //10 218 | add(1)(3, 4, 2) //10 219 | ``` 220 | 221 | ## 20. 用Promise实现图片的异步加载 222 | 223 | ```javascript 224 | let imageAsync = (url) => { 225 | return new Promise((resolve, reject) => { 226 | let img = new Image(); 227 | img.src = url; 228 | img.οnlοad = () => { 229 | console.log(`图片请求成功,此处进行通用操作`); 230 | resolve(image); 231 | } 232 | img.οnerrοr = (err) => { 233 | console.log(`失败,此处进行失败的通用操作`); 234 | reject(err); 235 | } 236 | }) 237 | } 238 | 239 | imageAsync("url").then(() => { 240 | console.log("加载成功"); 241 | }).catch((error) => { 242 | console.log("加载失败"); 243 | }) 244 | ``` 245 | ## 结语 246 | 247 | >   今天的面试题的分享就到这边啦,明天继续给大家分享~ 248 | 249 | ## 作者 250 | 251 | | Coder | 小红书ID | 创建时间 | 252 | | :---------- | :-------- | :--------- | 253 | | 落寞的前端👣 | 121450513 | 2022.10.28 | 254 | -------------------------------------------------------------------------------- /promise2.md: -------------------------------------------------------------------------------- 1 | ## 大多数前端工程师不了解的promise/async await《二》 2 | 3 | ## 3. Promise的回调地狱 (进阶) 4 | 5 | - 我还是以一个需求作为切入点,把知识点嚼碎了,一点一点喂进你们嘴里。 6 | - 当我发送网络请求的时候,需要拿到这次网络请求的数据,再发送网络请求,就这样重复三次,才能拿到我最终的结果。 7 | 8 | ### 3.1 卧龙解法 9 | 10 | ```javascript 11 | function requestData(url) { 12 | return new Promise((resolve, reject) => { 13 | setTimeout(() => { 14 | if (url.includes('iceweb')) { 15 | resolve(url) 16 | } else { 17 | reject('请求错误') 18 | } 19 | }, 1000); 20 | }) 21 | } 22 | 23 | 24 | requestData('iceweb.io').then(res => { 25 | requestData(`iceweb.org ${res}`).then(res => { 26 | requestData(`iceweb.com ${res}`).then(res => { 27 | console.log(res) 28 | }) 29 | }) 30 | }) 31 | //iceweb.com iceweb.org iceweb.io 32 | ``` 33 | 34 | - 虽然能够实现,但是多层代码的嵌套,可读性非常差,我们把这种多层次代码嵌套称之为回调地狱 35 | 36 | ### 3.2 凤雏解法 37 | 38 | ```javascript 39 | function requestData(url) { 40 | return new Promise((resolve, reject) => { 41 | setTimeout(() => { 42 | if (url.includes('iceweb')) { 43 | resolve(url) 44 | } else { 45 | reject('请求错误') 46 | } 47 | }, 1000); 48 | }) 49 | } 50 | 51 | requestData('iceweb.io').then(res => { 52 | return requestData(`iceweb.org ${res}`) 53 | }).then(res => { 54 | return requestData(`iceweb.com ${res}`) 55 | }).then(res => { 56 | console.log(res) 57 | }) 58 | //iceweb.com iceweb.org iceweb.io 59 | ``` 60 | 61 | - 利用了then链式调用这一特性,返回了一个新的promise,但是不够优雅,思考一下能不能写成同步的方式呢? 62 | 63 | ### 3.3 生成器+Promise解法 64 | 65 | ```javascript 66 | function requestData(url) { 67 | return new Promise((resolve, reject) => { 68 | setTimeout(() => { 69 | if (url.includes('iceweb')) { 70 | resolve(url) 71 | } else { 72 | reject('请求错误') 73 | } 74 | }, 1000); 75 | }) 76 | } 77 | 78 | function* getData(url) { 79 | const res1 = yield requestData(url) 80 | const res2 = yield requestData(res1) 81 | const res3 = yield requestData(res2) 82 | 83 | console.log(res3) 84 | } 85 | 86 | const generator = getData('iceweb.io') 87 | 88 | generator.next().value.then(res1 => { 89 | generator.next(`iceweb.org ${res1}`).value.then(res2 => { 90 | generator.next(`iceweb.com ${res2}`).value.then(res3 => { 91 | generator.next(res3) 92 | }) 93 | }) 94 | }) 95 | 96 | //iceweb.com iceweb.org iceweb.io 97 | ``` 98 | 99 | - 大家可以发现我们的`getData`已经变为同步的形式,可以拿到我最终的结果了。那么很多同学会问,generator一直调用`.next`不是也产生了回调地狱吗? 100 | - 其实不用关心这个,我们可以发现它这个是有规律的,我们可以封装成一个自动化执行的函数,我们就不用关心内部是如何调用的了。 101 | 102 | ### 3.4 自动化执行函数封装 103 | 104 | ```javascript 105 | function requestData(url) { 106 | return new Promise((resolve, reject) => { 107 | setTimeout(() => { 108 | if (url.includes('iceweb')) { 109 | resolve(url) 110 | } else { 111 | reject('请求错误') 112 | } 113 | }, 1000); 114 | }) 115 | } 116 | 117 | function* getData() { 118 | const res1 = yield requestData('iceweb.io') 119 | const res2 = yield requestData(`iceweb.org ${res1}`) 120 | const res3 = yield requestData(`iceweb.com ${res2}`) 121 | 122 | console.log(res3) 123 | } 124 | 125 | //自动化执行 async await相当于自动帮我们执行.next 126 | function asyncAutomation(genFn) { 127 | const generator = genFn() 128 | 129 | const _automation = (result) => { 130 | let nextData = generator.next(result) 131 | if(nextData.done) return 132 | 133 | nextData.value.then(res => { 134 | _automation(res) 135 | }) 136 | } 137 | 138 | _automation() 139 | } 140 | 141 | asyncAutomation(getData) 142 | 143 | //iceweb.com iceweb.org iceweb.io 144 | ``` 145 | 146 | - 利用promise+生成器的方式变相实现解决回调地狱问题,其实就是`async await`的一个变种而已 147 | - 最早为**TJ**实现,**前端大神人物** 148 | - async await核心代码就类似这些,内部主动帮我们调用`.next`方法 149 | 150 | ### 3.5 最终解决回调地狱的办法 151 | 152 | ```javascript 153 | function requestData(url) { 154 | return new Promise((resolve, reject) => { 155 | setTimeout(() => { 156 | if (url.includes('iceweb')) { 157 | resolve(url) 158 | } else { 159 | reject('请求错误') 160 | } 161 | }, 1000); 162 | }) 163 | } 164 | 165 | async function getData() { 166 | const res1 = await requestData('iceweb.io') 167 | const res2 = await requestData(`iceweb.org ${res1}`) 168 | const res3 = await requestData(`iceweb.com ${res2}`) 169 | 170 | console.log(res3) 171 | } 172 | 173 | getData() 174 | 175 | //iceweb.com iceweb.org iceweb.io 176 | ``` 177 | 178 | - 你会惊奇的发现,只要把`getData`生成器函数函数,改为`async`函数,`yeild`的关键字替换为`await`就可以实现异步代码同步写法了。 179 | 180 | ## 4. async/await 剖析 181 | 182 | - async(异步的) 183 | - async 用于申明一个异步函数 184 | 185 | ### 4.1 async内部代码同步执行 186 | 187 | - 异步函数的内部代码执行过程和普通的函数是一致的,默认情况下也是会被同步执行 188 | 189 | ```javascript 190 | async function sayHi() { 191 | console.log('hi ice') 192 | } 193 | 194 | sayHi() 195 | 196 | //hi ice 197 | ``` 198 | 199 | ### 4.2 异步函数的返回值 200 | 201 | - 异步函数的返回值和普通返回值有所区别 202 | - 普通函数主动返回什么就返回什么,不返回为`undefined` 203 | - 异步函数的返回值特点 204 | - 明确有返回一个普通值,相当于`Promise.resolve`(返回值) 205 | - 返回一个thenble对象则由,then方法中的`resolve`,或者`reject`有关 206 | - 明确返回一个promise,则由这个promise决定 207 | - 异步函数中可以使用`await`关键字,现在在全局也可以进行`await`,但是不推荐。会阻塞主进程的代码执行 208 | 209 | ### 4.3 异步函数的异常处理 210 | 211 | - 如果函数内部中途发生错误,可以通过try catch的方式捕获异常 212 | - 如果函数内部中途发生错误,也可以通过函数的返回值.catch进行捕获 213 | 214 | ```js 215 | async function sayHi() { 216 | console.log(res) 217 | } 218 | sayHi().catch(e => console.log(e)) 219 | 220 | //或者 221 | 222 | async function sayHi() { 223 | try { 224 | console.log(res) 225 | }catch(e) { 226 | console.log(e) 227 | } 228 | } 229 | 230 | sayHi() 231 | 232 | //ReferenceError: res is not defined 233 | ``` 234 | 235 | ### 4.4 await 关键字 236 | 237 | - 异步函数中可以使用`await`关键字,普通函数不行 238 | - await特点 239 | - 通常await关键字后面都是跟一个Promise 240 | - 可以是普通值 241 | - 可以是thenble 242 | - 可以是Promise主动调用`resolve或者reject` 243 | - 这个promise状态变为fulfilled才会执行`await`后续的代码,所以`await`后面的代码,相当于包括在`.then`方法的回调中,如果状态变为rejected,你则需要在函数内部`try catch`,或者进行链式调用进行`.catch`操作 244 | 245 | ```javascript 246 | function requestData(url) { 247 | return new Promise((resolve, reject) => { 248 | setTimeout(() => { 249 | if (url.includes('iceweb')) { 250 | resolve(url) 251 | } else { 252 | reject('请求错误') 253 | } 254 | }, 1000); 255 | }) 256 | } 257 | 258 | async function getData() { 259 | const res = await requestData('iceweb.io') 260 | console.log(res) 261 | } 262 | 263 | getData() 264 | // iceweb.io 265 | ``` 266 | 267 | ## 5. 结语 268 | 269 | - 在如今互联网寒冬下,如果现在真的看不到未来是怎样,你就不如一直往前走,不知道什么时候天亮,去奔跑就好,跑着跑着天就亮了。 270 | 271 | # 作者 272 | 273 | | Coder | 小红书ID | 创建时间 | 274 | | :---------- | :-------- | :--------- | 275 | | 落寞的前端👣 | 121450513 | 2022.10.01 | 276 | 277 | -------------------------------------------------------------------------------- /js面试题二(JS基础).md: -------------------------------------------------------------------------------- 1 | ## 前端常见面试题(JS篇)二 2 | 3 | ## 二、闭包与作用域 4 | 5 | ### 2.1 什么是闭包? 6 | 7 | - ✅ 官方说法:闭包就是指有权访问另一个函数作用域中的变量的函数。 8 | - ✅ MDN说法:闭包是一种特殊的对象。它由两部分构成:函数,以及创建该函数的环境。环境由闭包创建时在作用域中的任何局部变量组成。 9 | 10 | **深度回答** 11 | 12 | 浏览器在加载页面会把代码放在栈内存( ECStack )中执行,函数进栈执行会产生一个私有上下文( EC ),此上下文能保护里面的使用变量( AO 13 | )不受外界干扰,并且如果当前执行上下文中的某些内容,被上下文以外的内容占用,当前上下文不会出栈释放,这样可以保存里面的变量和变量值,所以我认为闭包是一种保存和保护内部私有变量的机制。 14 | 15 | ### 2.2 闭包的作用 16 | 17 | 闭包有两个常用的用途; 18 | 19 | - 闭包的第一个用途是使我们在函数外部能够访问到函数内部的变量。通过使用闭包,可以通过在外部调用闭包函数,从而在外部访问到函数内部的变量,可以使用这种方法来**创建私有变量**。 20 | - 闭包的另一个用途是使已经运行结束的函数上下文中的**变量对象继续留在内存中**,因为闭包函数保留了这个变量对象的引用,所以这个变量对象不会被回收。 21 | 22 | ### 2.3 闭包在项目中的引用场景,以及带来的问题 23 | 24 | 在实际的项目中,会基于闭包把自己编写的模块内容包裹起来,这样编写就可以保护自己的代码是私有的,防止和全局变量或者是其他的代码冲突,这一点是利用保护机制。 25 | 26 | 但是不建议过多的使用闭包,因为使用不被释放的上下文,是占用栈内存空间的,过多的使用会导致导致内存泄漏。 27 | 28 | 解决闭包带来的内存泄漏问题的方法是:使用完闭包函数后手动释放。 29 | 30 | ### 2.4 闭包的使用场景 31 | 32 | 1. `return` 回一个函数 33 | 2. 函数作为参数 34 | 3. IIFE(自执行函数) 35 | 4. 循环赋值 36 | 5. 使用回调函数就是在使用闭包 37 | 6. 节流防抖 38 | 7. 函数柯里化 39 | 40 | ### 2.5 闭包的执行过程 41 | 42 | 1. 形成私有上下文 43 | 44 | 2. 进栈执行 45 | 46 | 3. 一系列操作 47 | 48 | (1). 初始化作用域链(两头<当前作用域,上级作用域>) 49 | 50 | (2). 初始化this 51 | 52 | (3). 初始化arguments 53 | 54 | (4). 赋值形参 55 | 56 | (5). 变量提升 57 | 58 | (6). 代码执行 59 | 60 | - 遇到变量就先看是否是自己私有的,不是自己私有的按照作用域链上查找,如果不是上级的就继续线上查找,,直到 EC(G),变量的查找其实就是一个作用域链的拼接过程,拼接查询的链式就是作用域链。 61 | 62 | 4. 正常情况下,代码执行完成之后,私有上下文出栈被回收。但是遇到特殊情况,如果当前私有上下文执行完成之后中的某个东西被执行上下文以外的东西占用,则当前私有上下文就不会出栈释放,也就是形成了不被销毁的上下文,闭包。 63 | 64 | ### 2.6 执行上下文的类型 65 | 66 | **(1)全局执行上下文** 67 | 68 | 任何不在函数内部的都是全局执行上下文,它首先会创建一个全局的window对象,并且设置this的值等于这个全局对象,一个程序中只有一个全局执行上下文。 69 | 70 | **(2)函数执行上下文** 71 | 72 | 当一个函数被调用时,就会为该函数创建一个新的执行上下文,函数的上下文可以有任意多个。 73 | 74 | **(3)** `eval`**函数执行上下文** 75 | 76 | 执行在eval函数中的代码会有属于他自己的执行上下文,不过eval函数不常使用,不做介绍。 77 | 78 | ### 2.7 执行上下文栈是什么 79 | 80 | - JavaScript引擎使用执行上下文栈来管理执行上下文 81 | - 当JavaScript执行代码时,首先遇到全局代码,会创建一个全局执行上下文并且压入执行栈中,每当遇到一个函数调用,就会为该函数创建一个新的执行上下文并压入栈顶,引擎会执行位于执行上下文栈顶的函数,当函数执行完成之后,执行上下文从栈中弹出,继续执行下一个上下文。当所有的代码都执行完毕之后,从栈中弹出全局执行上下文。 82 | 83 | ### 2.8 执行上下文的三个阶段 84 | 85 | **创建阶段 → 执行阶段 → 回收阶段** 86 | 87 | ------ 88 | 89 | **创建阶段** 90 | 91 | (1)this绑定 92 | 93 | - 在全局执行上下文中,this指向全局对象(window对象) 94 | - 在函数执行上下文中,this指向取决于函数如何调用。如果它被一个引用对象调用,那么 this 会被设置成那个对象,否则 this 的值被设置为全局对象或者 undefined 95 | 96 | (2)创建词法环境组件 97 | 98 | - 词法环境是一种有**标识符——变量映射**的数据结构,标识符是指变量/函数名,变量是对实际对象或原始数据的引用。 99 | - 词法环境的内部有两个组件:**加粗样式**:环境记录器:用来储存变量个函数声明的实际位置**外部环境的引用**:可以访问父级作用域 100 | 101 | (3)创建变量环境组件 102 | 103 | - 变量环境也是一个词法环境,其环境记录器持有变量声明语句在执行上下文中创建的绑定关系。 104 | 105 | **执行阶段** 106 | 107 | 在这阶段,执行变量赋值、代码执行 108 | 109 | 如果 `Javascript` 引擎在源代码中声明的实际位置找不到变量的值,那么将为其分配 `undefined` 值 110 | 111 | **回收阶段** 112 | 113 | 执行上下文出栈等待虚拟机回收执行上下文 114 | 115 | ### 2.9 谈谈你对作用域的理解 116 | 117 | - 作用域可以视为一套规则,这套规则用来管理引擎如何在当前作用域以及嵌套的子作用域根据标识符名称进行变量查找。 118 | - 简单来说作用域就是变量的有效范围。在一定的空间里可以对变量数据进行读写操作,这个空间就是变量的作用域。 119 | 120 | #### (1)全局作用域 121 | 122 | - 直接写在script标签的JS代码,都在全局作用域。在全局作用域下声明的变量叫做全局变量(在块级外部定义的变量)。 123 | 124 | - 全局变量在全局的任何位置下都可以使用;全局作用域中无法访问到局部作用域的中的变量。 125 | 126 | - 全局作用域在页面打开的时候创建,在页面关闭时销毁。 127 | 128 | - **所有 window 对象的属性拥有全局作用域** 129 | 130 | *var和function命令声明的全局变量和函数是window对象的属性和方法* 131 | 132 | let命令、const命令、class命令声明的全局变量,不属于window对象的属性 133 | 134 | #### (2)函数作用域(局部作用域) 135 | 136 | - 调用函数时会创建函数作用域,函数执行完毕以后,作用域销毁。每调用一次函数就会创建一个新的函数作用域,他们之间是相互独立的。 137 | - 在函数作用域中可以访问全局变量,在函数的外面无法访问函数内的变量。 138 | - 当在函数作用域操作一个变量时,它会先在自身作用域中寻找,如果有就直接使用,如果没有就向上一作用域中寻找,直到找到全局作用域,如果全局作用域中仍然没有找到,则会报错。 139 | 140 | #### (3)块级作用域 141 | 142 | - ES6之前JavaScript采用的是函数作用域+词法作用域,ES6引入了块级作用域。 143 | - 任何一对花括号{}中的语句集都属于一个块,**在块中使用let和const声明的变量**,外部是访问不到的,这种作用域的规则就叫块级作用域。 144 | - 通过var声明的变量或者非严格模式下创建的函数声明没有块级作用域。 145 | 146 | #### (4)词法作用域 147 | 148 | - 词法作用域是静态的作用域,无论函数在哪里被调用,也无论它如何被调用,它的词法作用域都只由**函数被声明时所处的位置**决定。 149 | - 编译的词法分析阶段基本能够知道全部标识符在哪里以及是如何声明的,从而能够预测在执行过中如何对它们进行查找。 150 | - 换句话说,词法作用域就是你在写代码的时候就已经决定了变量的作用域。 151 | 152 | ### 2.10 什么是作用域链 153 | 154 | 当在`js`中使用一个变量的时候,首先`js`引擎会尝试在当前作用域下去寻找该变量,如果没找到,再到它的上层作用域寻找,以此类推直到找到该变量或是已经到了全局作用域,这样的变量作用域访问的链式结构, 被称之为作用域链 155 | 156 | **深度回答** 157 | 158 | 作用域链的本质上是一个指向变量对象的指针列表。变量对象是一个包含了执行环境中所有变量和函数的对象。作用域链的前端始终都是当前执行上下文的变量对象。全局执行上下文的变量对象(也就是全局对象)始终是作用域链的最后一个对象。 159 | 160 | ### 2.11 作用域链的作用 161 | 162 | 作用域链的作用是**保证对执行环境有权访问的所有变量和函数的有序访问,通过作用域链,可以访问到外层环境的变量和函数。** 163 | 164 | ### 2.12 作用域的常见应用场景 165 | 166 | 作用域的一个常见运用场景之一,就是 **模块化**。 167 | 168 | 由于 javascript 并未原生支持模块化导致了很多令人口吐芬芳的问题,比如全局作用域污染和变量名冲突,代码结构臃肿且复用性不高。在正式的模块化方案出台之前,开发者为了解决这类问题,想到了使用函数作用域来创建模块的方案。 169 | 170 | ### 2.13 说说Js中的预解析? 171 | 172 | JS 引擎在运行一份代码的时候,会按照下面的步骤进行工作: 173 | 174 | 1.把变量的声明提升到当前作用域的最前面,只会提升声明,不会提升赋值 175 | 176 | 2.把函数的声明提升到当前作用域的最前面,只会提升声明,不会提升调用 177 | 178 | 3.先提升 function,在提升 var 179 | 180 | ### 2.14 变量提升与函数提升的区别? 181 | 182 | **变量提升** 183 | 184 | 简单说就是在 JavaScript 代码执行前引擎会先进行预编译,预编译期间会将`变量声明与函数声明`提升至其`对应作用域的最顶端`,`函数内声明的变量`只会提升至`该函数作用域最顶层`,`当函数内部定义的一个变量与外部相同时` 185 | ,那么`函数体内的这个变量就会被上升到最顶端`。 186 | 187 | **函数提升** 188 | 189 | 函数提升只会提升函数声明式写法,函数表达式的写法不存在函数提升 190 | 191 | 函数提升的优先级大于变量提升的优先级,即函数提升在变量提升之上 192 | 193 | ### 2.15 如何延长作用域链? 194 | 195 | 作用域链是可以延长的。 196 | 197 | 延长作用域链: 执行环境的类型只有两种,全局和局部(函数)。但是有些语句可以在作用域链的前端临时增加一个变量对象,该变量对象会在代码执行后被移除。 198 | 199 | 具体来说就是执行这两个语句时,作用域链都会得到加强 200 | 201 | 1. try - catch 语句的 catch 块:会创建一个新的变量对象,包含的是被抛出的错误对 象的声明。 202 | 2. with 语句:with 语句会将指定的对象添加到作用域链中。 203 | 204 | ### 2.16 浏览器的垃圾回收机制 205 | 206 | #### (1)内存的生命周期 207 | 208 | JS 环境中分配的内存, 一般有如下生命周期: 209 | 210 | 1. 内存分配:当我们声明变量、函数、对象的时候,系统会自动为他们分配内存 211 | 212 | 2. 内存使用:即读写内存,也就是使用变量、函数等 213 | 214 | 3. 内存回收:使用完毕,由垃圾回收自动回收不再使用的内存 215 | 216 | 全局变量一般不会回收, 一般局部变量的的值, 不用了, 会被自动回收掉 217 | 218 | #### (2)垃圾回收的概念 219 | 220 | **垃圾回收**:JavaScript代码运行时,需要分配内存空间来储存变量和值。当变量不在参与运行时,就需要系统收回被占用的内存空间,这就是垃圾回收。 221 | 222 | **回收机制**: 223 | 224 | - Javascript 具有自动垃圾回收机制,会定期对那些不再使用的变量、对象所占用的内存进行释放,原理就是找到不再使用的变量,然后释放掉其占用的内存。 225 | - JavaScript中存在两种变量:局部变量和全局变量。全局变量的生命周期会持续要页面卸载;而局部变量声明在函数中,它的生命周期从函数执行开始,直到函数执行结束,在这个过程中,局部变量会在堆或栈中存储它们的值,当函数执行结束后,这些局部变量不再被使用,它们所占有的空间就会被释放。 226 | 227 | - 不过,当局部变量被外部函数使用时,其中一种情况就是闭包,在函数执行结束后,函数外部的变量依然指向函数内部的局部变量,此时局部变量依然在被使用,所以不会回收。 228 | 229 | #### (3)垃圾回收的方式 230 | 231 | **1.引用计数法** 232 | 233 | - 这个用的相对较少,IE采用的引用计数算法。引用计数就是跟踪记录每个值被引用的次数。当声明了一个变量并将一个引用类型赋值给该变量时,则这个值的引用次数就是1。相反,如果包含对这个值引用的变量又取得了另外一个值,则这个值的引用次数就减1。当这个引用次数变为0时,说明这个变量已经没有价值,因此,在在机回收期下次再运行时,这个变量所占有的内存空间就会被释放出来。 234 | 235 | - 这种方法会引起**循环引用**的问题:例如:`obj1`和`obj2`通过属性进行相互引用,两个对象的引用次数都是2。当使用循环计数时,由于函数执行完后,两个对象都离开作用域,函数执行结束,`obj1`和`obj2` 236 | 还将会继续存在,因此它们的引用次数永远不会是0,就会引起循环引用。 237 | 238 | **2.标记清除法** 239 | 240 | 现代的浏览器已经不再使用引用计数算法了。 241 | 242 | 现代浏览器通用的大多是基于标记清除算法的某些改进算法,总体思想都是一致的。 243 | 244 | - 标记清除是浏览器常见的垃圾回收方式,当变量进入执行环境时,就标记这个变量“进入环境”,被标记为“进入环境”的变量是不能被回收的,因为他们正在被使用。当变量离开环境时,就会被标记为“离开环境”,被标记为“离开环境”的变量会被内存释放。 245 | 246 | - 垃圾收集器在运行的时候会给存储在内存中的所有变量都加上标记。然后,它会去掉环境中的变量以及被环境中的变量引用的标记。而在此之后再被加上标记的变量将被视为准备删除的变量,原因是环境中的变量已经无法访问到这些变量了。最后。垃圾收集器完成内存清除工作,销毁那些带标记的值,并回收他们所占用的内存空间。 247 | 248 | #### (4)如何减少垃圾回收 249 | 250 | 虽然浏览器可以进行垃圾自动回收,但是当代码比较复杂时,垃圾回收所带来的代价比较大,所以应该尽量减少垃圾回收。 251 | 252 | - **对数组进行优化:** 在清空一个数组时,最简单的方法就是给其赋值为[ ],但是与此同时会创建一个新的空对象,可以将数组的长度设置为0,以此来达到清空数组的目的。 253 | - **对**`object`**进行优化:** 对象尽量复用,对于不再使用的对象,就将其设置为null,尽快被回收。 254 | - **对函数进行优化:** 在循环中的函数表达式,如果可以复用,尽量放在函数的外面。 255 | 256 | #### (5)内存泄漏是什么 257 | 258 | 是指由于疏忽或错误造成程序未能释放已经不再使用的内存 259 | 260 | #### (6)哪些情况会导致内存泄漏 261 | 262 | 以下四种情况会造成内存的泄漏: 263 | 264 | - **意外的全局变量:** 由于使用未声明的变量,而意外的创建了一个全局变量,而使这个变量一直留在内存中无法被回收。 265 | - **被遗忘的计时器或回调函数:** 设置了 setInterval 定时器,而忘记取消它,如果循环函数有对外部变量的引用的话,那么这个变量会被一直留在内存中,而无法被回收。 266 | - **脱离 DOM 的引用:** 获取一个 DOM 元素的引用,而后面这个元素被删除,由于一直保留了对这个元素的引用,所以它也无法被回收。 267 | - **闭包:** 不合理的使用闭包,从而导致某些变量一直被留在内存当中。 268 | 269 | ## 结语 270 | 271 | >   今天的面试题的分享就到这边啦,明天继续给大家分享~ 272 | 273 | ## 作者 274 | 275 | | Coder | 小红书ID | 创建时间 | 276 | | :---------- | :-------- | :--------- | 277 | | 落寞的前端👣 | 121450513 | 2022.10.12 | 278 | -------------------------------------------------------------------------------- /前端手撕代码面试题(三).md: -------------------------------------------------------------------------------- 1 | ## 前端手写代码题(三) 2 | 3 | ## 21. 手写发布-订阅模式 4 | 5 | ```typescript 6 | class EventCenter { 7 | // 1. 定义事件容器,用来装事件数组 8 | let 9 | handlers = {} 10 | 11 | // 2. 添加事件方法,参数:事件名 事件方法 12 | addEventListener(type, handler) { 13 | // 创建新数组容器 14 | if (!this.handlers[type]) { 15 | this.handlers[type] = [] 16 | } 17 | // 存入事件 18 | this.handlers[type].push(handler) 19 | } 20 | 21 | // 3. 触发事件,参数:事件名 事件参数 22 | dispatchEvent(type, params) { 23 | // 若没有注册该事件则抛出错误 24 | if (!this.handlers[type]) { 25 | return new Error('该事件未注册') 26 | } 27 | // 触发事件 28 | this.handlers[type].forEach(handler => { 29 | handler(...params) 30 | }) 31 | } 32 | 33 | // 4. 事件移除,参数:事件名 要删除事件,若无第二个参数则删除该事件的订阅和发布 34 | removeEventListener(type, handler) { 35 | if (!this.handlers[type]) { 36 | return new Error('事件无效') 37 | } 38 | if (!handler) { 39 | // 移除事件 40 | delete this.handlers[type] 41 | } else { 42 | const index = this.handlers[type].findIndex(el => el === handler) 43 | if (index === -1) { 44 | return new Error('无该绑定事件') 45 | } 46 | // 移除事件 47 | this.handlers[type].splice(index, 1) 48 | if (this.handlers[type].length === 0) { 49 | delete this.handlers[type] 50 | } 51 | } 52 | } 53 | } 54 | 55 | ``` 56 | 57 | ## 22. Object.defineProperty(简易版) 58 | 59 | ```javascript 60 | // Vue2的响应式原理,结合了Object.defineProperty的数据劫持,以及发布订阅者模式 61 | // Vue2的数据劫持,就是通过递归遍历data里的数据,用Object.defineProperty给每一个属性添加getter和setter, 62 | // 并且把data里的属性挂载到vue实例中,修改vue实例上的属性时,就会触发对应的setter函数,向Dep订阅器发布更新消息, 63 | // 对应的Watcher订阅者会收到通知,调用自身的回调函数,让编译器去更新视图。 64 | const obj = { 65 | name: '刘逍', 66 | age: 20 67 | } 68 | const p = {} 69 | for (let key in obj) { 70 | Object.defineProperty(p, key, { 71 | get() { 72 | console.log(`有人读取p里的${key}属性`); 73 | return obj[key] 74 | }, 75 | set(val) { 76 | console.log(`有人修改了p里的${key}属性,值为${val},需要去更新视图`); 77 | obj[key] = val 78 | } 79 | }) 80 | } 81 | ``` 82 | 83 | ## 23. Proxy数据劫持(简易版) 84 | 85 | ```javascript 86 | // Vue3的数据劫持通过Proxy函数对代理对象的属性进行劫持,通过Reflect对象里的方法对代理对象的属性进行修改, 87 | // Proxy代理对象不需要遍历,配置项里的回调函数可以通过参数拿到修改属性的键和值 88 | // 这里用到了Reflect对象里的三个方法,get,set和deleteProperty,方法需要的参数与配置项中回调函数的参数相同。 89 | // Reflect里的方法与Proxy里的方法是一一对应的,只要是Proxy对象的方法,就能在Reflect对象上找到对应的方法。 90 | const obj = { 91 | name: '刘逍', 92 | age: 20 93 | } 94 | const p = new Proxy(obj, { 95 | // 读取属性的时候会调用getter 96 | get(target, propName) { //第一个参数为代理的源对象,等同于上面的Obj参数。第二个参数为读取的那个属性值 97 | console.log(`有人读取p对象里的${propName}属性`); 98 | return Reflect.get(target, propName) 99 | }, 100 | // 添加和修改属性的时候会调用setter 101 | set(target, propName, value) { //参数等同于get,第三个参数为修改后的属性值 102 | console.log(`有人修改了p对象里的${propName}属性,值为${value},需要去修改视图`); 103 | Reflect.set(target, propName, value) 104 | }, 105 | // 删除属性时,调用deleteProperty 106 | deleteProperty(target, propName) { // 参数等同于get 107 | console.log(`有人删除了p对象里的${propName}属性,需要去修改视图`); 108 | return Reflect.deleteProperty(target, propName) 109 | } 110 | }) 111 | ``` 112 | 113 | ## 24. 实现路由(简易版) 114 | 115 | ```js 116 | // hash路由 117 | class Route { 118 | constructor() { 119 | // 路由存储对象 120 | this.routes = {} 121 | // 当前hash 122 | this.currentHash = '' 123 | // 绑定this,避免监听时this指向改变 124 | this.freshRoute = this.freshRoute.bind(this) 125 | // 监听 126 | window.addEventListener('load', this.freshRoute, false) 127 | window.addEventListener('hashchange', this.freshRoute, false) 128 | } 129 | 130 | // 存储 131 | storeRoute(path, cb) { 132 | this.routes[path] = cb || function () { 133 | } 134 | } 135 | 136 | // 更新 137 | freshRoute() { 138 | this.currentHash = location.hash.slice(1) || '/' 139 | this.routes[this.currentHash]() 140 | } 141 | } 142 | ``` 143 | 144 | ## 25. 使用 setTimeout 实现 setInterval 145 | 146 | 实现思路是使用递归函数,不断地去执行 setTimeout 从而达到 setInterval 的效果 147 | 148 | ```js 149 | function mySetInterval(fn, timeout) { 150 | // 控制器,控制定时器是否继续执行 151 | var timer = {flag: true}; 152 | 153 | // 设置递归函数,模拟定时器执行。 154 | function interval() { 155 | if (timer.flag) { 156 | fn(); 157 | setTimeout(interval, timeout); 158 | } 159 | } 160 | 161 | // 启动定时器 162 | setTimeout(interval, timeout);// 返回控制器 163 | return timer; 164 | } 165 | ``` 166 | 167 | ## 26. 使用setInterval实现setTimeout 168 | 169 | ```js 170 | function mySetInterval(fn, t) { 171 | const timer = setInterval(() => { 172 | clearInterval(timer) 173 | fn() 174 | }, t) 175 | } 176 | 177 | mySetInterval(() => { 178 | console.log('hoho'); 179 | }, 1000) 180 | ``` 181 | 182 | ## 27. 实现 jsonp 183 | 184 | ```js 185 | // 动态的加载js文件 186 | function addScript(src) { 187 | const script = document.createElement('script'); 188 | script.src = src; 189 | script.type = "text/javascript"; 190 | document.body.appendChild(script); 191 | } 192 | 193 | addScript("http://xxx.xxx.com/xxx.js?callback=handleRes"); 194 | 195 | // 设置一个全局的callback函数来接收回调结果 196 | function handleRes(res) { 197 | console.log(res); 198 | } 199 | 200 | // 接口返回的数据格式 201 | handleRes({a: 1, b: 2}); 202 | 203 | ``` 204 | 205 | ## 28. 提取出url 里的参数并转成对象 206 | 207 | ```javascript 208 | function getUrlParams(url) { 209 | let reg = /([^?&=]+)=([^?&=]+)/g 210 | let obj = {} 211 | url.replace(reg, function () { 212 | obj[arguments[1]] = arguments[2] 213 | }) 214 | // 或者 215 | const search = window.location.search 216 | search.replace(/([^&=?]+)=([^&]+)/g, (m, $1, $2) => { 217 | obj[$1] = decodeURIComponent($2) 218 | }) 219 | 220 | return obj 221 | } 222 | 223 | let url = 'https://www.junjin.cn?a=1&b=2' 224 | console.log(getUrlParams(url)) // { a: 1, b: 2 } 225 | ``` 226 | 227 | ## 29. 请写至少三种数组去重的方法?(原生js) 228 | 229 | ```javascript 230 | //利用filter 231 | function unique(arr) { 232 | return arr.filter(function (item, index, arr) { 233 | //当前元素,在原始数组中的第一个索引==当前索引值,否则返回当前元素 234 | return arr.indexOf(item, 0) === index; 235 | }); 236 | } 237 | 238 | var arr = [1, 1, 'true', 'true', true, true, 15, 15, false, false, undefined, undefined, null, null, NaN, NaN, 'NaN', 0, 0, 'a', 'a', {}, {}]; 239 | console.log(unique(arr)) 240 | 241 | //利用ES6 Set去重(ES6中最常用) 242 | function unique(arr) { 243 | return Array.from(new Set(arr)) 244 | } 245 | 246 | var arr = [1, 1, 'true', 'true', true, true, 15, 15, false, false, undefined, undefined, null, null, NaN, NaN, 'NaN', 0, 0, 'a', 'a', {}, {}]; 247 | console.log(unique(arr)) 248 | //[1, "true", true, 15, false, undefined, null, NaN, "NaN", 0, "a", {}, {}] 249 | 250 | //利用for嵌套for,然后splice去重(ES5中最常用) 251 | function unique(arr) { 252 | for (var i = 0; i < arr.length; i++) { 253 | for (var j = i + 1; j < arr.length; j++) { 254 | if (arr[i] == arr[j]) { //第一个等同于第二个,splice方法删除第二个 255 | arr.splice(j, 1); 256 | j--; 257 | } 258 | } 259 | } 260 | return arr; 261 | } 262 | 263 | var arr = [1, 1, 'true', 'true', true, true, 15, 15, false, false, undefined, undefined, null, null, NaN, NaN, 'NaN', 0, 0, 'a', 'a', {}, {}]; 264 | console.log(unique(arr)) 265 | //[1, "true", 15, false, undefined, NaN, NaN, "NaN", "a", {…}, {…}] 266 | //NaN和{}没有去重,两个null直接消失了 267 | ``` 268 | 269 | ## 结语 270 | 271 | >   今天的面试题的分享就到这边啦,明天继续给大家分享~ 272 | 273 | ## 作者 274 | 275 | | Coder | 小红书ID | 创建时间 | 276 | | :---------- | :-------- | :--------- | 277 | | 落寞的前端👣 | 121450513 | 2022.10.29 | 278 | 279 | -------------------------------------------------------------------------------- /前端手撕代码面试题(四).md: -------------------------------------------------------------------------------- 1 | ## 前端手写代码题(四) 2 | 3 | ## 算法基础 4 | 5 | ## 1. 时间&空间复杂度 6 | 7 | - 复杂度是数量级(方便记忆、推广),不是具体数字。 8 | - 常见复杂度大小比较:O(n^2) > O(nlogn) > O(n) > O(logn) > O(1) 9 | 10 | ### 1.1 时间复杂度 11 | 12 | 常见时间复杂度对应关系: 13 | 14 | - O(n^2):2层循环(嵌套循环) 15 | - O(nlogn):快速排序(循环 + 二分) 16 | - O(n):1层循环 17 | - O(logn):二分 18 | 19 | ### 1.2 空间复杂度 20 | 21 | 常见空间复杂度对应关系: 22 | 23 | - O(n):传入一个数组,处理过程生成一个新的数组大小与传入数组一致 24 | 25 | ## 2. 八大数据结构 26 | 27 | ### 1. 栈 28 | 29 | `栈`是一个`后进先出`的数据结构。`JavaScript`中没有`栈`,但是可以用`Array`实现`栈`的所有功能。 30 | 31 | ```js 32 | // 数组实现栈数据结构 33 | const stack = [] 34 | 35 | // 入栈 36 | stack.push(0) 37 | stack.push(1) 38 | stack.push(2) 39 | 40 | // 出栈 41 | const popVal = stack.pop() // popVal 为 2 42 | ``` 43 | 44 | **使用场景** 45 | 46 | - 场景一:十进制转二进制 47 | - 场景二:有效括号 48 | - 场景三:函数调用堆栈 49 | 50 | ### 2. 队列 51 | 52 | `队列`是一个`先进先出`的数据结构。`JavaScript`中没有`队列`,但是可以用`Array`实现`队列`的所有功能。 53 | 54 | ```js 55 | // 数组实现队列数据结构 56 | const queue = [] 57 | 58 | // 入队 59 | stack.push(0) 60 | stack.push(1) 61 | stack.push(2) 62 | 63 | // 出队 64 | const shiftVal = stack.shift() // shiftVal 为 0 65 | 66 | ``` 67 | 68 | **使用场景** 69 | 70 | - 场景一:日常测核酸排队 71 | - 场景二:JS异步中的任务队列 72 | - 场景三:计算最近请求次数 73 | 74 | ### 3. 链表 75 | 76 | `链表`是多个元素组成的列表,元素存储不连续,用`next`指针连在一起。`JavaScript`中没有`链表`,但是可以用`Object`模拟`链表`。 77 | 78 | **使用场景** 79 | 80 | - 场景一:JS中的原型链 81 | - 场景二:使用链表指针获取 JSON 的节点值 82 | 83 | ### 4. 集合 84 | 85 | `集合`是一个`无序且唯一`的数据结构。`ES6`中有集合:`Set`,集合常用操作:去重、判断某元素是否在集合中、求交集。 86 | 87 | ```js 88 | // 去重 89 | const arr = [1, 1, 2, 2] 90 | const arr2 = [...new Set(arr)] 91 | 92 | // 判断元素是否在集合中 93 | const set = new Set(arr) 94 | const has = set.has(3) // false 95 | 96 | // 求交集 97 | const set2 = new Set([2, 3]) 98 | const set3 = new Set([...set].filter(item => set2.has(item))) 99 | ``` 100 | 101 | **使用场景** 102 | 103 | - 场景一:求交集、差集 104 | 105 | ### 5. 字典(哈希) 106 | 107 | `字典`也是一种存储`唯一值`的数据结构,但它以`键值对`的形式存储。`ES6`中的字典名为`Map`, 108 | 109 | ```js 110 | // 字典 111 | const map = new Map() 112 | 113 | // 增 114 | map.set('key1', 'value1') 115 | map.set('key2', 'value2') 116 | map.set('key3', 'value3') 117 | 118 | // 删 119 | map.delete('key3') 120 | // map.clear() 121 | 122 | // 改 123 | map.set('key2', 'value222') 124 | 125 | // 查 126 | map.get('key2') 127 | 128 | ``` 129 | 130 | **使用场景** 131 | 132 | - 场景:leetcode刷题 133 | 134 | ### 6. 树 135 | 136 | `树`是一种`分层`的数据模型。前端常见的树包括:DOM、树、级联选择、树形控件……。`JavaScript`中没有`树`,但是可以通过`Object`和`Array`构建`树`。树的常用操作:深度/广度优先遍历、先中后序遍历。 137 | 138 | **使用场景** 139 | 140 | - 场景一:DOM树 141 | - 场景二:级联选择器 142 | 143 | ### 7. 图 144 | 145 | `图`是网络结构的抽象模型,是一组由边连接的节点。图可以表示任何二元关系,比如道路、航班。JS中没有图,但是可以用`Object`和`Array`构建`图`。图的表示法:邻接矩阵、邻接表、关联矩阵。 146 | 147 | **使用场景** 148 | 149 | - 场景一:道路 150 | - 场景二:航班 151 | 152 | ### 8. 堆 153 | 154 | `堆`是一种特殊的完全二叉树。所有的节点都大于等于(最大堆)或小于等于(最小堆)它的子节点。由于`堆`的特殊结构,我们可以用`数组`表示`堆`。 155 | 156 | **使用场景** 157 | 158 | - 场景:leetcode刷题 159 | 160 | ## 3. 排序方法 161 | 162 | ### 3.1 冒泡排序 163 | 164 | 比较两个记录键值的大小,如果这两个记录键值的大小出现逆序,则交换这两个记录 165 | 166 | **每遍历一个元素,都会把之前的所有相邻的元素都两两比较一遍,即便是已经排序好的元素** 167 | 168 | ```js 169 | //[1,3,4,2]->[1,3,2,4]->[1,2,3,4]->[1,2,3,4] 170 | 171 | let n = 0 172 | function bubbleSort(arr){ 173 | for(let i = 1;i < arr.length;i++){ 174 | for(let j = i;j > 0;j--){ 175 | n++ // 1+2+3+...+arr.length-1 176 | if(arr[j] < arr[j-1]){ 177 | [arr[j],arr[j-1]] = [arr[j-1],arr[j]]; 178 | } 179 | } 180 | } 181 | return arr; 182 | } 183 | 184 | ``` 185 | 186 | ### 3.2 插入排序 187 | 188 | 第i(i大于等于1)个记录进行插入操作时,R1、 R2,...,是排好序的有序数列,取出第i个元素,在序列中找到一个合适的位置并将她插入到该位置上即可。 189 | 190 | **相当于把当前遍历的元素取出,在序列中找到一个合适的位置将它插入。它的第二层循环不必遍历当前元素之前的所有元素,因为当前元素之前的序列是排序好的,碰到第一个小于当前元素的值,就可以停止继续向前查找了,然后把当前元素插入当前位置即可** 191 | 192 | ```js 193 | function insertSort(arr){ 194 | for(let i = 1;i < arr.length;i++){ 195 | let j = i-1; 196 | if(arr[i]= 0 && temp < arr[j]){ 199 | arr[j+1] = arr[j]; 200 | j--; 201 | } 202 | arr[j+1] = temp; 203 | } 204 | } 205 | return arr; 206 | } 207 | 208 | //[1,3,4,2] ->[1,3,4,4]->[1,3,3,4]->[1,2,3,4] 209 | //i=3 temp=2 j=2 arr[j]=4 arr[3]=4 [1,3,4,4]; j=1 arr[2]=3 [1,3,3,4]; j=0 [1,2,3,4] 210 | ``` 211 | 212 | ### 3.3 希尔排序 213 | 214 | 算法先将要排序的一组数按某个增量d(n/2,n为要排序数的个数)分成若干组,每组中记录的下标相差d.对每组中全部元素进行直接插入排序,然后再用一个较小的增量(d/2)对它进行分组,在每组中再进行直接插入排序。当增量减到1时,进行直接插入排序后,排序完成。 215 | 216 | ```js 217 | function hillSort(arr){ 218 | let len = arr.length; 219 | for(let gap = parseInt(len / 2);gap >= 1;gap = parseInt(gap / 2)){ 220 | for(let i = gap;i < len;i++){ 221 | if(arr[i] < arr[i-gap]){ 222 | let temp = arr[i]; 223 | let j = i - gap; 224 | while(j >= 0 && arr[j] > temp){ 225 | arr[j+gap] = arr[j]; 226 | j -= gap; 227 | } 228 | arr[j+gap] = temp; 229 | } 230 | } 231 | } 232 | return arr; 233 | } 234 | 235 | ``` 236 | 237 | ![1.img](https://raw.githubusercontent.com/summmer-is-hot/marks/main/assets/writeCode/writeCode4_1.webp?) 238 | 239 | ### 3.4 选择排序 240 | 241 | 在第i次选择操作中,通过n-i次键值间比较,从n-i+1个记录中选出键值最小的记录,并和第i(1小于等于1小于等于n-1)个记录交换 242 | 243 | **每一次遍历,都把当前元素与剩下元素里的最小值交换位置** 244 | 245 | ```js 246 | //[4,1,3,2]->[1,4,3,2]->[1,2,4,3]->[1,2,3,4] 247 | 248 | function selectSort(arr){ 249 | for(let i = 0;i < arr.length;i++){ 250 | let min = Math.min(...arr.slice(i)); 251 | let index 252 | for (let j = i; j < arr.length; j++) { 253 | if (arr[j] === min) { 254 | index = j 255 | break 256 | } 257 | } 258 | [arr[i],arr[index]] = [arr[index],arr[i]]; 259 | } 260 | return arr; 261 | } 262 | 263 | ``` 264 | 265 | ### 3.5 快排 266 | 267 | 在n个记录中取某一个记录的键值为标准,通常取第一个记录键值为基准,通过一趟排序将待排的记录分为小于或等于这个键值的两个独立的部分,这是一部分的记录键值均比另一部分记录的键值小,然后,对这两部分记录继续分别进行快速排序,以达到整个序列有序 268 | 269 | **取当前排序数组的第一个值作为基准值keys,通过一次遍历把数组分为right大于基准值和left小于等于基准值的两部分,然后对两个部分重复以上步骤排序,最后return的时候按照[left,keys,right]的顺序返回** 270 | 271 | ```js 272 | function quickSort(arr) { 273 | if (arr.length <= 1) return arr; 274 | let right = [], left = [], keys = arr.shift(); 275 | for (let value of arr) { 276 | if (value > keys) { 277 | right.push(value) 278 | } else { 279 | left.push(value); 280 | } 281 | } 282 | return quickSort(left).concat(keys, quickSort(right)); 283 | } 284 | 285 | //[4,1,3,2]-->quickSort([1,3,2]).concat(4,quickSort([])) 286 | // -->quickSort([]).concant(1,quickSort([3,2])).concat(4,quickSort([])) 287 | // -->quickSort([]).concant(1,quickSort([2]).concant(3)).concat(4,quickSort([])) 288 | // -->[1,2,3,4] 289 | //keys=4 R[] L[1,3,2] 290 | //-------quickSort(left) 291 | //keys=1 R[3,2] L[] 292 | //keys=3 R[] L[2] 293 | //quickSort(left)=[1,2,3] 294 | 295 | ``` 296 | 297 | ### **3.6各排序算法的稳定性,时间复杂度,空间复杂度** 298 | 299 | ![1.img](https://raw.githubusercontent.com/summmer-is-hot/marks/main/assets/writeCode/writeCode4_2.webp?) 300 | 301 | 每个语言的排序内部实现都是不同的。 302 | 303 | 对于 JS 来说,数组长度大于 10 会采用快排,否则使用插入排序。选择插入排序是因为虽然时间复杂度很差,但是在数据 量很小的情况下和 O(N * logN) 相差无几,然而插入排序需要的常数时间很小,所以相对别的排序来说更快。 304 | 305 | ## 4. JS尾递归优化斐波拉契数列 306 | 307 | 正常的斐波拉契数列js实现方式 308 | 309 | ```js 310 | const Fibonacci = (n) => { 311 | if (n <= 1) return 1; 312 | return Fibonacci(n - 1) + Fibonacci(n - 2); 313 | } 314 | Fibonacci(10) // 89 315 | Fibonacci(40) // 165580141 计算缓慢有延迟了 316 | Fibonacci(100) // 栈溢出,无法得到结果 317 | ``` 318 | 319 | 使用尾递归优化该方法 320 | 321 | ```js 322 | const Fibonacci = (n, sum1 = 1, sum2 = 1) => { 323 | if (n <= 1) return sum2; 324 | return Fibonacci(n - 1, sum2, sum1 + sum2) 325 | } 326 | Fibonacci(10) // 89 327 | Fibonacci(100) // 573147844013817200000 速度依旧很快 328 | Fibonacci(1000) // 7.0330367711422765e+208 还是没有压力 329 | ``` 330 | 331 | 尾递归优化可以在数量较大的计算中,可以起到很好的作用 332 | 333 | ## 结语 334 | 335 | >   今天的面试题的分享就到这边啦,明天继续给大家分享~ 336 | 337 | ## 作者 338 | 339 | | Coder | 小红书ID | 创建时间 | 340 | | :---------- | :-------- | :--------- | 341 | | 落寞的前端👣 | 121450513 | 2022.10.30 | 342 | 343 | -------------------------------------------------------------------------------- /原型.md: -------------------------------------------------------------------------------- 1 | ## 大多数前端工程师不了解的原型prototype 2 | 3 | ## 前言 4 | 5 | >  JS中的原型/继承,我个人觉得是JS中最难的一个知识点。从对象的`[[prototype]]`(即隐式原型),再到函数`prototype`(显示原型),再到原型链、再到继承的多种方式原型链继承、借用构造函数继承、组合借用继承、寄生组合式继承,最终再到`ES6`的`class`关键字实现继承。 6 | >  无论再多的术语,其实都离不开生活,技术也是如此,技术源于生活,却不会高于生活,大部分都是现实的一种抽象。 7 | >  今天给大家先分享一下原型。 8 | 9 | ## 1. 对象的原型(隐式原型) 10 | 11 | 在JS当中,每个对象中都有一个特殊的内置属性(`[[prototype]]`)/隐式原型,其实就是对于其他对象的引用而已,换句话说就是:这个特殊的内置属性指向着另外一个对象,它也称之为对象的隐式原型。 12 | 13 | ### 1.1 查看隐式原型 14 | 15 | - `obj.__proto__`,早期由浏览器实现,兼容性较差 16 | - `Object.getPrototypeOf`方法也能获取到 17 | 18 | ```javascript 19 | const info = { 20 | name: 'ice', 21 | age: 22 22 | } 23 | 24 | console.log(info.__proto__) 25 | console.log(Object.getPrototypeOf(info)) 26 | 27 | console.log(info.__proto__ === Object.getPrototypeOf(info)) // true 28 | 29 | ``` 30 | 31 | - 打印结果如下: 32 | - 我们从打印结果可以看出,通过`__proto__`和`Object.getPrototypeOf`方法,获取的对象应用是同一个 ![prototype_0.jpg](https://raw.githubusercontent.com/summmer-is-hot/picture-store/main/marks/prototype_0.webp?) 33 | - `constructor`这个特殊的属性,我们在后面再来剖析,它默认会指向当前的函数对象 34 | 35 | ### 1.2 隐式原型有什么用? 36 | 37 | 1. 当你访问该对象的属性时会触发`[[GET]]`操作(对象的存取描述符) 38 | 2. 当我访问`info.name`的时候,首先会查找该`info`对象中,是否有这个属性,有就使用它 39 | 40 | ```js 41 | const info = { 42 | name: 'ice', 43 | age: 22 44 | } 45 | 46 | console.log(info.name) 47 | ``` 48 | 49 | 1. 如果无法在该`info`对象中找到,就会沿着该对象的隐式原型`[[prototype]]`(沿着原型链),有就使用它。直到查询到“尽头”后,还未找到该属性的值,返回`undefined` 50 | 51 | ```javascript 52 | //1. 未找到 53 | const info1 = { 54 | name: 'ice', 55 | age: 22 56 | } 57 | 58 | console.log(info1.money) //undefined 59 | 60 | //2. 隐式原型中找到 61 | const info2 = { 62 | name: 'ice', 63 | age: 22 64 | } 65 | 66 | ///后续会讲到是什么 67 | Object.prototype.money = 100 68 | 69 | console.log(info2.money) //100 70 | ``` 71 | 72 | ## 2. 函数的显示原型 73 | 74 | 多年以来,js中有一种奇怪的行为,一直被“无耻”的滥用,那就是模仿类(早期是面向对象编程的天下)。这种奇怪的“类似类”的行为,就是利用函数的特殊属性,因为所有函数都会有一个显示原型(`prototype`)属性,它会指向另外一个对象,可以通过这一特性变相实现继承。 75 | 76 | ### 2.1 查看函数的原型 77 | 78 | - 所有的函数都一个`prototype`属性(**箭头函数除外**),注意:**不是对象的__proto__** 79 | 80 | ```javascript 81 | function bar() {} 82 | 83 | //所有的函数都有prototype属性 84 | console.log(bar.prototype) 85 | 86 | const info = {} 87 | //对象没有prototype属性 88 | console.log(info.prototype) 89 | ``` 90 | 91 | ### 2.2 显示原型有什么用呢? 92 | 93 | - 最直接的解释,通过`new`关键字调用的函数(`new Foo()`),创建的对象,最终这个对象的`[[prototype]]` (`__proto__`/隐式原型),会指向`Foo.prototype`(**函数的显示原型**)这个对象。 94 | 95 | ```javascript 96 | function Person() {} 97 | 98 | const p1 = new Person() 99 | const p2 = new Person() 100 | 101 | console.log(p1.__proto__ === Person.prototype) //true 102 | console.log(p1.__proto__ === p2.__proto__) //true 103 | ``` 104 | 105 | **内存表现** 106 | 107 | ![prototype_1.png](https://raw.githubusercontent.com/summmer-is-hot/picture-store/main/marks/prototype_1.webp?) 108 | 109 | ### 2.3 构造函数(类) 110 | 111 | 当首字母大写的函数,我们称之为构造函数(社区约定规范),早期ES5模仿类,在JS中也可以把`new Fn()`这种行为称之为`类` 112 | 113 | #### 2.3.1 `new Fn()` 发生了什么 114 | 115 | 1. 创建一个空对象 116 | 2. `this`指向创建出来的对象 117 | 3. 这个对象的隐式原型指向了函数的显示原型,(即`p1.__proto__ === Person.prototype`) 118 | 4. 运行函数体代码 119 | 5. 如果没有明确返回一个非空对象,那么返回的就是创建出来的对象 120 | 121 | ```js 122 | function Person(name, age) { 123 | this.name = name 124 | this.age = age 125 | } 126 | 127 | const p1 = new Person('ice', 22) 128 | 129 | console.log(p1) //{ name: 'ice', age: 22 } 130 | console.log(p1.__proto__ === Person.prototype) //true 131 | ``` 132 | 133 | **内存表现** 134 | 135 | ![image.png](https://raw.githubusercontent.com/summmer-is-hot/picture-store/main/marks/prototype_2.webp?) 136 | 137 | #### 2.3.2 显示原型上添加属性/方法 138 | 139 | 函数的显示原型,是一个对象,我们当然可以在对象上面添加我们自定义属性,思考以下代码 140 | 141 | ```javascript 142 | function Person() {} 143 | 144 | Person.prototype.money = 100 145 | Person.prototype.getDoubleMoney = function() { 146 | return this.money * 2 147 | } 148 | 149 | const p1 = new Person() 150 | 151 | console.log(p1.money) //100 152 | console.log(p1.getDoubleMoney()) //200 153 | ``` 154 | 155 | - 我们在构造函数的显示原型上,添加了`money`属性和`getDoubleMoney`方法,方法中设计到了`this`的概念,不懂的小伙伴可以翻一下,之前写的[有关this的文章](https://github.com/summmer-is-hot/marks/blob/main/this%E6%8C%87%E5%90%91.md) 156 | - 当我们对象自身上没有该`money`属性和`getDoubleMoney`方法,就会沿着对象的原型链一直查找,直至找到“尽头”,还未找到则为`undefined`,其实很明显这些属性/方法,存放在`Person`函数的原型上 157 | - `p1`对象为`Person`的实例(instance),即 `p1.__proto__ === Person.prototype` 158 | 159 | **内存表现** 160 | 161 | ![image.png](https://raw.githubusercontent.com/summmer-is-hot/picture-store/main/marks/prototype_3.webp?) 162 | 163 | - 每个函数都会有`name`和`length`属性,`name`为函数的名称,`length`为函数的形参个数 164 | 165 | **为什么添加到原型上?** 166 | 167 | - 为什么我要添加到原型上? 我为啥不能把属性/方法写在函数内部呢? 168 | - 思考以下代码 169 | 170 | ```javascript 171 | function Person(name, age, money) { 172 | this.name = name 173 | this.age = age 174 | this.money = money 175 | 176 | this.getDoubleMoney = function() { 177 | return this.money * 2 178 | } 179 | } 180 | 181 | const p1 = new Person('ice', 22, 100) 182 | const p2 = new Person('panda', 22, 200) 183 | 184 | console.log(p1.name, p1.money) //ice 100 185 | console.log(p1.name, p1.getDoubleMoney()) //ice 200 186 | 187 | console.log(p2.name, p2.money) //panda 200 188 | console.log(p2.name, p2.getDoubleMoney()) //panda 400 189 | ``` 190 | 191 | - 我这里直接说结论: 192 | - 属性应该写在函数内部,而不是写在原型对象上,因为对于构造函数来说,每个实例对象应该有属于自己的属性,而不是同一个。 193 | - **公共方法**应该放在构造函数的原型上,因为**可以复用**。当我创建一个实例都会运行构造函数,我创建多个实例,那么就要调用多次。如果方法在函数体中,调用多次构造函数,方法就会被定义多次,这样其实是没有必要的。(多次调用、多次定义、浪费性能) 194 | 195 | ### 2.4 constructor属性 196 | 197 | 我们无论是前面打印原型对象,还是查看内存表现图,多次提到了`constructor`,那`constructor`到底是什么呢? 198 | 199 | - 每个原型对象上都有一个`constructor`,它默认指向了当前的函数,形成循环引用,V8引擎GC采用标记清除算法,不会存在内存泄漏问题 200 | - `Person.prototype.constructor === Person` 201 | 202 | ```javascript 203 | function Person() {} 204 | 205 | const p1 = new Person() 206 | 207 | console.log(Person.prototype.constructor.name) //person 208 | console.log(Person.prototype.constructor === Person) //true 209 | console.log(p1.__proto__.constructor === Person.prototype.constructor) //true 210 | ``` 211 | 212 | ### 2.5 重写函数的显示原型 213 | 214 | ```javascript 215 | function Person(name, age, money) { 216 | this.name = name 217 | this.age = age 218 | this.money = money 219 | } 220 | 221 | //重写了显示原型,是一个全新的对象 222 | Person.prototype = { 223 | getDoubleMoney () { 224 | return this.money * 2 225 | } 226 | } 227 | 228 | const p1 = new Person('ice', 22, 100) 229 | 230 | console.log(p1.getDoubleMoney()) //200 231 | ``` 232 | 233 | - 我们之前采用赋值的方式,是添加属性的方式添加方法,即`Person.prototype.getDoubleMoney = function() {} `,这是相当于在原对象上添加 234 | - 这个案例是,是直接赋值了一个新对象,在内存中的表现为,指向了另外一个对象,而不是默认的显示原型了。 235 | 236 | **探究constructor** 237 | 上面的案例中我们把函数的原型进行了**重写**的操作,你们有思考过`constructor`属性吗,原来的原型已经被我重写,赋值为一个全新的对象了,那这个全新的对象`constructor`又指向谁了呢? 238 | 绕来绕去都快被绕晕了,什么乱七八糟的!接下来跟我一起探究 239 | 240 | ```javascript 241 | function Person(name, age, money) { 242 | this.name = name 243 | this.age = age 244 | this.money = money 245 | } 246 | 247 | Person.prototype = { 248 | getDoubleMoney () { 249 | return this.money * 2 250 | } 251 | } 252 | 253 | console.log(Person.prototype.constructor === Object) // true 254 | ``` 255 | 256 | - 你会惊讶的发现,woc,这是什么意思? 为什么指向的却是Object呢?难道是bug吗?其实并不是,原型/原型链/继承都是环环相扣,一环扣着一环,一个点没吃透,剩下的就很难明白。 257 | 258 | **继续推断探究** 259 | 260 | - 探索`Person.prototype.constructor === Object` 261 | 262 | ```js 263 | /*解: Person.prototype.constructor === Object 264 | 1. 首先我们对原对象进行了重写 265 | 2. 每个原型对象上都有constructor属性,我们赋值的那个对象身上肯定也有原型对象啊! 266 | Person.prototype = { getDoubleMoney () { return this.money * 2 } } 267 | 3. 此时的Person.prototype = {},等于了一个全新的对象,并不用关心里面的方法getDoubleMoney 268 | 4. 伪代码: const obj = {}(字面量写法) 等同于 const obj = new Object(),所以obj对象的隐式原型即(obj.__proto__ === Object.prototype),obj为Object的实例对象。 269 | 5. 我们赋值的新对象的隐式原型指向了Object.prototype,所以Person.prototype.constructor去查找constructor的时候,自己没有constructor,找到的其实Object.prototype.constructor,所以它的值等于Object 270 | 6. js中的一句话:万物皆对象(虽然不严谨),因为Object.prototype是"尽头",在查找就等于null了 271 | */ 272 | ``` 273 | 274 | **自定义constructor** 275 | 276 | ```javascript 277 | function Person(name, age, money) { 278 | this.name = name 279 | this.age = age 280 | this.money = money 281 | } 282 | 283 | Person.prototype = { 284 | getDoubleMoney () { 285 | return this.money * 2 286 | } 287 | } 288 | 289 | Object.defineProperty(Person.prototype, "constructor", { 290 | value: Person, 291 | writable:true, 292 | configurable: false, 293 | enumerable: false 294 | }) 295 | 296 | console.log(Person.prototype.constructor === Person) // true 297 | ``` 298 | 299 | - 这样才是正确重写函数显示原型的正确方法,跟默认提供的显示原型一致 300 | 301 | ## 3. 函数的隐式原型 302 | 303 | - 函数是一个函数,(写累了就开始水文章,说废话了是吧?),但它也是一个特殊的对象。函数就像我们人类一样,在生活中,扮演着许多角色(身份)。 304 | - 函数,既有函数身份,也有对象身份,既然是对象那肯定有隐式原型,我们又绕回来了哈哈哈哈哈哈。 305 | 306 | ```javascript 307 | function Person() {} 308 | 309 | Person.fullName = 'ice' 310 | 311 | console.log(Person.fullName) //ice 312 | console.log(Person.__proto__ === Function.prototype) //true 313 | ``` 314 | 315 | - 为什么 316 | 317 | ``` 318 | Person.__proto__ === Function.prototype 319 | ``` 320 | 321 | 相等,在介绍constructor,我们已经详细的分析过类似的了,其实本质都一样 322 | 323 | - `Person`作为对象的身份有`__proto__`,`Person`则是 `new Function()`的实例对象,所以`Person.__proto__ === Function.prototype` 324 | 325 | - 这里涉及到了原型的继承关系,我们不在这里继续深挖下去,不然不好理解。 326 | 327 | ## 结语 328 | >  今天原型的分享就到这边啦,明天分享继承相关内容~ 329 | 330 | ## 作者 331 | 332 | | Coder | 小红书ID | 创建时间 | 333 | | :---------- | :-------- | :--------- | 334 | | 落寞的前端👣 | 121450513 | 2022.10.07 | 335 | -------------------------------------------------------------------------------- /browser_two.md: -------------------------------------------------------------------------------- 1 | ## 前端常见面试题(浏览器篇)二 2 | 3 | ## 11. **什么是队头堵塞** 4 | 5 | 队头阻塞是由 HTTP 基本的“请求 - 应答”模型所导致的。HTTP 6 | 规定报文必须是“一发一收”,这就形成了一个先进先出的“串行”队列。队列里的请求是没有优先级的,只有入队的先后顺序,排在最前面的请求会被最优先处理。如果队首的请求因为处理的太慢耽误了时间,那么队列里后面的所有请求也不得不跟着一起等待,结果就是其他的请求承担了不应有的时间成本,造成了队头堵塞的现象。 7 | 8 | ### 11.2 **队头阻塞的解决方案:** 9 | 10 | (1)并发连接:对于一个域名允许分配多个长连接,那么相当于增加了任务队列,不至于一个队伍的任务阻塞其它所有任务。 (2)域名分片:将域名分出很多二级域名,它们都指向同样的一台服务器,能够并发的长连接数变多,解决了队头阻塞的问题。 11 | 12 | ## 12. HTTP和HTTPS协议的区别 13 | 14 | HTTP和HTTPS协议的主要区别如下: 15 | 16 | - HTTPS协议需要CA证书,费用较高;而HTTP协议不需要; 17 | - HTTP协议是超文本传输协议,信息是明文传输的,HTTPS则是具有安全性的SSL加密传输协议; 18 | - 使用不同的连接方式,端口也不同,HTTP协议端口是80,HTTPS协议端口是443; 19 | - HTTP协议连接很简单,是无状态的;HTTPS协议是有SSL和HTTP协议构建的可进行加密传输、身份认证的网络协议,比HTTP更加安全。 20 | 21 | ## 13. GET方法URL长度限制的原因 22 | 23 | 实际上HTTP协议规范并没有对get方法请求的url长度进行限制,这个限制是特定的**浏览器**及**服务器**对它的限制。 IE对URL长度的限制是**2083**字节(2K+35)。由于IE浏览器对URL长度的允许值是**最小的** 24 | ,所以开发过程中,只要URL不超过2083字节,那么在所有浏览器中工作都不会有问题。 25 | 26 | ```markdown 27 | GET的长度值 = URL(2083)- (你的Domain+Path)-2(2是get请求中?=两个字符的长度) 28 | ``` 29 | 30 | 下面看一下主流浏览器对get方法中url的长度限制范围: 31 | 32 | - Microsoft Internet Explorer (Browser):IE浏览器对URL的最大限制为**2083**个字符,如果超过这个数字,提交按钮没有任何反应。 33 | - Firefox (Browser):对于Firefox浏览器URL的长度限制为 **65,536** 个字符。 34 | - Safari (Browser):URL最大长度限制为 **80,000** 个字符。 35 | - Opera (Browser):URL最大长度限制为 **190,000** 个字符。 36 | - Google (chrome):URL最大长度限制为 **8182** 个字符。 37 | 38 | 主流的服务器对get方法中url的长度限制范围: 39 | 40 | - Apache (Server):能接受最大url长度为**8192**个字符。 41 | - Microsoft Internet Information Server(IIS):能接受最大url的长度为**16384**个字符。 42 | 43 | 根据上面的数据,可以知道,get方法中的URL长度最长不超过2083个字符,这样所有的浏览器和服务器都可能正常工作。 44 | 45 | ## 14.一个页面从输入 URL 到页面加载显示完成,这个过程中都发生了什么? 46 | 47 | (1)**解析URL:** **首先会对 URL 进行解析,分析所需要使用的传输协议和请求的资源的路径**。如果输入的 URL 中的协议或者主机名不合法,将会把地址栏中输入的内容传递给搜索引擎。如果没有问题,浏览器会检查 URL 48 | 中是否出现了非法字符,如果存在非法字符,则对非法字符进行转义后再进行下一过程。 49 | 50 | (2)**缓存判断:** **浏览器会判断所请求的资源是否在缓存里**,如果请求的资源在缓存里并且没有失效,那么就直接使用,否则向服务器发起新的请求。 51 | 52 | (3)**DNS解析:** 下一步首先需要获取的是输入的 URL 中的域名的 IP 地址,首先会**判断本地是否有该域名的 IP 地址的缓存**,如果有则使用,**如果没有则向本地 DNS 服务器发起请求**。**本地 DNS 53 | 服务器也会先检查是否存在缓存**,如果**没有就会先向根域名服务器发起请求**,获得负责的顶级域名服务器的地址后,**再向顶级域名服务器请求**,然后获得负责的权威域名服务器的地址后,**再向权威域名服务器发起请求**,**最终获得域名的 54 | IP 地址后,本地 DNS 服务器再将这个 IP 地址返回给请求的用户**。用户向本地 DNS 服务器发起请求属于递归请求,本地 DNS 服务器向各级域名服务器发起请求属于迭代请求。 55 | 56 | (4)**获取MAC地址(选说)** 当浏览器得到 IP 地址后,**数据传输还需要知道目的主机 MAC 地址**,因为应用层下发数据给传输层,TCP 协议会指定源端口号和目的端口号,然后下发给网络层。网络层会将本机地址作为源地址,获取的 57 | IP 地址作为目的地址。然后将下发给数据链路层,数据链路层的发送需要加入通信双方的 MAC 地址,本机的 MAC 地址作为源 MAC 地址,目的 MAC 地址需要分情况处理。通过将 IP 58 | 地址与本机的子网掩码相与,可以判断是否与请求主机在同一个子网里,如果在同一个子网里,可以使用 APR 协议获取到目的主机的 MAC 地址,如果不在一个子网里,那么请求应该转发给网关,由它代为转发,此时同样可以通过 ARP 协议来获取网关的 59 | MAC 地址,此时目的主机的 MAC 地址应该为网关的地址。 60 | 61 | (5)**TCP三次握手:** ,**确认客户端与服务器的接收与发送能力**,下面是 TCP 建立连接的三次握手的过程,首先客户端向服务器发送一个 SYN 连接请求报文段和一个随机序号,服务端接收到请求后向服务器端发送一个 SYN 62 | ACK报文段,确认连接请求,并且也向客户端发送一个随机序号。客户端接收服务器的确认应答后,进入连接建立的状态,同时向服务器也发送一个ACK 确认报文段,服务器端接收到确认后,也进入连接建立状态,此时双方的连接就建立起来了。 63 | 64 | (6)**HTTPS握手(选说):** **如果使用的是 HTTPS 协议,在通信前还存在 TLS 的一个四次握手的过程** 65 | 。首先由客户端向服务器端发送使用的协议的版本号、一个随机数和可以使用的加密方法。服务器端收到后,确认加密的方法,也向客户端发送一个随机数和自己的数字证书。客户端收到后,首先检查数字证书是否有效,如果有效,则再生成一个随机数,并使用证书中的公钥对随机数加密,然后发送给服务器端,并且还会提供一个前面所有内容的 66 | hash 值供服务器端检验。服务器端接收后,使用自己的私钥对数据解密,同时向客户端发送一个前面所有内容的 hash 67 | 值供客户端检验。这个时候双方都有了三个随机数,按照之前所约定的加密方法,使用这三个随机数生成一把秘钥,以后双方通信前,就使用这个秘钥对数据进行加密后再传输。 68 | 69 | (7)**发送HTTP请求** 70 | 71 | **服务器处理请求,返回HTTP报文**(响应)(文件) 72 | 73 | (8)**页面渲染:** 浏览器首先会根据 html 文件(响应) **建 DOM 树**,根据解析到的 css 文件构**建 CSSOM 树**,如果遇到 script 标签,则判端是否含有 defer 或者 async 属性,要不然 74 | script 的加载和执行会造成页面的渲染的阻塞。**当 DOM 树和 CSSOM 树建立好后,根据它们来构建渲染树**。渲染树构建好后,会根据渲染树来进行布局。布局完成后,最后使用浏览器的 UI 75 | 接口对页面进行绘制。这个时候整个页面就显示出来了。 76 | 77 | (9)**TCP四次挥手:** **最后一步是 TCP 断开连接的四次挥手过程**。若客户端认为数据发送完成,则它需要向服务端发送连接释放请求。服务端收到连接释放请求后,会告诉应用层要释放 TCP 链接。然后会发送 ACK 包,并进入 78 | CLOSE_WAIT 状态,此时表明客户端到服务端的连接已经释放,不再接收客户端发的数据了。但是因为 TCP 79 | 连接是双向的,所以服务端仍旧可以发送数据给客户端。服务端如果此时还有没发完的数据会继续发送,完毕后会向客户端发送连接释放请求,然后服务端便进入 LAST-ACK 状态。客户端收到释放请求后,向服务端发送确认应答,此时客户端进入 80 | TIME-WAIT 状态。该状态会持续 2MSL(最大段生存期,指报文段在网络中生存的时间,超时会被抛弃) 时间,若该时间段内没有服务端的重发请求的话,就进入 CLOSED 状态。当服务端收到确认应答后,也便进入 CLOSED 状态。 81 | 82 | ## 15.页面有多张图片,HTTP是怎样的加载表现? 83 | 84 | 在`HTTP 1`下,浏览器对一个域名下最大TCP连接数为6,所以会请求多次。可以用**多域名部署**解决。这样可以提高同时请求的数目,加快页面图片的获取速度。 85 | 86 | 在`HTTP 2`下,可以一瞬间加载出来很多资源,因为,HTTP2支持多路复用,可以在一个TCP连接中发送多个HTTP请求。 87 | 88 | ## 16. HTTP2的头部压缩算法是怎样的? 89 | 90 | HTTP2的头部压缩是HPACK算法。在客户端和服务器两端建立“字典”,用索引号表示重复的字符串,采用哈夫曼编码来压缩整数和字符串,可以达到50%~90%的高压缩率。 91 | 92 | 具体来说: 93 | 94 | - 在客户端和服务器端使用“首部表”来跟踪和存储之前发送的键值对,对于相同的数据,不再通过每次请求和响应发送; 95 | - 首部表在HTTP/2的连接存续期内始终存在,由客户端和服务器共同渐进地更新; 96 | - 每个新的首部键值对要么被追加到当前表的末尾,要么替换表中之前的值。 97 | 98 | ## 17. HTTP请求报文的是什么样的? 99 | 100 | 请求报⽂有4部分组成: 101 | 102 | - 请求⾏ 103 | - 请求头部 104 | - 空⾏ 105 | - 请求体 106 | 107 | **其中:** 108 | 109 | (1)请求⾏包括:请求⽅法字段、URL字段、HTTP协议版本字段。它们⽤空格分隔。例如,GET /index.html HTTP/1.1。 110 | 111 | (2)请求头部:请求头部由关键字/值对组成,每⾏⼀对,关键字和值⽤英⽂冒号“:”分隔 112 | 113 | - User-Agent:产⽣请求的浏览器类型。 114 | - Accept:客户端可识别的内容类型列表。 115 | - Host:请求的主机名,允许多个域名同处⼀个IP地址,即虚拟主机。 116 | 117 | (3)请求体: post put等请求携带的数据 118 | 119 | ## 18. HTTP响应报文的是什么样的? 120 | 121 | 请求报⽂有4部分组成: 122 | 123 | - 响应⾏:由网络协议版本,状态码和状态码的原因短语组成,例如 HTTP/1.1 200 OK 124 | - 响应头:响应部⾸组成 125 | - 空⾏ 126 | - 响应体:服务器响应的数据 127 | 128 | ## 19. HTTP协议的优点和缺点 129 | 130 | HTTP 是超文本传输协议,它定义了客户端和服务器之间交换报文的格式和方式,默认使用 80 端口。它使用 TCP 作为传输层协议,保证了数据传输的可靠性。 131 | 132 | HTTP协议具有以下**优点**: 133 | 134 | - 支持客户端/服务器模式 135 | - **简单快速**:客户向服务器请求服务时,只需传送请求方法和路径。由于 HTTP 协议简单,使得 HTTP 服务器的程序规模小,因而通信速度很快。 136 | - **无连接**:无连接就是限制每次连接只处理一个请求。服务器处理完客户的请求,并收到客户的应答后,即断开连接,采用这种方式可以节省传输时间。 137 | - **无状态**:HTTP 协议是无状态协议,这里的状态是指通信过程的上下文信息。缺少状态意味着如果后续处理需要前面的信息,则它必须重传,这样可能会导致每次连接传送的数据量增大。另一方面,在服务器不需要先前信息时它的应答就比较快。 138 | - **灵活**:HTTP 允许传输任意类型的数据对象。正在传输的类型由 Content-Type 加以标记。 139 | 140 | HTTP协议具有以下**缺点**: 141 | 142 | - **无状态:** HTTP 是一个无状态的协议,HTTP 服务器不会保存关于客户的任何信息。 143 | - **明文传输:** 协议中的报文使用的是文本形式,这就直接暴露给外界,不安全。 144 | - **不安全** 145 | 146 | (1)通信使用明文(不加密),内容可能会被窃听; (2)不验证通信方的身份,因此有可能遭遇伪装; (3)无法证明报文的完整性,所以有可能已遭篡改; 147 | 148 | ## 20. 说一下HTTP 3.0 149 | 150 | HTTP3.0,也称作HTTP over QUIC。HTTP3.0的核心是QUIC(读音quick)协议,由Google在 2015年提出的SPDY 151 | v3演化而来的新协议,传统的HTTP协议是基于传输层TCP的协议,而QUIC是基于传输层UDP上的协议,可以定义成:HTTP3.0基于UDP的安全可靠的HTTP2.0协议。 152 | 153 | ## 21. HTTP的两种连接模式 154 | 155 | HTTP 协议是基于 TCP/IP,并且使用了**请求-应答**的通信模式。 156 | 157 | **HTTP协议有两种连接模式,一种是持续连接,一种非持续连接**。 (1)非持续连接指的是服务器必须为每一个请求的对象建立和维护一个全新的连接。 (2)持续连接下,TCP 158 | 连接默认不关闭,可以被多个请求复用。采用持续连接的好处是可以避免每次建立 TCP 连接三次握手时所花费的时间。 159 | 160 | ## 22. URL有哪些组成部分 161 | 162 | 以下面的URL为例www.aspxfans.com:8080/news/index?ID=246188#name 163 | 164 | 从上面的URL可以看出,一个完整的URL包括以下几部分: 165 | 166 | - **协议部分**:该URL的协议部分为“http:”,这代表网页使用的是HTTP协议。在Internet中可以使用多种协议,如HTTP,FTP等等本例中使用的是HTTP协议。在"HTTP"后面的“//”为分隔符; 167 | - **域名部分**:该URL的域名部分为www.aspxfans.com。一个URL中,也可以使用IP地址作为域名使用 168 | - **端口部分**:跟在域名后面的是端口,域名和端口之间使用“:”作为分隔符。端口不是一个URL必须的部分,如果省略端口部分,将采用默认端口(HTTP协议默认端口是80,HTTPS协议默认端口是443); 169 | - **虚拟目录部分**:从域名后的第一个“/”开始到最后一个“/”为止,是虚拟目录部分。虚拟目录也不是一个URL必须的部分。本例中的虚拟目录是“/news/”; 170 | - **文件名部分** 171 | :从域名后的最后一个“/”开始到“?”为止,是文件名部分,如果没有“?”,则是从域名后的最后一个“/”开始到“#”为止,是文件部分,如果没有“?”和“#”,那么从域名后的最后一个“/”开始到结束,都是文件名部分。本例中的文件名是“index.asp”。文件名部分也不是一个URL必须的部分,如果省略该部分,则使用默认的文件名; 172 | - **锚部分**:从“#”开始到最后,都是锚部分。本例中的锚部分是“name”。锚部分也不是一个URL必须的部分; 173 | - **参数部分**:从“?”开始到“#”为止之间的部分为参数部分,又称搜索部分、查询部分。本例中的参数部分为“boardID=5&ID=24618&page=1”。参数可以允许有多个参数,参数与参数之间用“&”作为分隔符。 174 | 175 | ## 23.与缓存相关的HTTP请求头有哪些 176 | 177 | 强缓存: 178 | 179 | - Expires 180 | - Cache-Control 181 | 182 | 协商缓存: 183 | 184 | - Etag、If-None-Match 185 | - Last-Modified、If-Modified-Since 186 | 187 | ## 24. 强缓存和协商缓存 188 | 189 | **1.强缓存:** 不会向服务器发送请求,直接从缓存中读取资源,在chrome控制台的Network选项中可以看到该请求返回200的状态码,并且size显示from disk cache或from memory 190 | cache两种(灰色表示缓存)。 191 | 192 | **2.协商缓存:** 向服务器发送请求,服务器会根据这个请求的request header的一些参数来判断是否命中协商缓存,如果命中,则返回304状态码并带上新的response header通知浏览器从缓存中读取资源; 193 | 194 | > 共同点:都是从客户端缓存中读取资源; 区别是强缓存不会发请求,协商缓存会发请求。 195 | 196 | ## 25. HTTP的keep-alive有什么作用? 197 | 198 | **http1.0默认关闭,需要手动开启。http1.1后默认开启** 199 | 200 | **作用:** 使客户端到服务器端的链接持续有效(**长连接**),当出现对服务器的后续请求时,keep-Alive功能避免了建立或者重新建立链接。 201 | 202 | **使用方法:** 在请求头中加上Connection:keep-alive。 203 | 204 | **优点:** 205 | 206 | - 较少的CPU和内存的占用(因为要打开的连接数变少了,复用了连接) 207 | - 减少了后续请求的延迟(无需再进行握手) 208 | 209 | **缺点:** 本来可以释放的资源仍旧被占用。有的请求已经结束了,但是还一直连接着。 210 | 211 | **解决方法:** 服务器设置过期时间和请求次数,超过这个时间或者次数就断掉连接。 212 | 213 | ## 26. OSI的七层模型是什么? 214 | 215 | ISO于1978年开发的一套标准架构ISO模型,被引用来说明数据通信协议的结构和功能。 216 | 217 | OSI在功能上可以划分为两组: 218 | 219 | 网络群组:物理层、数据链路层、网络层 220 | 221 | 使用者群组:传输层、会话层、表示层、应用层 222 | 223 | | `OSI`七层网络模型 | `TCP/IP`四层概念模型 | 对应网络协议 | 224 | | ----------------- | -------------------- | ------------------------------------------------------------ | 225 | | 7:应用层 | 应用层 | `HTTP`、`RTSP` `TFTP(简单文本传输协议)、`FTP`、` NFS`(数域筛法,数据加密)、`WAIS`(广域信息查询系统) | 226 | | 6:表示层 | 应用层 | `Telnet`(internet远程登陆服务的标准协议)、`Rlogin`、`SNMP`(网络管理协议)、Gopher | 227 | | 5:会话层 | 应用层 | `SMTP`(简单邮件传输协议)、`DNS`(域名系统) | 228 | | 4:传输层 | 传输层 | `TCP`(传输控制协议)、`UDP`(用户数据报协议)) | 229 | | 3:网络层 | 网际层 | `ARP`(地域解析协议)、`RARP`、`AKP`、`UUCP`(Unix to Unix copy) | 230 | | 2:数据链路层 | 数据链路层 | `FDDI`(光纤分布式数据接口)、`Ethernet、Arpanet、PDN`(公用数据网)、`SLIP`(串行线路网际协议)`PPP`(点对点协议,通过拨号或专线方建立点对点连接发送数据) | 231 | | 1:物理层 | 物理层 | `SMTP`(简单邮件传输协议)、`DNS`(域名系统) | 232 | 233 | 其中高层(7、6、5、4层)定义了应用程序的功能,下面三层(3、2、1层)主要面向通过网络的端到端的数据流 234 | 235 | ## 结语 236 | 237 | >   今天的面试题的分享就到这边啦,明天继续给大家分享~ 238 | 239 | ## 作者 240 | 241 | | Coder | 小红书ID | 创建时间 | 242 | | :---------- | :-------- | :--------- | 243 | | 落寞的前端👣 | 121450513 | 2022.11.03 | 244 | | | | | 245 | -------------------------------------------------------------------------------- /this指向.md: -------------------------------------------------------------------------------- 1 | # 大多数前端工程师不了解的this指向问题 2 | 3 | ## 前言 4 | 5 | > 今天应某小薯提的问题,给大家分享大多数前端工程师不了解的`this`指向问题。 6 | 7 | ## 1. 调用位置 8 | 9 | - 作用域跟在哪里定义有关,与在哪里执行无关 10 | - `this`指向跟在哪里定义无关,跟如何调用,通过什么样的形式调用有关 11 | - `this`(这个) 这个函数如何被调用(方便记忆) 12 | - 为了方便理解,默认情况下不开启严格模式 13 | 14 | ## 2. 绑定规则 15 | 16 | ​ 上面我们介绍了,`this`的指向主要跟通过什么样的形式调用有关。接下来我就给大家介绍一下调用规则,没有规矩不成方圆,大家把这几种调用规则牢记于心就行了,没有什么难的地方。 17 | 18 | - 你必须找到调用位置,然后判断是下面四种的哪一种绑定规则 19 | - 其次你要也要晓得,这四种绑定规则的优先顺序 20 | - 这两点你都知道了,知道this的指向对于你来说就是易如反掌 21 | 22 | ### 2.1 默认绑定 23 | 24 | ​ 函数最常用的调用方式,调用函数的类型:独立函数调用 25 | 26 | ```js 27 | function bar() { 28 | console.log(this) // window 29 | } 30 | ``` 31 | 32 | - bar是不带任何修饰符的直接调用 所以为默认绑定 为`window` 33 | - 在严格模式下 这里的`this`为`undefined` 34 | 35 | ### 2.2 隐式绑定 36 | 37 | ​ 用最通俗的话表示就是:对象拥有某个方法,通过这个对象访问方法且直接调用(注:箭头函数特殊,下面会讲解) 38 | 39 | ```js 40 | const info = { 41 | fullName: 'ice', 42 | getName: function() { 43 | console.log(this.fullName) 44 | } 45 | } 46 | 47 | info.getName() // 'ice' 48 | ``` 49 | 50 | - 这个函数被`info`发起调用,进行了隐式绑定,所以当前的`this`为`info`,通过`this.fullName`毫无疑问的就访问值为`ice` 51 | 52 | **2.2.1 隐式丢失 普通** 53 | 54 |   有些情况下会进行隐式丢失,被隐式绑定的函数会丢失绑定对象,也就是说它为变为默认绑定,默认绑定的`this`值,为`window`还是`undefined`取决于您当前所处的环境,是否为严格模式。 55 | 56 | ```js 57 | const info = { 58 | fullName: 'ice', 59 | getName: function() { 60 | console.log(this.fullName) 61 | } 62 | } 63 | 64 | const fn = info.getName 65 | 66 | fn() //undefined 67 | ``` 68 | 69 |   这种情况下就进行了隐式丢失,丢失了绑定的对象,为什么会产生这样的问题呢?如果熟悉内存的小伙伴,就会很容易理解。 70 | 71 | - 这里并没有直接调用,而是通过`info`找到了对应`getName`的内存地址,赋值给变量`fn` 72 | - 然后通过`fn` 直接进行了调用 73 | - 其实这里的本质 就是独立函数调用 也就是为`window`,从`window`中取出`fullName`属性,必定为`undefined` 74 | 75 | **2.2.2 隐式丢失 进阶** 76 | 这里大家首先要理解什么是回调函数。其实可以这样理解,就是我现在不调用它,把他通过参数的形式传入到其他地方,在别的地方调用它。 77 | 78 | ```javascript 79 | 80 | //申明变量关键字必须为var 81 | var fullName = 'panpan' 82 | 83 | const info = { 84 | fullName: 'ice', 85 | getName: function() { 86 | console.log(this.fullName) 87 | } 88 | } 89 | 90 | function bar(fn) { 91 | //fn = info.getName 92 | fn() // panpan 93 | } 94 | 95 | bar(info.getName) 96 | ``` 97 | 98 | - 首先`bar`中的`fn`为一个回调函数 99 | 100 | - `fn = info.getName` 参数传递就是一种隐式赋值,其实跟上面的隐式丢失是一个意思,他们都是指向的`fn = info.getName`引用,也就是它们的内存地址 101 | 102 | - 因为他们的`this`丢失,也就是函数独立调用,默认绑定规则,`this`为全局的`window`对象 103 | 104 | - 注意: 为什么申明必须为`var`呢? 105 | 106 | - 因为只有`var`申明的变量才会加入到全局`window`对象上 107 | - 如果采用`let\const` 则不是,具体的后续介绍一下这两个申明变量的关键字 108 | 109 | - 但是有些场景,我不想让隐式丢失怎么办,下面就来给大家介绍一下显示绑定,也就是固定调用。 110 | 111 | ### 2.3 显示绑定 112 | 113 |   但是在某些场景下,`this`的改变都是意想不到的,实际上我们无法控制回调函数的执行方式,因此没有办法控制调用位置已得到期望的绑定即this指向。 114 | 115 | 接下来的显示绑定就可以用来解决这一隐式丢失问题。 116 | 117 | #### 2.3.1 call/apply/bind 118 | 119 |   js中的 ”所有“函数都有一些有用的特性,这个跟它的原型链有关系,后续我会在原型介绍,通过原型链js中变相实现继承的方法,其中`call/apply/bind`这三个方法就是函数原型链上的方法,可以在函数中调用它们。 120 | 121 | #### 2.3.2 call 122 | 123 | - `call()` 方法使用一个指定的`this` 值和单独给出的一个或多个参数来调用一个函数。 124 | - 第一个参数为固定绑定的`this`对象 125 | - 第二个参数以及二以后的参数,都是作为参数进行传递给所调用的函数 126 | 127 | - 备注 128 | 129 | - 该方法的语法和作用与`apply()` 方法类似,只有一个区别,就是 `call()` 方法接受的是**一个参数列表**,而 `apply()` 方法接受的是**一个包含多个参数的数组**。 130 | 131 | ```js 132 | var fullName = 'panpan' 133 | 134 | const info = { 135 | fullName: 'ice', 136 | getName: function(age, height) { 137 | console.log(this.fullName, age, height) 138 | } 139 | } 140 | 141 | function bar(fn) { 142 | fn.call(info, 20, 1.88) //ice 20 1.88 143 | } 144 | 145 | bar(info.getName) 146 | ``` 147 | 148 | #### 2.3.3 apply 149 | 150 | - 与`call`的方法类似,只是参数列表有所不同 151 | 152 | - 参数 153 | - `call` 参数为单个传递 154 | - `apply` 参数为数组传递 155 | 156 | ```js 157 | var fullName = 'panpan' 158 | 159 | const info = { 160 | fullName: 'ice', 161 | getName: function(age, height) { 162 | console.log(this.fullName, age, height) 163 | } 164 | } 165 | 166 | function bar(fn) { 167 | fn.apply(info, [20, 1.88]) //ice 20 1.88 168 | } 169 | 170 | bar(info.getName) 171 | ``` 172 | 173 | #### 2.3.4 bind 174 | 175 | - `bind`与`apply/call`之间有所不同,`bind`传入`this`,则是返回一个`this`绑定后的函数,调用返回后的函数,就可以拿到期望的this。 176 | - 参数传递则是 177 | - 调用`bind`时,可以传入参数 178 | - 调用`bind`返回的参数也可以进行传参 179 | 180 | ```javascript 181 | var fullName = 'panpan' 182 | 183 | const info = { 184 | fullName: 'ice', 185 | getName: function(age, height) { 186 | console.log(this.fullName, age, height) //ice 20 1.88 187 | } 188 | } 189 | 190 | function bar(fn) { 191 | let newFn = fn.bind(info, 20) 192 | newFn(1.88) 193 | } 194 | 195 | bar(info.getName) 196 | ``` 197 | 198 | ### 2.4 new绑定 199 | 200 |   谈到`new`关键字,就不得不谈构造函数,也就是JS中的 "类",后续原型篇章在跟大家继续探讨这个new关键字,首先要明白以下几点,`new Fn()`的时候发生了什么,有利于我们理解`this`的指向。 201 | 202 | 1. 创建了一个空对象 203 | 2. 将this指向所创建出来的对象 204 | 3. 把这个对象的`[[prototype]]` 指向了构造函数的prototype属性 205 | 4. 执行代码块代码 206 | 5. 如果没有明确返回一个非空对象,那么返回的对象就是这个创建出来的对象 207 | 208 | ```js 209 | function Person(name, age) { 210 | this.name = name 211 | this.age = age 212 | 213 | } 214 | 215 | const p1 = new Person('ice', 20) 216 | 217 | console.log(p1) // {name:'ice', age:20} 218 | 219 | ``` 220 | 221 | - 当我调用`new Person()`的时候,那个this所指向的其实就是`p1`对象 222 | 223 | ## 3. 绑定优先级 224 | 225 | ### 3.1 隐式绑定 > 默认绑定 226 | 227 | ```js 228 | function bar() { 229 | console.log(this) //info 230 | } 231 | 232 | const info = { 233 | bar: bar 234 | } 235 | info.bar() 236 | ``` 237 | 238 | - 虽然这边比较有些勉强,有些开发者会认为这是默认绑定的规则不能直接的显示谁的优先级高 239 | - 但是从另外一个角度来看,隐式绑定,的this丢失以后this才会指向`widonw或者undefined`,变相的可以认为隐式绑定 > 默认绑定 240 | 241 | ### 3.2 显示绑定 > 隐式绑定 242 | 243 | ```js 244 | var fullName = 'global ice' 245 | const info = { 246 | fullName: 'ice', 247 | getName: function() { 248 | console.log(this.fullName) 249 | } 250 | } 251 | 252 | info.getName.call(this) //global ice 253 | info.getName.apply(this) //global ice 254 | info.getName.bind(this)() //global ice 255 | ``` 256 | 257 | - 通过隐式绑定和显示绑定的一起使用很明显 显示绑定 > 隐式绑定 258 | 259 | ### 3.3 bind(硬绑定) > apply/call 260 | 261 | ```js 262 | function bar() { 263 | console.log(this) //123 264 | } 265 | 266 | const newFn = bar.bind(123) 267 | newFn.call(456) 268 | ``` 269 | 270 | ### 3.4 new绑定 > bind绑定 271 | 272 | 首先我们来说一下,为什么是和`bind`比较,而不能对`call`和`apply`比较,思考下面代码 273 | 274 | ```js 275 | const info = { 276 | height: 1.88 277 | } 278 | 279 | function Person(name, age) { 280 | this.name = name 281 | this.age = age 282 | } 283 | 284 | const p1 = new Person.call('ice', 20) 285 | //报错: Uncaught TypeError: Person.call is not a constructor 286 | ``` 287 | 288 | **new绑定和bind绑定比较** 289 | 290 | ```js 291 | const info = { 292 | height: 1.88 293 | } 294 | 295 | function Person(name, age) { 296 | this.name = name 297 | this.age = age 298 | } 299 | 300 | const hasBindPerson = Person.bind(info) 301 | 302 | const p1 = new hasBindPerson('ice', 20) 303 | 304 | console.log(info) //{height: 1.88} 305 | ``` 306 | 307 | - 我们通过`bind`对`Person`进行了一次劫持,硬绑定了this为`info`对象 308 | - `new` 返回的固定this的函数 309 | - 但是我们发现 并不能干预this的指向 310 | 311 | ### 3.5 总结 312 | 313 | `new关键字` > `bind` > `apply/call` > `隐式绑定` > `默认绑定` 314 | 315 | ## 4. 箭头函数 (arrow function) 316 | 317 | 首先箭头函数是`ES6`新增的语法 318 | 319 | ```JS 320 | const foo = () => {} 321 | ``` 322 | 323 | ### 4.1 箭头函数this 324 | 325 | ```js 326 | var fullName = 'global ice' 327 | 328 | const info = { 329 | fullName: 'ice', 330 | getName: () => { 331 | console.log(this.fullName) 332 | } 333 | } 334 | 335 | info.getName() //global ice 336 | ``` 337 | 338 | - 你会神奇的发现? 为什么不是默认绑定,打印结果为`ice` 339 | - 其实这是`ES6`的新特性,箭头函数不绑定`this`,它的`this`是上一层作用域,上一层作用域为`window` 340 | - 所以打印的结果是 `global ice` 341 | 342 | ### 4.2 箭头函数的应用场景 进阶 343 | 344 | - 需求: 在`getObjName`通过`this`拿到`info`中的`fullName` (值为`ice`的`fullName`) 345 | 346 | ```js 347 | const info = { 348 | fullName: 'ice', 349 | getName: function() { 350 | let _this = this 351 | return { 352 | fullName: 'panpan', 353 | getObjName: function() { 354 | console.log(this) // obj 355 | console.log(_this.fullName) 356 | } 357 | } 358 | } 359 | } 360 | 361 | const obj = info.getName() 362 | obj.getObjName() 363 | ``` 364 | 365 | 1. 当我调用 `info.getName()` 返回了一个新对象 366 | 2. 当我调用返回对象的`getObjName`方法时,我想拿到最外层的`fullName`,我通过,`getObjName`的this访问,拿到的this却是`obj`,不是我想要的结果 367 | 3. 我需要在调用`info.getName()` 把this保存下来,`info.getName()` 是通过隐式调用,所以它内部的this就是info对象 368 | 4. `getObjName`是obj对象,因为也是隐式绑定,this必定是obj对象,绕了一大圈我只是想拿到上层作用域的this而已,恰好箭头函数解决了这一问题 369 | 370 | ```js 371 | const info = { 372 | fullName: 'ice', 373 | getName: function() { 374 | return { 375 | fullName: 'panpan', 376 | getObjName: () => { 377 | console.log(this.fullName) 378 | } 379 | } 380 | } 381 | } 382 | 383 | const obj = info.getName() 384 | obj.getObjName() 385 | ``` 386 | 387 | ## 5. 总结 388 | 389 | ### 5.1 this的四种绑定规则 390 | 391 | 1. 默认绑定 392 | 2. 隐式绑定 393 | 3. 显示绑定 apply/call/bind(也称硬绑定) 394 | 4. new绑定 395 | 396 | ### 5.2 this的优先级 从高到低 397 | 398 | 1. new绑定 399 | 2. bind 400 | 3. call/apply 401 | 4. 隐式绑定 402 | 5. 默认绑定 403 | 404 | # 作者 405 | 406 | | Coder | 小红书ID | 创建时间 | 407 | | :---------- | :-------- | :--------- | 408 | | 落寞的前端👣 | 121450513 | 2022.10.02 | 409 | -------------------------------------------------------------------------------- /前端手撕代码面试题(一).md: -------------------------------------------------------------------------------- 1 | ## 前端手写代码题(一) 2 | 3 | ## 1. 手写Object.create 4 | 5 | 思路:将传入的对象作为原型 6 | 7 | ```javascript 8 | function create(obj) { 9 | function F() { 10 | } 11 | 12 | F.prototype = obj 13 | return new F() 14 | } 15 | ``` 16 | 17 | ## 2. 手写instanceof 18 | 19 | instanceof 运算符用于判断构造函数的 prototype 属性是否出现在对象的原型链中的任何位置。 20 | 21 | 实现步骤: 22 | 23 | 1. 首先获取类型的原型 24 | 2. 然后获得对象的原型 25 | 3. 然后一直循环判断对象的原型是否等于类型的原型,直到对象原型为 `null`,因为原型链最终为 `null` 26 | 27 | ```javascript 28 | function myInstanceof(left, right) { 29 | let proto = Object.getPrototypeOf(left), // 获取对象的原型 30 | prototype = right.prototype; // 获取构造函数的 prototype 对象 31 | 32 | // 判断构造函数的 prototype 对象是否在对象的原型链上 33 | while (true) { 34 | if (!proto) return false; 35 | if (proto === prototype) return true; 36 | 37 | proto = Object.getPrototypeOf(proto); 38 | } 39 | } 40 | 41 | ``` 42 | 43 | ## 3. 手写 new 44 | 45 | (1)首先创建了一个新的空对象 46 | 47 | (2)设置原型,将对象的原型设置为函数的 prototype 对象。 48 | 49 | (3)让函数的 this 指向这个对象,执行构造函数的代码(为这个新对象添加属性) 50 | 51 | (4)判断函数的返回值类型,如果是值类型,返回创建的对象。如果是引用类型,就返回这个引用类型的对象。 52 | 53 | ```javascript 54 | function myNew(fn, ...args) { 55 | // 判断参数是否是一个函数 56 | if (typeof fn !== "function") { 57 | return console.error("type error"); 58 | } 59 | // 创建一个对象,并将对象的原型绑定到构造函数的原型上 60 | const obj = Object.create(fn.prototype); 61 | const value = fn.apply(obj, args); // 调用构造函数,并且this绑定到obj上 62 | // 如果构造函数有返回值,并且返回的是对象,就返回value ;否则返回obj 63 | return value instanceof Object ? value : obj; 64 | } 65 | 66 | ``` 67 | 68 | ## 4. 手写promise(简易版) 69 | 70 | ```javascript 71 | class MyPromise { 72 | constructor(fn) { 73 | // 存储 reslove 回调函数列表 74 | this.callbacks = [] 75 | const resolve = (value) => { 76 | this.data = value // 返回值给后面的 .then 77 | while (this.callbacks.length) { 78 | let cb = this.callbacks.shift() 79 | cb(value) 80 | } 81 | } 82 | fn(resolve) 83 | } 84 | 85 | then(onResolvedCallback) { 86 | return new MyPromise((resolve) => { 87 | this.callbacks.push(() => { 88 | const res = onResolvedCallback(this.data) 89 | if (res instanceof MyPromise) { 90 | res.then(resolve) 91 | } else { 92 | resolve(res) 93 | } 94 | }) 95 | }) 96 | } 97 | } 98 | 99 | // 这是测试案例 100 | new MyPromise((resolve) => { 101 | setTimeout(() => { 102 | resolve(1) 103 | }, 1000) 104 | }).then((res) => { 105 | console.log(res) 106 | return new MyPromise((resolve) => { 107 | setTimeout(() => { 108 | resolve(2) 109 | }, 1000) 110 | }) 111 | }).then(res => { 112 | console.log(res) 113 | }) 114 | 115 | ``` 116 | 117 | ### 4.2 Promise.all() 118 | 119 | ```javascript 120 | MyPromise.all = function (promisesList) { 121 | return new MyPromise((resolve, reject) => { 122 | if (!Array.isArray(promiselList)) return reject(new Error('必须是数组')) 123 | if (!promisesList.length) return resolve([]) 124 | let arr = [], count = 0 125 | // 直接循环同时执行传进来的promise 126 | for (let i = 0, len = promisesList.length; i < len; i++) { 127 | // 因为有可能是 promise 有可能不是,所以用resolve()不管是不是都会自动转成promise 128 | Promise.resolve(promise).then(result => { 129 | // 由到promise在初始化的时候就执行了,.then只是拿结果而已,所以执行完成的顺序有可能和传进来的数组不一样 130 | // 也就是说直接push到arr的话,顺序有可能会出错 131 | count++ 132 | arr[i] = result 133 | // 不能用arr.length===len,是因为数组的特性 134 | // arr=[]; arr[3]='xx'; console.log(arr.length) 这打印出来会是4 而不是1 135 | if (count === len) resolve(arr) 136 | }).catch(err => reject(err)) 137 | } 138 | }) 139 | } 140 | ``` 141 | 142 | ### 4.3 Promise.race() 143 | 144 | 传参和上面的 all 一模一样,传入一个 Promise 实例集合的数组,然后全部同时执行,谁先快先执行完就返回谁,只返回一个结果 145 | 146 | ```javascript 147 | MyPromise.race = function (promisesList) { 148 | return new MyPromise((resolve, reject) => { 149 | // 直接循环同时执行传进来的promise 150 | for (const promise of promisesList) { 151 | // 直接返回出去了,所以只有一个,就看哪个快 152 | promise.then(resolve, reject) 153 | } 154 | }) 155 | } 156 | ``` 157 | 158 | ## 5. 防抖和节流 159 | 160 | 函数防抖是指在事件被触发 n 秒后再执行回调,如果在这 n 秒内事件又被触发,则重新计时。这可以使用在一些点击请求的事件上,避免因为用户的多次点击向后端发送多次请求。 161 | 162 | 函数节流是指规定一个单位时间,在这个单位时间内,只能有一次触发事件的回调函数执行,如果在同一个单位时间内某事件被触发多次,只有一次能生效。节流可以使用在 scroll 函数的事件监听上,通过事件节流来降低事件调用的频率。 163 | 164 | ```javascript 165 | // //防抖 166 | function debounce(fn, date) { 167 | let timer //声明接收定时器的变量 168 | return function (...arg) { // 获取参数 169 | timer && clearTimeout(timer) // 清空定时器 170 | timer = setTimeout(() => { // 生成新的定时器 171 | //因为箭头函数里的this指向上层作用域的this,所以这里可以直接用this,不需要声明其他的变量来接收 172 | fn.apply(this, arg) // fn() 173 | }, date) 174 | } 175 | } 176 | 177 | //-------------------------------- 178 | // 节流 179 | function debounce(fn, data) { 180 | let timer = +new Date() // 声明初始时间 181 | return function (...arg) { // 获取参数 182 | let newTimer = +new Date() // 获取触发事件的时间 183 | if (newTimer - timer >= data) { // 时间判断,是否满足条件 184 | fn.apply(this, arg) // 调用需要执行的函数,修改this值,并且传入参数 185 | timer = +new Date() // 重置初始时间 186 | } 187 | } 188 | } 189 | 190 | ``` 191 | 192 | ## 6. 手写 call 函数 193 | 194 | **call 函数的实现步骤:** 195 | 196 | 1. 判断调用对象是否为函数,即使我们是定义在函数的原型上的,但是可能出现使用 call 等方式调用的情况。 197 | 2. 判断传入上下文对象是否存在,如果不存在,则设置为 window 。 198 | 3. 处理传入的参数,截取第一个参数后的所有参数。 199 | 4. 将函数作为上下文对象的一个属性。 200 | 5. 使用上下文对象来调用这个方法,并保存返回结果。 201 | 6. 删除刚才新增的属性。 202 | 7. 返回结果。 203 | 204 | ```javascript 205 | // call函数实现 206 | Function.prototype.myCall = function (context) { 207 | // 判断调用对象 208 | if (typeof this !== "function") { 209 | console.error("type error"); 210 | } 211 | // 获取参数 212 | let args = [...arguments].slice(1), 213 | result = null; 214 | // 判断 context 是否传入,如果未传入则设置为 window 215 | context = context || window; 216 | // 将调用函数设为对象的方法 217 | context.fn = this; 218 | // 调用函数 219 | result = context.fn(...args); 220 | // 将属性删除 221 | delete context.fn; 222 | return result; 223 | }; 224 | ``` 225 | 226 | ## 7. 手写 apply 函数 227 | 228 | **apply 函数的实现步骤:** 229 | 230 | 1. 判断调用对象是否为函数,即使我们是定义在函数的原型上的,但是可能出现使用 call 等方式调用的情况。 231 | 2. 判断传入上下文对象是否存在,如果不存在,则设置为 window 。 232 | 3. 将函数作为上下文对象的一个属性。 233 | 4. 判断参数值是否传入 234 | 5. 使用上下文对象来调用这个方法,并保存返回结果。 235 | 6. 删除刚才新增的属性 236 | 7. 返回结果 237 | 238 | ```javascript 239 | // apply 函数实现 240 | Function.prototype.myApply = function (context) { 241 | // 判断调用对象是否为函数 242 | if (typeof this !== "function") { 243 | throw new TypeError("Error"); 244 | } 245 | let result = null; 246 | // 判断 context 是否存在,如果未传入则为 window 247 | context = context || window; 248 | // 将函数设为对象的方法 249 | context.fn = this; 250 | // 调用方法 251 | if (arguments[1]) { 252 | result = context.fn(...arguments[1]); 253 | } else { 254 | result = context.fn(); 255 | } 256 | // 将属性删除 257 | delete context.fn; 258 | return result; 259 | }; 260 | ``` 261 | 262 | ## 8. 手写 bind 函数 263 | 264 | **bind 函数的实现步骤:** 265 | 266 | 1. 判断调用对象是否为函数,即使我们是定义在函数的原型上的,但是可能出现使用 call 等方式调用的情况。 267 | 2. 保存当前函数的引用,获取其余传入参数值。 268 | 3. 创建一个函数返回 269 | 4. 函数内部使用 apply 来绑定函数调用,需要判断函数作为构造函数的情况,这个时候需要传入当前函数的 this 给 apply 调用,其余情况都传入指定的上下文对象。 270 | 271 | ```javascript 272 | // bind 函数实现 273 | Function.prototype.myBind = function (context) { 274 | // 判断调用对象是否为函数 275 | if (typeof this !== "function") { 276 | throw new TypeError("Error"); 277 | } 278 | // 获取参数 279 | var args = [...arguments].slice(1), 280 | fn = this; 281 | return function Fn() { 282 | // 根据调用方式,传入不同绑定值 283 | return fn.apply( 284 | this instanceof Fn ? this : context, 285 | args.concat(...arguments) 286 | ); 287 | }; 288 | }; 289 | ``` 290 | 291 | ## 9. 函数柯里化的实现 292 | 293 | 函数柯里化指的是一种将使用多个参数的一个函数转换成一系列使用一个参数的函数的技术。 294 | 295 | ```js 296 | function curry(fn, ...args) { 297 | return fn.length <= args.length ? fn(...args) : curry.bind(null, fn, ...args); 298 | } 299 | ``` 300 | 301 | ## 10. 手写AJAX请求 302 | 303 | **创建AJAX请求的步骤:** 304 | 305 | - 创建一个 XMLHttpRequest 对象。 306 | - 在这个对象上**使用 open 方法创建一个 HTTP 请求**,open 方法所需要的参数是请求的方法、请求的地址、是否异步和用户的认证信息。 307 | - 在发起请求前,可以为这个对象**添加一些信息和监听函数**。比如说可以通过 setRequestHeader 方法来为请求添加头信息。还可以为这个对象添加一个状态监听函数。一个 XMLHttpRequest 对象一共有 5 308 | 个状态,当它的状态变化时会触发onreadystatechange 事件,可以通过设置监听函数,来处理请求成功后的结果。当对象的 readyState 变为 4 309 | 的时候,代表服务器返回的数据接收完成,这个时候可以通过判断请求的状态,如果状态是 2xx 或者 304 的话则代表返回正常。这个时候就可以通过 response 中的数据来对页面进行更新了。 310 | - 当对象的属性和监听函数设置完成后,最后调**用 sent 方法来向服务器发起请求**,可以传入参数作为发送的数据体。 311 | 312 | ```javascript 313 | const SERVER_URL = "/server"; 314 | let xhr = new XMLHttpRequest(); 315 | // 创建 Http 请求 316 | xhr.open("GET", SERVER_URL, true); 317 | // 设置状态监听函数 318 | xhr.onreadystatechange = function () { 319 | if (this.readyState !== 4) return; 320 | // 当请求成功时 321 | if (this.status === 200) { 322 | handle(this.response); 323 | } else { 324 | console.error(this.statusText); 325 | } 326 | }; 327 | // 设置请求失败时的监听函数 328 | xhr.onerror = function () { 329 | console.error(this.statusText); 330 | }; 331 | // 设置请求头信息 332 | xhr.responseType = "json"; 333 | xhr.setRequestHeader("Accept", "application/json"); 334 | // 发送 Http 请求 335 | xhr.send(null); 336 | ``` 337 | ## 结语 338 | 339 | >   今天的面试题的分享就到这边啦,明天继续给大家分享~ 340 | 341 | ## 作者 342 | 343 | | Coder | 小红书ID | 创建时间 | 344 | | :---------- | :-------- | :--------- | 345 | | 落寞的前端👣 | 121450513 | 2022.10.27 | 346 | -------------------------------------------------------------------------------- /vue面试题四.md: -------------------------------------------------------------------------------- 1 | ## 前端常见面试题(Vue篇)(四) 2 | 3 | ## 27. 简述 mixin、extends 的覆盖逻辑 4 | 5 | **(1)mixin 和 extends** mixin 和 extends均是用于合并、拓展组件的,两者均通过 mergeOptions 方法实现合并。 6 | 7 | - mixins 接收一个混入对象的数组,其中混入对象可以像正常的实例对象一样包含实例选项,这些选项会被合并到最终的选项中。Mixin 钩子按照传入顺序依次调用,并在调用组件自身的钩子之前被调用。 8 | - extends 主要是为了便于扩展单文件组件,接收一个对象或构造函数。 9 | 10 | ![6.jpg](https://raw.githubusercontent.com/summmer-is-hot/marks/main/assets/vue/3_6.webp?) 11 | 12 | **(2)mergeOptions 的执行过程** 13 | 14 | - 规范化选项(normalizeProps、normalizeInject、normalizeDirectives) 15 | - 对未合并的选项,进行判断 16 | 17 | ![7.jpg](https://raw.githubusercontent.com/summmer-is-hot/marks/main/assets/vue/3_7.webp?) 18 | 19 | - 合并处理。根据一个通用 Vue 实例所包含的选项进行分类逐一判断合并,如 props、data、 methods、watch、computed、生命周期等,将合并结果存储在新定义的 options 对象里。 20 | - 返回合并结果 options。 21 | 22 | ### extend 有什么作用 23 | 24 | 这个 API 很少用到,作用是扩展组件生成一个构造器,通常会与 `$mount` 一起使用。 25 | 26 | ### 什么是 mixin ? 27 | 28 | Mixin 使我们能够为 Vue 组件编写可插拔和可重用的功能。 29 | 30 | 如果希望在多个组件之间重用一组组件选项,例如生命周期 hook、 方法等,则可以将其编写为 mixin,并在组件中简单的引用它。 31 | 32 | 然后将 mixin 的内容合并到组件中。如果你要在 mixin 中定义生命周期 hook,那么它在执行时将优化于组件自已的 hook。 33 | 34 | ### mixin 和 mixins 区别 35 | 36 | `mixin` 用于全局混入,会影响到每个组件实例,通常插件都是这样做初始化的。 37 | 38 | 虽然文档不建议在应用中直接使用 `mixin`,但是如果不滥用的话也是很有帮助的,比如可以全局混入封装好的 `ajax` 或者一些工具函数等等。 39 | 40 | `mixins` 应该是最常使用的扩展组件的方式了。如果多个组件中有相同的业务逻辑,就可以将这些逻辑剥离出来,通过 `mixins` 混入代码,比如上拉下拉加载数据这种逻辑等等。 另外需要注意的是 `mixins` 混入的钩子函数会先于组件内的钩子函数执行,并且在遇到同名选项的时候也会有选择性的进行合并。 41 | 42 | ## 28. data为什么是一个函数而不是对象 43 | 44 | 对象为引用类型,当复用组件时,由于数据对象都指向同一个data对象,当在一个组件中修改data时,其他重用的组件中的data会同时被修改;而使用返回对象的函数,由于每次返回的都是一个新对象(Object的实例),引用地址不同,则不会出现这个问题。 45 | 46 | ## 29. 动态给vue的data添加一个新的属性时会发生什么?怎样解决? 47 | 48 | **问题:** 数据虽然更新了, 但是页面没有更新 49 | 50 | **原因:** 51 | 52 | 1. `vue2`是用过`Object.defineProperty`实现数据响应式 53 | 2. 当我们访问定义的属性或者修改属性值的时候都能够触发`setter`与`getter` 54 | 3. 但是我们为`obj`添加新属性的时候,却无法触发事件属性的拦截 55 | 4. 原因是一开始`obj`的要定义的属性被设成了响应式数据,而`新增的属性`并没有通过`Object.defineProperty`设置成响应式数据 56 | 57 | **解决方案:** 58 | 59 | 1. Vue.set() 60 | - 通过`Vue.set`向响应式对象中添加一个`property`,并确保这个新 `property`同样是响应式的,且触发视图更新 61 | 2. Object.assign() 62 | - 直接使用`Object.assign()`添加到对象的新属性不会触发更新 63 | - 应创建一个新的对象,合并原对象和混入对象的属性 64 | 3. $forceUpdate 65 | - 如果你发现你自己需要在 `Vue`中做一次强制更新,99.9% 的情况,是你在某个地方做错了事 66 | - `$forceUpdate`迫使`Vue` 实例重新渲染 67 | - PS:仅仅影响实例本身和插入插槽内容的子组件,而不是所有子组件。 68 | 69 | 总结 70 | 71 | - 如果为对象添加**少量**的新属性,可以直接采用`Vue.set()` 72 | - 如果需要为新对象添加**大量**的新属性,则通过`Object.assign()`创建新对象 73 | - 如果你实在不知道怎么操作时,可采取`$forceUpdate()`进行强制刷新 (不建议) 74 | 75 | PS:`vue3`是用过`proxy`实现数据响应式的,直接动态添加新属性仍可以实现数据响应式 76 | 77 | ## 30. Vue data 中某一个属性的值发生改变后,视图会立即同步执行重新渲染吗 78 | 79 | **不会立即同步执行重新渲染**。Vue 实现响应式并不是数据发生变化之后 DOM 立即变化,而是按一定的策略进行 DOM 的更新。Vue 在更新 DOM 时是`异步`执行的。只要侦听到数据变化, Vue 将开启一个队列,并缓冲在同一事件循环中发生的所有数据变更。 80 | 81 | 如果同一个watcher被多次触发,只会被推入到队列中一次。这种在缓冲时去除重复数据对于避免不必要的计算和 DOM 操作是非常重要的。然后,在下一个的事件循环tick中,Vue 刷新队列并执行实际(已去重的)工作。 82 | 83 | ## 31. vue如何监听(检测)对象或者数组某个属性的变化 84 | 85 | 当在项目中直接设置数组的某一项的值,或者直接设置对象的某个属性值,这个时候,你会发现页面并没有更新。这是因为Object.defineProperty()限制,监听不到变化。 86 | 87 | 解决方式: 88 | 89 | - this.$set(你要改变的数组/对象,你要改变的位置/key,你要改成什么value) 90 | 91 | ```js 92 | this.$set(this.arr, 0, "OBKoro1"); // 改变数组 93 | this.$set(this.obj, "c", "OBKoro1"); // 改变对象 94 | ``` 95 | 96 | - 调用以下几个数组的方法 97 | 98 | ```markdown 99 | splice()、push()、pop()、shift()、unshift()、sort()、reverse() 100 | ``` 101 | 102 | vue源码里缓存了array的原型链,然后重写了这几个方法,触发这几个方法的时候会observer数据,意思是使用这些方法不用我们再进行额外的操作,视图自动进行更新。 推荐使用splice方法会比较好自定义,因为splice可以在数组的任何位置进行删除/添加操作 103 | 104 | **vm.`$set` 的实现原理是:** 105 | 106 | - 如果目标是数组,直接使用数组的 splice 方法触发相应式; 107 | - 如果目标是对象,会先判读属性是否存在、对象是否是响应式,最终如果要对属性进行响应式处理,则是通过调用 defineReactive 方法进行响应式处理( defineReactive 方法就是 Vue 在初始化对象时,给对象属性采用 Object.defineProperty 动态添加 getter 和 setter 的功能所调用的方法) 108 | 109 | ## **32. assets和static的区别** 110 | 111 | - **相同点:** `assets` 和 `static` 两个都是存放静态资源文件。项目中所需要的资源文件图片,字体图标,样式文件等都可以放在这两个文件下,这是相同点 112 | - **不相同点:** `assets` 中存放的静态资源文件在项目打包时,也就是运行 `npm run build` 时会将 `assets` 中放置的静态资源文件进行打包上传,所谓打包简单点可以理解为压缩体积,代码格式化。而压缩后的静态资源文件最终也都会放置在 `static` 文件中跟着 `index.html` 一同上传至服务器。`static` 中放置的静态资源文件就不会要走打包压缩格式化等流程,而是直接进入打包好的目录,直接上传至服务器。因为避免了压缩直接进行上传,在打包时会提高一定的效率,但是 `static` 中的资源文件由于没有进行压缩等操作,所以文件的体积也就相对于 `assets` 中打包后的文件提交较大点。在服务器中就会占据更大的空间。 113 | - **建议:** 将项目中 `template`需要的样式文件js文件等都可以放置在 `assets` 中,走打包这一流程。减少体积。而项目中引入的第三方的资源文件如`iconfoont.css` 等文件可以放置在 `static` 中,因为这些引入的第三方文件已经经过处理,不再需要处理,直接上传。 114 | 115 | ## 33. Vue的性能优化(项目优化)有哪些 116 | 117 | ### **(1)编码阶段** 118 | 119 | - 尽量减少data中的数据,data中的数据都会增加getter和setter,会收集对应的watcher 120 | - v-if和v-for不能连用 121 | - 如果需要使用v-for给每项元素绑定事件时使用事件代理 122 | - SPA 页面采用keep-alive缓存组件 123 | - 在更多的情况下,使用v-if替代v-show 124 | - key保证唯一 125 | - 使用路由懒加载、异步组件 126 | - 防抖、节流 127 | - 第三方模块按需导入 128 | - 长列表滚动到可视区域动态加载 129 | - 图片懒加载 130 | 131 | ### **(2)SEO优化** 132 | 133 | - 预渲染 134 | - 服务端渲染SSR 135 | 136 | ### **(3)打包优化** 137 | 138 | - 压缩代码 139 | - Tree Shaking/Scope Hoisting 140 | - 使用cdn加载第三方模块 141 | - 多线程打包happypack 142 | - splitChunks抽离公共文件 143 | - sourceMap优化 144 | 145 | ### **(4)用户体验** 146 | 147 | - 骨架屏 148 | - PWA 149 | - 还可以使用缓存(客户端缓存、服务端缓存)优化、服务端开启gzip压缩等。 150 | 151 | ## 34. Vue的template模版编译原理 152 | 153 | vue中的模板template无法被浏览器解析并渲染,因为这不属于浏览器的标准,不是正确的HTML语法,所有需要将template转化成一个JavaScript函数,这样浏览器就可以执行这一个函数并渲染出对应的HTML元素,就可以让视图跑起来了,这一个转化的过程,就成为模板编译。模板编译又分三个阶段,解析parse,优化optimize,生成generate,最终生成可执行函数render。 154 | 155 | - **解析阶段**:使用大量的正则表达式对template字符串进行解析,将标签、指令、属性等转化为抽象语法树AST。 156 | - **优化阶段**:遍历AST,找到其中的一些静态节点并进行标记,方便在页面重渲染的时候进行diff比较时,直接跳过这一些静态节点,优化runtime的性能。 157 | - **生成阶段**:将最终的AST转化为render函数字符串。 158 | 159 | ## 35. template和jsx的有什么分别? 160 | 161 | 对于 runtime 来说,只需要保证组件存在 render 函数即可,而有了预编译之后,只需要保证构建过程中生成 render 函数就可以。在 webpack 中,使用`vue-loader`编译.vue文件,内部依赖的`vue-template-compiler`模块,在 webpack 构建过程中,将template预编译成 render 函数。与 react 类似,在添加了jsx的语法糖解析器`babel-plugin-transform-vue-jsx`之后,就可以直接手写render函数。 162 | 163 | 所以,**template和jsx的都是render的一种表现形式**,不同的是:JSX相对于template而言,具有更高的灵活性,在复杂的组件中,更具有优势,而 template 虽然显得有些呆滞。但是 template 在代码结构上更符合视图与逻辑分离的习惯,更简单、更直观、更好维护。 164 | 165 | ## 35.2 讲讲什么是 JSX ? 166 | 167 | jsx是JavaScript的一种语法扩展,它跟模板语言很接近,但是它充分具备JavaScript的能力 当 Facebook 第一次发布 React 时,他们还引入了一种新的 JS 方言 JSX,将原始 HTML 模板嵌入到 JS 代码中。JSX 代码本身不能被浏览器读取,必须使用Babel和webpack等工具将其转换为传统的JS。 JSX中的标签可以是单标签,也可以是双标签,但必须保证标签是闭合的。 168 | 169 | ## 36. 对SSR的理解 170 | 171 | SSR也就是服务端渲染,也就是将Vue在客户端把标签渲染成HTML的工作放在服务端完成,然后再把html直接返回给客户端 172 | 173 | **SSR的优势**: 174 | 175 | - 更好的SEO 176 | - 首屏加载速度更快 177 | 178 | **SSR的缺点**: 179 | 180 | - 开发条件会受到限制,服务器端渲染只支持beforeCreate和created两个钩子; 181 | - 当需要一些外部扩展库时需要特殊处理,服务端渲染应用程序也需要处于Node.js的运行环境; 182 | - 更多的服务端负载。 183 | 184 | ## 37. vue初始化页面闪动问题 185 | 186 | 使用vue开发时,在vue初始化之前,由于div是不归vue管的,所以我们写的代码在还没有解析的情况下会容易出现花屏现象,看到类似于{{message}}的字样,虽然一般情况下这个时间很短暂,但是还是有必要让解决这个问题的。 187 | 188 | 首先:在css里加上以下代码: 189 | 190 | ![8.png](https://raw.githubusercontent.com/summmer-is-hot/marks/main/assets/vue/3_8.webp?) 191 | 192 | 如果没有彻底解决问题,则在根元素加上`style="display: none;" :style="{display: 'block'}"` 193 | 194 | ## 38. 虚拟DOM 195 | 196 | ### (1). 什么是虚拟DOM 197 | 198 | 虚拟(Virtual) DOM 其实就是一棵以 JavaScript 对象(VNode 节点)作为基础的树,用对象属性来描述节点,**相当于在js和真实dom中间加来一个缓存**,利用dom **diff算法避免没有必要的dom操作**,从而提高性能。当然算法有时并不是最优解,因为它需要兼容很多实际中可能发生的情况,比如后续会讲到两个节点的dom树移动。 199 | 200 | 在vue中一般都是通过修改元素的state,订阅者根据state的变化进行编译渲染,底层的实现可以简单理解为三个步骤: 201 | 202 | - 1、用JavaScript对象结构表述dom树的结构,然后用这个树构建一个真正的dom树,插到浏览器的页面中。 203 | - 2、当状态改变了,也就是我们的state做出修改,vue便会重新构造一棵树的对象树,然后用这个新构建出来的树和旧树进行对比(只进行同层对比),记录两棵树之间的差异。 204 | - 3、把记录的差异再重新应用到所构建的真正的dom树,视图就更新了。 205 | 206 | 它的表达方式就是把每一个标签都转为一个对象,这个对象可以有三个属性:`tag`、`props`、`children` 207 | 208 | - **tag**:必选。就是标签。也可以是组件,或者函数 209 | - **props**:非必选。就是这个标签上的属性和方法 210 | - **children**:非必选。就是这个标签的内容或者子节点,如果是文本节点就是字符串,如果有子节点就是数组。换句话说 如果判断 children 是字符串的话,就表示一定是文本节点,这个节点肯定没有子元素 211 | 212 | ### (2). 虚拟DOM的解析过程 213 | 214 | 虚拟DOM的解析过程: 215 | 216 | - 首先对将要插入到文档中的 DOM 树结构进行分析,使用 js 对象将其表示出来,比如一个元素对象,包含 TagName、props 和 Children 这些属性。然后将这个 js 对象树给保存下来,最后再将 DOM 片段插入到文档中。 217 | - 当页面的状态发生改变,需要对页面的 DOM 的结构进行调整的时候,首先根据变更的状态,重新构建起一棵对象树,然后将这棵新的对象树和旧的对象树进行比较,记录下两棵树的的差异。 218 | - 最后将记录的有差异的地方应用到真正的 DOM 树中去,这样视图就更新了。 219 | 220 | ### (3). 为什么要用虚拟DOM 221 | 222 | **① 保证性能下限,在不进行手动优化的情况下,提供过得去的性能** 看一下页面渲染的流程:**解析HTML -> 生成DOM -> 生成 CSSOM -> Layout -> Paint -> Compiler** 下面对比一下修改DOM时真实DOM操作和Virtual DOM的过程,来看一下它们重排重绘的性能消耗∶ 223 | 224 | - 真实DOM∶ 生成HTML字符串+重建所有的DOM元素 225 | - 虚拟DOM∶ 生成vNode+ DOMDiff+必要的dom更新 226 | 227 | Virtual DOM的更新DOM的准备工作耗费更多的时间,也就是JS层面,相比于更多的DOM操作它的消费是极其便宜的。尤雨溪在社区论坛中说道∶ 框架给你的保证是,你不需要手动优化的情况下,依然可以给你提供过得去的性能。 228 | 229 | **② 跨平台** Virtual DOM本质上是JavaScript的对象,它可以很方便的跨平台操作,比如服务端渲染、uniApp等。 230 | 231 | ### (4). 虚拟DOM真的比真实DOM性能好吗 232 | 233 | - 首次渲染大量DOM时,由于多了一层虚拟DOM的计算,会比innerHTML插入慢。 234 | - 正如它能保证性能下限,在真实DOM操作的时候进行针对性的优化时,还是更快的。 235 | 236 | ## 39. Diff算法 237 | 238 | 在新老虚拟DOM对比时: 239 | 240 | - 首先,对比节点本身,**判断是否为同一节点**,如果不为相同节点,则删除该节点重新创建节点进行替换 241 | - 如果为相同节点,进行**patchVNode**,判断如何对该节点的子节点进行处理,先判断一方有子节点一方没有子节点的情况(如果新的children没有子节点,将旧的子节点移除) 242 | - 比较如果都有子节点,则进行**updateChildren**,判断如何对这些新老节点的子节点进行操作(diff核心)。 243 | - 匹配时,找到相同的子节点,递归比较子节点 244 | 245 | 在diff中,只对同层的子节点进行比较,放弃跨级的节点比较,使得时间复杂从O(n3)降低值O(n),也就是说,只有当新旧children都为多个子节点时才需要用核心的Diff算法进行同层级比较。 246 | 247 | ## 40. SPA单页面应用 248 | 249 | **概念:** 250 | 251 | - SPA单页面应用(SinglePage Web Application),指只有一个主页面的应用,一开始只需要加载一次js、css等相关资源。所有内容都包含在主页面,对每一个功能模块组件化。单页应用跳转,就是切换相关组件,仅仅刷新局部资源。 252 | - MPA多页面应用 (MultiPage Application),指有多个独立页面的应用,每个页面必须重复加载js、css等相关资源。多页应用跳转,需要整页资源刷新。 253 | 254 | **单页应用优缺点** 255 | 256 | 1. 优点: 257 | - 具有桌面应用的即时性、网站的可移植性和可访问性 258 | - 用户体验好、快,内容的改变不需要重新加载整个页面 259 | 2. 缺点: 260 | - 首次渲染速度相对较慢 261 | - 不利于搜索引擎的抓取 262 | 263 | **解决首次加载慢的问题** 264 | 265 | ```markdown 266 | 1. 减少入口文件体积 267 | 2. 静态资源本地缓存 268 | 3. UI框架按需加载 269 | 4. 路由懒加载 270 | ``` 271 | 272 | ## 41. 使用异步组件有什么好处? 273 | 274 | 1. 节省打包出的结果,异步组件分开打包,采用jsonp的方式进行加载,有效解决文件过大的问题。 275 | 2. 核心就是包组件定义变成一个函数,依赖`import()` 语法,可以实现文件的分割加载。 276 | 277 | ## 结语 278 | 279 | >   今天的面试题的分享就到这边啦,明天继续给大家分享~ 280 | 281 | ## 作者 282 | 283 | | Coder | 小红书ID | 创建时间 | 284 | | :---------- | :-------- | :--------- | 285 | | 落寞的前端👣 | 121450513 | 2022.10.22 | 286 | -------------------------------------------------------------------------------- /vue面试题六.md: -------------------------------------------------------------------------------- 1 | ## 前端常见面试题(Vue篇)(六) 2 | 3 | ## Vuex 4 | 5 | ## 1. 什么Vuex ,谈谈你对它的理解? 6 | 7 | 1. 首先vuex的出现是为了解决web组件化开发的过程中,各组件之间传值的复杂和混乱的问题 8 | 2. 将我们在多个组件中需要共享的数据放到state中, 9 | 3. 要获取或格式化数据需要使用getters, 10 | 4. 改变state中的数据,可以使用mutation,但是只能包含同步的操作,在具体组件里面调用的方式`this.$store.commit('xxxx')` 11 | 5. Action也是改变state中的数据,不过是提交的mutation,并且可以包含异步操作,在组件中的调用方式`this.$store.dispatch('xxx')`; 在actions里面使用的commit('调用mutation') 12 | 13 | **Vuex** 是一个专为 Vue.js应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。Vuex 也集成到 Vue 的官方调试工具 devtools extension,提供了诸如零配置的 time-travel 调试、状态快照导入导出等高级调试功能。 14 | 15 | ## 2. Vuex**各模块在核心流程中的主要功能:** 16 | 17 | `Vue Components`∶ Vue组件。HTML页面上,负责接收用户操作等交互行为,执行dispatch方法触发对应action进行回应。 18 | 19 | `dispatch`∶操作行为触发方法,是唯一能执行action的方法。 20 | 21 | `actions`∶ 操作行为处理模块。负责处理Vue Components接收到的所有交互行为。包含同步/异步操作,支持多个同名方法,按照注册的顺序依次触发。向后台API请求的操作就在这个模块中进行,包括触发其他action以及提交mutation的操作。该模块提供了Promise的封装,以支持action的链式触发。 22 | 23 | `commit`∶状态改变提交操作方法。对mutation进行提交,是唯一能执行mutation的方法。 24 | 25 | `mutations`∶状态改变操作方法。是Vuex修改state的唯一推荐方法,其他修改方式在严格模式下将会报错。该方法只能进行同步操作,且方法名只能全局唯一。操作之中会有一些hook暴露出来,以进行state的监控等。 26 | 27 | `state`∶ 页面状态管理容器对象。集中存储VueComponents中data对象的零散数据,全局唯一,以进行统一的状态管理。页面显示所需的数据从该对象中进行读取,利用Vue的细粒度数据响应机制来进行高效的状态更新。 28 | 29 | `getters`∶ state对象读取方法。图中没有单独列出该模块,应该被包含在了render中,Vue Components通过该方法读取全局state对象。 30 | 31 | ## 2.1 简述Vuex的数据传输流程 32 | 33 | 当组件进行数据修改的时候我们需要调**用dispatch来触发actions里面的方法**。actions里面的每个方法中都会有一个commit方法,当方法执行的时候会通过**commit来触发mutations里面的方法进行数据的修改**。mutations里面的每个函数都会有一个state参数,这样就可以在**mutations里面进行state的数据修改**,当数据修改完毕后,会传导给页面。页面的数据也会发生改变 34 | 35 | ## 3. vuex中有几个核心属性,分别是什么? 36 | 37 | 一共有5个核心属性,分别是: 38 | 39 | - `state`唯一数据源,Vue 实例中的 data 遵循相同的规则 40 | - `mutation`更改 Vuex 的 store 中的状态的唯一方法是提交 mutation,非常类似于事件,通过store.commit 方法触发 41 | - `action`action 类似于 mutation,不同在于action 提交的是 mutation,而不是直接变更状态,action 可以包含任意异步操作 42 | - `module` 由于使用单一状态树,应用的所有状态会集中到一个比较大的对象。当应用变得非常复杂时,store 对象就有可能变得相当臃肿。为了解决以上问题,Vuex 允许我们将 store 分割成模块(module)。 43 | 44 | ```js 45 | const moduleA = { 46 | state: () => ({ ... }), 47 | mutations: { ... }, 48 | actions: { ... }, 49 | getters: { ... } 50 | } 51 | 52 | const moduleB = { 53 | state: () => ({ ... }), 54 | mutations: { ... }, 55 | actions: { ... } 56 | } 57 | 58 | const store = new Vuex.Store({ 59 | modules: { 60 | a: moduleA, 61 | b: moduleB 62 | } 63 | }) 64 | 65 | store.state.a // -> moduleA 的状态 66 | store.state.b // -> moduleB 的状态 67 | ``` 68 | 69 | - `getters` 可以认为是 store 的计算属性,就像计算属性一样,getter 的返回值会根据它的依赖被缓存起来,且只有当它的依赖值发生了改变才会被重新计算。Getter 会暴露为 store.getters 对象,你可以以属性的形式访问这些值. 70 | 71 | ```js 72 | const store = new Vuex.Store({ 73 | state: { 74 | todos: [ 75 | { id: 1, text: '...', done: true }, 76 | { id: 2, text: '...', done: false } 77 | ] 78 | }, 79 | getters: { 80 | doneTodos: state => { 81 | return state.todos.filter(todo => todo.done) 82 | } 83 | } 84 | }) 85 | 86 | store.getters.doneTodos // -> [{ id: 1, text: '...', done: true }] 87 | ``` 88 | 89 | ## 4. Vuex中action和mutation的区别 90 | 91 | `mutation`中的操作是一系列的同步函数,用于修改state中的变量的的状态。当使用vuex时需要通过commit来提交需要操作的内容。mutation 非常类似于事件:每个 mutation 都有一个字符串的 事件类型 (type) 和 一个 回调函数 (handler)。这个回调函数就是实际进行状态更改的地方,并且它会接受 state 作为第一个参数: 92 | 93 | ![1.png](https://raw.githubusercontent.com/summmer-is-hot/marks/main/assets/vue/6_1.webp?) 94 | 95 | 当触发一个类型为 increment 的 mutation 时,需要调用此函数: 96 | 97 | ![2.png](https://raw.githubusercontent.com/summmer-is-hot/marks/main/assets/vue/6_2.webp?) 98 | 99 | 而`action`类似于mutation,不同点在于: 100 | 101 | - action 可以包含任意异步操作。 102 | - action 提交的是 mutation,而不是直接变更状态。 103 | 104 | ![3.png](https://raw.githubusercontent.com/summmer-is-hot/marks/main/assets/vue/6_3.webp?) 105 | 106 | `action`函数接受一个与 store 实例具有相同方法和属性的 context 对象,因此你可以调用 context.commit 提交一个 mutation,或者通过 context.state 和 context.getters 来获取 state 和 getters。 所以,两者的不同点如下: 107 | 108 | - mutation专注于修改State,理论上是修改State的唯一途径;action 用来处理业务代码、异步请求。 109 | - mutation:必须同步执行;action :可以异步,但不能直接操作State。 110 | - 在视图更新时,先触发actions,actions再触发mutation 111 | - mutation的参数是state,它包含store中的数据;store的参数是context,它是 state 的父级,包含 state、getters 112 | 113 | ## 5. vuex的getter的作用 114 | 115 | `getter`有点类似 Vue.js 的**计算属性**,当我们需要从 store 的 state 中派生出一些状态,那么我们就需要使用 getter,getter 会接收 state 作为第 一个参数,而且 getter 的返回值会根据它的依赖被缓存起来,只有 getter 中的依赖值(state 中的某个需要派生状态的值)发生改变的时候才会被重新计算。 116 | 117 | ## 6. Vuex 和 localStorage 的区别 118 | 119 | **(1)** **最重要的区别** 120 | 121 | - vuex存储在**内存**中 122 | - localstorage 则以**文件**的方式存储在**本地**,只能存储字符串类型的数据,存储对象需要 JSON的stringify和parse方法进行处理。 读取内存比读取硬盘速度要快 123 | 124 | **(2)应用场景** 125 | 126 | - Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。vuex用于组件之间的传值。 127 | - localstorage是本地存储,是将数据存储到浏览器的方法,一般是在跨页面传递数据时使用 。 128 | - Vuex能做到数据的响应式,localstorage不能 129 | 130 | **(3)永久性** 131 | 132 | **刷新页面时vuex存储的值会丢失,localstorage不会。** 133 | 134 | **注意:** 对于不变的数据确实可以用localstorage可以代替vuex,但是当两个组件共用一个数据源(对象或数组)时,如果其中一个组件改变了该数据源,希望另一个组件响应该变化时,localstorage无法做到,原因就是上面的最重要的区别。 135 | 136 | ## 7. Vuex页面刷新时丢失怎么处理 137 | 138 | 用sessionStorage 或者 localstorage 存储数据 139 | 140 | 存储: sessionStorage.setItem( '名', JSON.stringify(值) ) 使用: sessionStorage.getItem('名') ---得到的值为字符串类型,用JSON.parse()去引号; 141 | 142 | ## 8. Vuex和单纯的全局对象有什么区别? 143 | 144 | - Vuex 的状态存储是**响应式**的。当 Vue 组件从 store 中读取状态的时候,若 store 中的状态发生变化,那么相应的组件也会相应地得到高效更新。 145 | - 不能直接改变 store 中的状态。改变 store 中的状态的唯一途径就是显式地提交 (commit) mutation。这样可以方便地跟踪每一个状态的变化,从而能够实现一些工具帮助更好地了解我们的应用。 146 | 147 | ## 9. Redux(react的) 和 Vuex 有什么区别,它们的共同思想 148 | 149 | **(1)Redux 和 Vuex区别** 150 | 151 | - Vuex改进了Redux中的Action和Reducer函数,以mutations变化函数取代Reducer,无需switch,只需在对应的mutation函数里改变state值即可 152 | - Vuex由于Vue自动重新渲染的特性,无需订阅重新渲染函数,只要生成新的State即可 153 | - Vuex数据流的顺序是∶View调用store.commit提交对应的请求到Store中对应的mutation函数->store改变(vue检测到数据变化自动渲染) 154 | 155 | 通俗点理解就是,vuex 弱化 dispatch,通过commit进行 store状态的一次更变;取消了action概念,不必传入特定的 action形式进行指定变更;弱化reducer,基于commit参数直接对数据进行转变,使得框架更加简易; 156 | 157 | **(2)共同思想** 158 | 159 | - 单—的数据源 160 | - 变化可以预测 161 | 162 | 本质上:redux与vuex都是对mvvm思想的服务,将数据从视图中抽离的一种方案; 形式上:vuex借鉴了redux,将store作为全局的数据中心,进行mode管理; 163 | 164 | ## 10. 为什么要用 Vuex 或者 Redux 165 | 166 | 由于传参的方法对于多层嵌套的组件将会非常繁琐,并且对于兄弟组件间的状态传递无能为力。我们经常会采用父子组件直接引用或者通过事件来变更和同步状态的多份拷贝。以上的这些模式非常脆弱,通常会导致代码无法维护。 167 | 168 | 所以需要把组件的**共享**状态抽取出来,以一个全局单例模式管理。在这种模式下,组件树构成了一个巨大的"视图",不管在树的哪个位置,任何组件都能获取状态或者触发行为。 169 | 170 | 另外,通过定义和隔离状态管理中的各种概念并强制遵守一定的规则,代码将会变得更结构化且易维护。 171 | 172 | ## 11. 为什么 Vuex 的 mutation 中不能做异步操作? 173 | 174 | - Vuex中所有的状态更新的唯一途径都是mutation,异步操作通过 Action 来提交 mutation实现,这样可以方便地跟踪每一个状态的变化,从而能够实现一些工具帮助更好地了解我们的应用。 175 | - 每个mutation执行完成后都会对应到一个新的状态变更,这样devtools就可以打个快照存下来,然后就可以实现 time-travel 了。如果mutation支持异步操作,就没有办法知道状态是何时更新的,无法很好的进行状态的追踪,给调试带来困难。 176 | 177 | ## 12. Vuex的严格模式是什么,有什么作用,如何开启? 178 | 179 | 在严格模式下,无论何时发生了状态变更且不是由mutation函数引起的,将会抛出错误。这能保证所有的状态变更都能被调试工具跟踪到。 180 | 181 | 在Vuex.Store 构造器选项中开启,如下 182 | 183 | ![4.png](https://raw.githubusercontent.com/summmer-is-hot/marks/main/assets/vue/6_4.webp?) 184 | 185 | ## 13. 如何在组件中批量使用Vuex的getter属性 186 | 187 | 使用`mapGetters`辅助函数, 利用对象展开运算符将getter混入computed 对象中 188 | 189 | ![5.png](https://raw.githubusercontent.com/summmer-is-hot/marks/main/assets/vue/6_5.webp?) 190 | 191 | ## 14. 如何在组件中重复使用Vuex的mutation 192 | 193 | 使用`mapMutations`辅助函数,在组件中这么使用 194 | 195 | ![6.png](https://raw.githubusercontent.com/summmer-is-hot/marks/main/assets/vue/6_6.webp?) 196 | 197 | 然后调用`this.setNumber(10)`相当调用`this.$store.commit('SET_NUMBER',10)` 198 | 199 | ## 15. Vuex的辅助函数怎么用 200 | 201 | 比如当一个组件需要获取多个状态时候,将这些状态都声明为计算属性会有些重复和冗余。为了解决这个问题,我们可以使用 `mapState`辅助函数帮助我们生成计算属性,让你少按几次键。 202 | 203 | ### mapState 204 | 205 | ```javascript 206 | import { mapState } from 'vuex' 207 | 208 | export default { 209 | // ... 210 | computed:{ 211 | ...mapState({ 212 | // 箭头函数可使代码更简练 213 | count: state => state.count, 214 | // 传字符串参数 'count' 等同于 `state => state.count` 215 | countAlias: 'count', 216 | 217 | // 为了能够使用 `this` 获取局部状态,必须使用常规函数 218 | countPlusLocalState (state) { 219 | return state.count + this.localCount 220 | } 221 | }) 222 | } 223 | } 224 | ``` 225 | 226 | 定义的属性名与state中的名称相同时,可以传入一个数组 227 | 228 | ```vue 229 | //定义state 230 | const state={ 231 | count:1, 232 | } 233 | 234 | //在组件中使用辅助函数 235 | computed:{ 236 | ...mapState(['count']) 237 | } 238 | ``` 239 | 240 | ### mapGetters 241 | 242 | ```vue 243 | computed:{ 244 | ...mapGetters({ 245 | // 把 `this.doneCount` 映射为 `this.$store.getters.doneTodosCount` 246 | doneCount: 'doneTodosCount' 247 | }) 248 | } 249 | ``` 250 | 251 | 当属性名与getters中定义的相同时,可以传入一个数组 252 | 253 | 总结: 254 | 255 | - mapState与mapGetters都用computed来进行映射 256 | - 在组件中映射完成后,通过this.映射属性名进行使用 257 | 258 | ### mapMutations 259 | 260 | ```vue 261 | methods:{ 262 | ...mapMutations({ 263 | add: 'increment' // 将 `this.add()` 映射为 `this.$store.commit('increment')` 264 | }) 265 | } 266 | ``` 267 | 268 | 当属性名与mapMutations中定义的相同时,可以传入一个数组 269 | 270 | ```vue 271 | methods:{ 272 | ...mapMutations([ 273 | 'increment', // 将 `this.increment()` 映射为 `this.$store.commit('increment')` 274 | 275 | // `mapMutations` 也支持载荷: 276 | 'incrementBy' // 将 `this.incrementBy(amount)` 映射为 `this.$store.commit('incrementBy', amount)` 277 | ]), 278 | } 279 | ``` 280 | 281 | ### mapActions 282 | 283 | ```vue 284 | methods:{ 285 | ...mapActions({ 286 | add: 'increment' // 将 `this.add()` 映射为 `this.$store.dispatch('increment')` 287 | }) 288 | } 289 | ``` 290 | 291 | 当属性名与mapActions中定义的相同时,可以传入一个数组 292 | 293 | ```vue 294 | methods:{ 295 | ...mapActions([ 296 | 'increment', // 将 `this.increment()` 映射为 `this.$store.dispatch('increment')` 297 | // `mapActions` 也支持载荷: 298 | 'incrementBy' // 将 `this.incrementBy(amount)` 映射为 `this.$store.dispatch('incrementBy', amount)` 299 | ]), 300 | } 301 | ``` 302 | 303 | 总结 304 | 305 | - mapMutations与mapActions都在methods中进行映射 306 | - 映射之后变成一个方法 307 | 308 | ## 结语 309 | 310 | >   今天的面试题的分享就到这边啦,明天继续给大家分享~ 311 | 312 | ## 作者 313 | 314 | | Coder | 小红书ID | 创建时间 | 315 | | :---------- | :-------- | :--------- | 316 | | 落寞的前端👣 | 121450513 | 2022.10.24 | 317 | -------------------------------------------------------------------------------- /vue面试题二.md: -------------------------------------------------------------------------------- 1 | ## 前端常见面试题(Vue篇)(二) 2 | 3 | ## 13. v-model 可以被用在自定义组件上吗?如果可以,如何使用? 4 | 5 | **可以**。v-model 实际上是一个语法糖,用在自定义组件上也是同理: 6 | 7 | ![1.png](https://raw.githubusercontent.com/summmer-is-hot/marks/main/assets/vue/2_1.webp?) 8 | 9 | 相当于 10 | 11 | ![2.png](https://raw.githubusercontent.com/summmer-is-hot/marks/main/assets/vue/2_2.webp?) 12 | 13 | 显然,custom-input 与父组件的交互如下: 14 | 15 | 1. 父组件将`searchText`变量传入custom-input 组件,使用的 prop 名为`value`; 16 | 2. custom-input 组件向父组件传出名为`input`的事件,父组件将接收到的值赋值给`searchText`; 17 | 18 | 所以,custom-input 组件的实现应该类似于这样: 19 | 20 | ![3.png](https://raw.githubusercontent.com/summmer-is-hot/marks/main/assets/vue/2_3.webp?) 21 | 22 | ## 14. v-model和.sync的对比 23 | 24 | v-model与.sync的共同点:都是`语法糖`,都可以实现父子组件中的数据的双向通信。 25 | 26 | v-model与.sync的不共同点: 27 | 28 | **v-model:** 29 | 30 | 1.父组件 v-model="" 子组件 @(input,value) 31 | 32 | 2.一个组件只能绑定`一个`v-model 33 | 34 | 3.v-model针对更多的是最终操作结果,是`双向绑定的结果`,是`value`,是一种`change操作`。 35 | 36 | **.sync:** 37 | 38 | 1.父组件 :my-prop-name.sync 子组件@update:my-prop-name 的模式来替代事件触发,实现父子组件间的双向绑定。 39 | 40 | 2.一个组件可以`多个`属性用.sync修饰符,可以同时"双向绑定多个“prop” 41 | 42 | 3..sync针对更多的是各种各样的状态,是`状态的互相传递`,是`status`,是一种`update操作`。 43 | 44 | ## 15. 计算属性computed 和watch 的区别是什么? 45 | 46 | #### **`Computed`:** 47 | 48 | - 它**支持缓存**,只有依赖的数据发生了变化,才会重新计算 49 | - **不支持异步**,当Computed中有异步操作时,无法监听数据的变化 50 | - computed的值会**默认走缓存**,计算属性是基于它们的响应式依赖进行缓存的,也就是基于data声明过,或者父组件传递过来的props中的数据进行计算的。 51 | - **如果一个属性是由其他属性计算而来的,这个属性依赖其他的属性,一般会使用computed** 52 | - 如果computed属性的属性值是函数,那么**默认使用get方法**,函数的返回值就是属性的属性值;在computed中,属性有一个get方法和一个set方法,当数据发生变化时,会调用set方法。 53 | 54 | #### **`Watch`:** 55 | 56 | - 它**不支持缓存**,数据变化时,它就会触发相应的操作 57 | - **支持异步**监听 58 | - 监听的函数接收**两个参数**,第一个参数是最新的值,第二个是变化之前的值 59 | - 当一个属性发生变化时,就需要执行相应的操作 60 | - 监听数据必须是data中声明的或者父组件传递过来的props中的数据,当发生变化时,会触发其他操作,函数有两个的参数: 61 | - **immediate**:组件加载立即触发回调函数 62 | - **deep**:深度监听,发现数据内部的变化,在复杂数据类型中使用,例如数组中的对象发生变化。需要注意的是,deep无法监听到数组和对象内部的变化。 63 | 64 | 当想要执行异步或者昂贵的操作以响应不断的变化时,就需要使用watch。 65 | 66 | **总结:** 67 | 68 | - `computed` 计算属性 : 依赖其它属性值,并且 computed 的值有缓存,只有它依赖的属性值发生改变,下一次获取 computed 的值时才会重新计算 computed 的值。 69 | - `watch` 侦听器 : 更多的是**观察**的作用,**无缓存性**,类似于某些数据的监听回调,每当监听的数据变化时都会执行回调进行后续操作。 70 | 71 | **运用场景:** 72 | 73 | - 当需要进行数值计算,并且依赖于其它数据时,应该使用 computed,因为可以利用 computed 的缓存特性,避免每次获取值时都要重新计算。 74 | - 当需要在数据变化时执行异步或开销较大的操作时,应该使用 watch,使用 watch 选项允许执行异步操作 ( 访问一个 API ),限制执行该操作的频率,并在得到最终结果前,设置中间状态。这些都是计算属性无法做到的。 75 | 76 | ## 16. Computed 和 Methods 的区别 77 | 78 | 可以将同一函数定义为一个 method 或者一个计算属性。对于最终的结果,两种方式是相同的 79 | 80 | **不同点:** 81 | 82 | - `computed`: 计算属性是基于它们的依赖进行缓存的,只有在它的相关依赖发生改变时才会重新求值; 83 | - `method` 调用总会执行该函数。 84 | 85 | ## 17. 什么是组件 86 | 87 | **组件就是把图形、非图形的各种逻辑均抽象为一个`统一的概念`(组件)来实现开发的模式,在`Vue`中每一个`.vue`文件都可以视为一个组件** 88 | 89 | #### **组件的优势** 90 | 91 | 1. 降低整个系统的耦合度,在保持接口不变的情况下,我们可以替换不同的组件快速完成需求 92 | 2. 调试方便,由于整个系统是通过组件组合起来的,在出现问题的时候,可以用排除法直接移除组件,或者根据报错的组件快速定位问题,之所以能够快速定位,是因为每个组件之间低耦合,职责单一,所以逻辑会比分析整个系统要简单 93 | 3. 提高可维护性,由于每个组件的职责单一,并且组件在系统中是被复用的,所以对代码进行优化可获得系统的整体升级 94 | 95 | ## 18. 什么是插件 96 | 97 | **插件通常用来为 `Vue` 添加全局功能**。插件的功能范围没有严格的限制——一般有下面几种: 98 | 99 | - 添加全局方法或者属性。如: `vue-custom-element` 100 | - 添加全局资源:指令/过滤器/过渡等。如 `vue-touch` 101 | - 添加全局公共组件 Vue.component() 102 | - 添加全局公共指令 Vue.directive() 103 | - 通过全局混入来添加一些组件选项。如`vue-router` 104 | - 添加 `Vue` 实例方法,通过把它们添加到 `Vue.prototype` 上实现。 105 | - 一个库,提供自己的 `API`,同时提供上面提到的一个或多个功能。如`vue-router` 106 | 107 | ### 18.2 Vue2和Vue3怎么注册全局组件 108 | 109 | Vue2使用 110 | ```js 111 | //main.js 112 | import Vue from 'vue' 113 | import header from "@/components/header.vue" 114 | Vue.component("header", header) 115 | ``` 116 | 117 | Vue3使用 118 | ```js 119 | //main.js 120 | import { createApp } from 'vue' 121 | import App from './App.vue' 122 | import header from "@/components/header.vue" 123 | 124 | const app = createApp(App) 125 | app.component('header', header ) 126 | 127 | ``` 128 | 129 | ```js 130 | const app = createApp(App) 131 | app.component('组件名','组件对象') 132 | ``` 133 | 134 | ### 18.3 Vue2、Vue3怎么封装自定义插件并使用/ Vue.use() (install) 135 | 136 | **Vue2** 137 | 138 | 在components.index.js里,定义一个函数或对象,在里面可以使用Vue.component全局注册组件,并暴露出去 139 | 140 | 在main.js里使用Vue.use( ),参数类型必须是 object 或 Function 141 | 142 | **Vue3** 143 | 144 | 在components.index.ts里,定义一个函数或对象,在里面可以使用app.component全局注册组件,并暴露出去 145 | 146 | 在main.ts里使用app.use( ),参数类型必须是 object 或 Function 147 | 148 | ------ 149 | 150 | 如果是 Function 那么这个函数就被当做 install 方法 151 | 152 | 如果是 object 则需要定义一个 install 方法 153 | 154 | ## 19. 组件通信/ 组件传值的方法 155 | 156 | **什么是组件通信** 157 | 158 | 组件(`.vue`)通过某种方式来传递信息以达到某个目的 159 | 160 | **组件通信解决了什么问题** 161 | 162 | 每个组件之间的都有独自的作用域,组件间的数据是无法共享的但实际开发工作中我们常常需要让组件之间共享数据,这也是组件通信的目的要让它们互相之间能进行通讯,这样才能构成完整系统 163 | 164 | ### **(1)props / $emit (父子)** 165 | 166 | 父组件通过`props`向子组件传递数据,子组件通过`$emit`和父组件通信 167 | 168 | **1. 父组件向子组件传值** 169 | 170 | `props`只能是父组件向子组件进行传值,`props`使得父子组件之间形成了一个单向下行绑定。子组件的数据会随着父组件不断更新。 171 | 172 | `props` 可以显示定义一个或一个以上的数据,对于接收的数据,可以是各种数据类型,同样也可以传递一个函数。 173 | 174 | `props`属性名规则:若在`props`中使用驼峰形式,模板中需要使用短横线的形式 175 | 176 | ![4.png](https://raw.githubusercontent.com/summmer-is-hot/marks/main/assets/vue/2_4.webp?) 177 | 178 | ![5.png](https://raw.githubusercontent.com/summmer-is-hot/marks/main/assets/vue/2_5.webp?) 179 | 180 | **2. 子组件向父组件传值** 181 | 182 | - `$emit`绑定一个自定义事件,当这个事件被执行的时就会将参数传递给父组件,而父组件通过`v-on`监听并接收参数。 183 | 184 | ### (2)依赖注入 provide / inject(父子、祖孙) 185 | 186 | 这种方式就是Vue中的**依赖注入**,该方法用于**父子组件之间的通信**。当然这里所说的父子不一定是真正的父子,也可以是祖孙组件,在层数很深的情况下,可以使用这种方法来进行传值。就不用一层一层的传递了。 187 | 188 | `provide / inject`是Vue提供的两个钩子,和`data`、`methods`是同级的。并且`provide`的书写形式和`data`一样。 189 | 190 | - `provide` 钩子用来发送数据或方法 191 | - `inject`钩子用来接收数据或方法 192 | 193 | 在父组件中: 194 | 195 | ![6.png](https://raw.githubusercontent.com/summmer-is-hot/marks/main/assets/vue/2_6.webp?) 196 | 197 | 在子组件中: 198 | 199 | ![7.png](https://raw.githubusercontent.com/summmer-is-hot/marks/main/assets/vue/2_7.webp?) 200 | 201 | 还可以这样写,这样写就可以访问父组件中的所有属性: 202 | 203 | ![8.png](https://raw.githubusercontent.com/summmer-is-hot/marks/main/assets/vue/2_8.webp?) 204 | 205 | **注意:** 依赖注入所提供的属性是**非响应式**的。 206 | 207 | ### (3)ref / $refs (父子,兄弟) 208 | 209 | `ref`: 这个属性用在子组件上,它的引用就指向了子组件的实例。可以通过实例来访问组件的数据和方法。 210 | 211 | 这种方式也是实现**兄弟组件**之间的通信。子组件1通过**this.`$emit`\**通知父组件调用函数,父组件的函数里用\**this.$refs**拿到子组件2的方法,这样就实现兄弟组件之间的通信。 212 | 213 | 在子组件中: 214 | 215 | ![9.png](https://raw.githubusercontent.com/summmer-is-hot/marks/main/assets/vue/2_9.webp?) 216 | 217 | 在父组件中: 218 | 219 | ![10.png](https://raw.githubusercontent.com/summmer-is-hot/marks/main/assets/vue/2_10.webp?) 220 | 221 | ### (4)`$parent` / `$children` (父子) 222 | 223 | - 使用`$parent`可以让组件访问父组件的实例(访问的是上一级父组件的属性和方法) 224 | - 使用`$children`可以让组件访问子组件的实例,但是,`$children`并不能保证顺序,并且访问的数据也不是响应式的。 225 | 226 | 在子组件中: 227 | 228 | ![11.png](https://raw.githubusercontent.com/summmer-is-hot/marks/main/assets/vue/2_11.webp?) 229 | 230 | 在父组件中: 231 | 232 | ![12.png](https://raw.githubusercontent.com/summmer-is-hot/marks/main/assets/vue/2_12.webp?) 233 | 234 | 在上面的代码中,子组件获取到了父组件的`parentVal`值,父组件改变了子组件中`message`的值。 **需要注意:** 235 | 236 | - 通过`$parent`访问到的是上一级父组件的实例,可以使用`$root`来访问根组件的实例 237 | - 在组件中使用`$children`拿到的是所有的子组件的实例,它是一个数组,并且是无序的 238 | - 在根组件`#app`上拿`$parent`得到的是`new Vue()`的实例,在这实例上再拿`$parent`得到的是`undefined`,而在最底层的子组件拿`$children`是个空数组 239 | - `$children` 的值是**数组**,而`$parent`是个**对象** 240 | 241 | ### (5)`$attrs` / `$listeners` (祖孙) 242 | 243 | 考虑一种场景,如果A是B组件的父组件,B是C组件的父组件。如果想要组件A给组件C传递数据,这种隔代的数据,该使用哪种方式呢? 244 | 245 | 如果是用`props/$emit`来一级一级的传递,确实可以完成,但是比较复杂;如果使用事件总线,在多人开发或者项目较大的时候,维护起来很麻烦;如果使用Vuex,的确也可以,但是如果仅仅是传递数据,那可能就有点浪费了。 246 | 247 | 针对上述情况,Vue引入了`$attrs / $listeners`,实现组件之间的跨代通信。 248 | 249 | 先来看一下`inheritAttrs`,它的默认值true,继承所有的父组件属性除`props`之外的所有属性;`inheritAttrs:false` 只继承class属性 。 250 | 251 | - `$attrs`:继承所有的父组件属性(除了prop传递的属性、class 和 style ),一般用在子组件的子元素上 252 | - `$listeners`:该属性是一个对象,里面包含了作用在这个组件上的所有监听器,可以配合 `v-on="$listeners"` 将所有的事件监听器指向这个组件的某个特定的子元素。(相当于子组件继承父组件的事件) 253 | 254 | A组件(`APP.vue`): 255 | 256 | ![13.png](https://raw.githubusercontent.com/summmer-is-hot/marks/main/assets/vue/2_13.webp?) 257 | 258 | B组件(`Child1.vue`): 259 | 260 | ![14.png](https://raw.githubusercontent.com/summmer-is-hot/marks/main/assets/vue/2_14.webp?) 261 | 262 | C 组件 (`Child2.vue`): 263 | 264 | ![15.png](https://raw.githubusercontent.com/summmer-is-hot/marks/main/assets/vue/2_15.webp?) 265 | 266 | 在上述代码中: 267 | 268 | - C组件中能直接触发test的原因在于 B组件调用C组件时 使用 v-on 绑定了`$listeners` 属性 269 | - 在B组件中通过v-bind 绑定`$attrs`属性,C组件可以直接获取到A组件中传递下来的props(除了B组件中props声明的) 270 | 271 | ### (6)eventBus事件总线(`$emit / $on`)(任意组件通信) 272 | 273 | `eventBus`事件总线适用于**父子组件**、**非父子组件**等之间的通信,使用步骤如下: 274 | 275 | **(1)创建事件中心管理组件之间的通信** 276 | 277 | ![16.png](https://raw.githubusercontent.com/summmer-is-hot/marks/main/assets/vue/2_16.webp?) 278 | 279 | **(2)发送事件** 假设有两个兄弟组件`firstCom`和`secondCom`: 280 | 281 | ![17.png](https://raw.githubusercontent.com/summmer-is-hot/marks/main/assets/vue/2_17.webp?) 282 | 283 | 在`firstCom`组件中发送事件: 284 | 285 | ![18.png](https://raw.githubusercontent.com/summmer-is-hot/marks/main/assets/vue/2_18.webp?) 286 | 287 | **(3)接收事件** 在`secondCom`组件中接收事件: 288 | 289 | ![19.png](https://raw.githubusercontent.com/summmer-is-hot/marks/main/assets/vue/2_19.webp?) 290 | 291 | 在上述代码中,这就相当于将`num`值存贮在了事件总线中,在其他组件中可以直接访问。事件总线就相当于一个桥梁,不用组件通过它来通信。 292 | 293 | 虽然看起来比较简单,但是这种方法也有不变之处,如果项目过大,使用这种方式进行通信,后期维护起来会很困难。 294 | 295 | ### (7)总结 296 | 297 | **(1)父子组件间通信** 298 | 299 | - 子组件通过 props 属性来接受父组件的数据,然后父组件在子组件上注册监听事件,子组件通过 emit 触发事件来向父组件发送数据。 300 | - 通过 ref 属性给子组件设置一个名字。父组件通过 `$refs` 组件名来获得子组件,子组件通过 `$parent` 获得父组件,这样也可以实现通信。 301 | - 使用 provide/inject,在父组件中通过 provide提供变量,在子组件中通过 inject 来将变量注入到组件中。不论子组件有多深,只要调用了 inject 那么就可以注入 provide中的数据。 302 | 303 | **(2)兄弟组件间通信** 304 | 305 | - 使用 eventBus 的方法,它的本质是通过创建一个空的 Vue 实例来作为消息传递的对象,通信的组件引入这个实例,通信的组件通过在这个实例上监听和触发事件,来实现消息的传递。 306 | - 通过 `$parent/$refs` 来获取到兄弟组件,也可以进行通信。 307 | 308 | **(3)任意组件之间** 309 | 310 | - 使用 eventBus ,其实就是创建一个事件中心,相当于中转站,可以用它来传递事件和接收事件。 311 | 312 | 如果业务逻辑复杂,很多组件之间需要同时处理一些公共的数据,这个时候采用上面这一些方法可能不利于项目的维护。这个时候可以使用 vuex ,vuex 的思想就是将这一些公共的数据抽离出来,将它作为一个全局的变量来管理,然后其他组件就可以对这个公共数据进行读写操作,这样达到了解耦的目的。 313 | 314 | 315 | ## 结语 316 | 317 | >   今天的面试题的分享就到这边啦,明天继续给大家分享~ 318 | 319 | ## 作者 320 | 321 | | Coder | 小红书ID | 创建时间 | 322 | | :---------- | :-------- | :--------- | 323 | | 落寞的前端👣 | 121450513 | 2022.10.20 | 324 | -------------------------------------------------------------------------------- /vue面试题三.md: -------------------------------------------------------------------------------- 1 | ## 前端常见面试题(Vue篇)(三) 2 | 3 | ## 20. 子组件可以直接改变父组件的数据吗? 4 | 5 | 子组件不可以直接改变父组件的数据。这样做主要是为了维护父子组件的单向数据流。每次父级组件发生更新时,子组件中所有的 prop 都将会刷新为最新的值。如果这样做了,Vue 会在浏览器的控制台中发出警告。 6 | 7 | Vue提倡单向数据流,即父级 props 的更新会流向子组件,但是反过来则不行。这是为了防止意外的改变父组件状态,使得应用的数据流变得难以理解,导致数据流混乱。如果破坏了单向数据流,当应用复杂时,debug 的成本会非常高。 8 | 9 | **只能通过 `$emit` 派发一个自定义事件,父组件接收到后,由父组件修改。** 10 | 11 | ## 21. 生命周期 12 | 13 | ### (1). 说一下Vue的生命周期 14 | 15 | vue 实例从创建到销毁的过程就是生命周期。 16 | 17 | 也就是从开始创建、初始化数据、编译模板、挂在 dom -> 渲染、更新 -> 渲染、准备销毁、销毁在等一系列过程 18 | 19 | vue的声明周期常见的主要分为4大阶段8大钩子函数 20 | 21 | **第一阶段:创建前 / 后** 22 | 23 | **beforeCreate(创建前)** :数据观测和初始化事件还未开始,此时 data 的响应式追踪、event/watcher 都还没有被设置,也就是说不能访问到data、computed、watch、methods上的方法和数据。 24 | 25 | **created(创建后)** :实例创建完成,实例上配置的 options 包括 data、computed、watch、methods 等都配置完成,但是此时渲染得节点还未挂载到 DOM,所以不能访问到 `$el` 属性。 26 | 27 | **第二阶段: 渲染前 / 后** 28 | 29 | **beforeMount(挂载前)** :在挂载开始之前被调用,相关的render函数首次被调用。实例已完成以下的配置:编译模板,把data里面的数据和模板生成html。此时还没有挂载html到页面上。 30 | 31 | **mounted(挂载后)** :在el被新创建的 vm.$el 替换,并挂载到实例上去之后调用。实例已完成以下的配置:用上面编译好的html内容替换el属性指向的DOM对象。完成模板中的html渲染到html 页面中。此过程中进行ajax交互。 32 | 33 | **第三阶段: 更新前 / 后** 34 | 35 | **beforeUpdate(更新前)** :响应式数据更新时调用,此时虽然响应式数据更新了,但是对应的真实 DOM 还没有被渲染。 36 | 37 | **updated(更新后)** :在由于数据更改导致的虚拟DOM重新渲染和打补丁之后调用。此时 DOM 已经根据响应式数据的变化更新了。调用时,组件 DOM已经更新,所以可以执行依赖于DOM的操作。然而在大多数情况下,应该避免在此期间更改状态,因为这可能会导致更新无限循环。该钩子在服务器端渲染期间不被调用。 38 | 39 | **第四阶段: 销毁前 / 后** 40 | 41 | **beforeDestroy(销毁前)** :实例销毁之前调用。这一步,实例仍然完全可用,`this` 仍能获取到实例。 42 | 43 | **destroyed(销毁后)** :实例销毁后调用,调用后,Vue 实例指示的所有东西都会解绑定,所有的事件监听器会被移除,所有的子实例也会被销毁。该钩子在服务端渲染期间不被调用。 44 | 45 | **另外三个生命周期函数不常用** 46 | 47 | 另外还有 `keep-alive` 独有的生命周期,分别为 `activated` 和 `deactivated` 。用 `keep-alive` 包裹的组件在切换时不会进行销毁,而是缓存到内存中并执行 `deactivated` 钩子函数,命中缓存渲染后会执行 `activated` 钩子函数。 48 | 49 | `errorCapured`钩子,当捕获一个来自子孙组件的错误时被调用。此钩子会收到三个参数:错误对象、发生错误的组件实例以及一个包含错误来源信息的字符串。此钩子可以返回 `false` 以阻止该错误继续向上传播。 50 | 51 | ![1.jpg](https://raw.githubusercontent.com/summmer-is-hot/marks/main/assets/vue/3_1.webp?) 52 | 53 | ![2.jpg](https://raw.githubusercontent.com/summmer-is-hot/marks/main/assets/vue/3_2.webp?) 54 | 55 | ### (2). Vue 子组件和父组件执行顺序 56 | 57 | **加载渲染过程:** 58 | 59 | 1. 父组件 beforeCreate 60 | 2. 父组件 created 61 | 3. 父组件 beforeMount 62 | 4. 子组件 beforeCreate 63 | 5. 子组件 created 64 | 6. 子组件 beforeMount 65 | 7. 子组件 mounted 66 | 8. 父组件 mounted 67 | 68 | **更新过程:** 69 | 70 | 1. 父组件 beforeUpdate 71 | 2. 子组件 beforeUpdate 72 | 3. 子组件 updated 73 | 4. 父组件 updated 74 | 75 | **销毁过程:** 76 | 77 | 1. 父组件 beforeDestroy 78 | 2. 子组件 beforeDestroy 79 | 3. 子组件 destroyed 80 | 4. 父组件 destroyed 81 | 82 | ### (3). created和mounted的区别 83 | 84 | - `created`:在模板渲染成html前调用,即通常初始化某些属性值,然后再渲染成视图。 85 | - `mounted`:在模板渲染成html后调用,通常是初始化页面完成后,再对html的dom节点进行一些需要的操作。 86 | 87 | ### (4). 一般在哪个生命周期请求异步数据 88 | 89 | 我们可以在钩子函数`created`、`beforeMount`、`mounted` 中进行调用,因为在这三个钩子函数中,data 已经创建,可以将服务端端返回的数据进行赋值。 90 | 91 | 推荐在 created 钩子函数中调用异步请求,因为在 created 钩子函数中调用异步请求有以下优点: 92 | 93 | - 能更快获取到服务端数据,减少页面加载时间,用户体验更好; 94 | - SSR不支持 beforeMount 、mounted 钩子函数,放在 created 中有助于一致性。 95 | 96 | ## 22. 组件缓存 keep-alive 97 | 98 | **组件缓存** 99 | 100 | 组件的缓存可以在进行动态组件切换的时候对组件内部数据进行缓存,而不是走销毁流程 101 | 102 | 使用场景: 多表单切换,对表单内数据进行保存 103 | 104 | **keep-alive** 105 | 106 | 包裹动态组件时,会缓存不活动的组件实例,而不是销毁它们。 107 | 108 | 是一个抽象组件:它自身不会渲染一个 DOM 元素,也不会出现在父组件链中。 当组件在``内被切换,它的`activated`和 `deactivated`这两个生命周期钩子函数将会被对应执行 。 109 | 110 | ### keep-alive的参数(include,exclude) 111 | 112 | - include(包含): 名称匹配的组件会被缓存-->include的值为组件的name。 113 | - exclude(排除): 任何名称匹配的组件都不会被缓存。 114 | - max - 数量 决定最多可以缓存多少组件。 115 | 116 | ### **keep-alive的使用** 117 | 118 | 1. 搭配``使用 119 | 2. 搭配路由使用 ( 需配置路由meta信息的`keepAlive`属性 ) 120 | 3. 清除缓存组件 121 | - 在组件跳转之前使用后置路由守卫判断组件是否缓存 122 | - ( beforeRouteLeave( to, from, next ){ from.meta.keepAlive = false } 123 | 124 | ### **keep-alive的两个钩子函数** 125 | 126 | | activated | deactivated | 127 | | ------------------------------------ | -------------------------------- | 128 | | 在 `keep-alive` 组件激活时调用 | 在`keep-alive` 组件停用时调用 | 129 | | 该钩子函数在服务器端渲染期间不被调用 | 该钩子在服务器端渲染期间不被调用 | 130 | 131 | 使用`keep-alive`会将数据保留在内存中,如果要在每次进入页面的时候获取最新的数据,需要在 `activated`阶段获取数据,承担原来`created`钩子函数中获取数据的任务。 132 | 133 | **注意:** 只有组件被`keep-alive`包裹时,这两个生命周期函数才会被调用,如果作为正常组件使用,是不会被调用的 134 | 135 | 使用 exclude 排除之后,就算被包裹在 keep-alive 中,这两个钩子函数依然不会被调用!在服务端渲染时,此钩子函数也不会被调用。 136 | 137 | 设置了缓存的组件钩子调用情况: 138 | 139 | 第一次进入:beforeRouterEnter ->created->…->activated->…->deactivated> beforeRouteLeave 140 | 141 | 后续进入时:beforeRouterEnter ->activated->deactivated> beforeRouteLeave 142 | 143 | ### **keep-alive主要流程** 144 | 145 | 1. 判断组件 name ,不在 include 或者在 exclude 中,直接返回 VNode,说明该组件不被缓存。 146 | 2. 获取组件实例 key ,如果有获取实例的 key,否则重新生成。 147 | 3. key生成规则,cid +"∶∶"+ tag ,仅靠cid是不够的,因为相同的构造函数可以注册为不同的本地组件。 148 | 4. 如果缓存对象内存在,则直接从缓存对象中获取组件实例给 VNode ,不存在则添加到缓存对象中。 5.最大缓存数量,当缓存组件数量超过 max 值时,清除 keys 数组内第一个组件。 149 | 150 | ### **keep-alive 的实现** 151 | 152 | ![3.jpg](https://raw.githubusercontent.com/summmer-is-hot/marks/main/assets/vue/3_3.webp?) 153 | 154 | ## 23. 过滤器的作用,如何实现一个过滤器 155 | 156 | 根据过滤器的名称,过滤器是用来过滤数据的,在Vue中使用`filters`来过滤数据,`filters`不会修改数据,而是过滤数据,改变用户看到的输出(计算属性 `computed` ,方法 `methods` 都是通过修改数据来处理数据格式的输出显示)。 157 | 158 | **使用场景:** 159 | 160 | - 需要格式化数据的情况,比如需要处理时间、价格等数据格式的输出 / 显示。 161 | - 比如后端返回一个 **年月日的日期字符串**,前端需要展示为 **多少天前** 的数据格式,此时就可以用`fliters`过滤器来处理数据。 162 | 163 | 过滤器是一个函数,它会把表达式中的值始终当作函数的第一个参数。过滤器用在**插值表达式 {{ }} 和 v-bind 表达式** 中,然后放在操作符“ `|` ”后面进行指示。 164 | 165 | 例如,在显示金额,给商品价格添加单位: 166 | 167 | ![4.png](https://raw.githubusercontent.com/summmer-is-hot/marks/main/assets/vue/3_4.webp?) 168 | 169 | ## 24. 说说你对slot插槽的理解?slot使用场景有哪些? 170 | 171 | ### **slot是什么** 172 | 173 | `slot`又名插槽,**是Vue的内容分发机制**,组件内部的模板引擎使用slot元素作为承载分发内容的出口。插槽slot是子组件的一个模板标签元素,而这一个标签元素是否显示,以及怎么显示是由父组件决定的。 174 | 175 | 通过插槽可以让用户可以拓展组件,去更好地复用组件和对其做定制化处理 176 | 177 | 通过`slot`插槽向组件内部指定位置传递内容,完成这个复用组件在不同场景的应用 178 | 179 | 比如布局组件、表格列、下拉选、弹框显示内容等 180 | 181 | **分类** 182 | 183 | ### **默认插槽** 184 | 185 | 子组件用``标签来确定渲染的位置,标签里面可以放`DOM`结构,当父组件使用的时候没有往插槽传入内容,标签内`DOM`结构就会显示在页面 186 | 187 | 父组件在使用的时候,直接在子组件的标签内写入内容即可 188 | 189 | ### **具名插槽** 190 | 191 | 子组件用`name`属性来表示插槽的名字,不传为默认插槽 192 | 193 | 父组件中在使用时在默认插槽的基础上加上`slot`属性,值为子组件插槽`name`属性值 194 | 195 | ### **作用域插槽** 196 | 197 | 子组件在作用域上绑定属性来将子组件的信息传给父组件使用,这些属性会被挂在父组件`v-slot`接受的对象上 198 | 199 | 父组件中在使用时通过`v-slot:`(简写:#)获取子组件的信息,在内容中使用 200 | 201 | ### **小结** 202 | 203 | - `v-slot`属性只能在`