1.实现新的运算符
新的操作员做这些事情:
它创造了一个全新的物体。它将由[[原型]]链接(即__proto__)。它指向新创建的对象。通过new创建的每个对象最终都将通过[[Prototype]]链接到该函数的原型对象。如果函数不返回Object类型对象(包括functoin、array、date、regxg、error),新表达式中的函数调用将返回对象引用。函数New(func){ var RES={ };if (func.prototype!==null){ RES . _ _ proto _ _=func . prototype;} var ret=func.apply(res,Array . prototype . slice . call(arguments,1));if((type of ret==' object ' | | type of ret==' function ')ret!==null){ ret ret;}返回res}var obj=New(A,1,2);//equals tovar obj=new A(1,2);2.实现一个JSON.stringify
JSON.stringify(value[,replacer [,space]]):
类型布尔值|数字|字符串会自动转换为相应的原始值。未定义的、任意的函数和符号被忽略(当它们出现在非数组对象的属性值中时)或被转换为null(当它们出现在数组中时)。不可枚举的属性将被忽略。如果一个对象的属性值以某种间接的方式引用回对象本身,即循环引用,那么这个属性也会被忽略。函数jsonStringify(obj){ let type=type of obj;if(键入!==' object '){ if(/string | undefined | function/。test(type)){ obj=' ' ' obj ' ' ';}返回字符串(obj);} else { let JSON=[]let arr=array . isarray(obj)for(let k in obj){ let v=obj[k];let type=的类型;if (/string|undefined|function/。test(type)){ v=' ' ' v ' ' ';} else if(type==' object '){ v=jsonStringify(v);} json.push((arr?' : ''' k '': ')字符串(v));}返回(arr?[' : '{ ')字符串(json) (arr?]' : ' } ')} } jsonstringify({ x : 5 })//' { ' x ' :5 } ' jsonstringify([1,' false,false])/'[1,' false,false]' JSON
JSON.parse(text[,reviver])
用于解析JSON字符串并构造由字符串描述的JavaScript值或对象。提供了可选的reviver函数,用于在返回之前对获得的对象执行转换(操作)。
3.1类型1:直接调用评估
函数jsonpalse(opt){ return eval('(' opt ')');} jsonpalse(jsonStringify({ x : 5 })//Object { x : 5 } jsonpalse(jsonStringify([1,' false ',false])//[1,' false ',Falsr]JSON Parse(JSON Stringify({ b : undefined })//Object { b : ' undefined ' }避免在不必要的情况下使用eval。eval()是一个危险的函数,它执行的代码拥有执行器的权限。如果您使用eval()运行的字符串代码被恶意方(恶意人员)操纵和修改,您最终可能会在您的网页/扩展程序的许可下在用户的计算机上运行恶意代码。
它可以执行JS代码,并且具有XSS漏洞。
如果你只想记住这个方法,你必须检查参数json。
var rx _ one=/^[],{}s]*$/;var rx_two=/\(?[' /BF nrt]| u[0-9a-Fa-F]{ 4 })/g;var rx _ three=/'[^'\nr]*'|true|false|null|-?d(?.d*)?(?[eE][ -]?d)?/g;var rx_four=/(?^|:|,)(? s * [)/g;if (rx_one.test(json。替换(rx_two,' @ ')。替换(rx_three,']')。replace(rx_four,' '))){ var obj=eval('(' JSON ')');}3.2类型2:功能
源魔术eval()和新函数()
核心:函数和eval具有相同的字符串参数特征。
var func=新函数(arg1,arg2,FunctionBody);在转换JSON的实际应用中,只需要这样做。
var jsonStr='{ 'age': 20,' name ' : ' jack ' } ' var JSON=(new Function(' return ' jsonStr))();Eval和Function可以动态编译js代码,但在实际编程中不推荐使用。
这是面向面试的编程,写这两种就够了。至于第三和第四个,涉及到繁琐的递归和状态机相关原理,可以看出如下:
《JSON.parse 三种实现方式》
4.实施呼叫或申请
实现适配来源:调用和应用#11的JavaScript深度模拟实现
调用语法:
Fun.call(thisArg,arg1,arg2,),它调用具有指定的该值和单独提供的参数(参数列表)的函数。
应用语法:
Func.apply(thisArg,[argsArray]),调用作为数组(或类似数组的对象)提供的函数和参数。
4.1函数调用按例程实现
呼叫核心:
将函数设置为对象的属性并删除它。将此指定给函数,并传入给定的参数来执行函数。如果没有参数传入,默认点是窗口。为什么是常规执行?因为在真正的面试中,面试官喜欢让你一步一步深入思考。这时,你可以反过来,先写一个简单的版本:
4.1.1简单版本
Varfoo={value: 1,bar : function(){ console . log(this。value)}} foo.bar ()//14.1.2完美版
当面试官有进一步的问题时,或者你可以假装在这个时候想一想。然后写出以下版本:
function . prototype . call 2=function(content=window){ content . fn=this;让args=[.参数]。切片(1);让结果=content.fn(.args);删除content.fn返回结果;}让foo={ value: 1 }函数栏(名称,年龄){ console.log(名称)console.log(年龄)console . log(this . value);} bar的模拟实现。call2 (foo,' black ',' 18')//black18 14.2函数。应用
apply()的实现类似于call(),只是参数形式不同。直接粘贴代码:
function . prototype . apply 2=function(context=window){ context . fn=这个let结果;//判断是否有第二个参数if(arguments[1]){ result=context . fn(.参数[1])} else { result=context . fn()} delete context . fn返回结果} 5。实现函数。绑定()
Bind()方法:
一个新的函数被创建。当调用这个新函数时,bind()的第一个参数将在运行时用作这个参数,一系列后续参数将在传递的参数之前作为它的参数传入。(来自MDN)
另外,bind的实现需要考虑实例化对原型链的影响。
function . prototype . bind 2=function(content){ if(type of this!=' function') {throwerror('不是函数')}//如果不问参数类型,从这里写let fn=this让args=[.参数]。切片(1);让resFn=function(){ return fn . apply(resFn的这个实例?这个:内容,args.concat(.arguments()}函数tmp(){ } tmp . prototype=this . prototype;resfn . prototype=new tmp();返回resFn}6.实现继承
寄生组合遗传
一般只建议这样写,因为其他继承方式会在一个实例中两次调用父类的构造函数,或者有其他缺点。
核心实现是用空的构造函数f替换父构造函数。
函数Parent(name){ this . name=name;} parent . prototype . SayName=function(){ console . log(' parent name : ',this . name);}函数Child(name,parentName) { Parent.call(this,parent name);this.name=name} function create(proto){ function F(){ } F . prototype=proto;返回新的F();} child . prototype=create(parent . prototype);child . prototype . SayName=function(){ console . log(' child name : ',this . name);} Child . prototype . constructor=Child;var parent=new Parent('父');parent . SayName();//父名称:父var child=new Child('son ','父');7.实现一个JS函数
什么是科里化?
在计算机科学中,Currying是一种将接受多个参数的函数转换为接受单个参数(原始函数的第一个参数)的函数,并返回接受剩余参数的新函数并返回结果的技术。
函数核心化的主要功能和特点是参数重用、提前返回和延迟执行。
7.1普通版
函数curry(fn,args){ var length=fn。长度;var args=args | |[];return函数(){ new args=args。concat(数组。原型。切片。调用(参数));if(新参数。长度长度){返回咖喱。调用(this,fn,new args);}else{ return fn.apply(this,Newargs);} } }函数多重网络(a、b、c){ 0返回a * b * c;} var multi=curry(multi TN);多(2)(3)(4);multi(2,3,4);multi(2)(3,4);multi(2,3)(4);7.2 ES6骚写法
const curry=(fn,arr=[])=(.args=(arg=arg。长度===fn。长度?fn(.arg) : curry(fn,arg))([.啊,args])让curryTest=curry((a,b,c,d)=a b c d)curryTest(1,2,3)(4) //返回10电流测试(1,2)(4)(3) //返回10curryTest(1,2)(3,4) //返回108.手写一个承诺(中高级必考)
我们来过一遍承诺/答规范:
三种状态待定|已完成(已解决)|已拒绝当处于悬而未决的状态的时候,可以转移到已完成(已解决)或者拒绝状态当处于已完成(已解决)状态或者拒绝状态的时候,就不可变。必须有一个然后异步执行方法,那么接受两个参数且必须返回一个承诺:
//已完成用来接收承诺成功的值//onRejected用来接收承诺失败的原因承诺1=承诺。然后(完成时,弹出时);8.1承诺的流程图分析
来回顾下承诺用法:
var promise=新的承诺(解决,拒绝)={ if(操作成功){ resolve(value)} else { reject(error)} })承诺。然后(函数(值){ //成功},函数(值){//失败})8.2面试够用版
来源:实现一个完美符合承诺/答规范的承诺
函数myPromise(构造函数){ let self=this self . status=' pending '//定义状态改变前的初始状态self.value=undefined//定义状态为断然的的时候的状态self.reason=undefined//定义状态为拒绝的时候的状态函数解析(值){ //两个==='待定,保证了状态的改变是不可逆的如果(自我。状态==='挂起'){ self。值=值;self.status='已解决;} }功能拒绝(原因){ //两个==='待定,保证了状态的改变是不可逆的如果(自我。状态==='挂起'){ self。原因=理由;self.status='已拒绝;} } //捕获构造异常尝试{构造函数(解析,拒绝);}捕获(e){拒绝(e );}}
同时,需要在我的承诺的原型上定义链式调用的然后方法:
我的承诺。原型。然后=函数(onFullField,onRejected){让self=this开关(自。状态){案例'已解决' :关于完全填充(自我。值);打破;案例"被拒绝":关于弹出(自我。原因);打破;default: }}测试一下:
var p=new myPromise(函数(解析,拒绝){ resolve(1)});p .然后(函数(x){console.log(x)})//输出18.3 大厂专供版
直接贴出来吧,这个版本还算好理解
const PENDING=' PENDING CONST PROCESSED=' PROCESSED ';const REJECTED=' rejected '函数承诺(执行者){ let that=this//缓存当前承诺实例对象that.status=PENDING//初始状态that.value=undefined//已完成状态时返回的信息that.reason=undefined//被拒绝状态时拒绝的原因那个。onfluildcallbacks=[];//存储履行状态对应的完成函数在弹出的回调中回调=[];//存储拒绝状态对应的弹出时函数函数解析(值){ //值成功态时接收的终值if(Promise的值实例){返回值。然后(解析,拒绝);} //实践中要确保完成和弹出时方法异步执行,且应该在然后方法被调用的那一轮事件循环之后的新执行栈中执行setTimeout(()={ //调用分解回调对应完成函数if (that.status===PENDING) { //只能由悬而未决的状态=已完成状态(避免调用多次解决拒绝)即。status=PROCESSED值=值;那个。onfluildcallbacks。foreach(CB=CB(那个。值));} });}函数拒绝(原因){ //原因失败态时接收的拒因setTimeout(()={ //调用拒绝回调对应弹出时函数if (that.status===PENDING) { //只能由悬而未决的状态=拒绝状态(避免调用多次解决拒绝)即。that.status=REJECTED原因=理由;那个。弹出时回调。foreach(CB=CB(即。原因));} });} //捕获在借口者执行器中抛出的异常//新承诺(解析,拒绝)={ //抛出新错误('执行者'中的错误)//})尝试{执行人(解析,拒绝);}捕获(e) {拒绝(e );} }承诺。原型。然后=函数(onFulfilled,onRejected){ const that=this;让newPromise//处理参数默认值保证参数后续能够继续执行完成=类型为onFulfilled===' function '?完成:值=值;onRejected=onRejected的类型==='函数'?弹出:原因={抛出原因};如果(那个。status===PROCESSED){//成功态返回新承诺=新承诺((解析,拒绝)={ setTimeout(()={ try { let x=on fulfilled(即。值);resolvePromise(newPromise,x,resolve,reject);//新的承诺决心上一个完成的返回值}捕获(e) {拒绝(e );//捕获前面完成中抛出的异常然后(完成,弹出);} });})}如果(那。status===REJECTED){//失败态返回新承诺=新承诺((解析,拒绝)={ setTimeout(()={ try { let x=OnRejected(即。原因);resolvePromise(newPromise,x,resolve,reject);}捕获(e) {拒绝(e );} });});} if (that.status===PENDING) { //等待态//当异步调用解决/拒绝时将完成/弹出收集暂存到集合中返回新承诺=新承诺((解析,拒绝)={那个。onfulfiledcallbacks。推动((值)={ try { let x=onfulfiledcallbacks(值));resolvePromise(newPromise,x,resolve,reject);}捕获(e) {拒绝(e );} });那个。弹出时回调。push((reason)={ try { let x=onRejected(reason));resolvePromise(newPromise,x,resolve,reject);}捕获(e) {拒绝(e );} });});}};emmm,我还是乖乖地写回进阶版吧。
9.手写防抖(去抖)和节流(节流)
卷起事件本身会触发页面的重新渲染,同时卷起事件的处理者又会被高频度的触发,因此事件的处理者内部不应该有复杂操作,例如数字正射影像图操作就不应该放在事件处理中。针对此类高频度触发事件问题(例如页面滚动,屏幕调整大小,监听用户输入等),有两种常用的解决方法,防抖和节流。
9.1 防抖(去抖)实现
典型例子:限制鼠标连击触发。一个比较好的解释是:
当事件发生时,事件处理程序必须等待一定的阈值时间。如果在此时间之后没有事件发生,它将处理最后一个事件。假设距离指定时间还有0.01秒,当另一个事件到来时,之前的等待无效,需要再次等待指定时间。
//图像稳定器功能去抖(fn,wait=50,立即){ let timer返回函数(){if (immediate) {fn。apply (this,arguments)} if(timer)clear time out(timer)timer=settimeout(()={ fn。apply (this,arguments)},wait)}}组合示例:滚动防抖。
//简单的图像稳定器函数//Handler函数real func(){ console . log(' success ')实际想要绑定到滚动事件;}//采用图像稳定器窗口。addeventlistener ('scroll ',去抖(realfunc,500));//未使用图像稳定器window . addeventlistener(' scroll ',real func);9.2节流实施
可以理解,事件是在一个管道中传输的,加上这个节流阀后,事件的流速会变慢。其实这个功能就是这样的。它可以将一个函数的调用频率限制在某个阈值内,例如1s,所以这个函数在1s内不会被调用两次
简单节流功能:
函数节流(fn,wait){让prev=new Date();return function(){ const args=args;立即常量=新日期();if (now - prev wait) {fn.apply(this,args);prev=新日期();}}9.3结合实践,
使用第三个参数切换模式。
const throttle=function(fn,delay,IsDebound){ let timer let last call=0返回function(.args){ if(isdeboune){ if(timer)cleartime out(timer)timer=setTimeout(()={ fn(.args)},delay)} else {const now=newdate()。gettime () if(现在-最后一次呼叫延迟)return last call=now fn(.args)}}} 10。手写一份深度的JS
有一个最著名的乞丐版本实现,也在《你不知道的JavaScript(上)》中提到:
10.1乞丐版
var new obj=JSON . parse(JSON . stringify(someObj));10.2面试足够的版本
函数deepCopy(obj){ //确定它是否是简单数据类型,如果(typeof obj=='object'){ //复杂数据类型var result=obj . constructor==array?[] : {};for(let I in obj){ result[I]=type of obj[I]==' object '?deep CoPY(obj[I]): obj[I];} }else {//简单数据类型direct==赋值var result=obj}返回结果;}每天都有关于深度复制的讨论。让我们在这里张贴两个。毕竟,我.
11.实现实例
函数instanceOf(左,右){ let proto=left。_ _ proto _ _让prototype=right . prototype while(true){ if(proto===null)返回false if(proto===prototype)返回true proto=proto。_ _ proto _ _}}以上是边肖介绍的JavaScript手写代码的详细讲解和集成,希望对大家有所帮助。如果你有任何问题,请给我留言,边肖会及时回复你。非常感谢您对我们网站的支持!