星光不问赶路人,时光不负有心人。 加油!
前端面试 - JS总结(1) - 基础 (数据类型, 事件与函数, 原型链)
前端面试 - JS总结(2) - ES6 (let, 箭头函数, this)
目录
一、异步
1. 异步概念
javascript语言是一门“单线程”的语言。所谓单线程,是指在JS引擎中负责解释和执行JavaScript代码的线程只有一个。 作为浏览器脚本语言,JavaScript的主要用途是与用户互动,以及操作DOM。这决定了它只能是单线程,否则会带来很复杂的同步问题 。
Javascript语言将任务的执行模式分成两种:同步(Synchronous)和异步(Asynchronous)。同步和异步区别:异步是基于JS单线程语言;异步不会阻塞代码执行;同步会阻塞代码执行.
在JS中,异步编程只有四种情况:( 网络请求,如Ajax图片加载; 定时任务,如setTimeout)
- –> 定时器都是异步编程的
- –> 所有的事件绑定都是异步编程的
- –> Ajax读取数据都是异步编程的,我们一般设置为异步编程
- –> 回调函数都是异步编程的
2. 异步机制
具体来说,异步运行机制如下:
(1)所有同步任务都在主线程上执行,形成一个执行栈(execution context stack)。
(2)主线程之外,还存在一个"任务队列"(task queue)。只要异步任务有了运行结果,就在"任务队列"之中放置一个事件。(事件就是回调函数,用于触发函数的执行)。
(3)一旦"执行栈"中的所有同步任务执行完毕,系统就会读取"任务队列",看看里面有哪些事件。那些对应的异步任务,于是结束等待状态,进入执行栈,开始执行。
(4)主线程不断重复上面的第三步。
只要主线程空了,就会去读取"任务队列",这就是JavaScript的运行机制。这个过程会不断重复。
3. 宏任务和微任务
宏任务(macrotask )和微任务(microtask )表示异步任务的两种分类。在挂起任务时,JS 引擎会将所有任务按照类别分到这两个队列中,首先在 macrotask 的队列(这个队列也被叫做 task queue)中取出第一个任务,执行完毕后取出 microtask 队列中的所有任务顺序执行;之后再取 macrotask 任务,周而复始,直至两个队列的任务都取完。同一次事件循环中 微任务永远在宏任务之前执行
参考链接:https://www.cnblogs.com/ckAng/p/11133643.html
4. JS常见的异步模式
(1)回调函数 -- 回调函数作为参数传递给另一个函数,在另一个函数中被调用。但是使用回调函数,经常会写出回调地狱,这是非常致命的。回调地狱的根本问题是:嵌套函数存在耦合性; 嵌套函数变多,处理问题的困难也变大
(2)事件监听 -- 事件监听模式,异步任务的执行取决于,某个事件的发生。比如点击事件(onClick)和内容改变时间(onChange)等
(3)发布/订阅模式 -- 想象有一个类似消息中心的地方,可以在消息中心“注册”一条消息,然后就会有若干对这消息感兴趣的人“订阅”,一旦该消息被“发布”,所有”订阅“了该消息的用户都会得到提醒。
(4)promise -- Promise是ES6推出的一种解决异步编程的解决方案。有三种状态:等待中(pending)、完成(resolved)、拒绝(rejected)。一旦状态从等待改变为其他状态就不再可变了。Promise是个构造函数,接受一个函数作为参数。作为参数的函数有两个参数:resolve和reject,分别对应完成和拒绝两种状态。我们可以选择在不同时候执行resolve或reject去触发下一个动作,执行then方法里的函数。Promise实现了链式调用,每次调用then之后返回的都是一个Promise对象。
(5)Generator(ES6) -- Generator 最大的特点就是可以控制函数的执行。Generator 函数调用和普通函数不同,它会返回一个迭代器
(6)async/await(ES7) -- 一个函数如果加上async,那么该函数就会返回一个Promise对象。async函数执行后可以继续使用then等方法来继续执行后面的逻辑。async函数执行遇到await后,等待后面的Promise对象的状态从pending变成resolve后,将resolve的参数返回并自动往下执行知道下一个await或结束。
参考链接:https://www.cnblogs.com/bzsheng/p/12456944.html
5. 回调函数(Callback)
回调函数是一段可执行的代码段,它作为一个参数传递给其他的代码,其作用是在需要的时候方便调用这段(回调函数)代码
回调地狱的根本问题就是:
- 嵌套函数存在耦合性,一旦有所改动,就会牵一发而动全身
- 嵌套函数一多,就很难处理错误
当然,回调函数还存在着别的几个缺点,比如不能使用 try catch 捕获错误,不能直接 return。
6. Async await
async/await是异步操作的终极解决方案: async 用于声明一个 function 是异步的,而await 用于等待一个异步方法执行完成。
语法简洁,更像是同步代码,也更符合普通的阅读习惯;改进JS中异步操作串行执行的代码组织方式,减少callback的嵌套;Promise中不能自定义使用try/catch进行错误捕获,但是在Async/await中可以像处理同步代码处理错误。
不过也存在一些缺点,因为 await 将异步代码改造成了同步代码,如果多个异步代码没有依赖性却使用了 await 会导致性能上的降低。
JS 异步编程进化史:callback -> promise -> generator -> async/await
7. Promise
Promise是ES6推出的一种解决异步编程的解决方案。Promise 对象是一个代理对象(代理一个值),被代理的值在Promise对象创建时可能是未知的。它允许你为异步操作的成功和失败分别绑定相应的处理方法(handlers)。这让异步方法可以像同步方法那样返回值,但并不是立即返回最终执行结果,而是一个能代表未来出现的结果的promise对象。
有三种状态:等待中(pending)、完成(resolved)、拒绝(rejected)。一旦状态从等待改变为其他状态就不再可变了。Promise是个构造函数,接受一个函数作为参数。作为参数的函数有两个参数:resolve和reject,分别对应完成和拒绝两种状态。我们可以选择在不同时候执行resolve或reject去触发下一个动作,执行then方法里的函数。Promise实现了链式调用,每次调用then之后返回的都是一个Promise对象。当我们在构造 Promise 的时候,构造函数内部的代码是立即执行的
缺点:无法取消 Promise,错误需要通过回调函数捕获。
参考链接:https://blog.csdn.net/itkingone/article/details/86492104
8. Ajax
ajax 是一种异步通信的方法,通过直接由 js 脚本向服务器发起 http 通信,然后根据服务器返回的数据,更新网页的相应部分,而不用刷新整个页面的一种方法。
优点:通过异步模式,提升了用户体验.;优化了浏览器和服务器之间的传输,减少不必要的数据往返,减少了带宽占用.;Ajax在客户端运行,承担了一部分本来由服务器承担的工作,减少了大用户量下的服务器负载。Ajax可以实现动态不刷新(局部刷新)
缺点:安全问题 AJAX暴露了与服务器交互的细节。对搜索引擎的支持比较弱。不容易调试。
描述AJAX的工作原理
- 第一步: 创建
AJAX
对象(XMLHttpRequest/ActiveXObject(Microsoft.XMLHttp)
); - 第二步: 使用
open
打开连接,格式为open
(请求方式,'请求路径',同步/异步); - 第三步: 发送
send()
; - 第四步:当
ajax
对象完成第四步(onreadystatechange)
,数据接收完成。再判断对象状态码(readystate)
当状态码为成功接收的状态码时,HTTP
响应完全接收 。 再判断http
响应状态(200-300
之间或者304
),(缓存)执行回调函数 获取的数据转成字符串格式(responseText)
。
9. jsonp的原理,以及为什么不是真正的ajax
Jsonp原理: 利用浏览器可以动态的插入一段JS代码并执行;
为什么不是真正的AJAX
- 首先,
ajax
和jsonp
在调用方式上虽然长得差不多(目的一样,都是请求一个url
,然后把服务器返回的数据进行处理),但是ajax
和jsonp
本质上是不同的东西; - 哪里不同?核心不同!
ajax
的核心是通过XmlHttpRequest
获取非本页内容,而jsonp
的核心则是动态添加<script>
标签来调用服务器提供的js
脚本; - 此外,需要注意的是,
ajax
和jsonp
的区别不在于是否跨域。因为ajax
通过服务端代理一样可以实现跨域,jsonp
本身也不排斥同域的数据的获取。还有就是jsonp
是一种方式或者说是非强制协议,如同ajax
一样,它也不一定非要jsonp
格式来传递数据; jsonp
只支持get
请求,ajax
支持get
和post
请求.
10. 常用定时器函数
常见的定时器函数有 setTimeout、setInterval、
(1)setTimeout
在指定的毫秒数后,将定时任务处理的函数添加到执行队列的队尾。setTimeout只在指定时间后执行一次. setTimeout用于延迟执行某方法或功能
(2)setInterval
按照指定的周期(以毫秒数计时),将定时任务处理函requestAnimationFrame。数添加到执行队列的队尾。setInterval以指定时间为周期循环执行。setInterval则一般用于刷新表单,对于一些表单的假实时指定时间刷新同步
(3)requestAnimationFrame
HTML5 新增加的 API,类似于 setTimeout 定时器。window 对象的一个方法, window.requestAnimationFrame。使用 requestAnimationFrame 执行动画,最大优势是能保证回调函数在屏幕每一次刷 新间隔中只被执行一次,这样就不会引起丢帧,动画也就不会卡顿。(setTimeout 通过设置一个间隔时间不断改变图像,达到动画效果。该方法在一些低端机 上会出现卡顿、抖动现象。参考链接:https://blog.csdn.net/weixin_40851188/article/details/89669416)
二、性能优化
11. 如何入手性能优化
(1)加载更快
减少资源体积:压缩代码
减少访问次数:合并代码,SSR服务器端渲染,缓存
使用更快的网络:CDN
(2)渲染更快/流畅
CSS放在head,JS放在body最下面
尽早开始执行JS,用DOMContentLoaded触发
懒加载(图片懒加载,上滑加更多)
对DOM查询进行缓存
频繁DOM操作,合并到一起插入DOM结构
节流throttle和防抖debounce
12. js 延迟加载的方式有哪些?
js 的加载、解析和执行会阻塞页面的渲染过程,因此我们希望 js 脚本能够尽可能的延迟加载,提高页面的渲染速度。
-
将 js 脚本放在文档的底部,来使 js 脚本尽可能的在最后来加载执行。
-
给 js 脚本添加 defer属性,这个属性会让脚本的加载与文档的解析同步解析,然后在文档解析完成后再执行这个脚本文件,这样的话就能使页面的渲染不被阻塞。多个设置了 defer 属性的脚本按规范来说最后是顺序执行的,但是在一些浏览器中可能不是这样。
-
给 js 脚本添加 async属性,这个属性会使脚本异步加载,不会阻塞页面的解析过程,但是当脚本加载完成后立即执行 js脚本,这个时候如果文档没有解析完成的话同样会阻塞。多个 async 属性的脚本的执行顺序是不可预测的,一般不会按照代码的顺序依次执行。
-
动态创建 DOM 标签的方式,我们可以对文档的加载事件进行监听,当文档加载完成后再动态的创建 script 标签来引入 js 脚本。
13. 在浏览器中输入URL到整个页面显示
(1)DNS解析
(2)TCP三次握手
(3)浏览器向服务器发送http请求 一旦建立了TCP
链接,web
浏览器就会向web
服务器发送请求命令。
(4)浏览器发送请求头信息 浏览器发送其请求命令之后,还要以头信息的形式想web
服务器发送一些别的信息,之后浏览器发送了一空白行开通知服务器,它已经结束了该头信息的发送。
(5)服务器处理请求 服务器收到http
请求之后,确定用相应的一眼来处理请求。读取参数并进行逻辑操作后,生成指定的数据。
(6)服务器做出响应 客户端向服务器发送请求后,服务端向客户端做出应答。
(7)服务器发送应答头信息 正如客户端会随同请求发送关于自身的信息一样,服务器也会随同应答向用户发送关于它自己的数据以及被请求的文档。
(8)服务器发送数据 web
服务器向浏览器发送信息后,它会发送一个空白行来表示头信息的发送到此结束。接着,它会以Content-Type
应答头信息所描述的格式发送用户所请求的实际数据。
(9)TCP关闭(四次挥手)
一般情况下,一旦web
服务器向浏览器发送了请求数据,它就要关闭tcp
链接。如果浏览器或服务器在其头信息加入了Connection:keep-alive
,则会保持长连接状态,也就是TCP链接在发送后仍保持打开状态,浏览器可以继续通过仙童的链接发送请求。
14. DNS解析过程
DNS( Domain Name System)是“域名系统”的英文缩写,是一种组织成域层次结构的计算机和网络服务命名系统,它用于TCP/IP网络,它所提供的服务是用来将主机名和域名转换为IP地址的工作。DNS是应用层协议,事实上他是为其他应用层协议工作的,包括不限于HTTP和SMTP以及FTP,用于将用户提供的主机名解析为ip地址。
关于DNS的获取流程具体过程如下:
(1) 用户主机上运行着DNS的客户端,就是我们的PC机或者手机客户端运行着DNS客户端了
(2) 浏览器将接收到的url中抽取出域名字段,就是访问的主机名, 并将这个主机名传送给DNS应用的客户端
(3) DNS客户机端向DNS服务器端发送一份查询报文,报文中包含着要访问的主机名字段(中间包括一些列缓存查询以及分布式DNS集群的工作)
(4) 该DNS客户机最终会收到一份回答报文,其中包含有该主机名对应的IP地址
(5) 一旦该浏览器收到来自DNS的IP地址,就可以向该IP地址定位的HTTP服务器发起TCP连接
15. 回流和重回
影响页面渲染速度主要有:reflow(回流)和repaint(重绘)
reflow(回流)当浏览器发现某个部分发生了点变化影响了布局,需要倒回去重新渲染, 该过程称为reflow(回流)。reflow 几乎是无法避免的。现在界面上流行的一些效果,比如树状目录的折叠、展开(实质上是元素的显 示与隐藏)等,都将引起浏览器的 reflow。鼠标滑过、点击……只要这些行为引起了页面上某些元素的占位面积、定位方式、边距等属性的变化,都会引起它内部、周围甚至整个页面的重新渲 染。通常我们都无法预估浏览器到底会 reflow 哪一部分的代码,它们都彼此相互影响着。
repaint(重绘): 如果只是改变某个元素的背景色、文 字颜色、边框颜色等等不影响它周围或内部布局的属性,将只会引起浏览器 repaint(重绘)。 reflow是不可避免的,只能将reflow对性能的影响减到最小。
repaint 的速度明显快于 reflow. reflow(回流)是导致DOM脚本执行低效的关键因素之一。页面上任何一个结点触发reflow,都会导致它的子结点及祖先结点重新渲染。
导致reflow发生:改变窗囗大小,设置style属性,计算offsetWidth和offsetHeight,脚本操作DOM,操作class属性,激活伪类,如:hover (IE里是一个兄弟结点的伪类被激活),内容的改变,如用户在输入框中敲字,添加/删除样式表,改变文字大小
如何避免:
- 尽可能限制reflow的影响范围。需要改变元素的样式,不要通过父级元素影响子元素。最好直接加在子元素上。
- 通过设置style属性改变结点样式的话,每设置一次都会导致一次reflow。所以最好通过设置class的方式。
参考链接:https://www.cnblogs.com/Peng2014/p/4687218.html
16. 防抖与节流
函数防抖 是指在事件被触发 n 秒后再执行回调,如果在这 n 秒内事件又被触发,则重新计时。这可以使用在一些点击请求的事件上,避免因为用户的多次点击向后端发送多次请求。
函数节流 是指规定一个单位时间,在这个单位时间内,只能有一次触发事件的回调函数执行,如果在同一个单位时间内某事件被触发多次,只有一次能生效。节流可以使用在 scroll 函数的事件监听上,通过事件节流来降低事件调用的频率。
三、错误处理
17. try - catch - finally
(1)try种没有发生异常,没有走catch,执行顺序是 try=>finally=>return(try);
(2)try发生异常,没有正常返回,进入catch方法块:try=>catch=>finally=>return(catch);
(3)finally方法中修改try或catch中的变量,返回结果不会被改变。也就是如果finally是在return之后执行的那么他会把返回结果先保存起来,然后不管finally代码执行了什么,都不会影响到返回结果,等finally执行完成在返回结果。
(4)finally也不是必须的。如果catch没有return 就会返回底部return方法。这是我们的常识。
(5)不写catch。try发生了异常,finally始终执行,然后异常被调用方法内的catch捕获执行顺序:try=>finally=>catch(主方法)
(6)throw 用于抛出异常,在catch中捕获异常。
- 不管try有没有出错finally方法块都会被执行。【快记笔记,知识点。】
- 就算try和catch方法都有return,finally都会执行;
- 只要try或者catch return返回,try catch 之外的return都无效;
- 不能在finally中写return 否则会报错。 不可以写重复多个try 但是可以多个catch
参考链接:https://www.cnblogs.com/yanbigfeg/p/9295541.html
四、其他
18. 模块化开发的理解?
一个模块是实现一个特定功能的一组方法。在最开始的时候,js 只实现一些简单的功能,所以并没有模块的概念 ,但随着程序越来越复杂,代码的模块化开发变得越来越重要。
由于函数具有独立作用域的特点,最原始的写法是使用函数来作为模块,几个函数作为一个模块,但是这种方式容易造成全局变量的污 染,并且模块间没有联系。
后面提出了对象写法,通过将函数作为一个对象的方法来实现,这样解决了直接使用函数作为模块的一些缺点,但是这种办法会暴露所 有的所有的模块成员,外部代码可以修改内部属性的值。
现在最常用的是立即执行函数的写法,通过利用闭包来实现模块私有作用域的建立,同时不会对全局作用域造成污染。
19. js 的几种模块规范?
js 中现在比较成熟的有四种模块加载方案:
-
第一种是 CommonJS 方案,它通过 require 来引入模块,通过 module.exports 定义模块的输出接口。这种模块加载方案是服务器端的解决方案,它是以同步的方式来引入模块的,因为在服务端文件都存储在本地磁盘,所以读取非常快,所以以同步的方式加载没有问题。但如果是在浏览器端,由于模块的加载是使用网络请求,因此使用异步加载的方式更加合适。
-
第二种是 AMD 方案,这种方案采用异步加载的方式来加载模块,模块的加载不影响后面语句的执行,所有依赖这个模块的语句都定义在一个回调函数里,等到加载完成后再执行回调函数。require.js 实现了 AMD 规范。
-
第三种是 CMD 方案,这种方案和 AMD 方案都是为了解决异步模块加载的问题,sea.js 实现了 CMD 规范。它和require.js的区别在于模块定义时对依赖的处理不同和对依赖模块的执行时机的处理不同。
-
第四种方案是 ES6 提出的方案,使用 import 和 export 的形式来导入导出模块。
面试总结:计算机网络:https://blog.csdn.net/Sabrina_cc/article/details/106339144