12JavaScript高频速写题
1。防抖防抖:可以和你的电脑设定了10分钟睡眠时间的场景结合起来理解如果你一直在用电脑,那么电脑就不会睡眠(频繁的把前一个定时器关掉,开启新的定时器)当你最后一次没操作电脑10分钟之后,电脑陷入睡眠constdebouncefunction(func,delay){lettimernullreturnfunction(。。。args){clearTimeout(timer)timersetTimeout((){func。apply(this,args)},delay)}}测试html部分inputtypetextidinputjs部分constshowNamedebounce(function(name){console。log(input。value,this,name)},500)input。addEventListener(input,(e){500ms内停止输入才会输出showName。call({name:前端胖头鱼},前端胖头鱼)})2。节流
节流:任凭你怎么触发,其在指定的时间间隔内只会触发一次基于时间戳(方式1)constthrottlefunction(func,delay){letstartTimeDate。now()returnfunction(。。。args){letlastTimeDate。now()if(lastTimestartTimedelay){func。apply(this,args)startTimeDate。now()}}}测试lett1Date。now()constshowNamethrottle(function(name){constt2Date。now()console。log(this,name,t2t1)t1Date。now()},1000)虽然设置了每隔10毫秒就会执行一次showName函数,但是实际还是会每隔1秒才输出setInterval((){showName。call({name:前端胖头鱼},前端胖头鱼)},10){name:前端胖头鱼}前端胖头鱼1013{name:前端胖头鱼}前端胖头鱼1001{name:前端胖头鱼}前端胖头鱼1006{name:前端胖头鱼}前端胖头鱼1006{name:前端胖头鱼}前端胖头鱼1005基于setTimeout(方式2)constthrottle2function(func,delay){lettimernullreturnfunction(。。。args){if(!timer){timersetTimeout((){func。apply(this,args)timernull},delay)}}}测试lett1Date。now()constshowNamethrottle2(function(name){constt2Date。now()console。log(this,name,t2t1)t1Date。now()},1000)setInterval((){showName。call({name:前端胖头鱼},前端胖头鱼)},10){name:前端胖头鱼}前端胖头鱼1014{name:前端胖头鱼}前端胖头鱼1001{name:前端胖头鱼}前端胖头鱼1007{name:前端胖头鱼}前端胖头鱼1011{name:前端胖头鱼}前端胖头鱼1009{name:前端胖头鱼}前端胖头鱼10083。函数柯里化constcurry(func,。。。args){获取函数的参数个数constfnLenfunc。lengthreturnfunction(。。。innerArgs){innerArgsargs。concat(innerArgs)参数未搜集足的话,继续递归搜集if(innerArgs。lengthfnLen){returncurry。call(this,func,。。。innerArgs)}else{否则拿着搜集的参数调用funcfunc。apply(this,innerArgs)}}}测试constaddcurry((num1,num2,num3){console。log(num1,num2,num3,num1num2num3)})add(1)(2)(3)1236add(1,2)(3)1236add(1,2,3)1236add(1)(2,3)12364。bind
bind()方法创建一个新的函数,在bind()被调用时,这个新函数的this被指定为bind()的第一个参数,而其余参数将作为新函数的参数,供调用时使用。MDN
姐妹篇call实现
姐妹篇apply实现Function。prototype。bind2function(context,。。。args){if(typeofthis!function){thrownewTypeError(Bindmustbecalledonafunction)}constexecuteBoundfunction(sourceFunc,boundFunc,context,callingContext,args){if(!(callingContextinstanceofboundFunc)){如果调用方式不是newfunc的形式就直接调用sourceFunc,并且给到对应的参数即可returnsourceFunc。apply(context,args)}else{类似于执行new的几个过程constselfObject。create(sourceFunc。prototype)处理new调用的形式constresultsourceFunc。apply(self,args)判断函数执行后的返回结果非对象函数,则返回selfif(resulttypeofresultobjecttypeofresultfunction){returnresult}else{returnself}}}constfuncthisconstboundfunction(。。。innerArgs){returnexecuteBound(func,bound,context,this,args。concat(innerArgs))}returnbound}测试1。普通调用constshowNamefunction(sex,age){console。log(this,sex,age)}showName。bind2({name:前端胖头鱼},boy)(100){name:前端胖头鱼}boy1002。new调用constPersonfunction(name){this。namename}Person。prototype。showNamefunction(age){console。log(this,this。name,age)}constbindPersonPerson。bind(null,boy)constp1newbindPerson(前端胖头鱼)p1。showName(100)Person{name:boy}boy1005。实现一个简易版模板引擎
jQuery时代,模板引擎用的还是比较多的,可以理解为它是这样一个函数,通过模板数据经过一段黑盒操作最后得到需要展示的页面constrender(template,data){s?是为了兼容{{name}}{{name}}这种写法returntemplate。replace({{s?(w)s?}}g,(match,key){匹配中了则读取替换,否则替换为空字符串returnkeydata。hasOwnProperty(key)?data〔key〕:})}constdata{name:前端胖头鱼,age:100}consttemplate我是:{{name}}年龄是:{{age}}console。log(render(template,data))我是:前端胖头鱼年龄是:1006。类数组转化为数组的4种方式类数组转化为数组constarrayLikeObj{0:前端胖头鱼,1:100,length:2}1。〔〕。sliceconsole。log(〔〕。slice。call(arrayLikeObj))2。Array。fromconsole。log(Array。from(arrayLikeObj))3。Array。applyconsole。log(Array。apply(null,arrayLikeObj))4。〔〕。concatconsole。log(〔〕。concat。apply(〔〕,arrayLikeObj))7。请实现DOM2JSON一个函数,可以把一个DOM节点输出JSON的格式
曾经在字节的面试中出现过constdom2json(rootDom){if(!rootDom){return}letrootObj{tagName:rootDom。tagName,children:〔〕}constchildrenrootDom。children读取子节点(元素节点)if(childrenchildren。length){Array。from(children)。forEach((ele,i){递归处理rootObj。children〔i〕dom2json(ele)})}returnrootObj}
测试!DOCTYPEhtmlhtmllangenheadmetacharsetUTF8metahttpequivXUACompatiblecontentIEedgemetanameviewportcontentwidthdevicewidth,initialscale1。0titledom2jsontitleheadbodypclassphelloworldspanclassname前端胖头鱼spanspanclassage100spanbodyhtml
image。png8。列表转树形结构
相信大家工作中也遇到过类似的问题,前端需要的是树形结构的数据,但是后台返回的是一个list,我们需要将list转化为树形结构(当然这里你也可以把你的后端同学干啪为啥不给我想要的数据)。constarrayToTree(array){consthashMap{}letresult〔〕array。forEach((it){const{id,pid}it不存在时,先声明children树形这一步也有可能在下面出现if(!hashMap〔id〕){hashMap〔id〕{children:〔〕}}hashMap〔id〕{。。。it,children:hashMap〔id〕。children}处理当前的itemconsttreeIthashMap〔id〕根节点,直接pushif(pid0){result。push(treeIt)}else{也有可能当前节点的父父节点还没有加入hashMap,所以需要单独处理一下if(!hashMap〔pid〕){hashMap〔pid〕{children:〔〕}}非根节点的话,找到父节点,把自己塞到父节点的children中即可hashMap〔pid〕。children。push(treeIt)}})returnresult}测试constdata〔注意这里,专门把pid为1的元素放一个在前面{id:2,name:部门2,pid:1},{id:1,name:部门1,pid:0},{id:3,name:部门3,pid:1},{id:4,name:部门4,pid:3},{id:5,name:部门5,pid:4},{id:7,name:部门7,pid:6},〕console。log(JSON。stringify(arrayToTree(data),null,2))〔{id:1,name:部门1,pid:0,children:〔{id:2,name:部门2,pid:1,children:〔〕},{id:3,name:部门3,pid:1,children:〔{id:4,name:部门4,pid:3,children:〔{id:5,name:部门5,pid:4,children:〔〕}〕}〕}〕}〕9。树形结构转列表
反过来也可以试试看consttree2list(tree){letlist〔〕letqueue〔。。。tree〕while(queue。length){从前面开始取出节点constnodequeue。shift()constchildrennode。children取出当前节点的子节点,放到队列中,等待下一次循环if(children。length){queue。push(。。。children)}删除多余的children树形deletenode。children放入列表list。push(node)}returnlist}测试constdata〔{id:1,name:部门1,pid:0,children:〔{id:2,name:部门2,pid:1,children:〔〕},{id:3,name:部门3,pid:1,children:〔{id:4,name:部门4,pid:3,children:〔{id:5,name:部门5,pid:4,children:〔〕}〕}〕}〕}〕console。log(tree2list(data))〔{id:1,name:部门1,pid:0},{id:2,name:部门2,pid:1},{id:3,name:部门3,pid:1},{id:4,name:部门4,pid:3},{id:5,name:部门5,pid:4}〕10。sleep
实现一个函数,n秒后执行函数funcconstsleep(func,delay){returnnewPromise((resolve){setTimeout((){resolve(func())},delay)})}constconsoleStr(str){return(){console。log(str)returnstr}}constdoFnsasync(){constnameawaitsleep(consoleStr(前端胖头鱼),1000)constsexawaitsleep(consoleStr(boy),1000)constageawaitsleep(consoleStr(100),1000)console。log(name,sex,age)}doFns()前端胖头鱼1slaterboy2slater1003slater前端胖头鱼boy10011。菲波那切数列斐波那契数,通常用F(n)表示,形成的序列称为斐波那契数列。该数列由0和1开始,后面的每一项数字都是前面两项数字的和。也就是:F(0)0,F(1)1F(n)F(n1)F(n2),其中n1给你n,请计算F(n)。示例1:输入:2输出:1解释:F(2)F(1)F(0)101示例2:输入:3输出:2解释:F(3)F(2)F(1)112示例3:输入:4输出:3解释:F(4)F(3)F(2)213暴力实现
根据题目意思,很容易写出下面递归的暴力代码constfib(n){if(n0){return0}if(n1n2){return1}returnfib(n2)fib(n1)}测试console。log(fib(1))1console。log(fib(2))1试着统计一下计算时间constt1Date。now()console。log(fib(44))701408733console。log(Date。now()t1)接近4393缓存优化
上面的代码可以实现效果,但是性能堪忧,来看一个计算fib(10)的过程计算101098需要计算9和8987需要计算8和7876需要计算7和6765需要计算6和5654需要计算5和4543需要计算4和3432需要计算3和2210需要计算1和0
这个过程中如果按照上面暴力实现的代码会重复多次计算某些曾经计算过的值,比如8、7、6、5。。。等等,这个损耗是没有必要的,所以我们可以把计算的结果进行缓存,下次遇到求同样的值,直接返回即可constfib(n){缓存过直接返回if(typeoffib〔n〕!undefined){returnfib〔n〕}if(n0){return0}if(n1n2){return1}constresfib(n2)fib(n1)缓存计算的结果fib〔n〕resreturnres}console。log(fib(1))1console。log(fib(2))1constt1Date。now()console。log(fib(44))701408733console。log(Date。now()t1)1ms12。实现一个函数sum函数
实现一个函数sum函数满足以下规律sum(1,2,3)。valueOf()6sum(2,3)(2)。valueOf()7sum(1)(2)(3)(4)。valueOf()10sum(2)(4,1)(2)。valueOf()9
分析
仔细观察这几种调用方式可以得到以下信息sum函数可以传递一个或者多个参数sum函数调用后返回的是一个新的函数且参数可传递一个或者多个调用。valueOf时完成最后计算
看起来是不是有点函数柯里化的感觉,前面的函数调用仅仅是在缓存每次调用的参数,而valueOf的调用则是拿着这些参数进行一次求和运算并返回结果constsum(。。。args){声明add函数,其实主要是缓存参数的作用注意add调用完成还是会返回add函数本身,使其可以链式调用constadd(。。。args2){args〔。。。args,。。。args2〕returnadd}求和计算add。valueOf()args。reduce((ret,num)retnum,0)returnadd}测试console。log(sum(1,2,3)。valueOf())6console。log(sum(2,3)(2)。valueOf())7console。log(sum(1)(2)(3)(4)。valueOf())10console。log(sum(2)(4,1)(2)。valueOf())9