# 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 ``` 优势:简单有效 问题:循环引用导致内存泄漏 ![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. 最后微任务清空,宏任务也清空