# interview-questions-JS
**Repository Path**: MindExplode/js_interview_questions
## Basic Information
- **Project Name**: interview-questions-JS
- **Description**: No description available
- **Primary Language**: Unknown
- **License**: Not specified
- **Default Branch**: master
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 0
- **Forks**: 1
- **Created**: 2021-06-06
- **Last Updated**: 2021-06-06
## Categories & Tags
**Categories**: Uncategorized
**Tags**: None
## README
## GET和POST区别
+ GET传参通过url方式进行传递参数,url长度有限制,IE浏览器对URL的最大限制为2083个字符,
对于Firefox浏览器URL的长度限制为65,536个字符
+ POST通过request body传递参数里,参数没有限制
+ GET方式不安全,POST比较安全
+ 一半获取数据用get
+ 提交数据用POST
+ get和服务器只会有一次请求
+ post为了在网路中保密特性,会发两次传输请求
## pushState和popstate
pushState:HTML5新接口,可以改变网址(存在跨域限制)而不刷新页面,这个强大的特性后来用到了单页面应用如:vue-router,react-router-dom中。
**注意:仅改变网址,网页不会真的跳转,也不会获取到新的内容,本质上网页还停留在原页面!**仅仅使url的地址发生变化
popstate:用来监听页面前进后退等事件
```js
```
## call和apply和bind
+ call的使用,可以改变this指向,第一个参数为宿主对象,也就是要改变的this指向,后面的参数全部为当前函数的实参
```js
const fn = function(sex,like) {
this.sex = sex
this.like = like
console.log(this)
}
var obj = {
name: '张三',
age: 19
}
fn.call(obj,'男','打篮球')
```
+ apply:可以改变this指向,第一个参数为宿主对象,第二个参数为数组,数组每一项为当前函数的所有实参
```js
const fn = function(sex,like) {
this.sex = sex
this.like = like
console.log(this)
}
var obj = {
name: '张三',
age: 19
}
fn.apply(obj,['男','打篮球'])
```
+ bind:可以改变this指向,bind会基于原函数创建一个新的函数,并改变其his指向,指向bind的第一个参数,一旦通过bind改变this指向之后,后续不管通过什么方式调用,this都不会在改变了
```js
const fn = function(sex,like) {
this.sex = sex
this.like = like
console.log(this)
}
var obj = {
name: '张三',
age: 19
}
var fn1 = fn.bind(obj,'男','打篮球')
fn1()
```
## 递归
递归就是函数自己调用自己,一般像三级数据嵌套,进行三层层层循环,或者不知道有多少层,建议使用递归,递归必须要给一个终止条件,如果不给终止条件则进入死循环。
navigator对象 :可以获取浏览器的一些基本信息,例如可以通过navigato.userAgent来判断当前的环境
location对象 :用来获取当前url的一些基本信息,当然也可以修改url进行跳转和修改hash进行url更新,但是hash不会让页面重新加载
history对象 :用来获取当前的浏览历史记录的,也可以用来进行前进后退和pushState
## for\in for\of forEach
for in:一般用来遍历对象,并且可以遍历原型对象
for of:不可以进行遍历普通对象的,可以遍历数字字符串数组和newSet对象等等,并且可以进行break终止,不可以return
forEach:不可以进行break、return
## 如何创建一个没有原型对象的对象
通过Object.create(null)可以创建一个没有原型的对象
## 如何判断一个对象为空对象
+ JSON.stringify({}) === '{}'
+ Object.keys({}).length === 0
+ for in循环
## js静态成员和实例成员
静态成员:静态成员 在构造函数本身上添加的成员 如下列代码中 sex 就是静态成员,静态成员只能通过构造函数来访问
实例成员:实例成员就是构造函数内部通过this添加的成员 如下列代码中uname age sing 就是实例成员,实例成员只能通过实例化的对象来访问
## js垃圾回收机制
### 概念
js的内存是自动进行分配和回收的,内存在不使用的时候会被垃圾回收器自动进行回收,但是我们需要了解垃圾回收的机制,从而防止内存泄漏(内存无法被回收)
### 生命周期
内存创建分配: 申请变量\对象\函数等
内存使用: 对内存进行读写,也就是使用变量或函数对象等
内存销毁: 变量\函数\对象等不再使用,即被垃圾回收自动回收掉
### 核心算法
判断内存是否不再使用,如果是则回收
### 引用计数
ie采用的是引用计数
计算当前内存被引用的次数,被引用一次计数+1,不被引用一次计数-1,当计数为0,该内存释放回收
```js
var a = { name: '张三', age: '李四' }// a地址 => {name: '张三', age: '李四'} 被引用次数 1
var b = a // b地址 => {name: '张三', age: '李四'} 被引用次数 2
var c = a // c地址 => {name: '张三', age: '李四'} 被引用次数 3
a = 1
b = null
c = true
```
优势:简单有效
问题:循环引用导致内存泄漏

