# JavaScript相关面试题

# JS数据类型有哪些,区别是什么？
JS数据类型分为两类：
基本数据类型，分别是Number 、String、Boolean、BigInt、Symbol、Null、Undefined。基本数据类型是直接存储在栈中的简单数据段，占据空间小
引用数据类型，通常用Object代表，普通对象，数组，正则，日期，Math数学函数都属于Object。
引用数据类型是存储在堆内存中，占据空间大。引用数据类型在栈中存储了指针，该指针指向堆中该实体的起始地址，当解释器寻找引用值时，会检索其在栈中的地址，取得地址后从堆中获得实体。

# JS变量提升？
1. var声明会存在变量提示，函数声明也会提升。
2. let，const不会存在变量提升问题 变量提升。
3. 函数声明提示，都是在js编译阶段提升的，将当前变量、函数提升至当前作用域的最顶端 并且由于编译阶段，并没有开始执行代码，所以都会被赋初始值undefined，由于函数不同，函数会在堆内存中开辟一个空间用于来保存函数的执行体，会将当前内存地址赋值给函数变量，所以可以在函数声明上面，来调用该函数 。
4. 并且let，const声明变量会形成暂时性死区，就是在该作用域当中，无法在变量声明之前去访问，即使全局存在一个同名变量，也无法访问。

# 原型与原型链
1. 原型是函数特有的属性。 
2. 对象和数组都是没有原型这个属性的。
3. 函数的原型可以挂载属性和方法上去。
4. __proto__    这就是原型链。浏览器最新的表达： [[prototype]] 
5. 任何的数据都是有原型链的。对象obj、数组arr、number等。
6. 原型： 原型分为隐式原型和显式原型，每个对象都有一个隐式原型，它指向自己的构造函数的显式原型。
7. 原型链： 多个__proto__组成的集合成为原型链。
8. 所有实例的__proto__都指向他们构造函数的prototype。
9. 所有的prototype都是对象，自然它的__proto__指向的是Object()的prototype。
10. 所有的构造函数的隐式原型指向的都是Function()的显示原型。
11. Object的隐式原型是null。

通用规则
1. 对象有__proto__属性，函数有prototype属性；
2. 对象由函数生成;
3. 生成对象时，对象的__proto__属性指向函数的prototype属性。

# 闭包的理解？
1.内层函数引用外层函数中变量，这些变量的集合就是闭包 
2.通过作用域链，当前作用域可以访问上级作用域中的变量 
3.解决的问题：保存变量，保护变量 
4.带来的问题：内存泄露 
5.块级作用域

# new会发生什么？
1. 创建一个新对象 
2. 将新对象的__proto__（原型）指向构造函数的prototype（原型对象）
3. 构造函数绑定新对象的this并执行返回结果 
4. 判断返回结果是否为null，如果为null,返回新对象，否则直接返回执行结果。

# 事件循环Event loop，宏任务与微任务？
1. js是单线程的，会循环往复的先执行同步任务，当同步任务执行完毕后去执行异步队列中的异步任务。
2. 事件循环中是执行栈中的内容，栈中内容执行后执行微任务，微任务清空后执行宏任务，所以微任务的优先级高于宏任务。
3. js中常见的微任务：promise.then，MutationObserver，process.nextTick；
4. js中常见的宏任务：setTimeout、setInterval定时器、DOM事件、AJSX事件

# promise是什么与使用方法？
1. 概念：异步编程的一种解决方案，解决了地狱回调的问题 
2. 使用方法：new Promise((resolve,reject) => { resolve（）; reject（）; })里面有多个resovle或者reject只执行第一个。如果第一个是resolve的话后面可以接.then查看成功消息。如果第一个是reject的话，.catch查看错误消息。
3. Promise是宏任务（同步执行），只有Promise的回调是异步微任务。

# JavaScript有几种方法判断变量的类型？
1. typeof（根据二进制判断）,不能判断数据类型：null和object 
2. intanceof(根据原型链判断),原生数据类型不能判断 
3. constructor.name(根据构造器判断)，不能判断null数据类型 
4. Object.prototype.toString.call()（用Object的toString方法判断）所有类型数据都能判断,记住判断结果打印为：'[object Xxx]'

# JS实现异步的方法？
1. 异步微任务：promise， async/await 
2. 异步宏任务：setTimeout setIntervial dom事件

# map 和 forEach 的区别？
1. map一般不修改原数组，forEach能改变原数组。
2. map有返回值，创建新数组，而forEach无返回值，返回结果undefined。
3. map和forEach用只能用try...catch...中断抛出错误中断，而return不能，用其效果相当于for循环的continue会跳过此次循环进入下一次循环。

# 事件扩展符用过吗(...)？
1. 数组克隆 let a = [1,2,3];let b = [...a] 。
2. 数组合并 let a = [1,2,3];let b = [4,5,6];let c = [...a,...b] 。
3. 类数组转成真正的数组 let a = new Set([1,2,3]); let b = [...a]。

# 数组去重都有哪些方法？
1. new Set 
2. filter + 对象属性不重复 
3. filter + indexOf

