# interview-questions-JS
**Repository Path**: destiny001/js_interview_questions
## Basic Information
- **Project Name**: interview-questions-JS
- **Description**: js面试题
- **Primary Language**: Unknown
- **License**: Not specified
- **Default Branch**: master
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 11
- **Forks**: 1
- **Created**: 2020-03-17
- **Last Updated**: 2022-09-07
## 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. 最后微任务清空,宏任务也清空
## ES6-12
### Symbol
#### Why
例如:已有person对象,该对象包含的方法属性很多,现在需要给person添加一个say的方法,我们不需要考虑person内部有没有say这个方法,我们就是要添加一个say方法,并且如果person内部有say这个方法,还不能影响内部的say方法
此时就可以使用Symbol来解决该问题
#### What
Symbol是es6引入的第七个原始数据类型,Symbol数据表示一个唯一值,独一无二的
Symbol数据无法进行运算
Symbol数据无法通过new 关键字使用
Symbol数据如果作为对象的key不能通过for in遍历
#### How
##### 常量
Symbol('该Symbol数据的注释')
```js
const name = Symbol('猴哥')
const name2 = Symbol('猴哥')
console.log(name === name2) // false
```
Symbol.for创建,该方法会先检查是否有Symbol.for()创建的同名数据,如果有则返回,如果没有则创建
```js
const name3 = Symbol.for('猴哥')
const name4 = Symbol.for('猴哥')
console.log(name3 === name4) // false
```
##### 对象属性
Symbol作为对象的key
```js
// person对象,内部可能有很多方法,并且也有可能有say方法
const person = {
say: function () {
console.log('person的say')
},
....
}
// Symbol 数据 通过常量say保存
const say = Symbol('person的自定义的say')
// 给person添加key为Symbol('person的自定义的say')的方法
person[say] = function () {
console.log('自定义的say')
}
// 调用person中我们自定义的方法
person[say]()
// 调用person中自己的say
person.say()
```

### Set
Set是es6新增的数据结构,类似于数组,区别在于不包含重复的值
```js
const nameArr = new Set(['张三', '李四', '张三', '王麻子'])
// Set(3) {"张三", "李四", "王麻子"}
```
#### add
添加元素
```js
const nameArr = new Set(['张三', '李四', '张三', '王麻子'])
nameArr.add('赵六') // Set(4) {"张三", "李四", "王麻子", "赵六"}
```
#### delete
删除元素
```js
const nameArr = new Set(['张三', '李四', '张三', '王麻子'])
nameArr.delete('张三') // // Set(2) {"李四", "王麻子"}
```
#### clear
清楚所有
```js
const nameArr = new Set(['张三', '李四', '张三', '王麻子'])
nameArr.clear() // Set(0) {}
```
#### has
监测
```js
const nameArr = new Set(['张三', '李四', '张三', '王麻子'])
nameArr.has('张三') // true
```
#### 实战应用
数组去重
```js
const name = ['张三', '李四', '王五', '赵六', '张三']
const name2 = new Set(name) // Set(4) {"张三", "李四", "王五", "赵六"}
```
数组交集
```js
const name = ['张三', '李四', '王五', '赵六']
const name2 = ['张三', '李四', '王五', '王麻子']
const name3 = name.filter(item => {
return new Set(name2).has(item)
})
// (3) ["张三", "李四", "王五"]
```
数组并集
```js
const name = ['张三', '李四', '王五', '赵六']
const name2 = ['张三', '李四', '王五', '王麻子']
const name3 = new Set([...name, ...name2])
//Set(5) {"张三", "李四", "王五", "赵六", "王麻子"}
```
数组差集
```js
const name = ['张三', '李四', '王五', '赵六', '王麻子']
const name2 = ['张三', '李四', '王五']
const name3 = name.filter(item => {
return !new Set(name2).has(item)
})
// (2) ["赵六", "王麻子"]
```
### Map
#### why
+ Object的对象key只能是字符串或者 Symbols, Map 的键可以是任意值
+ Object的对象是无序的, 而Map的是有序的
+ Object 的键值对个数只能手动计算, 而Map的键值对个数可以从 size 属性获取
#### what
Map 对象保存键值对。任何值(对象或者原始值) 都可以作为一个键或一个值。
#### how
##### 创建Map对象
```js
const myMap = new Map()
console.log(myMap)
```
##### 添加属性
Map.prototype.set(), 会返回改map实例
```js
const myMap = new Map()
const obj = { a: 1 }
myMap.set(obj, 'content')
```
```js
myMap.set('a', 1).set('b', 2).set('c', 3)
```
```js
const myMap = new Map([
['name', '张三'],
['age', 19]
])
```
##### 读取数据
```js
const myMap = new Map()
const obj = { a: 1 }
myMap.get(obj)
```
读取未定义的数据同样返回undefined
##### size 属性
`size`属性返回 Map 结构的成员总数。
```javascript
const myMap = new Map([
['name', '张三'],
['age', 19]
])
map.size // 2
```
##### 克隆数据
```js
const myMap = new Map([
['name', '张三'],
['age', 19]
])
// 浅拷贝
const myMap1 = myMap
// 深拷贝
const myMap2 = new Map(myMap)
console.log(myMap === myMap1) // false
console.log(myMap1 === myMap2) // true
```
##### has 检测
##### delete 删除
##### clear清除
##### forEach遍历
```js
myMap.forEach((value, key, map) => {
console.log(value, key, map)
})
```
### 运算扩展
#### 链式判断运算符
es2020(es 11)
当我们在访问某一对象内部的多层级数据时,为了代码的安全,我们会先判断第一层是否存在,存在再取下一层的数据,以此类推最后访问到需要用到的数据
```js
const res = {
data: {
meta: {
status: 200
}
}
}
// 旧的写法
if (res.data && res.data.meta && res.data.meta.status === 200)
// 新的写法
if (res?.data?.meta?.status === 200)
```
#### Null 判断运算符
es2020(es11)
读取对象属性的时候,如果某个属性的值是`null`或`undefined`,有时候需要为它们指定默认值。常见做法是通过`||`运算符指定默认值。
```js
const data = {
users: null
}
const text = data.users || '没有数据'
```
上面的代码通过`||`运算符指定默认值,但是这样写是错的。开发者的原意是,只要属性的值为`null`或`undefined`,默认值就会生效,但是属性的值如果为空字符串或`false`或`0`,默认值也会生效。
```js
const data = {
users: null
}
const text = data.users ?? '没有数据'
```
#### 逻辑赋值运算符
es2021(es12)
它们的一个用途是,为变量或属性设置默认值
```js
const data = {
users: null
}
data.users = data.users || '没有数据'
console.log(data)
```
新版写法
判断是否条件成立
```js
const data = {
users: null
}
data.users ||= '没有数据'
console.log(data)
```
判断 是否为null或undefined 并赋值
```js
const data = {
users: 0
}
data.users ??= '没有数据'
console.log(data)
```