上图解析:
1. 函数调用,分别创建a 地址 指向 一个内存(1号内存),b地址指向一个内存(2号内存)
2. a.a1 也指向 2号内存,b.b1 指向1号内存
3. 此时1号内存被a、b.b1 引用 计数2
4. 此时2号内存被b、a.a1 引用 计数2
5. fn函数调用执行后,fn内部数据不再使用所以要进行回收,将a指向1号内存 取消,b指向2号内存取消
6. 1号内存还被 b.b1所引用,计数1 无法回收
7. 2号内存还被 a.a1 所引用,计数1 无法回收
结论:1号内存、2号内存造成循环引用无法回收,使其内存泄漏
### 标记清楚
现在浏览器采用的是标记清除
标记就是通过根节点(全局),标记所有从根节点开始的能够访问到的对象。未被标记的对象就是未被全局引用的垃圾对象。
最终清除所有未被标记的对象
```js
function fn() {
var a = {}
var b = {}
a.a1 = b
b.b1 = a
}
fn()
```
因为fn函数内部的数据在全局无法访问到,所以fn执行后,函数内部的数据自动被清除
```js
function fn() {
var a = {}
var b = {}
a.a1 = b
b.b1 = a
return a
}
var obj = fn()
```
fn函数调用后,全局在引用着fn函数内部a的数据,a又用着b的数据,所以fn函数内部的数据全都不会清除
## 闭包
### 概念
一个函数和对其周围状态(**lexical environment,词法环境**)的引用捆绑在一起(或者说函数被引用包围),这样的组合就是**闭包**(**closure**)。大白话也就是说,闭包让你可以在一个内层函数中访问到其外层函数的作用域
```js
function fn() {
var a = 10
return function getData() {
return a
}
}
var getData = fn()
var a1 = getData()
```
### 原理
闭包的原理就是利用作用域链的特性,首先在当前作用域访问数据,当前作用域访问不到,则向父级访问,父级也没有,一直找到全局。
### 作用
数据私有化,防止污染全局
```js
var a = 10
function fn() {
console.log(a)
}
console.log(a)
```
将闭包代码修改后也同样可以访问到a数据,但是此时a的数据在全局,全局可以直接对a数据进行修改,而且全局也多了一个a这个数据,要尽量避免直接将数据直接放到全局
### 缺点
闭包会造成内存泄漏,因为闭包的数据没有被回收
```js
function fn() {
var a = 10
return function getData() {
return a
}
}
var getData = fn()
var a1 = getData()
```
解决方案:将全局指向的函数重新置为null,利用标记清除的特性
```js
function fn() {
var a = 10
return function getData() {
return a
}
}
var getData = fn()
getData = null
```
## 异步
### 概念
js是单线程的,也就代表js只能一件事情一件事情执行,那如果一件事情执行时间太久,后面要执行的就需要等待,需要等前面的事情执行完成,后面的才会执行。
所以为了解决这个问题,js委托宿主环境(浏览器)帮忙执行耗时的任务,执行完成后,在通知js去执行回调函数,而宿主环境帮我们执行的这些耗时任务也就是异步任务
js本身是无法发起异步的,但是es5之后提出了Promise可以进行异步操作
### 执行流程
1. 主线程先判断任务类型
+ 如果是同步任务,主线程自己执行
+ 如果是异步任务,交给宿主环境(浏览器)执行
2. 浏览器进行异步任务的执行,每个异步执行完后,会将回调放进任务队列,先执行完成的先放进任务队列,依次放入
3. 等主线程任务全部执行完后,发现主线程没有任务可执行了,会取任务队列中的任务,由于任务队列里是依次放入进来的,所以取得时候也会先取先进来的,也就是先进先出原则
4. 在任务队列中取出来的任务执行完后,在取下一个,依次重复,这个过程也称为eventLoop 事件轮训
### 宏任务
由宿主环境发起的异步:宏任务
setTimeOut、setInterval、特殊的(代码块、script)
setTimeOut

setInterval

setImmediate

### 微任务
由javascript自身发起的异步:微任务