# [] == []/[] === []?
每次使用 [] 都是新建一个数组对象。当数组比较的时候其实比较的是他们的引用。[] == []/[] === []的时候，从值上尽管两边都是[]但是从引用上两边是不相等的。

# 伪数组和数组的区别？
1. 伪数组的特点：类型是object、不能使用数组方法、可以获取长度、可以使用for in遍历。
2. 伪数组可以转换为数组的方法：
> 1. Array.prototype.slice.call() 
> 2. Array.from() 
> 3. [...伪数组]
3. 有哪些是伪数组：
> 1. 函数的参数arguments
> 2. Map和Set的keys()、values()和entires()

# null 和 undefined 的区别，如何让一个属性变为null
1. null是定义并赋值null 
2. undefined是定义未赋值

# 箭头函数
1. 写法简洁 
2. 无自己的this，继承上一个作用域的this（全局或上一个函数） 
3. 内部this无法被改变 
4. arguments的特殊性（window下保存，this指向上一个函数则arguments表示上一个函数的参数） 没有arguments
5. 不能作为构造函数（无自己的this、constructor） 
6. 没有自己的prototype(原型)
7. 不能作为generator函数，不能使用yield命令

# this指向（普通函数、箭头函数）？
普通函数： 
1、普通函数的this指向window(默认情况，严格模式指向undefined，否则指向全局对象)。
2、事件处理函数中，this指向触发事件的对象，点谁谁就是this 
3、对象方法中的this指向该对象(由new调用的话，this指向新创建的对象；)
4、由call或apply调用，this指向指定的对象；
箭头函数： 
1、箭头函数没有this，不能作为构造函数，所以也没有原型和new 
2、箭头函数的this取决于它的外部函数 
3、没有arguments
普通函数指向调用处，箭头函数指向定义处

# js继承的方法和优缺点？
1. 原型链继承。
> 缺点： 
> 1.引用类型的属性被所有实例共享 
> 2.在创建 Child 的实例时，不能向 Parent 传参

2. 借用构造函数(经典继承) 
> 优点： 
> 1.避免了引用类型的属性被所有实例共享 
> 2.可以在 Child 中向 Parent 传参 
> 缺点：
> 1.方法都在构造函数中定义，每次创建实例都会创建一遍方法。
3. 组合继承 
> 优点： 
> 1.融合原型链继承和构造函数的优点，是 JavaScript 中最常用的继承模式。
4. 原型式继承 
> 缺点： 
> 1.包含引用类型的属性值始终都会共享相应的值，这点跟原型链继承一样。
5. 寄生式继承 
> 缺点： 
> 1.跟借用构造函数模式一样，每次创建对象都会创建一遍方法。
6. 寄生组合式继承 
> 优点： 
> 1.这种方式的高效率体现它只调用了一次 Parent 构造函数，并且因此避免了在 Parent.prototype 上面创建不必要的、多余的属性。 
> 2.与此同时，原型链还能保持不变； - 3.因此，还能够正常使用 instanceof 和 isPrototypeOf。

# call apply bind的作用和区别？
相同点：call apply bind三个方法都可以用来改变函数的this指向。
不同点：
1、fn.call (newThis,params) call函数的第一个参数是this的新指向，后面依次传入函数fn要用到的参数。会立即执行fn函数。
2、fn.apply (newThis,paramsArr) apply函数的第一个参数是this的新指向,第二个参数是fn要用到的参数数组，会立即执行fn函数。
3、fn.bind (newThis,params) bind函数的第一个参数是this的新指向，后面依次传入函数fn要用到的参数。  不会立即执行fn函数，且只能改变一次fn函数的指向，后续再用bind更改无效。

# cookie sessionStorage localStorage 区别？
相同点：Cookie、SessionStorage、 LocalStorage都是浏览器的本地存储。都是存储在浏览器本地的。
不同点：
1. cookie是由服务器端写入的，而SessionStorage、 LocalStorage都是由前端写入的。
2. cookie的生命周期是由服务器端在写入的时候就设置好的，LocalStorage是写入就一直存在，除非手动清除，SessionStorage是页面关闭的时候就会自动清除。
3. cookie的存储空间比较小大概4KB，SessionStorage、 LocalStorage存储空间比较大，大概5M。

# 如何实现可过期的localstorage数据？
1. 惰性删除：惰性删除是指获取数据的时候，拿到存储的时间和当前时间做对比，如果超过过期时间就清除Cookie。 
2. 定时删除：每隔一段时间执行一次删除操作，并通过限制删除操作执行的次数和频率，来减少删除操作对CPU的长期占用。 LocalStorage清空应用场景：token存储在LocalStorage中，要清空token

# 创建ajax过程？
1. 创建`XMLHttpRequest`对象,创建一个异步调用对象. 
2. 创建一个新的`HTTP`请求,并指定该`HTTP`请求的方法、`URL`及验证信息. 
3. 设置响应`HTTP`请求状态变化的函数. 
4. 发送`HTTP`请求

