本周面试题一览:什么是闭包?闭包的作用是什么?Promise.all方法异步加载js脚本的实现方式有哪些?请实现一个flattenDeep函数来展平嵌套数组。可迭代对象有哪些特点?更多优质文章可以戳:https://github.com/YvetteLau/...15.什么是闭包?闭包的作用是什么?什么是闭包?闭包是一个函数,它可以访问另一个函数范围内的变量。创建闭包最常见的方法是在函数内部创建另一个函数。创建一个闭包函数foo(){vara=2;返回函数fn(){console.log(a);}}让func=foo();func();//输出2个闭包,以便函数在访问定义时可以继续词法作用域。感谢fn,在foo()执行后,foo内部的作用域不会被破坏。无论内部函数通过何种方式传递到其词法范围之外,它都会持有对原始定义范围的引用,并且在函数执行的任何地方都会使用闭包。如:functionfoo(){vara=2;函数内部(){console.log(a);外(内);}函数外部(fn){fn();//闭包}foo();闭包提供对定义函数的词法范围的访问(防止它被回收)。私有变量函数base(){letx=10;//私有变量return{getX:function(){returnx;}}}让obj=base();console.log(obj.getX());//10模拟块级作用域vara=[];对于(vari=0;i<10;i++){a[i]=(function(j){returnfunction(){console.log(j);}})(i);}a[6]();//6创建模块functioncoolModule(){letname='Yvette';让年龄=20;函数sayName(){console.log(名称);}functionsayAge(){console.log(age);}return{sayName,sayAge}}letinfo=coolModule();info.sayName();//'Yvette'模块模式有两个先决条件(来自《你不知道的JavaScript》)必须有一个外部封闭函数,必须至少调用一次(每次调用创建一个新的模块实例)封闭函数必须至少返回一个内部函数,这样内部函数就可以在私有范围内形成一个闭包,并且可以访问或修改私有状态。闭包的缺点闭包会导致函数的变量一直保存在内存中,过多的闭包可能会导致内存泄漏16、实现Promise.all方法在实现Promise.all方法之前,我们首先要知道Promise.all的函数和特点,因为我们在了解了Promise.all的功能和特点之后,可以进一步去编写和实现。Promise.all函数Promise.all(iterable)返回一个新的Promise实例。当可迭代参数中的所有promise都被fulfilled或参数不包含promise时,该实例的状态变为fulfilled;如果参数中有一个promise被rejected,则本次回调失败,失败原因为第一个失败promise的返回结果。让p=Promise.all([p1,p2,p3]);p的状态由p1、p2、p3决定,分为以下两种情况:(1)只有p1、p2、p3的状态变为fulfilled,p的状态才会变为fulfilled。此时p1、p2、p3的返回值组成一个数组,传递给p的回调函数。(2)只要p1、p2、p3其中之一被拒绝,p的状态就会变成被拒绝。这时候第一个被拒绝的实例的返回值就会传递给p的回调函数。Promise.all的特点Promise.all的返回值是一个promise实例。如果传入的参数是一个空的可迭代对象,Promise.all会同步返回一个fulfilledpromise。如果传入的参数不包含任何promise,Promise.all将异步返回一个处于完成状态的promise。在其他情况下,Promise.all返回一个处于挂起状态的承诺。如果传递参数中的所有承诺都已完成,则Promise.all返回承诺的状态。状态,由Promise.all返回的承诺异步实现。如果其中一个传入参数失败,则Promise.all将失败的结果异步发送给失败状态的回调函数,而不管其他promise是否完成。无论如何,Promise.all返回的promise的完成状态结果是一个数组Promise.all的实现只考虑传入参数是数组的情况/**只考虑promise以数组形式传递的情况*/Promise.all=function(promises){returnnewPromise((resolve,reject)=>{if(promises.length===0){resolve([]);}else{letresult=[];letindex=0;for(leti=0;i{result[i]=data;if(++index===promises.length){//所有的promises状态为fulfilled,promise.all返回的实例变为fulfilled状态resolve(result);}},err=>{reject(err);return;});}}});}可以使用MDN上的代码进行测试,考虑iterableobjectPromise.all=function(promises){/**promises是一个可迭代对象,省略参数类型的判断*/returnnewPromise((resolve,reject)=>{if(promises.length===0){//如果传入参数是一个空的可迭代对象returnresolve([]);}else{letresult=[];letindex=0;letj=0;for(letvalueofpromises){(function(i){Promise.resolve(value).then(data=>{result[i]=data;//保证顺序index++;if(index===j){//此时j为length.resolve(result);}},err=>{//Apromisefailedreject(err);return;});})(j)j++;//length}}});}测试代码:letp2=Promise.all({一个:1,[Symbol.iterator](){让索引=0;返回{下一个(){索引++;if(index==1){return{value:newPromise((resolve,reject)=>{setTimeout(resolve,100,'foo');}),done:false}}elseif(index==2){return{value:newPromise((resolve,reject)=>{resolve(222);}),done:false}}elseif(index===3){return{value:3,done:false}}else{return{done:true}}}}}});setTimeout(()=>{console.log(p2)},200);17.异步加载js脚本的方法有哪些?将async(html5)或defer(html4)属性添加到defer和async的区别是:defer要等到整个页面在内存中正常渲染(DOM结构完全生成,等脚本执行)),在window.onload之前执行;一旦异步文件下载完毕,渲染引擎就会中断渲染,执行完这段脚本后继续渲染。如果有多个defer脚本,多个async脚本将按照它们在页面上出现的顺序加载。无法保证加载顺序。Dynamicallycreatedscript标签动态创建脚本。设置src不会开始下载,但是会添加到document,JS文件开始下载。letscript=document.createElement('script');script.src='XXX.js';//添加到html文件开始下载document.body.append(script);XHR异步加载JSletxhr=newXMLHttpRequest();xhr.open("get","js/xxx.js",true);xhr.send();xhr.onreadystatechange=function(){if(xhr.readyState==4&&xhr.status==200){eval(xhr.responseText);}}18.请实现一个flattenDeep函数来展平嵌套数组。使用Array.prototype.flatES6为数组实例添加一个flat方法,用于将嵌套数组“Flatten”展平,成为一维数组。该方法返回一个新数组,对原数组没有影响。默认情况下,flat只会“压平”一层。如果要“展平”多层嵌套数组,需要传递一个整数给flat,表示要展平的层数。functionflattenDeep(arr,deepLength){returnarr.flat(deepLength);}console.log(flattenDeep([1,[2,[3,[4]],5],3));当传入的整数大于数组嵌套的层数时,数组将被展平为一维数组。JS能表示的最大数是Math.pow(2,53)-1,所以我们可以定义flattenDeep函数functionflattenDeep(arr){//当然大部分时候我们没有那么多层次的嵌套返回arr.flat(Math.pow(2,53)-1);}console.log(flattenDeep([1,[2,[3,[4]],5]]));使用reduce和concat函数flattenDeep(arr){returnarr.reduce((acc,val)=>Array.isArray(val)?acc.concat(flattenDeep(val)):acc.concat(val),[]);}console.log(flattenDeep([1,[2,[3,[4]],5]]));使用栈无限去嵌套多层嵌套数组functionflattenDeep(input){conststack=[..。输入];constres=[];while(stack.length){//使用pop从栈中移除值constnext=stack.pop();if(Array.isArray(next)){//使用push返回内部数组中的元素而不改变原始输入originalinputstack.push(...next);}else{res.push(下一步);}}//使用reverse恢复原数组的顺序returnres.reverse();}console.log(flattenDeep([1,[2,[3,[4]],5]]));19、可迭代对象有什么特点ES6规定默认的Iterator接口部署在数据结构的Symbol.iterator属性中,换个角度也可以认为只要一个数据结构具有Symbol.iterator属性(Symbol.iterator方法对应遍历器生成函数,返回一个遍历器对象),那么可以认为可迭代的Iterable对象具有Symbol.iterator属性,Symbol.iterator()返回一个可迭代器对象循环使用for...ofletarry=[1,2,3,4];letiter=arry[Symbol.iterator]();console.log(iter.next());//{值:1,完成:假}console.log(iter.next());//{值:2,完成:假}console.log(iter.next());//{value:3,done:false}带有Iterator接口的原生数据结构:argumentsobjectArrayMapSetStringTypedArray函数的NodeList对象自定义一个可迭代对象我们上面说了一个对象只有拥有正确的Symbol.iterator属性才是可迭代的,所以我们可以通过向对象添加Symbol.iterator使其可迭代。letobj={name:"Yvette",age:18,job:'engineer',*[Symbol.iterator](){constself=this;constkeys=Object.keys(self);for(letindex=0;index