### 执行顺序
1. 先执行宏任务
2. 宏任务执行完后看微任务队列是否有微任务
3. 没有微任务执行下一个宏任务
4. 有微任务将所有微任务执行
5. 执行完微任务,执行下一个宏任务
### 练习案例
案例1:
```js
console.log(1)
setTimeout(function(){
console.log(2)
}, 0)
new Promise(function(resolve){
console.log(3)
resolve()
}).then(function(){
console.log(4)
})
console.log(5)
```
答案:1、3、5、4、2
解析:
1. 主线程判断是同步代码还是异步代码
```js
console.log(1) // 同步任务
setTimeout(function(){
console.log(2) // 异步任务:宏任务
}, 0)
new Promise(function(resolve){
console.log(3) // 同步任务
resolve()
}).then(function(){
console.log(4) // 异步任务:微任务
})
console.log(5) // 同步任务
```
2. 执行同步任务
```js
console.log(1) // 同步任务
console.log(3) // 同步任务
console.log(5) // 同步任务
```
3. 执行异步任务:微任务
```js
console.log(4) // 异步任务:微任务
```
4. 执行异步任务:宏任务
```js
console.log(2) // 异步任务:宏任务
```
案例2:
注意点:await的执行顺序为从右到左,会阻塞后面的代码执行,但并不是直接阻塞await的表达式
await下面(下面不是右面)的代码可以理解为promise.then(function(){ 回调执行的 })
```js
async function async1() {
console.log('async1 start')
await async2()
// await后面的代码可以理解为promise.then(function(){ 回调执行的 })
console.log('async1 end')
}
async function async2() {
console.log('async2')
}
console.log('script start')
setTimeout(function() {
console.log('setTimeout')
}, 0)
async1()
console.log('script end')
```
答案:script start、async1 start、async2、script end、async1 end、setTimeout
解析:
1. 主线程判断同步异步
```js
async function async1() {
console.log('async1 start')
await async2() // 同步任务
// await后面的代码可以理解为promise.then(function(){ 回调执行的 })
console.log('async1 end') // 异步任务:微任务
}
async function async2() {
console.log('async2')
}
console.log('script start') // 同步任务
setTimeout(function() {
console.log('setTimeout') // 异步任务:宏任务
}, 0)
async1() // 同步任务
console.log('script end') // 同步任务
```
2. 执行同步任务
```js
console.log('script start')
console.log('async1 start')
console.log('async2')
console.log('script end')
```
3. 执行异步任务:微任务
```js
console.log('async1 end')
console.log('setTimeout')
```
案例3:
```js
console.log(1)
setTimeout(function(){
console.log(2)
}, 2000)
new Promise(function(resolve){
console.log(3)
resolve()
}).then(function(){
console.log(4)
})
setTimeout(function(){
console.log(5)
new Promise(function(resolve){
console.log(6)
resolve()
}).then(function(){
console.log(7)
})
}, 3000)
setTimeout(function(){
console.log(8)
new Promise(function(resolve){
console.log(9)
resolve()
}).then(function(){
console.log(10)
})
}, 1000)
```
答案:1、3、4、8、9、10、2、5、6、7
解析:
1. 区分同步任务和异步任务
```js
console.log(1) // 同步任务
setTimeout(function(){
console.log(2) // 异步任务
}, 2000)
new Promise(function(resolve){
console.log(3) // 同步任务
resolve()
}).then(function(){
console.log(4) // 异步任务
})
setTimeout(function(){
console.log(5) // 异步任务
new Promise(function(resolve){
console.log(6)
resolve()
}).then(function(){
console.log(7)
})
}, 3000)
setTimeout(function(){
console.log(8) // 异步任务
new Promise(function(resolve){
console.log(9)
resolve()
}).then(function(){
console.log(10)
})
}, 1000)
```
2. 执行同步任务
```js
console.log(1)
console.log(3)
```
3. 异步任务添加到不同任务队列中
微任务添加到微任务队列[ console.log(4) ]
```js
new Promise(function(resolve){
console.log(3)
resolve()
}).then(function(){
console.log(4) // 微任务
})
```
宏任务
由宿主发起异步,异步完成将回调放入宏任务队列
```js
setTimeout(function(){
console.log(2) // 异步任务
}, 2000)
setTimeout(function(){
console.log(5) // 异步任务
new Promise(function(resolve){
console.log(6)
resolve()
}).then(function(){
console.log(7)
})
}, 3000)
setTimeout(function(){
console.log(8) // 异步任务
new Promise(function(resolve){
console.log(9)
resolve()
}).then(function(){
console.log(10)
})
}, 1000)
```
进入宏任务队列
```js
// 宏任务1:
function(){
console.log(8) // 异步任务
new Promise(function(resolve){
console.log(9)
resolve()
}).then(function(){
console.log(10)
})
}
// 宏任务2:
function(){
console.log(2) // 异步任务
}
// 宏任务3:
function(){
console.log(5) // 异步任务
new Promise(function(resolve){
console.log(6)
resolve()
}).then(function(){
console.log(7)
})
}
```
4. 执行微任务[]
```js
console.log(4)
```
5. 微任务已全部执行完成,接下来执行下一个宏任务
```js
// 宏任务1:
function(){
console.log(8)
new Promise(function(resolve){
console.log(9)
resolve()
}).then(function(){
console.log(10) // 进入微任务
})
}
```
```js
console.log(8)
console.log(9)
```
6. console.log(10)进入微任务:[console.log(10)]
```js
console.log(10) // 微任务[]
```
7. 微任务空,执行下一个宏任务
```js
// 宏任务2:
function(){
console.log(2) // 异步任务
}
```
```js
console.log(2)
```
8. 微任务中还是空,继续执行下一个宏任务
```js
function(){
console.log(5) // 异步任务
new Promise(function(resolve){
console.log(6)
resolve()
}).then(function(){
console.log(7) // 进入微任务
})
}
```
```js
console.log(5)
console.log(6)
```
9. 微任务中有[console.log(7)]
```js
console.log(7)
```
10. 最后微任务清空,宏任务也清空