JS面试类型思维导图
js面试题类型进行梳理汇总
基本概念
1. 类型
1.1. 值类型和引用类型有哪些?
答:值类型有Number、String、Boolean、symbol、undefined、null
引用类型有:Array、Object、Function、Date、RegExp、Map、Set
1.2. 类型的判断方式有哪些?
答:typeof、instanceof、Object.prototype.toString
1.3. typeof和instanceof的区别?
答:
(1)typeof返回的是一个变量的基本类型,instanceof返回的是一个布尔值
(2)typeof可以精准的判断值类型,引用类型里只会区分函数类型,其它的引用类型区分不出来。
(3)instanceof可以区分具体的引用类型,但是值类型区分不出来
1.4. 实现深拷贝有哪些方法?
答:
(1)可以利用递归来实现深拷贝
(2)用loadsh这种js库
(3)使用JSON.parse(JSON.stringify(obj))
扩展:使用JSON.parse(JSON.stringify(obj)) 这个json序列化和反序列化的方式实现深拷贝的局限性有哪些?
答:
(1)无法深拷贝函数、RegExp、Date对象等
(2)无法处理循环引用的问题
1.5. 手写一个深拷贝
答:
function deepClone(obj={}){
// 如果不是引用类型或者是null,则返回
if(typeof obj !== 'object' || obj == null) return obj;
let result
if(obj instanceof Array) {
result = []
} else {
result = {}
}
for(let key in obj) {
// 保证key不是原型的属性
if(obj.hasOwnProperty(key)) {
result[key] = deepClone(obj[key])
}
}
return result
}
2. 常用API
2.1. 数组的常用方法有哪些?
答:
增删改查的方法有:
红色标注的是不会更改原数组
增的方法有:push、unshift、splice、concat
删的方法有:pop、shift、splice、slice
改的方法有:splice
查的方法有:indexof、find
排序的方法有:
reverse、sort
转换的方法有:
join
迭代的方法有:
forEach、map、filter、some、every
2.2. some方法和every方法的区别?
答:some和every都是用来测试数组的方法,some是如果其中一项符合检查条件则返回true。every是数组的每一项都符合检查条件才返回true。
2.3. 数组中map和forEach的区别?
答:
(1)返回值不同,map会返回一个新的数组,forEach不会返回新数组。
(2)应用场景不同,forEach主要用于遍历数组并对每个元素执行操作,而map则用于生成一个新的数组,新数组的内容是对原数组中的元素进行处理后的结果。
2.4. for...in 和for...of的区别?
答:
(1)for...in循环出来的是key值,而for...of循环出来的是value值
(2)for...in 会遍历出所有可以枚举属性,包括原型上的属性,可以使用hasOwnProperty来判断是否是对象实例属性。而for...of不会遍历出原型上的属性。
3. 原型和原型链
3.1. 讲一下什么是原型和原型链?
答:原型是指:每个对象都有一个原型对象prototype,当访问对象的属性的时候,不但会在自身寻找,也会在对象上的原型对象上寻找。
原型链是指:对象实例可以通过__proto__去访问原型对象,并且原型对象也可能拥有原型。这样通过__proto__,一层层向上寻找的方式叫原型链。最终会找到object,它的原型对象为null,然后终止寻找。
3.2. instanceof的实现原理?
答:instanceof是根据原型链的方法去判断的,根据Object.getProtypeOf()这个api获取左侧的原型对象,和右侧的prototype去做比较,全相等返回true,否则返回false
4. 作用域
4.1. 什么是作用域?
答:作用域代表了某个变量合法的使用范围。
4.2. 改变this指向的方法有哪些?它们之间的区别是什么?
答:有call、bind、apply。
(1)传参的格式不同,apply的传参是一个数组形式,call的传参是是参数列表,bind的传参是参数列表
(2)执行函数时机不同,call和apply是改变完this后立即执行函数,bind只是改变this,不执行函数
(3)改变this指向的时间不同,call和apply都是临时改变this的指向一次,bind是永久改变this的指向
4.3. 什么是闭包?
答:闭包就是一个函数中返回了另一个函数,这个内层函数跟其词法环境捆绑在一起的这个组合就叫闭包。
4.4. 必包的应用场景?
答:必包的应用场景就是需要创建私有变量或者是延长变量生命周期的时候使用。
4.5. 内存泄露的几种情形?
答:
(1)定义了意外的全局变量
(2)组件销毁时,未取消事件监听
(3)组件销毁时,未清除定时器
(4)未把闭包手动赋值成null
(5)没有清理对dom元素的引用
4.6. 讲一下垃圾回收机制?
答:js有自动的垃圾回收机制,垃圾收集器会周期性的寻找那些不再使用的变量,然后将其释放。
主要有两种方法来实现:
(1)标记清除法:
当变量进入执行环境的时候,就标记这个变量为“进入环境”。进入环境的变量所占有的内存就不能释放,当这个变量离开环境,就标记成“离开环境”。然后垃圾回收机制就会根据这些标签进行回收内存。
(2)引用计数法
语言引擎有一张表是引用表,里面保存了资源被引用的次数,当次数为0就代表该资源不会再被用到,则会被垃圾收集器清理并回收内存
5. 异步
5.1. 描述一下Event Loop(事件循环)机制?
答:当任务进入执行栈的时候,会区分是同步任务还是异步任务,如果是同步任务则进入主线程,异步任务放入任务队列中等待执行。主线程内的任务执行为空,会去任务队列中查看是否有待执行的任务,有的话则推入主线程。上述过程的不断重复就是事件循环。在异步任务队列中还区分了宏任务队列和微任务队列,微任务队列里的任务比宏任务队列里的任务先执行。
5.2. 有哪些宏任务、微任务?
答:
宏任务:setTimeout、setInterval
微任务:Promise、async、await
5.3. Promise中有哪些常用的api?它们分别是怎么用的?
(1)Promise.all
接收一个 Promise 数组作为参数,只有所有的promise都是成功状态才会触发成功,并返回一个所有promise成功返回值组成的数组,如果有一个promise是失败的,则它的状态就是失败,并把第一个失败的promise对象的错误信息返回出去。
(2)Promise.allSettled
接收一个 Promise 数组作为参数,并返回一个新的Promise,这个promise执行后是所有promise的一个结果组成的数组,无论成功还是失败。
(3)Promise.race
接收一个 Promise 数组作为参数,第一个完成的promise的结果状态就是最终的结果状态,可以用来做超时错误。
(4)Promise.any
接收一个 Promise 数组作为参数,只要有一个promise成功,就是成功状态并把成功的值返回出去。如果所有的promise是失败的,则返回的promise是失败
5.4. Promise.all的内部实现机制
6. 事件
6.1. 描述一下DOM事件流?
答:事件流是自上而下进行的,首先是事件捕获,然后是目标阶段,然后是事件冒泡
6.2. 什么是事件代理(事件委托)?
答:就是把事件挂载到目标元素上的外层元素上,然后通过冒泡来触发。这样做的好处就是可以减少整个页面所需的内存,提升整体性能。
7. ES6
7.1. ES6都新增了哪些什么?
答:
(1)let、const
(2)箭头函数
(3)解构赋值
(4)Map、Set数据结构
(5)模板字符串
(6)展开运算符
7.2. Map和Set的区别?
答:
Map是类对象的的数据结构,里面的数据都以key-value的形式存储,常用的api是has,delete、set、get。
Set是类数组的数据结构,可以存储任何类型的唯一值,常用的api是has、delete、clear、add
高级概念
1. new操作符
1.1. new操作符都做了哪些事情?
答:
(1)new的关键字首先创建了一个空对象,
(2)然后将空对象的原型指向构造函数的原型。
(3)将这个空对象作为构造函数的上下文(也就是改变构造函数的this指向)
(4)返回出创建的这个对象
扩展:对构造函数有返回值的情形进行处理,如果构造函数返回的是引用类型,则new 就无效了
2. 导出导入
2.1. import 和require有什么区别?
答:(1)加载方式不同,require是同步加载模块,在加载完成之前后续代码不会执行。import是异步的方式。
(2)调用时机不同,require是运行时调用,所以可以放在任何地方;import是编译后调用,所以只能放在文件的开头。
(3)require是赋值的过程,import是解构的过程。
3. 浏览器存储
3.1. cookie、localStorage、sessionStorage的区别?
答:
(1)大小不同,cookie是4kb,而localStorage和sessionStorage是5M。
(2)数据携带方式不同,cookie会在每次http请求中携带,即使不需要;而localStorage、sessionStorage会只保存在本地。
(3)过期时间不同,cookie是通过max-age来设置过期时间,localStorage是永久保存,sessionStorage是关闭浏览器窗口后会销毁。
(4)作用域不同,sessionStorage不在不同的浏览器窗口中共享,localStorage和cookie在所有同源的浏览器窗口中共享数据。
4. Token
4.1. Cookie、Session、Token的区别是什么?
答:Session是在服务器端保存身份标识的方式,Token是指在浏览器本地保存身份标识信息的一种方式,可以放在Cookie或者Storage中。而Cookie是保存数据的载体。
Web API
1. DOM、BOM
1.1. 常用的DOM api有哪些?
答:
(1)创建元素creatElement
(2)获取节点 querySelect、querySelectAll、getElementById、getElementsByClassName、getElementsByTagName、getElementByName
(3)添加节点innerHTML
(4)删除节点removeChild
1.2. 常用的BOM api有哪些?
答:
(1)刷新当前页面location.reload()
(2)跳转:history.go()
(3)滚动:scrollTo(x,y)、scrollBy(x,y)
应用题
1. 防抖和节流
1.1. 什么是防抖和节流?
答:
防抖:在单位时间内频繁触发事件只响应最后一次的触发。每次触发都会重新开始计时,直到距离上一次响应过去了一个单位时间。
节流:在一段时间内频繁触发事件,会根据单位时间做切割,单位时间内只会响应一次。
1.2. 防抖节流的使用场景有哪些?
答:防抖的使用场景:
(1)改变页面大小的统计
(2)滚动页面位置的统计
(3)输入框连续输入的请求次数控制
节流的使用场景:
(1)按钮重复点击
(2)页面滚动事件
1.3. 手写一个防抖和节流的代码?
// 防抖函数
function debounce(func,delay) {
// 利用了必包的特点,保存住了timer这个变量
let timer
return function(){
const context = this
const args = arguments
clearTimeout(timer)
timer = setTimeout(function(){
// 由于是返回的函数,所以this被挂载到了window上了,
// 这里需要改变this的指向
func.apply(context,args)
}, delay);
}
}
// 节流函数
function throttle(func,delay) {
let timer
return function(){
if(timer){
return;
}
const conext = this;
const args = arguments;
timer = setTimeout(function(){
func.apply(conext, args)
timer = null
}, delay)
}
}
2. 大文件上传
2.1. 说一下大文件上传的实现思路?
答:大文件上传可以采用分片上传
(1)将需要上传的文件通过 file.slice 按照一定的大小切割成多个数据块
(2)初始化上传任务,使用md5来做文件的唯一标识
(3)服务器接受到一个个的分片任务后存储起来,当接受到已完成全部上传的接口指令后,再根据定好的规则(数据块的索引)进行文件拼接。
2.2. 如何实现断点续传?
答:断点续传是如果本次上传中断,下一次从中断的位置开始上传,不再重现上传。
首先是当上传分片文件的时候携带上分片文件的md5,服务器根据分片文件的md5去查看之前是否被上传了,如果被上传了,可以查看当前已经上传了多少个分片任务,然后把未完成的分片任务的索引返回给前端,前端从这个索引开始继续上传。
3. 大数据渲染
3.1. 如果后端一次性返回来了10w条数据,不考虑分页的方式,前端如何渲染?
答:可以使用虚拟列表的方式或者分片渲染的方式
虚拟列表的方式是:只渲染当前能看见的列表的数据,通过对滚动进行监听,然后用scrollTop计算出当前列表开始的位置和结束的位置。这样就可以用js的方式,获取到当前需要渲染列表的数据进行渲染。
分片渲染的方式是:利用的是一步步去渲染大数据,而一次性的去渲染的方式。通过一个requestIdleCallback 这个api,里面传递一个函数,对当前的大数据做切割,然后把值更新到渲染的新列表中。而不是直接使用原数据做渲染。
3.2. 分片渲染的时候,为什么使用requestIdleCallback这个api而不是直接使用setTimeout?
答:因为setTimout这个api的触发时机有可能和浏览器重绘的时间对不上,因为浏览器一般是60hz每秒,也就是1秒中重绘60次,如果重绘的时间没有和定时器的时间对齐的话。会有卡顿的现象。而requsetIdleCallback这个api是在浏览器渲染的时候触发,这样时间就对齐了,看起来不会有卡顿的现象。
4. 单点登录
4.1. 如何实现单点登录?
答:
同域名下的单点登录:利用同域时Cookie可以共享的特点来实现,首先把Cookie的domain属性设置成当前域的父域,同时将Cookie的path属性设置为根路径,再将Token保存到父域中,这样所有的子域应用都可以访问到这个Cookie。
不同域名下的单点登录:
(1)建立一个独立的认证中心(passport),子系统的登录均通过passport,不再通过子系统进行登录。
(2)用户在认证中心进行登录后,把token写入cookie中
(3)当其它的子系统需要登录跳转到认证中心,通过认证中心的Cookie得知已经登录过,会跳转目标url,并在跳转之前生成一个token拼接到url后面。
(4)子系统拿到token后,还需要向认证中心去确认Token的合法性,防止用户伪造,确认无误后记录用户的登录状态,给访问放行。
5. web攻击方式
5.1. web的攻击方式有哪些?
答:常见的攻击方式有XSS、CSRF、SQL注入攻击
6. 数字精度丢失
6.1. 怎么解决数字精度丢失的问题?
答:
(1)使用Math.js这种专用的数学计算的库。
(2)自定义一些数学方法来解决。
自定义方法的原理:
把小数转化成整数再去做运算,得出结果后,在除以之前所乘的倍数,然后得到准确的值。
7. 跨域
7.1. 什么是同源策略?
答:协议、主机、端口号、都一致的url地址。
7.2. 如何解决跨域?
答:解决跨域一般有三种方法,JSONP,CORS,设置服务器反向代理。
JSONP: JSONP是利用了script标签可以跨域加载脚本这个特点来获取数据。具体来说就是通过script的src属性里的url地址传递参数给服务端,服务端根据这些参数会执行相应的代码,然后把真实的数据外包一个JS函数再以JSON数据的形式返回给客户端,客户端获取到数据后,执行服务端回传来的函数获取数据。这就是JSON witdh Padding,也就是JSONP。这样做的一个弊端是只支持GET请求不支持POST请求。
CORS: 在服务端的响应头中设置Access-Control-Allow-Origin需要指定源,这样浏览器就知道可以跨域请求了。
服务器反向代理: 不直接访问服务器,而是通过反向代理间接访问服务器,反向代理服务器里的响应头中的Access-Control-Allow-Origin需要指定源
一般是用CORS或者服务器反向代理来解决跨域的
7.3. 用CORS来实现跨域相较于用服务器反向代理的方式来实现跨域,为什么首屏加载速度会慢?
8. 图片懒加载
8.1. 图片懒加载怎么做?
答:利用intersectionObserver这个浏览器提供的api来实现的,它是一个构造函数,可以用它构造一个实例,然后通过这个 实例的observe这个方法来绑定dom节点,就可以观察到这个节点和可视区域有没有交叉区域,再intersectionObserver里接收一个回掉函数,当观察到有交叉的时候会触发,这里面可以做一些操作来实现图片懒加载。
所以,
(1)首先利用data-src这个自定义属性来代替src这个属性,为img标签的图片地址做标记。由于是自定义属性,img标签里的图片并不会加载出来,只会占了一个位置。
(2)通过new intersectionObserver(callback)这个构造函数,得到observer这个实例,然后通过observer.observe这个方法,里面传入img的DOM节点来对其监听。
(3)当图片滚动到视图窗口的时候,会触发刚刚定义的callback方法,在里面把img这个节点的data-src赋值给src,这样图片就加载出来,来实现懒加载
(4)赋值完之后,再通过observer.unobserve(dom节点)这个方法来取消对这个图片的监听,以此来避免重复执行赋值操作。
9. 上拉刷新下拉加载
9.1. 如何实现上拉加载和下拉刷新?
9.2. 下拉加载的时候如果客户新增了前面的一项数据,怎么保证下一页的数据不会重复?
答:可以使用page token的方式来标识当前需要从哪个位置获取下一页数据。当进行分页请求时,前端会将当前页面的 "page token" 发送给服务器。服务器使用这个标识符来确定应该从哪个位置或状态开始准备下一页数据,然后返回相应的数据和下一页的 "page token" 给前端,以便在下一次请求时使用。
10. token
10.1. 怎么实现token的无感刷新?
答:有三种方法可以实现。
(1)第一种方法:当token快过期的时候,如果发起一个接口请求,后端可以返回状态码401,前端通过if去判断这个状态码,进入到一个refresh Token这么一个请求,因为axios本身是一个promise对象,所以可以在refres Token请求成功以后,在.then()中去return用户上一步请求,请求数据通过响应拦截器中的config参数获取。
(2)第二种方法:后端定义一个token过期时间字段,前端每次请求都会用本地时间和token过期时间比较,当快过期了,就去重新请求新的token。这样做的缺点是,如果用户更改了本地时间,则拦截就会失败。
(3)第三种方法:写一个定时器,定时去刷新token。这样的缺点是我们需要不断请求,不利于项目 的性能。
11. 手写题
11.1. 手写一个EventBus事件总线?
12. 首屏时间
12.1. 白屏时间(FCP)和首屏时间(LCP)的区别是什么?
答:
白屏时间:从浏览器开始加载到第一个一点点的内容渲染出来的这一段时间就是白屏时间。
首屏时间:从浏览器开始加载到主要内容渲染出来的这一段时间就是首屏时间。
12.2. 怎么获取白屏时间和首屏时间?
答:通过使用PerformanceObserver这个api来计算时间。