前言纸上实现的总是浅薄的,深知这件事必须要做。看懂一道算法题是很快的,但是一定要梳理这道题的思路,手写出来。三道js手写题的思路和代码实现了数组扁平化的演示效果。将[1,[1,2],[1,[2]]]变为[1,1,2,1,2]第一种:直接使用.flatconsole.log([1,[1,2],[1,[2]]].平坦(3));多维数组可以降维,传递的参数个数多少,参数个数多少就可以减少多少。一般直接传参为Infinity(简单粗暴)第二种方法:递归的方法+借用数组的API完成(1)functionflattten(arr){varresult=[];for(vari=0,len=arr.length;i{returnpre.concat(Array.isArray(cur)?flattten(cur):cur);},[]);}第四种:some+...(展开运算符)+.concatfunctionflattten(arr){//some()方法用于检测数组是否的元素满足指定条件(由函数提供)。//some()方法会依次执行数组的每个元素://如果一个元素满足条件,则表达式返回true,其余元素将不再检查。//如果没有满足条件的元素,则返回false。while(arr.some(item=>Array.isArray(item))){console.log(arr)arr=[].concat(...arr)//...将降低多维数组的维度}returnarr}第五种:将多维数组转为字符串并进行运算(1)functionflatten(arr){letstr=arr.toString();str=str.replace(/(\[|\])/g,'').split(',').map(Number)returnstr;}/([|])/g正则表达式()代表一个组,\是转义符(因为正则表达式规则语法中有[和],用\让规则忽略[和])/g全局匹配,只要遇到[和],就用''反而。replace()方法用于将字符串中的某些字符替换为其他字符,或者替换与正则表达式匹配的子字符串。(2)functionflatten(arr){letresult=arr.toString();结果=result.replace(/(\[|\])/g,'');结果='['+结果+']';结果=JSON.parse(结果);//JSON.parse()可以将JSON规则的字符串转化为JSONObject返回结果;}浅拷贝的实现理解浅拷贝的局限性:只能复制一层对象。如果存在对象嵌套,那么浅拷贝将无力对基本数据类型进行基本拷贝,为引用类型开辟新的存储空间,再拷贝一层对象属性functiondeepClone(target){if(typeoftarget==='object'&&target!=null){//判断是数组还是对象consttargetclone=Array.isArray(target)?[]:{}//key值是否存在for(letpropintarget){if(target.hasOwnProperty(prop)){//hasOwnProperty()方法不检测对象的原型链,//仅检测当前对象本身,只有当前对象本身存在该属性时才返回true。targetclone[prop]=(typeoftarget[prop]==='object')?deepClone(target[prop]):target[prop]}}returntargetclone;}else{返回目标;}}让arr1=[1,2,{val:4,xdm:{dd:99}}];让str=shallowClone(arr1)console.log(arr1,'arr1')console.log(str,'str')str.push({mo:'Brothers'})console.log('str.push-----------')console.log(arr1,'arr1')console.log(str,'str+push')深拷贝的最终版本,深拷贝的思路:对于date和regular类型,处理new一个新的paira:{val:a}这个循环引用时,使用weakMap进行巧妙处理,使用Reflect.ownKeys返回一个由目标对象本身的属性键组成的数组,递归对其余为object和function但不为null的拷贝类型进行操作,对上述以外的类型直接进行“key”赋值操作。细节处理:使用getOwnPropertyDescriptors返回指定对象所有自身属性(非继承属性)的描述对象。要获取的属性由Object.create继承。原型链由weakMap.set处理并获取fora:{val:a}循环引用。实现代码constisComplexDataType=obj=>(typeofobj==='object'||typeofobj==='function')&&(obj!==null)constdeepClone=function(obj,hash=newWeakMap()){if(obj.constructor===Date)returnnewDate(obj)//日期对象直接返回一个新的日期对象if(obj.constructor===RegExp)returnnewRegExp(obj)//正则对象返回直接一个newregularobject//如果有循环引用,用weakMap解决if(hash.has(obj))returnhash.get(obj)letallDesc=Object.getOwnPropertyDescriptors(obj)//遍历所有key传入参数的特征letcloneObj=Object.create(Object.getPrototypeOf(obj),allDesc)//继承原型链hash.set(obj,cloneObj)for(letkeyofReflect.ownKeys(obj)){//对于可以遍历的对象,对于不可枚举的属性和Symbol类型,我们可以使用Reflect.ownKeys的方法cloneObj[key]=(isComplexDataType(obj[key])&&typeofobj[key]!=='function')?deepClone(obj[key],hash):obj[key]//typeofobj[key]!=='function')}returncloneObj}测试代码letobj详细答案参考前端高级面试题={num:0,str:'',boolean:true,unf:undefined,nul:null,obj:{name:'我是一个对象',id:1},arr:[0,1,2],func:function(){console.log('我是一个函数')},date:newDate(0),reg:newRegExp('/我是普通人/ig'),[Symbol('1')]:1,};Object.defineProperty(obj,'innumerable',{enumerable:false,value:'不可枚举的属性'});obj=Object.create(obj,Object.getOwnPropertyDescriptors(obj))obj.loop=obj//设置循环为循环引用属性letcloneObj=deepClone(obj)cloneObj.arr.push(4)console.log('obj',obj)console.log('cloneObj',cloneObj)console.log(cloneObj.func)实现对象循环应用的拷贝。解释一下上面的代码:Object.getOwnPropertyDescriptors返回指定对象的所有属性(非继承属性)的描述对象。您可以在此处了解更多信息。apiObject.create()方法创建一个新对象,并使用现有对象提供新创建对象Object的__proto__。create如果指定了该参数且没有undefined,则传入的对象自身的可枚举属性(即自己定义的属性,而不是其原型链上的可枚举属性)会添加指定的属性值和对应的属性描述符。constperson={isHuman:false,};constme=Object.create(person);console.log(me.__proto__===person);//trueObject.getPrototypeOf方法返回指定对象的原型(内部[[Prototype]]属性值)继承原型链WeakMap对象是一组键值对,其中键是弱引用对象,值可以是任何。因为WeakMap是弱引用类型,可以有效防止内存泄露,对检测循环引用很有帮助。如果有循环,则引用直接返回存储在WeakMap中的值。你可以从这里了解更多关于WeapMap和Map的区别Reflect.ownKeys==Object.getOwnPropertyNames(target)contact(Object.getOwnPropertySymbols(target).Object.getOwnPropertyNames()方法返回一个属性名所有它自己的属性指定的对象(包括不可枚举的属性,但不包括名称为Symbol值的属性)。Object.getOwnPropertySymbols()方法返回给定对象本身所有Symbol属性的数组。事件总线(发布-订阅模式)原理:事件总线是发布/订阅模式的实现,发布者发布数据,订阅者可以监听数据,并根据数据进行处理。这样松散耦合了发布者和订阅者。发布者发布data事件到事件总线,总线负责将它们发送到订阅者上或者addListener(event,listenr)是在监听数组的末尾添加监听器e指定的事件。off或removeListener(event,listenr)移除指定事件的侦听器,该侦听器必须是该事件已注册的侦听器事件。emit(event,[arg1],[arg2]...)按照参数的顺序执行每个监听器,如果事件有注册的监听器则返回true,否则返回false。使用Node.js理解事件总线name);})eventEmitter.emit('say','若离老师');functionhelloA(name){console.log("helloAAAAAAAA",name)}functionhelloB(name){console.log("helloBBBBBBB",name)}eventEmitter.on('say',helloA)eventEmitter.on('say',helloB)eventEmitter.emit('say','若离老师')eventEmitter.off('say',helloB);eventEmitter.emit('say','Ruoliteacher')新定义的eventEmitter是接收到events.EventEmitter模块new后返回的实例,eventEmitter的emit方法发出say事件,通过eventEmitter的on方法监听,执行相应的功能。当off被触发时,删除say事件上的响应函数。on实现代码:on的实现思路对于on,给指定事件添加监听:形式为{"say":[{listener:(function),once:(falseortrue)},{},{}]}参数有两个(name,fn)name是指定事件,fn是回调函数,判断fn:是否不存在,是否合法(function),下面代码判断是eventon不能重复添加functionEventEmitter(){this.__events={}}//判断是否是有效监听函数isValidListener(listener){if(typeoflistener==='function'){returntrue;}elseif(listener&&typeoflistener==='object'){//监听器作为自定义事件的回调,必须是一个函数。//另外判断是否为对象,递归查找对象中是否有函数。如果不是函数,//自定义事件没有回调肯定不行returnisValidListener(listener.listener);}else{返回错误;}}//顾名思义,用于判断是否存在新的自定义事件functionindexOf(array,item){varresult=-1item=typeofitem==='object'?item.listener:项目;对于(vari=0,len=array.length;i