当前位置: 首页 > 科技观察

【前端】一网打尽──前端进阶、面试必备的8个手写代码

时间:2023-03-17 21:43:42 科技观察

写在前面。我们知道在前端进阶和面试的时候,会考察很多手写源码的问题。这些都是通过学习和实践。可以掌握,下面列出8个手写代码系列,希望对大家有所帮助。1手写Promise系列在Promise的学习中,我之前写过相关的分享文章,请参考《从小白视角上手Promise、Async/Await和手撕代码》。1.1Promise.all//手写promise.allPromise.prototype._all=promiseList=>{//当输入是promise列表时constlen=promiseList.length;constresult=[];letcount=0;//returnnewPromise((resolve,reject)=>{//循环遍历promise列表中的promise事件for(leti=0;i{returnreject(error);})}})}1.2Promise.race//手写promise.racePromise。prototype._race=promiseList=>{constlen=promiseList.length;returnnewPromise((resolve,reject)=>{//遍历承诺列表中的承诺事件for(leti=0;i{returnresolve(data);},error=>{returnreject(error);})}})}1.3Promise.finallyPromise.prototype._finally=function(promiseFunc){returnthis.then(data=>Promise.resolve(promiseFunc()).then(data=>data),error=>Promise.reject(promiseFunc()).then(error=>{throwerror}))}2手写Aysnc/AwaitfunctionalasyncGenertor(genFunc){returnnewPromise((resolve,reject)=>{//生成一个迭代器constgen=genFunc();conststep=(type,args)=>{letnext;try{next=gen[type](args);}catch(e){returnreject(e);}//从next获取done的值和valueconst{done,value}=next;//如果迭代器的状态为trueif(done)returnresolve(value);Promise.resolve(value).then(val=>step("next",val),err=>step("throw",err))}step("next");})}3深拷贝深拷贝:复制所有属性值和属性地址指向的值的内存空间3.1丢失引用的深拷贝遇到对象时,打开一个新的对象,然后是二级的属性值在新打开的对象中将源对象完全复制到此。//丢失引用的深拷贝函数deepClone(obj){//判断obj的类型是否为对象类型if(!obj&&typeofobj!=="object")return;//判断对象是数组类型还是对象类型letnewObj=Array.isArray(obj)?[]:{};//遍历obj的键值对for(const[key,value]ofObject.entries(obj)){newObj[key]=typeofvalue==="string"?deepClone(value):value;};returnnewObj;}3.2深拷贝终极解法(栈和深度优先思想)思路是:引入一个数组uniqueList存放拷贝的数组,判断是否是object在uniqueList中,如果存在则不执行复制逻辑。functiondeepCopy(obj){//用于去重constuniqueList=[];//设置根节点letroot={};//遍历数组constloopList=[{parent:root,key:undefined,data:obj}];//遍历循环while(loopList.length){//深度优先——取出数组的最后一个元素const{parent,key,data}=loopList.pop();//初始化赋值目标,复制到父元素whenkey--undefined,否则复制到子元素letresult=parent;if(typeofkey!=="undefined")result=parent[key]={};//当数据已经存在时letuniqueData=uniqueList.find(item=>item.source===data);if(uniqueData){parent[key]=uniqueData.target;//中断此循环继续;}//当数据不存在时//保存源数据,并复制对应引用uniqueList.push({source:data,target:result});//遍历数据for(letkindata){if(data.hasOwnProperty(k)){typeofdata[k]==="object"?//下一次循环loopList.push({parent:result,key:k,data:data[k]}):result[k]=data[k];}}}returnroot;}4手写一个singletonpattern单例模式:确保只有一个类实例,并为其提供一个全局访问点。实现方式一般是先判断实例是否存在,存在则直接返回,不存在则先创建再返回。//创建单例对象,使用闭包constgetSingle=function(func){letresult;returnfunction(){returnresult||(result=func.apply(this,arguments));}}//使用Proxy拦截constproxy=function(func){letreuslt;consthandler={construct:function(){if(!result)result=Reflect.construct(func,arguments);returnresult;}}returnnewProxy(func,hendler);}5手写封装一个ajax函数/*封装自己的ajax函数参数1:{string}method请求方法参数2:{string}url请求地址参数2:{Object}params请求参数参数3:{function}done请求完成后执行的回调函数*/functionajax(方法、url、参数、完成){//1。创建一个xhr对象,兼容letxhr=window.XMLHttpRequest?newXMLHttpRequest():newActiveXObject("Microsoft.XMLHTTP");//将方法转为大写method=method.toUpperCase();//参数拼接letnewParams=[];for(letkeyinparams){newParams.push(`${key}=${params[k]}`);}letstr=newParams.join("&");//判断请求方式if(method==="GET")url+=`?${str}`;//开启请求方法xhr.open(method,url);letdata=null;if(method==="POST"){//设置请求头xhr.setRequestHeader((“内容类型”,“应用程序/x-www-form-urlencoded”));data=str;}xhr.send(data);//指定xhr状态变化事件处理器//执行回调函数xhr.onreadystatechange=function(){if(this.readyState===4)done(JSON.parse(xhr.responseText));}}6手写“防抖”和“节流”在Promise的学习中,之前写过相关的分享文章,请参考《一网打尽──他们都在用这些”防抖“和”节流“方法》6.1防抖/*func:Required的functionforanti-shaftprocessingdelay:延迟的时间immediate:是否使用立即执行true立即执行false非立即执行*/functiondebounce(func,delay,immediate){lettimeout;//timerreturnfunction(arguments){//判断定时器是否存在,存在则清除,重新计数定时器if(timeout)clearTimeout(timeout);//判断是否是立即执行的防抖if(immediate){//immediatelyExecuteconstflag=!timeout;//这里是取反操作timeout=setTimeout(()=>{timeout=null;},delay);//函数会在事件t后立即执行已触发,然后在n秒内无法触发该事件继续执行函数的效果。if(flag)func.call(this,arguments);}else{//非立即执行timeout=setTimeout(()=>{func.call(this,arguments);},delay)}}}6.2Throttling//Throttling--定时器版本functionthrottle(func,delay){lettimeout;//定义一个定时器标记returnfunction(arguments){//判断是否有定时器if(!timeout){//创建一个定时器timeout=setTimeout(()=>{//delay时间间隔清除定时器clearTimeout(timeout);func.call(this,arguments);},delay)}}}7手写apply,bind,call7.1Apply传递给函数的参数处理不一样,其他部分同call。apply接受第二个参数为类数组对象,这里我们使用《JavaScript权威指南》中的方法来判断是否为类数组对象。Function.prototype._apply=function(context){if(context===null||context===undefined){context=window//指定为null和undefined的this值会自动指向全局对象(在browseriswindow)}else{context=Object(context)//this的值为原值(数字、字符串、布尔值)将指向原值的实例对象}//JavaScript权威指南判断是否它是一个类似数组的对象functionisArrayLike(o){if(o&&//o不为空,未定义等。typeofo==='object'&&//o是一个对象isFinite(o.length)&&//o.length是一个有限值o.length>=0&&//o.length是一个非负值o.length===Math.floor(o.length)&&//o.length是一个整数o.length<4294967296)//o.length<2^32returntrueelsereturnfalse}constspecialPrototype=Symbol('特殊属性Symbol')//用于暂存函数context[specialPrototype]=this;//隐式绑定this指向contextletargs=arguments[1];//获取参数数组letresult//process传入的第一个两个参数if(args){//是否传递第二个参数if(!Array.isArray(args)&&!isArrayLike(args)){thrownewTypeError('myApply的第二个参数不是数组,也不是一个array-likeobjectThrowanerror');}else{args=Array.from(args)//转换为数组result=context[specialPrototype](...args);//执行函数并扩展数组,传递函数参数}}else{result=context[specialPrototype]();//执行函数}deletecontext[specialPrototype];//删除上下文对象的属性returnresult;//返回函数执行结果};7.2bind复制源函数:使用Object.create将源函数的原型复制到fToBind返回复制后的函数调用副本,将??源函数存入变量函数:新的调用判断:使用instanceof判断函数是否被调用new判断绑定上下文bindthis+传参返回源函数的执行结果Function.prototype._bind=function(objThis,...params){constthisFn=this;//存放源函数和上面的params(functionparameters)//传递返回函数的第二个参数secondParamsletfToBind=function(...secondParams){constisNew=thisinstanceoffToBind//这是fToBind的一个实例吗?返回的fToBind调用constcontext=isNew?this:Object(objThis)//new调用是否绑定到this,否则绑定到传入的objThisreturnthisFn.call(context,...params,...secondParams);//使用call调用源函数绑定this的指向并传递参数,并返回执行结果};if(thisFn.prototype){//将源函数的原型复制到fToBind某些情况下,该函数确实没有原型,比如箭头函数fToBind.prototype=Object.create(thisFn.prototype);}returnfToBind;//返回复制的函数};7.3call根据call的规则设置上下文对象,即this的指向通过设置context的属性,函数的This指向隐式绑定到上下文来执行函数,通过隐式绑定传递参数。删除临时属性并返回函数执行结果Function.prototype._call=function(context,...arr){if(context===null||context===undefined){//this值指定为null而undefined会自动指向全局对象(浏览器中的window)context=window}else{context=Object(context)//this的值为原值(数字,字符串,布尔值)会指向原始值的实例对象}constspecialPrototype=Symbol('特殊属性Symbol')//用于暂存函数context[specialPrototype]=this;//函数的this点隐式绑定到contextletresult=context[specialPrototype](...arr);//通过隐式绑定执行函数并传递参数deletecontext[specialPrototype];//删除上下文对象的属性returnresult;//返回函数执行结果};8手写继承8.1构造器式继承构造器式继承不继承类原型上的父方法。functionfatherUser(username,password){let_password=passwordthis.username=usernamefatherUser.prototype.login=function(){console.log(this.username+'登录父亲账号,密码为'+_password)}}functionsonUser(username,password){fatherUser.call(this,username,password)this.articles=3//文章数}constyichuanUser=newsonUser('yichuan','xxx')console.log(yichuanUser.username)//yichuanconsole.log(yichuanUser.username)//xxxconsole.log(yichuanUser.login())//TypeError:yichuanUser.loginisnotafunction8.2联合继承函数fatherUser(username,password){let_password=passwordthis.username=usernamefatherUser.prototype.login=function(){安慰。log(this.username+'要登录fatherUser,密码是'+_password)}}functionsonUser(username,password){fatherUser.call(this,username,password)//fatherUser的构造函数的第二次执行this.articles=3//文章数}sonUser.prototype=newfatherUser();//fatherUser的构造函数第二次执行constyichuanUser=newsonUser('yichuan','xxx')8.3寄生组合继承上面的继承方式有缺陷,所以这样写要么方式。functionParent(){this.name='parent';}functionChild(){Parent.call(this);this.type='children';}Child.prototype=Object.create(Parent.prototype);Child.prototype。constructor=Child;参考文章《前端进阶之必会的JavaScript技巧总结》《js基础-面试官想知道你有多理解call,apply,bind?[不看后悔系列]》