# 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 ``` 优势:简单有效 问题:循环引用导致内存泄漏 ![image-20210526112211836](/Users/liufusong/Desktop/extend/interview-questions/js_interview_questions/images/image-20210526112211836.png) 上图解析: 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 ![image-20210526210658716](/Users/liufusong/Desktop/extend/interview-questions/js_interview_questions/images/image-20210526210658716.png) setInterval ![image-20210526211326280](/Users/liufusong/Desktop/extend/interview-questions/js_interview_questions/images/image-20210526211326280.png) setImmediate ![image-20210526211510519](/Users/liufusong/Desktop/extend/interview-questions/js_interview_questions/images/image-20210526211510519.png) ### 微任务 由javascript自身发起的异步:微任务 ![image-20210527092520991](/Users/liufusong/Desktop/extend/interview-questions/js_interview_questions/images/promiseThen.png) ![image-20210527093318892](/Users/liufusong/Desktop/extend/interview-questions/js_interview_questions/images/PromiseAll.png) ### 执行顺序 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() ``` ![image-20210612214455183](./images/image-20210612214455183.png) ### 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) ```