专栏电商日志财经减肥爱情
投稿投诉
爱情常识
搭配分娩
减肥两性
孕期塑形
财经教案
论文美文
日志体育
养生学堂
电商科学
头戴业界
专栏星座
用品音乐

死磕36个JS手写题(搞懂后,提升真的大)

  为什么要写这类文章
  作为一个程序员,代码能力毋庸置疑是非常非常重要的,就像现在为什么大厂面试基本都问什么API怎么实现可见其重要性。我想说的是居然手写这么重要,那我们就必须掌握它,所以文章标题用了死磕,一点也不过分,也希望不被认为是标题党。
  作为一个普通程序员,我是真的写不出PromiseA规范,但是没关系,我们可以站在巨人的肩膀上,要相信我们现在要走的路,前人都走过,所以可以找找现在社区已经存在的那些优秀的文章,比如工业聚大佬写的100行代码实现PromisesA规范,找到这些文章后不是收藏夹吃灰,得找个时间踏踏实实的学,一行一行的磨,直到搞懂为止。我现在就是这么干的。
  能收获什么
  这篇文章总体上分为2类手写题,前半部分可以归纳为是常见需求,后半部分则是对现有技术的实现;对常用的需求进行手写实现,比如数据类型判断函数、深拷贝等可以直接用于往后的项目中,提高了项目开发效率;对现有关键字和API的实现,可能需要用到别的知识或API,比如在写forEach的时候用到了无符号位右移的操作,平时都不怎么能够接触到这玩意,现在遇到了就可以顺手把它掌握了。所以手写这些实现能够潜移默化的扩展并巩固自己的JS基础;通过写各种测试用例,你会知道各种API的边界情况,比如Promise。all,你得考虑到传入参数的各种情况,从而加深了对它们的理解及使用;
  阅读的时候需要做什么
  阅读的时候,你需要把每行代码都看懂,知道它在干什么,为什么要这么写,能写得更好嘛?比如在写图片懒加载的时候,一般我们都是根据当前元素的位置和视口进行判断是否要加载这张图片,普通程序员写到这就差不多完成了。而大佬程序员则是会多考虑一些细节的东西,比如性能如何更优?代码如何更精简?比如yeyan1996写的图片懒加载就多考虑了2点:比如图片全部加载完成的时候得把事件监听给移除;比如加载完一张图片的时候,得把当前img从imgList里移除,起到优化内存的作用。
  除了读通代码之外,还可以打开Chrome的Scriptsnippet去写测试用例跑跑代码,做到更好的理解以及使用。
  在看了几篇以及写了很多测试用例的前提下,尝试自己手写实现,看看自己到底掌握了多少。条条大路通罗马,你还能有别的方式实现嘛?或者你能写得比别人更好嘛?
  好了,还楞着干啥,开始干活。数据类型判断
  typeof可以正确识别:Undefined、Boolean、Number、String、Symbol、Function等类型的数据,但是对于其他的都会认为是object,比如Null、Date等,所以通过typeof来判断数据类型会不准确。但是可以使用Object。prototype。toString实现。functiontypeOf(obj){letresObject。prototype。toString。call(obj)。split()〔1〕resres。substring(0,res。length1)。toLowerCase()returnres评论区里提到的更好的写法returnObject。prototype。toString。call(obj)。slice(8,1)。toLowerCase()}typeOf(〔〕)arraytypeOf({})objecttypeOf(newDate)date复制代码继承原型链继承functionAnimal(){this。colors〔black,white〕}Animal。prototype。getColorfunction(){returnthis。colors}functionDog(){}Dog。prototypenewAnimal()letdog1newDog()dog1。colors。push(brown)letdog2newDog()console。log(dog2。colors)〔black,white,brown〕复制代码
  原型链继承存在的问题:问题1:原型中包含的引用类型属性将被所有实例共享;问题2:子类在实例化的时候不能给父类构造函数传参;借用构造函数实现继承functionAnimal(name){this。namenamethis。getNamefunction(){returnthis。name}}functionDog(name){Animal。call(this,name)}Dog。prototypenewAnimal()复制代码
  借用构造函数实现继承解决了原型链继承的2个问题:引用类型共享问题以及传参问题。但是由于方法必须定义在构造函数中,所以会导致每次创建子类实例都会创建一遍方法。组合继承
  组合继承结合了原型链和盗用构造函数,将两者的优点集中了起来。基本的思路是使用原型链继承原型上的属性和方法,而通过盗用构造函数继承实例属性。这样既可以把方法定义在原型上以实现重用,又可以让每个实例都有自己的属性。functionAnimal(name){this。namenamethis。colors〔black,white〕}Animal。prototype。getNamefunction(){returnthis。name}functionDog(name,age){Animal。call(this,name)this。ageage}Dog。prototypenewAnimal()Dog。prototype。constructorDogletdog1newDog(奶昔,2)dog1。colors。push(brown)letdog2newDog(哈赤,1)console。log(dog2){name:哈赤,colors:〔black,white〕,age:1}复制代码寄生式组合继承
  组合继承已经相对完善了,但还是存在问题,它的问题就是调用了2次父类构造函数,第一次是在newAnimal(),第二次是在Animal。call()这里。
  所以解决方案就是不直接调用父类构造函数给子类原型赋值,而是通过创建空函数F获取父类原型的副本。
  寄生式组合继承写法上和组合继承基本类似,区别是如下这里:Dog。prototypenewAnimal()Dog。prototype。constructorDogfunctionF(){}F。prototypeAnimal。prototypeletfnewF()f。constructorDogDog。prototypef复制代码
  稍微封装下上面添加的代码后:functionobject(o){functionF(){}F。prototypeoreturnnewF()}functioninheritPrototype(child,parent){letprototypeobject(parent。prototype)prototype。constructorchildchild。prototypeprototype}inheritPrototype(Dog,Animal)复制代码
  如果你嫌弃上面的代码太多了,还可以基于组合继承的代码改成最简单的寄生式组合继承:Dog。prototypenewAnimal()Dog。prototype。constructorDogDog。prototypeObject。create(Animal。prototype)Dog。prototype。constructorDog复制代码class实现继承classAnimal{constructor(name){this。namename}getName(){returnthis。name}}classDogextendsAnimal{constructor(name,age){super(name)this。ageage}}复制代码数组去重
  ES5实现:functionunique(arr){varresarr。filter(function(item,index,array){returnarray。indexOf(item)index})returnres}复制代码
  ES6实现:varuniquearr〔。。。newSet(arr)〕复制代码数组扁平化
  数组扁平化就是将〔1,〔2,〔3〕〕〕这种多层的数组拍平成一层〔1,2,3〕。使用Array。prototype。flat可以直接将多层数组拍平成一层:〔1,〔2,〔3〕〕〕。flat(2)〔1,2,3〕复制代码
  现在就是要实现flat这种效果。
  ES5实现:递归。functionflatten(arr){varresult〔〕;for(vari0,lenarr。length;ilen;i){if(Array。isArray(arr〔i〕)){resultresult。concat(flatten(arr〔i〕))}else{result。push(arr〔i〕)}}returnresult;}复制代码
  ES6实现:functionflatten(arr){while(arr。some(itemArray。isArray(item))){arr〔〕。concat(。。。arr);}returnarr;}复制代码深浅拷贝
  浅拷贝:只考虑对象类型。functionshallowCopy(obj){if(typeofobj!object)returnletnewObjobjinstanceofArray?〔〕:{}for(letkeyinobj){if(obj。hasOwnProperty(key)){newObj〔key〕obj〔key〕}}returnnewObj}复制代码
  简单版深拷贝:只考虑普通对象属性,不考虑内置对象和函数。functiondeepClone(obj){if(typeofobj!object)return;varnewObjobjinstanceofArray?〔〕:{};for(varkeyinobj){if(obj。hasOwnProperty(key)){newObj〔key〕typeofobj〔key〕object?deepClone(obj〔key〕):obj〔key〕;}}returnnewObj;}复制代码
  复杂版深克隆:基于简单版的基础上,还考虑了内置对象比如Date、RegExp等对象和函数以及解决了循环引用的问题。constisObject(target)(typeoftargetobjecttypeoftargetfunction)target!null;functiondeepClone(target,mapnewWeakMap()){if(map。get(target)){returntarget;}获取当前值的构造函数:获取它的类型letconstructortarget。constructor;检测当前对象target是否与正则、日期格式对象匹配if((RegExpDate)i。test(constructor。name)){创建一个新的特殊对象(正则类日期类)的实例returnnewconstructor(target);}if(isObject(target)){map。set(target,true);为循环引用的对象做标记constcloneTargetArray。isArray(target)?〔〕:{};for(letpropintarget){if(target。hasOwnProperty(prop)){cloneTarget〔prop〕deepClone(target〔prop〕,map);}}returncloneTarget;}else{returntarget;}}复制代码事件总线(发布订阅模式)classEventEmitter{constructor(){this。cache{}}on(name,fn){if(this。cache〔name〕){this。cache〔name〕。push(fn)}else{this。cache〔name〕〔fn〕}}off(name,fn){lettasksthis。cache〔name〕if(tasks){constindextasks。findIndex(fffnf。callbackfn)if(index0){tasks。splice(index,1)}}}emit(name,oncefalse,。。。args){if(this。cache〔name〕){创建副本,如果回调函数内继续注册相同事件,会造成死循环lettasksthis。cache〔name〕。slice()for(letfnoftasks){fn(。。。args)}if(once){deletethis。cache〔name〕}}}}测试leteventBusnewEventEmitter()letfn1function(name,age){console。log({name}{age})}letfn2function(name,age){console。log(hello,{name}{age})}eventBus。on(aaa,fn1)eventBus。on(aaa,fn2)eventBus。emit(aaa,false,布兰,12)布兰12hello,布兰12复制代码解析URL参数为对象functionparseParam(url){constparamsStr。?(。)。exec(url)〔1〕;将?后面的字符串取出来constparamsArrparamsStr。split();将字符串以分割后存到数组中letparamsObj{};将params存到对象中paramsArr。forEach(param{if(。test(param)){处理有value的参数let〔key,val〕param。split();分割key和valuevaldecodeURIComponent(val);解码vald。test(val)?parseFloat(val):val;判断是否转为数字if(paramsObj。hasOwnProperty(key)){如果对象有key,则添加一个值paramsObj〔key〕〔〕。concat(paramsObj〔key〕,val);}else{如果对象没有这个key,创建key并设置值paramsObj〔key〕val;}}else{处理没有value的参数paramsObj〔param〕true;}})returnparamsObj;}复制代码字符串模板functionrender(template,data){constreg{{(w)}};模板字符串正则if(reg。test(template)){判断模板里是否有模板字符串constnamereg。exec(template)〔1〕;查找当前模板里第一个模板字符串的字段templatetemplate。replace(reg,data〔name〕);将第一个模板字符串渲染returnrender(template,data);递归的渲染并返回渲染后的结构}returntemplate;如果模板没有模板字符串直接返回}复制代码
  测试:lettemplate我是{{name}},年龄{{age}},性别{{sex}};letperson{name:布兰,age:12}render(template,person);我是布兰,年龄12,性别undefined复制代码图片懒加载
  与普通的图片懒加载不同,如下这个多做了2个精心处理:图片全部加载完成后移除事件监听;加载完的图片,从imgList移除;letimgList〔。。。document。querySelectorAll(img)〕letlengthimgList。length修正错误,需要加上自执行constimgLazyLoadfunction(){constimgLazyLoad(function(){letcount0returnfunction(){letdeleteIndexList〔〕imgList。forEach((img,index){letrectimg。getBoundingClientRect()if(rect。topwindow。innerHeight){img。srcimg。dataset。srcdeleteIndexList。push(index)countif(countlength){document。removeEventListener(scroll,imgLazyLoad)}}})imgListimgList。filter((img,index)!deleteIndexList。includes(index))}}})()这里最好加上防抖处理document。addEventListener(scroll,imgLazyLoad)复制代码
  参考:图片懒加载函数防抖
  触发高频事件N秒后只会执行一次,如果N秒内事件再次触发,则会重新计时。
  简单版:函数内部支持使用this和event对象;functiondebounce(func,wait){vartimeout;returnfunction(){varcontextthis;varargsarguments;clearTimeout(timeout)timeoutsetTimeout(function(){func。apply(context,args)},wait);}}复制代码
  使用:varnodedocument。getElementById(layout)functiongetUserAction(e){console。log(this,e)分别打印:node这个节点和MouseEventnode。innerHTMLcount;};node。onmousemovedebounce(getUserAction,1000)复制代码
  最终版:除了支持this和event外,还支持以下功能:支持立即执行;函数可能有返回值;支持取消功能;functiondebounce(func,wait,immediate){vartimeout,result;vardebouncedfunction(){varcontextthis;varargsarguments;if(timeout)clearTimeout(timeout);if(immediate){如果已经执行过,不再执行varcallNow!timeout;timeoutsetTimeout(function(){timeoutnull;},wait)if(callNow)resultfunc。apply(context,args)}else{timeoutsetTimeout(function(){func。apply(context,args)},wait);}returnresult;};debounced。cancelfunction(){clearTimeout(timeout);timeoutnull;};returndebounced;}复制代码
  使用:varsetUseActiondebounce(getUserAction,10000,true);使用防抖node。onmousemovesetUseAction取消防抖setUseAction。cancel()复制代码
  参考:JavaScript专题之跟着underscore学防抖函数节流
  触发高频事件,且N秒内只执行一次。
  简单版:使用时间戳来实现,立即执行一次,然后每N秒执行一次。functionthrottle(func,wait){varcontext,args;varprevious0;returnfunction(){varnownewDate();contextthis;argsarguments;if(nowpreviouswait){func。apply(context,args);previousnow;}}}复制代码
  最终版:支持取消节流;另外通过传入第三个参数,options。leading来表示是否可以立即执行一次,opitons。trailing表示结束调用的时候是否还要执行一次,默认都是true。注意设置的时候不能同时将leading或trailing设置为false。functionthrottle(func,wait,options){vartimeout,context,args,result;varprevious0;if(!options)options{};varlaterfunction(){previousoptions。leadingfalse?0:newDate()。getTime();timeoutnull;func。apply(context,args);if(!timeout)contextargsnull;};varthrottledfunction(){varnownewDate()。getTime();if(!previousoptions。leadingfalse)previousnow;varremainingwait(nowprevious);contextthis;argsarguments;if(remaining0remainingwait){if(timeout){clearTimeout(timeout);timeoutnull;}previousnow;func。apply(context,args);if(!timeout)contextargsnull;}elseif(!timeoutoptions。trailing!false){timeoutsetTimeout(later,remaining);}};throttled。cancelfunction(){clearTimeout(timeout);previous0;timeoutnull;}returnthrottled;}复制代码
  节流的使用就不拿代码举例了,参考防抖的写就行。
  参考:JavaScript专题之跟着underscore学节流函数柯里化
  什么叫函数柯里化?其实就是将使用多个参数的函数转换成一系列使用一个参数的函数的技术。还不懂?来举个例子。functionadd(a,b,c){returnabc}add(1,2,3)letaddCurrycurry(add)addCurry(1)(2)(3)复制代码
  现在就是要实现curry这个函数,使函数从一次调用传入多个参数变成多次调用每次传一个参数。functioncurry(fn){letjudge(。。。args){if(args。lengthfn。length)returnfn(。。。args)return(。。。arg)judge(。。。args,。。。arg)}returnjudge}复制代码偏函数
  什么是偏函数?偏函数就是将一个n参的函数转换成固定x参的函数,剩余参数(nx)将在下次调用全部传入。举个例子:functionadd(a,b,c){returnabc}letpartialAddpartial(add,1)partialAdd(2,3)复制代码
  发现没有,其实偏函数和函数柯里化有点像,所以根据函数柯里化的实现,能够能很快写出偏函数的实现:functionpartial(fn,。。。args){return(。。。arg){returnfn(。。。args,。。。arg)}}复制代码
  如上这个功能比较简单,现在我们希望偏函数能和柯里化一样能实现占位功能,比如:functionclg(a,b,c){console。log(a,b,c)}letpartialClgpartial(clg,,2)partialClg(1,3)依次打印:1,2,3复制代码
  占的位其实就是1的位置。相当于:partial(clg,1,2),然后partialClg(3)。明白了原理,我们就来写实现:functionpartial(fn,。。。args){return(。。。arg){args〔index〕returnfn(。。。args,。。。arg)}}复制代码JSONP
  JSONP核心原理:script标签不受同源策略约束,所以可以用来进行跨域请求,优点是兼容性好,但是只能用于GET请求;constjsonp({url,params,callbackName}){constgenerateUrl(){letdataSrcfor(letkeyinparams){if(params。hasOwnProperty(key)){dataSrc{key}{params〔key〕}}}dataSrccallback{callbackName}return{url}?{dataSrc}}returnnewPromise((resolve,reject){constscriptEledocument。createElement(script)scriptEle。srcgenerateUrl()document。body。appendChild(scriptEle)window〔callbackName〕data{resolve(data)document。removeChild(scriptEle)}})}复制代码AJAXconstgetJSONfunction(url){returnnewPromise((resolve,reject){constxhrXMLHttpRequest?newXMLHttpRequest():newActiveXObject(Microsoft。XMLHTTP);xhr。open(GET,url,false);xhr。setRequestHeader(Accept,applicationjson);xhr。onreadystatechangefunction(){if(xhr。readyState!4)return;if(xhr。status200xhr。status304){resolve(xhr。responseText);}else{reject(newError(xhr。responseText));}}xhr。send();})}复制代码实现数组原型方法forEachArray。prototype。forEach2function(callback,thisArg){if(thisnull){thrownewTypeError(thisisnullornotdefined)}if(typeofcallback!function){thrownewTypeError(callbackisnotafunction)}constOObject(this)this就是当前的数组constlenO。length0后面有解释letk0while(klen){if(kinO){callback。call(thisArg,O〔k〕,k,O);}k;}}复制代码
  参考:forEachpolyfill
  O。length0是什么操作?就是无符号右移0位,那有什么意义嘛?就是为了保证转换后的值为正整数。其实底层做了2层转换,第一是非number转成number类型,第二是将number转成Uint32类型。感兴趣可以阅读something0是什么意思?。map
  基于forEach的实现能够很容易写出map的实现:Array。prototype。forEach2function(callback,thisArg){Array。prototype。map2function(callback,thisArg){if(thisnull){thrownewTypeError(thisisnullornotdefined)}if(typeofcallback!function){thrownewTypeError(callbackisnotafunction)}constOObject(this)constlenO。length0letk0letk0,res〔〕while(klen){if(kinO){callback。call(thisArg,O〔k〕,k,O);res〔k〕callback。call(thisArg,O〔k〕,k,O);}k;}returnres}复制代码filter
  同样,基于forEach的实现能够很容易写出filter的实现:Array。prototype。forEach2function(callback,thisArg){Array。prototype。filter2function(callback,thisArg){if(thisnull){thrownewTypeError(thisisnullornotdefined)}if(typeofcallback!function){thrownewTypeError(callbackisnotafunction)}constOObject(this)constlenO。length0letk0letk0,res〔〕while(klen){if(kinO){callback。call(thisArg,O〔k〕,k,O);if(callback。call(thisArg,O〔k〕,k,O)){res。push(O〔k〕)}}k;}returnres}复制代码some
  同样,基于forEach的实现能够很容易写出some的实现:Array。prototype。forEach2function(callback,thisArg){Array。prototype。some2function(callback,thisArg){if(thisnull){thrownewTypeError(thisisnullornotdefined)}if(typeofcallback!function){thrownewTypeError(callbackisnotafunction)}constOObject(this)constlenO。length0letk0while(klen){if(kinO){callback。call(thisArg,O〔k〕,k,O);if(callback。call(thisArg,O〔k〕,k,O)){returntrue}}k;}returnfalse}复制代码reduceArray。prototype。reduce2function(callback,initialValue){if(thisnull){thrownewTypeError(thisisnullornotdefined)}if(typeofcallback!function){thrownewTypeError(callbackisnotafunction)}constOObject(this)constlenO。length0letk0,accif(arguments。length1){accinitialValue}else{没传入初始值的时候,取数组中第一个非empty的值为初始值while(klen!(kinO)){k}if(klen){thrownewTypeError(Reduceofemptyarraywithnoinitialvalue);}accO〔k〕}while(klen){if(kinO){acccallback(acc,O〔k〕,k,O)}k}returnacc}复制代码实现函数原型方法call
  使用一个指定的this值和一个或多个参数来调用一个函数。
  实现要点:this可能传入null;传入不固定个数的参数;函数可能有返回值;Function。prototype。call2function(context){varcontextcontextwindow;context。fnthis;varargs〔〕;for(vari1,lenarguments。length;ilen;i){args。push(arguments〔i〕);}varresulteval(context。fn(args));deletecontext。fnreturnresult;}复制代码apply
  apply和call一样,唯一的区别就是call是传入不固定个数的参数,而apply是传入一个数组。
  实现要点:this可能传入null;传入一个数组;函数可能有返回值;Function。prototype。apply2function(context,arr){varcontextcontextwindow;context。fnthis;varresult;if(!arr){resultcontext。fn();}else{varargs〔〕;for(vari0,lenarr。length;ilen;i){args。push(arr〔i〕);}resulteval(context。fn(args))}deletecontext。fnreturnresult;}复制代码bind
  bind方法会创建一个新的函数,在bind()被调用时,这个新函数的this被指定为bind()的第一个参数,而其余参数将作为新函数的参数,供调用时使用。
  实现要点:bind()除了this外,还可传入多个参数;bing创建的新函数可能传入多个参数;新函数可能被当做构造函数调用;函数可能有返回值;Function。prototype。bind2function(context){varselfthis;varargsArray。prototype。slice。call(arguments,1);varfNOPfunction(){};varfBoundfunction(){varbindArgsArray。prototype。slice。call(arguments);returnself。apply(thisinstanceoffNOP?this:context,args。concat(bindArgs));}fNOP。prototypethis。prototype;fBound。prototypenewfNOP();returnfBound;}复制代码实现new关键字
  new运算符用来创建用户自定义的对象类型的实例或者具有构造函数的内置对象的实例。
  实现要点:new会产生一个新对象;新对象需要能够访问到构造函数的属性,所以需要重新指定它的原型;构造函数可能会显示返回;functionobjectFactory(){varobjnewObject()Constructor〔〕。shift。call(arguments);obj。protoConstructor。prototype;varretConstructor。apply(obj,arguments);retobj这里这么写考虑了构造函数显示返回null的情况returntypeofretobject?retobj:obj;};复制代码
  使用:functionperson(name,age){this。namenamethis。ageage}letpobjectFactory(person,布兰,12)console。log(p){name:布兰,age:12}复制代码实现instanceof关键字
  instanceof就是判断构造函数的prototype属性是否出现在实例的原型链上。functioninstanceOf(left,right){letprotoleft。protowhile(true){if(protonull)returnfalseif(protoright。prototype){returntrue}protoproto。proto}}复制代码
  上面的left。proto这种写法可以换成Object。getPrototypeOf(left)。实现Object。create
  Object。create()方法创建一个新对象,使用现有的对象来提供新创建的对象的proto。Object。create2function(proto,propertyObjectundefined){if(typeofproto!objecttypeofproto!function){thrownewTypeError(ObjectprototypemayonlybeanObjectornull。)if(propertyObjectnull){newTypeError(Cannotconvertundefinedornulltoobject)}functionF(){}F。prototypeprotoconstobjnewF()if(propertyObject!undefined){Object。defineProperties(obj,propertyObject)}if(protonull){创建一个没有原型对象的对象,Object。create(null)obj。protonull}returnobj}复制代码实现Object。assignObject。assign2function(target,。。。source){if(targetnull){thrownewTypeError(Cannotconvertundefinedornulltoobject)}letretObject(target)source。forEach(function(obj){if(obj!null){for(letkeyinobj){if(obj。hasOwnProperty(key)){ret〔key〕obj〔key〕}}}})returnret}复制代码实现JSON。stringify
  JSON。stringify(〔,replacer〔,space〕)方法是将一个JavaScript值(对象或者数组)转换为一个JSON字符串。此处模拟实现,不考虑可选的第二个参数replacer和第三个参数space,如果对这两个参数的作用还不了解,建议阅读MDN文档。基本数据类型:undefined转换之后仍是undefined(类型也是undefined)boolean值转换之后是字符串falsetruenumber类型(除了NaN和Infinity)转换之后是字符串类型的数值symbol转换之后是undefinednull转换之后是字符串nullstring转换之后仍是stringNaN和Infinity转换之后是字符串null函数类型:转换之后是undefined如果是对象类型(非函数)如果是一个数组:如果属性值中出现了undefined、任意的函数以及symbol,转换成字符串null;如果是RegExp对象:返回{}(类型是string);如果是Date对象,返回Date的toJSON字符串值;如果是普通对象;如果有toJSON()方法,那么序列化toJSON()的返回值。如果属性值中出现了undefined、任意的函数以及symbol值,忽略。所有以symbol为属性键的属性都会被完全忽略掉。对包含循环引用的对象(对象之间相互引用,形成无限循环)执行此方法,会抛出错误。functionjsonStringify(data){letdataTypetypeofdata;if(dataType!object){letresultdata;data可能是stringnumbernullundefinedbooleanif(Number。isNaN(data)dataInfinity){NaN和Infinity序列化返回nullresultnull;}elseif(dataTypefunctiondataTypeundefineddataTypesymbol){function、undefined、symbol序列化返回undefinedreturnundefined;}elseif(dataTypestring){resultdata;}boolean返回String()returnString(result);}elseif(dataTypeobject){if(datanull){returnnull}elseif(data。toJSONtypeofdata。toJSONfunction){returnjsonStringify(data。toJSON());}elseif(datainstanceofArray){letresult〔〕;如果是数组toJSON方法可以存在于原型链中data。forEach((item,index){if(typeofitemundefinedtypeofitemfunctiontypeofitemsymbol){result〔index〕null;}else{result〔index〕jsonStringify(item);}});result〔result〕;returnresult。replace(g,);}else{普通对象循环引用抛错(暂未检测,循环引用时,堆栈溢出)symbolkey忽略undefined、函数、symbol为属性值,被忽略letresult〔〕;Object。keys(data)。forEach((item,index){if(typeofitem!symbol){key如果是symbol对象,忽略if(data〔item〕!undefinedtypeofdata〔item〕!functiontypeofdata〔item〕!symbol){键值如果是undefined、函数、symbol为属性值,忽略result。push(item:jsonStringify(data〔item〕));}}});return({result})。replace(g,);}}}复制代码
  参考:实现JSON。stringify实现JSON。parse
  介绍2种方法实现:eval实现;newFunction实现;eval实现
  第一种方式最简单,也最直观,就是直接调用eval,代码如下:varjson{a:1,b:2};varobjeval((json));obj就是json反序列化之后得到的对象复制代码
  但是直接调用eval会存在安全问题,如果数据中可能不是json数据,而是可执行的JavaScript代码,那很可能会造成XSS攻击。因此,在调用eval之前,需要对数据进行校验。varrxone〔〕,:{}s〕;varrxtwo(?:〔bfnrt〕u〔09afAF〕{4})g;varrxthree〔r〕truefalsenull?d(?:。d)?(?:〔eE〕〔〕?d)?g;varrxfour(?::,)(?:s〔)g;if(rxone。test(json。replace(rxtwo,)。replace(rxthree,〕)。replace(rxfour,))){varobjeval((json));}复制代码
  参考:JSON。parse三种实现方式newFunction实现
  Function与eval有相同的字符串参数特性。varjson{name:小姐姐,age:20};varobj(newFunction(returnjson))();复制代码实现Promise
  实现Promise需要完全读懂PromiseA规范,不过从总体的实现上看,有如下几个点需要考虑到:then需要支持链式调用,所以得返回一个新的Promise;处理异步问题,所以得先用onResolvedCallbacks和onRejectedCallbacks分别把成功和失败的回调存起来;为了让链式调用正常进行下去,需要判断onFulfilled和onRejected的类型;onFulfilled和onRejected需要被异步调用,这里用setTimeout模拟异步;处理Promise的resolve;constPENDINGpending;constFULFILLEDfulfilled;constREJECTEDrejected;classPromise{constructor(executor){this。statusPENDING;this。valueundefined;this。reasonundefined;this。onResolvedCallbacks〔〕;this。onRejectedCallbacks〔〕;letresolve(value){if(this。statusPENDING){this。statusFULFILLED;this。valuevalue;this。onResolvedCallbacks。forEach((fn)fn());}};letreject(reason){if(this。statusPENDING){this。statusREJECTED;this。reasonreason;this。onRejectedCallbacks。forEach((fn)fn());}};try{executor(resolve,reject);}catch(error){reject(error);}}then(onFulfilled,onRejected){解决onFufilled,onRejected没有传值的问题onFulfilledtypeofonFulfilledfunction?onFulfilled:(v)v;因为错误的值要让后面访问到,所以这里也要抛出错误,不然会在之后then的resolve中捕获onRejectedtypeofonRejectedfunction?onRejected:(err){throwerr;};每次调用then都返回一个新的promiseletpromise2newPromise((resolve,reject){if(this。statusFULFILLED){PromiseA2。2。4setTimeoutsetTimeout((){try{letxonFulfilled(this。value);x可能是一个proimiseresolvePromise(promise2,x,resolve,reject);}catch(e){reject(e);}},0);}if(this。statusREJECTED){PromiseA2。2。3setTimeout((){try{letxonRejected(this。reason);resolvePromise(promise2,x,resolve,reject);}catch(e){reject(e);}},0);}if(this。statusPENDING){this。onResolvedCallbacks。push((){setTimeout((){try{letxonFulfilled(this。value);resolvePromise(promise2,x,resolve,reject);}catch(e){reject(e);}},0);});this。onRejectedCallbacks。push((){setTimeout((){try{letxonRejected(this。reason);resolvePromise(promise2,x,resolve,reject);}catch(e){reject(e);}},0);});}});returnpromise2;}}constresolvePromise(promise2,x,resolve,reject){自己等待自己完成是错误的实现,用一个类型错误,结束掉promisePromiseA2。3。1if(promise2x){returnreject(newTypeError(ChainingcycledetectedforpromisePromise));}PromiseA2。3。3。3。3只能调用一次letcalled;后续的条件要严格判断保证代码能和别的库一起使用if((typeofxobjectx!null)typeofxfunction){try{为了判断resolve过的就不用再reject了(比如reject和resolve同时调用的时候)PromiseA2。3。3。1letthenx。then;if(typeofthenfunction){不要写成x。then,直接then。call就可以了因为x。then会再次取值,Object。definePropertyPromiseA2。3。3。3then。call(x,(y){根据promise的状态决定是成功还是失败if(called)return;calledtrue;递归解析的过程(因为可能promise中还有promise)PromiseA2。3。3。3。1resolvePromise(promise2,y,resolve,reject);},(r){只要失败就失败PromiseA2。3。3。3。2if(called)return;calledtrue;reject(r);});}else{如果x。then是个普通值就直接返回resolve作为结果PromiseA2。3。3。4resolve(x);}}catch(e){PromiseA2。3。3。2if(called)return;calledtrue;reject(e);}}else{如果x是个普通值就直接返回resolve作为结果PromiseA2。3。4resolve(x);}};复制代码
  Promise写完之后可以通过promisesaplustests这个包对我们写的代码进行测试,看是否符合A规范。不过测试前还得加一段代码:promise。js这里是上面写的Promise全部代码Promise。deferPromise。deferredfunction(){letdfd{}dfd。promisenewPromise((resolve,reject){dfd。resolveresolve;dfd。rejectreject;});returndfd;}module。exportsPromise;复制代码
  全局安装:npmipromisesaplustestsg复制代码
  终端下执行验证命令:promisesaplustestspromise。js复制代码
  上面写的代码可以顺利通过全部872个测试用例。
  参考:BAT前端经典面试问题:史上最最最详细的手写Promise教程100行代码实现PromisesA规范Promise。resolve
  Promsie。resolve(value)可以将任何值转成值为value状态是fulfilled的Promise,但如果传入的值本身是Promise则会原样返回它。Promise。resolvefunction(value){如果是Promsie,则直接输出它if(valueinstanceofPromise){returnvalue}returnnewPromise(resolveresolve(value))}复制代码
  参考:深入理解PromisePromise。reject
  和Promise。resolve()类似,Promise。reject()会实例化一个rejected状态的Promise。但与Promise。resolve()不同的是,如果给Promise。reject()传递一个Promise对象,则这个对象会成为新Promise的值。Promise。rejectfunction(reason){returnnewPromise((resolve,reject)reject(reason))}复制代码Promise。all
  Promise。all的规则是这样的:传入的所有Promsie都是fulfilled,则返回由他们的值组成的,状态为fulfilled的新Promise;只要有一个Promise是rejected,则返回rejected状态的新Promsie,且它的值是第一个rejected的Promise的值;只要有一个Promise是pending,则返回一个pending状态的新Promise;Promise。allfunction(promiseArr){letindex0,result〔〕returnnewPromise((resolve,reject){promiseArr。forEach((p,i){Promise。resolve(p)。then(val{indexresult〔i〕valif(indexpromiseArr。length){resolve(result)}},err{reject(err)})})})}复制代码Promise。race
  Promise。race会返回一个由所有可迭代实例中第一个fulfilled或rejected的实例包装后的新实例。Promise。racefunction(promiseArr){returnnewPromise((resolve,reject){promiseArr。forEach(p{Promise。resolve(p)。then(val{resolve(val)},err{rejecte(err)})})})}复制代码Promise。allSettled
  Promise。allSettled的规则是这样:所有Promise的状态都变化了,那么新返回一个状态是fulfilled的Promise,且它的值是一个数组,数组的每项由所有Promise的值和状态组成的对象;如果有一个是pending的Promise,则返回一个状态是pending的新实例;Promise。allSettledfunction(promiseArr){letresult〔〕returnnewPromise((resolve,reject){promiseArr。forEach((p,i){Promise。resolve(p)。then(val{result。push({status:fulfilled,value:val})if(result。lengthpromiseArr。length){resolve(result)}},err{result。push({status:rejected,reason:err})if(result。lengthpromiseArr。length){resolve(result)}})})})}复制代码Promise。any
  Promise。any的规则是这样:空数组或者所有Promise都是rejected,则返回状态是rejected的新Promsie,且值为AggregateError的错误;只要有一个是fulfilled状态的,则返回第一个是fulfilled的新实例;其他情况都会返回一个pending的新实例;Promise。anyfunction(promiseArr){letindex0returnnewPromise((resolve,reject){if(promiseArr。length0)returnpromiseArr。forEach((p,i){Promise。resolve(p)。then(val{resolve(val)},err{indexif(indexpromiseArr。length){reject(newAggregateError(Allpromiseswererejected))}})})})}复制代码后话
  能看到这里的对代码都是真爱了,毕竟代码这玩意看起来是真的很枯燥,但是如果看懂了后,就会像打游戏赢了一样开心,而且这玩意会上瘾,当你通关了越多的关卡后,你的能力就会拔高一个层次。用标题的话来说就是:搞懂后,提升真的大。加油吧,干饭人
  噢不,代码人。

魅力世界杯,激情卡塔尔头条创作挑战赛卡塔尔世界杯,是亚洲足球的最高荣誉,也是对全球足球的挑战和考验。本届世界杯卡塔尔一共派出了三支球队,分别是东道主两队以及来自巴林沙特等国家的四支队伍。从首场小组赛就拉2022年虎女宝宝的名字带薇字起名高贵坚强热情真诚的女孩名字薇字在名字中的寓意读音wi五笔atmt部首艹字形结构上下结构五行属性木字义薇字指一种草本植物,蔷薇。象征着坚强纯洁热情真挚高贵和丰收用作人名意指美丽高贵坚强之义薇配哪个字更有寓意女iPhone手机还能滚动截长图?这4种截图方法,不知道就亏大了分享最实在的玩机技巧,洞察最前沿的科技资讯!大家好,这里是手机科技园!我们在使用手机的过程中,经常用到截屏功能,当需要分享长图的时候,iPhone用户就傻眼了。其实iPhone手机网信知识栏量子互联网如今,各国把量子互联网作为科技发展重点之一。那么,什么是量子互联网呢?什么是量子互联网量子互联网是以量子力学为理论依据建立的新型互联网。不同于二进制的数据,量子互联网是一种以量子比继428万粉丝网红发声之后,特斯拉的回应来了,网友早该如此了对于敢说真话的车评人,还是要给予充分肯定,但毕竟人红是非多。网络上有些车评人,对于产品提出问题,却收到了汽车品牌的律师函。当然也存在很多被充值的车评人,在说车时,有股浓重的充值味,微信上线刷掌支付,网友以后不敢随便抬手打招呼了以后走在大街上,还敢跟人随便抬手打招呼吗?此前推出的刷脸支付就已经涉及到太多生物识别,现在掌纹指纹的信息全部都被识别,万一今后这些数据都被泄露了怎么办?近日,微信上线刷掌支付话题冲这是恩比德身边,资源搭配最合理的一年前瞻系列的最后一篇。这个系列未能覆盖的球队,其徽章评分也在这篇文章中给出,以供大家比较参考。下图为76人全队徽章的拥有量和综合评分情况。图1为根据徽章等级的综合评分,图2为拥有至少曝光进军自动驾驶出租车领域?理想汽车申请理想智行等商标文懂车帝原创彩丽美懂车帝原创行业近日,懂车帝从企查查获悉,理想汽车关联公司北京车和家信息技术有限公司申请注册了多个理想智行理行商标,国际分类涉及运输工具燃料油脂机械设备等,当前商标全球最大无暇钻石在迪拜展出,市场估价约为1亿多元极目新闻记者孙喆据太阳报等媒体报道,近日,一颗重达303。1克拉的梨形黄钻在迪拜展出。报道称,这颗钻石名为金色金丝雀,被美国宝石学院评定为现今全球最大的无暇钻石,市场估价约为13026岁国际超模贝拉真敢穿,用钻石和水晶当胸衣,闪闪发光气场十足据英媒每日星报10月17日报道,26岁国际超模贝拉哈迪德近日为著名珠宝品牌施华洛世奇拍摄了一组照片,照片中的她以钻石和水晶当胸衣,美轮美奂让人惊叹。在知名摄影师默特阿拉斯(Mert重达300多克拉的世界上最大无瑕钻石,预计售价至少为1300万英镑世界上最大的无暇钻石将以至少1300万英镑的价格出售。重量超过300克拉的梨形金丝雀GoldenCanary,昨天在迪拜展出。20世纪80年代,一个小女孩在她叔叔的花园里玩耍时,在
天津哪里中医好?我转了几圈,走了几多弯路,最后还是选择了天津市中医药大学第一附属医院。连续几年了,每次到了换季的时候就会肚子疼,比如冬春之交,秋冬之交,肚子都非常的不舒服,还很胀。找了很多医生,吃最近比较烦,女朋友想要辆小跑车,不知买什么车,给个推荐?由于不清楚您的购车预算,只能从便宜到贵推荐一百万以内的几款口碑不错的车。推荐第一款斯巴鲁BRZ(官方指导价27。3828。38万元)车身尺寸424017751320,为2门4座硬顶簸箕粄的米浆要怎么调?簸箕粄,顾名思义,是用簸箕蒸制的粄。是著名的客家风味小吃。这种长条形的美味,有着白如羊脂晶莹如玉的米浆外皮,里面裹着炒熟的香菇瘦肉豆角等,表层是葱油,白与绿相互映衬,格外赏心悦目。用一句话说明你家乡在哪?仙鹤飞进大峡谷土家山寨无觅处富硒茶叶此山中康庄惠民不用愁得中原者得天下,致革开放砖厂厂,户户村村路相连。游山玩景桃花源,一08道街中过。东南西北车奔驰,交通方便顺心行。迎着清晨的阳成都好玩吗?好玩,去杜甫草堂,武侯祠,青羊宫,去春熙路小吃,领略成都草馆。成都我觉得还是蛮好玩的,成都是一个适合慢旅行的城市!今天分享下说走就走的成都三天自由行,基本都是吃吃喝喝睡逛逛。因为前北京南苑机场为什么全是kn的飞机?KN正是中国联航在国际航空运输协会(IATA)的航空公司两字代码,联航曾具有军方背景。而且南苑机场只对中国联合航空独家开放,所以在南苑机场只能看到联合航空公司的飞机。中国联合航空公可以推荐中学生用的性价比高的笔记本电脑吗?1。戴尔XPS13戴尔再次提高游戏水平,并提供世界上最好的学生笔记本电脑配置CPU第8代IntelCorei5i7显示卡IntelUHDGraphics620内存4GB16GB屏幕买手机在京东自营店买,还是官方旗舰店买,哪个比较好?买手机在京东自营店买,还是官方旗舰店买,哪个比较好?我的观点是在哪里买都可以。关键看价格!!!同等质量比价格我就是京东平台的卖家,先给您介绍一下自营店铺和官方旗舰店的区别。自营店说苹果笔记本电脑一般可以用几年?我有一台2015年的MacBookpro升级了最新的macOSMojave,电脑一如既往的运行稳定流畅。算下今年我的MacBookpro已经是第五个年头了。电脑不关做文字编辑,也做中央司法警官学院和安徽公安职业学院哪个好?一个本科,肯定强于后者不要听那些专家乱忽悠,不懂装懂,都是拿一些百度的数据说话,建议安徽公安职业学院公安专业,毕业公安联考入警率百分之90以上后者好,不二之选。安徽公安职业学院比较在医学类财经类政法类大学中,有哪些四非高校是最强的?四非高校是指非985工程大学,非211工程大学,非世界一流大学建设高校,非一流学科建设高校。我国高校中一般来说985211,双一流都代表了各个领域里最顶尖的大学。但是,并不是说没有
友情链接:快好找快生活快百科快传网中准网文好找聚热点快软网