# fetch 请求方式？
> 1. fetch是一种HTTP数据请求的方式。
> 2. fetch()方法返回一个Promise解析Response来自Request显示状态（成功与否）的方法。

# 跨域是什么？如何解决跨域问题？
1. 跨域：当前页面中的某个接口请求的地址和当前页面的地址如果协议、域名、端口其中有一项不同，就说该接口跨域了。
2. 跨域限制的原因：浏览器为了保证网页的安全，出的同源协议策略。
3. 跨域解决方案： - jsonp - 前端proxy后端cors - 线上Nginx - websockt

# 浏览器垃圾回收机制？
有两种机制：
1、标记清除：对所有活动对象进行标记，清除阶段会将没有标记的对象清除；标记整理算法：标记结束后，算法将活动对象压入内存一端，则需要清理的对象在边界，直接被清理掉就行。（效率低） 。
2、引用计数：将对象是否不再需要简化定义为有没有其他对象引用它，如果没有引用指向这个对象，则会被垃圾回收机制回收。（内存空间不连续）。

# defer和async区别？
1. html文件都是按顺序执行的，script标签中没有加defer和async时，浏览器在解析文档时遇到script标签就会停止解析阻塞文档解析，先加载JS文件，加载完之后立即执行，执行完毕后才能继续解析文档。 
2. script标签中写入defer或者async时，就会使JS文件异步加载，即html执行到script标签时，JS加载和文档解析同时进行。
3. async是在JS加载完成后立即执行JS脚本，阻塞文档解析。
4. defer则是JS加载完成后，在文档解析完成后执行JS脚本。

# 有什么方法可以保持前后端实时通信？
1. 轮询：轮询是客户端和服务器之间会一直进行连接，每隔一段时间就询问一次。
2. 长轮询：长轮询是对轮询的改进版，客户端发送HTTP给服务器之后，如果没有新消息，就一直等待。有新消息，才会返回给客户端。在某种程度上减小了网络带宽和CPU利用率等问题。
3. iframe流：iframe流方式是在页面中插入一个隐藏的iframe，利用其src属性在服务器和客户端之间创建一条长连接，服务器向iframe传输数据（通常是HTML，内有负责插入信息的javascript），来实时更新页面。
4. WebSocket：WebSocket是类似Socket的TCP长连接的通讯模式，一旦WebSocket连接建立后，后续数据都以帧序列的形式传输。
5. SSE：SSE 是单向通道，只能服务器向浏览器发送，因为 streaming 本质上就是下载。

# 浏览器如何渲染页面的？
1. HTML被HTML解析器解析成DOM树。 
2. CSS被CSS解析器解析成CSS规则树。 
3. 浏览器会将CSS规则树附着在DOM树上，并结合两者生成渲染树Render Tree。 
4. 生成布局（flow），浏览器通过解析计算出每一个渲染树节点的位置和大小，在屏幕上画出渲染树的所有节点。 
5. 将布局绘制（paint）在屏幕上，显示出整个页面

# 浏览器输入URL发生了什么？
1. url解析：判断是搜索内容还是请求URL。 
2. 查找本地缓存：如果有缓存直接返回给页面，没有缓存则进入网络请求阶段。
3. DNS解析。
4. 通过三次握手建立TCP连接。
5. 合成请求头信息，发送http请求。
6. 处理响应信息。
7. 通过四次挥手断开TCP连接。
8. 如果响应状态码301，则重定向。
9. 浏览器进行页面渲染：1）解析html，生成DOM树；2）根据css计算节点样式，生成stylesheet；3）生成布局树；4）为特定的元素生成独立图层；5）布局渲染

# axios的拦截器原理及应用？
axios拦截器分为响应和请求拦截器。
> 1. 请求拦截器 在请求发送前进行必要操作处理，例如添加统一cookie、请求体加验证、设置请求头等，相当于是对每个接口里相同操作的一个封装。
> 2. 响应拦截器 同理，响应拦截器也是如此功能，只是在请求得到响应之后，对响应体的一些处理，通常是数据统一处理等，也常来判断登录失效等。

# 重绘、重排区别如何避免？
> 1. 重排(回流)：当dom的变化影响元素的几何信息（元素的位置和尺寸大小），浏览器需要重新计算元素的几何属性，将其安放在界面的正确的位置。这个过程叫重排。
> 2. 重绘：一个元素外观发生改变，但没有改变布局，重新把元素绘制出来的过程。
> 3. 重排(回流)一定会发生重绘，重绘就不一定要重排(回流)。
> 4. 重排对性能的消耗要更多一些。
> 5. 触发重排的方法：改变元素的位置；改变元素的尺寸，比如边距，填充，边框，宽度，高度等；页面初始渲染，这是开销最大的一次重排；改变元素的字体大小；改变浏览器窗口大小，比如resize事件发生时；改变元素内容，字体数量，图片大小；激活css伪类；设置style属性的值，因为设置style属性会改变结点样式，就会触发重排。查询某些属性、或者调用某些计算方法:offsetwidth，offheight。
> 6. 避免重排的方法是样式集中改变；使用absolute或fixed脱离文档流；使用gpu加速，transform。
