防抖原理 事件高频触发后,n秒内函数只会执行一次,若n秒内事件再次触发,则重新计时,总之就是要等触发完事件n秒内不再触发事件,函数才执行代码实现functiondebounce(callback,wait){lettimerreturnfunction(。。。args){clearTimeout(timer)timersetTimeout((){callback。call(this,args)},wait)}}使用document。body。addEventListener(mousemove,debounce((e){console。log(this,e,mousemovedebounce)},1000))节流原理 如果事件持续触发,在指定时间内,只执行一次事件代码实现 时间戳方式时间戳方式使用时间戳,当触发事件的时候,我们取出当前的时间戳,然后减去之前的时间戳(最一开始值设为0),如果大于设置的时间周期,就执行函数,然后更新时间戳为当前的时间戳,如果小于,就不执行。functionthrottle(callback,wait){letstart0returnfunction(。。。args){constnownewDate()if(nowstartwait){callback。call(this,args)startnow}}}使用constcbthrottle(function(e){console。log(this)},1000)document。body。addEventListener(mousemove,(){cb。call({name:张三})},1000){name:张三} 定时器方式定时器方式当触发事件的时候,我们设置一个定时器,再触发事件的时候,如果定时器存在,就不执行,直到定时器执行,然后执行函数,清空定时器。functionthrottle(callback,wait){lettimerreturnfunction(。。。args){if(!timer){timersetTimeout((){timernullcallback。call(this,args)},wait)}}}constcbthrottle(function(e){console。log(this)},1000)document。body。addEventListener(mousemove,(){cb。call({name:张三})},1000){name:张三}模拟new运算符 new运算符创建一个用户定义的对象类型的实例或具有构造函数的内置对象的实例。原理新建一个空对象链接到原型绑定this返回该对象代码实现functionmyNew(){1。新建一个空对象letobj{}2。获得构造函数letcon〔〕。shift。call(arguments)3。链接原型,实例的proto属性指向构造函数的prototypeobj。protocon。prototype4。绑定this,执行构造函数letrescon。apply(obj,arguments)5。返回新对象returntypeofresobject?res:obj}functionPerson(name){this。namename}letpersonmyNew(Person,nanjiu)console。log(person){name:nanjiu}console。log(typeofpersonobject)trueconsole。log(personinstanceofPerson)true模拟instanceof instanceof用于检测构造函数的prototype是否在实例的原型链上,需要注意的是instanceof只能用来检测引用数据类型,对于基本数据检测都会返回false原理 通过循环检测实例的proto属性是否与构造函数的prototype属性相等代码实现instanceof用于检测构造函数的prototype是否在实例的原型链上functionmyInstanceof(left,right){先排除基本数据类型if(typeofleft!objectleftnull)returnfalseletprotoleft。protowhile(proto){if(protoright。prototype)returntrueprotoproto。proto}returnfalse}functionPerson(){}letpersonnewPerson()console。log(myInstanceof(person,Person))true模拟Function。prototype。apply() apply()方法调用一个具有给定this值的函数,以及以一个数组(或类数组对象)的形式提供的参数。Function。prototype。myApplyfunction(context){varcontextcontextwindow获取需要绑定的thiscontext。fnthis获取需要改变this的函数constargarguments〔1〕获取传递给函数的参数if(!(arginstanceofArray)){throwError(参数需要是一个数组)}constrescontext。fn(。。。arg)执行函数deletecontext。fn删除该方法returnres返回函数返回值}functionsay(a,b,c){console。log(this。name,a,b,c)}say。myApply({name:nanjiu},〔1,2,3〕)nanjiu123say。apply({name:nanjiu},〔1,2,3〕)nanjiu123模拟Function。prototype。call() call()方法使用一个指定的this值和单独给出的一个或多个参数来调用一个函数。Function。prototype。myCallfunction(context){varcontextcontextwindow获取需要改变的thiscontext。fnthis获取需要改变this的函数constargs〔。。。arguments〕。slice(1)获取参数列表constrescontext。fn(。。。args)将参数传给函数并执行deletecontext。fn删除该方法returnres返回函数返回值}functionsay(a,b,c){console。log(this。name,a,b,c)}say。myCall({name:nanjiu},1,2,3)nanjiu123say。call({name:nanjiu},1,2,3)nanjiu123模拟Function。prototype。bind() bind()方法创建一个新的函数,在bind()被调用时,这个新函数的this被指定为bind()的第一个参数,而其余参数将作为新函数的参数,供调用时使用。Function。prototype。myBindfunction(context){varcontextcontextwindow获取需要改变的thiscontext。fnthis获取需要改变this的函数获取函数参数constargs〔。。。arguments〕。slice(1)与apply,call不同的是这里需要返回一个函数return(){returncontext。fn。apply(context,〔。。。args〕)}}functionsay(a,b,c){console。log(this。name,a,b,c)}say。bind({name:nanjiu},1,2,3)()nanjiu123say。myBind({name:nanjiu},1,2,3)()nanjiu123模拟Array。prototype。forEach() forEach()方法对数组的每个元素执行一次给定的函数,无返回值。语法arr。forEach(callback(currentValue〔,index〔,array〕〕)〔,thisArg〕)参数callback为数组中每个元素执行的函数,该函数接收一至三个参数:currentValue数组中正在处理的当前元素。index可选数组中正在处理的当前元素的索引。array可选forEach()方法正在操作的数组。thisArg可选可选参数。当执行回调函数callback时,用作this的值。代码实现Array。prototype。myForEachfunction(callback,context){constarrthis获取调用的数组constlenarr。length0letindex0数组下标while(indexlen){callback。call(context,arr〔index〕,index)index}}letarr〔1,2,3〕arr。forEach((item,index){console。log(key:{index}item:{item})})console。log()arr。myForEach((item,index){console。log(key:{index}item:{item})})key:0item:1key:1item:2key:2item:3key:0item:1key:1item:2key:2item:3模拟Array。prototype。map() map()方法创建一个新数组,其结果是该数组中的每个元素是调用一次提供的函数后的返回值。语法varnewarrayarr。map(functioncallback(currentValue〔,index〔,array〕〕){Returnelementfornewarray}〔,thisArg〕)参数 callback 生成新数组元素的函数,使用三个参数:currentValue数组中正在处理的当前元素。index可选数组中正在处理的当前元素的索引。array可选map方法调用的数组。 thisArg可选 执行callback函数时值被用作this。代码实现map()方法创建一个新数组,其结果是该数组中的每个元素是调用一次提供的函数后的返回值。Array。prototype。myMapfunction(callback,context){constarrthis,res〔〕constlenarr。length0letindex0while(indexlen){res。push(callback。call(context,arr〔index〕,index))index}returnres与forEach不同的是map有返回值}constarr〔1,2,3〕letres1arr。map((item,index){returnk:{index}v:{item}})letres2arr。myMap((item,index){returnk:{index}v:{item}})console。log(res1)〔k:0v:1,k:1v:2,k:2v:3〕console。log(res2)〔k:0v:1,k:1v:2,k:2v:3〕模拟Array。prototype。filter() filter()方法创建一个新数组,其包含通过所提供函数实现的测试的所有元素。语法varnewArrayarr。filter(callback(element〔,index〔,array〕〕)〔,thisArg〕)参数 callback 用来测试数组的每个元素的函数。返回true表示该元素通过测试,保留该元素,false则不保留。它接受以下三个参数:element数组中当前正在处理的元素。index可选正在处理的元素在数组中的索引。array可选调用了filter的数组本身。 thisArg可选 执行callback时,用于this的值。代码实现filter()方法创建一个新数组,其包含通过所提供函数实现的测试的所有元素。Array。prototype。myFilterfunction(callback,context){constarrthis,res〔〕constlenarr。lengthletindex0while(indexlen){if(callback。call(context,arr〔index〕,index)){res。push(arr〔index〕)}index}returnres}constarr〔1,2,3〕letres1arr。filter((item,index){returnitem3})letres2arr。myFilter((item,index){returnitem3})console。log(res1)〔1,2〕console。log(res2)〔1,2〕函数柯里化 柯里化,英语:Currying(果然是满满的英译中的既视感),是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数而且返回结果的新函数的技术。 先来理解一下什么是函数柯里化,上面文绉绉的内容可能不是那么容易理解,我们还是直接上代码来理解吧假如有这样一个函数functionadd(a,b,c){console。log(abc)}add(1,2,3)6我们希望可以通过add(1,2)(3)或add(1)(2)(3)或add(1)(2,3)这样调用也能够得倒正确的计算结果这就是函数柯里化的简单应用代码实现functioncurry(fn,curArgs){constlenfn。length需要柯里化函数的参数个数curArgscurArgs〔〕returnfunction(){letargs〔〕。slice。call(arguments)获取参数argscurArgs。concat(args)拼接参数基本思想就是当拼接完的参数个数与原函数参数个数相等才执行这个函数,否则就递归拼接参数if(args。lengthlen){returncurry(fn,args)}else{returnfn。apply(this,args)}}}letfncurry(function(a,b,c){console。log(〔a,b,c〕)})fn(1,2,3)〔1,2,3〕fn(1,2)(3)〔1,2,3〕fn(1)(2,3)〔1,2,3〕fn(1)(2)(3)〔1,2,3〕类数组转数组 类数组是具有length属性,但不具有数组原型上的方法。常见的类数组有arguments、DOM操作方法返回的结果。functiontranslateArray(){方法一:Array。fromconstres1Array。from(arguments)console。log(res1instanceofArray,res1)true〔1,2,3〕方法二:Array。prototype。slice。callconstres2Array。prototype。slice。call(arguments)console。log(res2instanceofArray,res2)true〔1,2,3〕方法三:concateconstres3〔〕。concat。apply(〔〕,arguments)console。log(res3instanceofArray,res3)true〔1,2,3〕方法四:扩展运算符constres4〔。。。arguments〕console。log(res4instanceofArray,res4)true〔1,2,3〕}translateArray(1,2,3)实现深拷贝 在拷贝的时候判断一下属性值的类型,如果是对象,递归调用深拷贝函数在拷贝的时候判断一下属性值的类型,如果是对象,递归调用深拷贝函数functiondeepClone(obj,cachenewMap()){基本数据类型直接返回if(typeofobj!objectobjnull)returnobj防止循环引用constcacheTargetcache。get(obj)已经存在就直接返回if(cacheTarget)returncacheTargetletnewObjobjinstanceofArray?〔〕:{}新建一个对象cache。set(obj,newObj)遍历原对象for(letkeyinobj){if(obj。hasOwnProperty(key)){newObj〔key〕typeofobj〔key〕object?deepClone(obj〔key〕):obj〔key〕}}returnnewObj}constobj{name:张三}constobj1objconstobj2deepClone(obj)console。log(obj1obj)trueconsole。log(obj2obj)false继承的实现原型链继承 原型链继承实现的原理就是将构造函数的原型设置为另一个构造函数的实例对象,这样就可以继承另一个原型对象的所有属性和方法,可以继续往上,最终形成原型链。functionParent1(name,age){this。namename,this。ageage}Parent1。prototype。sayfunction(){console。log(this。name)}functionChild1(name){this。namename}Child1。prototypenewParent1()Child1。prototype。constructorChild1letchild1newChild1(诚实,18)console。log(child1)Child1{name:诚实}child1。say()诚实 缺点:当实现继承后,另一个原型的实例属性,变成了现在这个原型的原型属性,然后该原型的引用类型属性会被所有的实例共享,这样继承原型引用类型属性的实例之间不再具有自己的独特性了。在创建子类型的实例时,没有办法在不影响所有对象实例的情况下给超类型的构造函数中传递参数。构造函数继承为了解决原型中包含引用类型值的问题,开始使用借用构造函数,也叫伪造对象或经典继承 构造函数继承实现的原理就是在子类中调用父类构造函数来实现继承functionParent2(age){this。ageagethis。sayfunction(){console。log(this。name)}}functionChild2(name,age,gender){this。namenameParent2。call(this,age)this。gendergender}letchild2newChild2(张三,18,boy)console。log(child2)Child2{name:张三,age:18,gender:boy}child2。say()张三 优点:可以传递参数以及避免了引用类型的属性被所有实例共享 缺点:所有方法都在构造函数内,每次创建对象都会创建对应的方法,大大浪费内存组合继承 也叫伪经典继承,将原型链和借用构造函数的技术组合到一块。使用原型链实现对原型属性和方法的继承,而通过构造函数来实现对实例属性的继承。functionParent3(age){this。ageage}Parent3。prototype。sayfunction(){console。log(this。name)}functionChild3(name,age,gender){this。namenameParent3。call(this,age)this。gendergender}Child3。prototypenewParent3Child3。prototype。constructorChild3letchild3newChild3(张三,18,boy)console。log(child3)Child3{name:张三,age:18,gender:boy}child2。say()张三将Child3的原型指定为Parent3的一个实例,大致步骤和原型链继承类似,只是多了在Child3中借调Parent3的过程。实例属性定义在构造函数中,而方法则定义在构造函数的新原型中,同时将新原型的constructor指向构造函数。可以通过instanceof和isPrototypeOf()来识别基于组合继承创建的对象。避免了原型链和借用构造函数的缺陷,融合了它们的优点,成为JS中最常用的继承模式。原型式继承 借助原型可以基于已有的对象创建新对象,同时还不必因此创建自定义类型。functionobject(obj){functionF(){}F。prototypeobjreturnnewF()}letparent4{age:18,name:张三,say(){console。log(this。name)}}letchild4object(parent4)console。log(child4){〔〔Prototype〕〕:Objectage:18name:张三say:say()〔〔Prototype〕〕:Object}child4。say()张三 在object()函数内部,先创建了一个临时性的构造函数,然后将传入的对象作为这个构造函数的原型,最后返回了这个临时类型的一个新实例。从本质上讲,object()对传入其中的对象执行了一次浅复制这种原型式继承,要求必须要有一个对象可以作为另一个对象的基础用这种方式创建的对象相当于是传入参数对象的副本 它其实就是ES5Object。create的模拟实现,将传入的对象作为创建对象的原型 在只想让一个对象与另一个对象保持类似的情况下,原型继承是完全可以胜任的。原型模式下的缺点:引用类型属性的共享问题。寄生继承 寄生式继承与原型式继承紧密相关,与寄生构造函数和工厂模式类似,即创建一个仅用于封装继承过程的函数,该函数在内部以某种方式来增强对象,最后再返回对象。functioncreateAnother(original){varcloneobject(original)通过调用函数创建一个新对象clone。sayfunction(){以某种方式来增强这个对象console。log(nanjiu)};returnclone返回这个对象} 缺点:跟借用构造函数模式一样,每次创建对象都会创建一遍方法。寄生组合式继承 组合继承是JavaScript最常用的继承模式;不过,它也有自己的不足。组合继承最大的问题就是无论什么情况下,都会调用两次超类型构造函数:一次是在创建子类型原型的时候,另一次是在子类型构造函数内部。没错,子类型最终会包含超类型对象的全部实例属性,但我们不得不在调用子类型构造函数时重写这些属性。functionobject(o){functionF(){}F。returnnewF();}functionprototype(child,parent){varprototypeobject(parent。prototype);prototype。child。}functionParent6(age){this。ageage}Parent6。prototype。sayfunction(){console。log(this。name)}functionChild6(name,gender){this。namenamethis。gendergender}使用prototype(Child6,Parent6);letchild6newChild6(nanjiu,boy)console。log(child6)Child6{name:nanjiu,gender:boy}child6。say()nanjiu总结 JavaScript主要通过原型链实现继承。原型链的构建是通过将一个类型的实例赋值给另一个构造函数的原型实现的。这样,子类型就能够访问超类型的所有属性和方法,这一点与基于类的继承很相似。原型链的问题是对象实例共享所有继承的属性和方法,因此不适宜单独使用。解决这个问题的技术是借用构造函数,即在子类型构造函数的内部调用超类型构造函数。这样就可以做到每个实例都具有自己的属性,同时还能往超类型构造函数中传递参数,但是没有函数复用。使用最多的继承模式是组合继承,这种模式使用原型链继承共享的属性和方法,而通过借用构造函数继承实例属性。此外,还存在下列可供选择的继承模式。原型式继承,可以在不必预先定义构造函数的情况下实现继承,其本质是执行对给定对象的浅复制。而复制得到的副本还可以得到进一步改造。寄生式继承,与原型式继承非常相似,也是基于某个对象或某些信息创建一个对象,然后增强对象,最后返回对象。为了解决组合继承模式由于多次调用超类型构造函数而导致的低效率问题,可以将这个模式与组合继承一起使用。寄生组合式继承,集寄生式继承和组合继承的优点与一身,是实现基于类型继承的最有效方式。实现AJAX 步骤:创建XMLHttpRequest对象打开链接(指定请求类型,需要请求数据在服务器的地址,是否异步i请求)向服务器发送请求(get类型直接发送请求,post类型需要设置请求头)接收服务器的响应数据(需根据XMLHttpRequest的readyState属性判定调用哪个回调函数)functionajax(url,method,datanull){constxhrXMLHttpRequest()咱们这里就不管IE低版本了open()方法,它接受3个参数:要发送的请求的类型,请求的url和是否异步发送请求的布尔值。xhr。open(method,url,false)开启一个请求,当前还未发送xhr。onreadyStatechangefunction(){if(xhr。readyState4){if((xhr。status200xhr。status300)xhr。status304){alert(xhr。responseText);}else{console。log(Requestwasunsuccessful:xhr。status);}}}if(methodpost){xhr。setRequestHeader(ContentType,applicationxwwwformurlencoded);}xhr。send(data)get请求,data应为null,参数拼接在URL上}多维数组扁平化functionflat(arr){constres〔〕递归实现conststack〔。。。arr〕复制一份while(stack。length){取出复制栈内第一个元素constvalstack。shift()if(Array。isArray(val)){如果是数组,就展开推入栈的最后stack。push(。。。val)}else{否则就推入res返回值res。push(val)}}returnres}constarr〔1,〔2〕,〔3,4,〔5,6,〔7,〔8〕〕〕〕〕console。log(flat(arr))〔1,2,3,4,5,6,7,8〕 当然你也可以用数组自带的方法flat,将展开层数指定为Infinity无穷大,看看面试官搭不搭理你constarr〔1,〔2〕,〔3,4,〔5,6,〔7,〔8〕〕〕〕〕console。log(arr。flat(Infinity))〔1,2,3,4,5,6,7,8〕setTimeout模拟setInterval 思路就是递归调用setTimeoutfunctionmySetInterval(callback,delay){lettimernullletinterval(){timersetTimeout((){callback()interval()递归},delay)}interval()先执行一次return{id:timer,clear:(){clearTimeout(timer)}}}lettimemySetInterval((){console。log(1)},1000)setTimeout((){time。clear()},2000)setInterval模拟setTimeout 思路就是setInterval执行一次后将setInterval清除即可functionmySetTimeout(callback,delay){lettimernulltimersetInterval((){callback()clearInterval(timer)},delay)}mySetTimeout((){console。log(1)},1000)sleep 实现一个函数,n秒后执行指定函数functionsleep(func,delay){returnnewPromise((resolve,reject){setTimeout((){resolve(func())},delay)})}functionsay(name){console。log(name)}asyncfunctiongo(){awaitsleep(()say(nanjiu),1000)过一秒打印nanjiuawaitsleep(()say(前端张三),2000)再过两秒打印前端张三}go()数组去重的多种实现方式使用Setletarr〔1,2,3,2,4,5,3,6,2〕functionarrayToHeavy1(arr){return〔。。。newSet(arr)〕}console。log(arrayToHeavy1(arr))〔1,2,3,4,5,6〕使用indexOffunctionarrayToHeavy2(arr){letnewArr〔〕for(leti0;ipreh1classpgcharrowrightdatatrack164使用filterh1precodefunctionarrayToHeavy3(arr){returnarr。filter((item,index){returnarr。indexOf(item)index})}console。log(arrayToHeavy3(arr))〔1,2,3,4,5,6〕使用MapfunctionarrayToHeavy4(arr){letmapnewMap()for(leti0;ipreh1classpgcharrowrightdatatrack168使用includeh1precodefunctionarrayToHeavy5(arr){letres〔〕for(leti0;ipreh1classpgcharrowrightdatatrack170解析URL参数h1blockquotepdatatrack171spanstyleletterspacing:1。5spanstylecolor:000000;ttdarkmodecolor:000000;spanstylebackgroundcolor:EFEBE9;ttdarkmodebgcolor:C0BDBC;根据key获取URL上的参数值spanspanspanblockquoteprecodefunctionqueryData(key){leturlwindow。location。href,obj{}letstrurl。split(?)〔1〕先拿到问号后面的所有参数letarrstr。split()分割参数for(leti0;iarr。i){letkvarr〔i〕。split()obj〔kv〔0〕〕decodeURIComponent(kv〔1〕)}console。log(url,obj){a:1,b:2,c:3,name:张三}returnobj〔key〕}http:127。0。0。1:5500srcjs2022E6898BE58699index。html?a1b2c3nameE58D97E78E96console。log(queryData(name))张三斐波那契数列 F(n)F(n1)F(n2),其中n1暴力递归版本functionfib(n){if(n0)return0if(n1n2)return1returnfib(n1)fib(n2)}console。log(fib(4))F(4)F(3)F(2)F(2)F(1)F(2)1113lettnewDate()console。log(fib(40))102334155console。log(newDate()t)783ms优化版本functionfib2(n){if(fib2〔n〕!undefined)returnfib2〔n〕if(n0)return0if(n1n2)return1constresfib2(n1)fib2(n2)fib2〔n〕resreturnres}lett1newDate()console。log(fib2(40))102334155console。log(newDate()t1)5ms发布订阅 用过Vue的eventBus的同学应该很熟悉,on订阅事件,emit发布事件classEventEmitter{constructor(){this。events{}}订阅事件on(event,callback){if(!this。events〔event〕){this。events〔event〕〔〕}将事件对应的回调放入该事件的事件队列中this。events〔event〕。push(callback)returnthis}发布事件emit(event,args){constcallbackListthis。events〔event〕if(callbackList。length){callbackList。forEach(cbcb。apply(this,args))}returnthis}删除订阅off(event,callback){event没传,则删除所有订阅的事件if(typeofeventundefined){deletethis。events}elseif(typeofeventstring){删除指定事件的回调if(typeofcallbackfunction){this。events〔event〕this。events〔event〕。filter((cb)cb!callback)}else{删除整个事件deletethis。events〔event〕}}returnthis}只进行一次的事件订阅once(event,callback,context){constproxyCallback(。。。args){callback。apply(context,args)回调函数执行完成之后就删除事件订阅this。off(event,proxyCallback)}this。on(event,proxyCallback,context)}}写完测一把constbusnewEventEmitter()先订阅一个事件bus。on(add,(){console。log(nanjiu)})发布事件