# 高频手写题目
面试高频手写题目
# 1 实现防抖函数(debounce)
防抖函数原理:在事件被触发n秒后再执行回调,如果在这n秒内又被触发,则重新计时
手写简化版:
// func是用户传入需要防抖的函数// wait是等待时间constdebounce=(func, wait=50)=>{// 缓存一个定时器idlet timer=0// 这里返回的函数是每次用户实际调用的防抖函数// 如果已经设定过定时器了就清空上一次的定时器// 开始一个新的定时器,延迟执行用户传入的方法returnfunction(...args){if(timer)clearTimeout(timer) timer=setTimeout(()=>{func.apply(this, args)}, wait)}}
适用场景:
按钮提交场景:防止多次提交按钮,只执行最后提交的一次 服务端验证场景:表单验证需要服务端配合,只执行一段连续的输入事件的最后一次,还有搜索联想词功能类似
# 2 实现节流函数(throttle)
节流函数原理:规定在一个单位时间内,只能触发一次函数。如果这个单位时间内触发多次函数,只有一次生效
手写简版
// func是用户传入需要防抖的函数// wait是等待时间constthrottle=(func, wait=50)=>{// 上一次执行该函数的时间let lastTime=0returnfunction(...args){// 当前时间let now=+newDate()// 将当前时间和上一次执行函数时间对比// 如果差值大于设置的等待时间就执行函数if(now- lastTime> wait){ lastTime= nowfunc.apply(this, args)}}}setInterval(throttle(()=>{ console.log(1)},500),1)
适用场景:
- 拖拽场景:固定时间内只执行一次,防止超高频次触发位置变动
- 缩放场景:监控浏览器
resize
- 动画场景:避免短时间内多次触发动画引起性能问题
# 3 深克隆(deepclone)
简单版:
const newObj = JSON.parse(JSON.stringify(oldObj));
局限性:
- 他无法实现对函数 、RegExp等特殊对象的克隆
- 会抛弃对象的constructor,所有的构造函数会指向Object
- 对象有循环引用,会报错
面试够用版
functiondeepCopy(obj){//判断是否是简单数据类型,if(typeof obj=="object"){//复杂数据类型var result= obj.constructor== Array?[]:{};for(let iin obj){ result[i]=typeof obj[i]=="object"?deepCopy(obj[i]): obj[i];}}else{//简单数据类型 直接 == 赋值var result= obj;}return result;}
# 4 实现Event(event bus)
event bus既是node中各个模块的基石,又是前端组件通信的依赖手段之一,同时涉及了订阅-发布设计模式,是非常重要的基础
简单版:
classEventEmeitter{constructor(){this._events=this._events||newMap();// 储存事件/回调键值对this._maxListeners=this._maxListeners||10;// 设立监听上限}}// 触发名为type的事件EventEmeitter.prototype.emit=function(type,...args){let handler;// 从储存事件键值对的this._events中获取对应事件回调函数 handler=this._events.get(type);if(args.length>0){handler.apply(this, args);}else{handler.call(this);}returntrue;};// 监听名为type的事件EventEmeitter.prototype.addListener=function(type, fn){// 将type事件以及对应的fn函数放入this._events中储存if(!this._events.get(type)){this._events.set(type, fn);}};
面试版:
classEventEmeitter{constructor(){this._events=this._events||newMap();// 储存事件/回调键值对this._maxListeners=this._maxListeners||10;// 设立监听上限}}// 触发名为type的事件EventEmeitter.prototype.emit=function(type,...args){let handler;// 从储存事件键值对的this._events中获取对应事件回调函数 handler=this._events.get(type);if(args.length>0){handler.apply(this, args);}else{handler.call(this);}returntrue;};// 监听名为type的事件EventEmeitter.prototype.addListener=function(type, fn){// 将type事件以及对应的fn函数放入this._events中储存if(!this._events.get(type)){this._events.set(type, fn);}};// 触发名为type的事件EventEmeitter.prototype.emit=function(type,...args){let handler; handler=this._events.get(type);if(Array.isArray(handler)){// 如果是一个数组说明有多个监听者,需要依次此触发里面的函数for(let i=0; i< handler.length; i++){if(args.length>0){ handler[i].apply(this, args);}else{ handler[i].call(this);}}}else{// 单个函数的情况我们直接触发即可if(args.length>0){handler.apply(this, args);}else{handler.call(this);}}returntrue;};// 监听名为type的事件EventEmeitter.prototype.addListener=function(type, fn){const handler=this._events.get(type);// 获取对应事件名称的函数清单if(!handler){this._events.set(type, fn);}elseif(handler&&typeof handler==="function"){// 如果handler是函数说明只有一个监听者this._events.set(type,[handler, fn]);// 多个监听者我们需要用数组储存}else{ handler.push(fn);// 已经有多个监听者,那么直接往数组里push函数即可}};EventEmeitter.prototype.removeListener=function(type, fn){const handler=this._events.get(type);// 获取对应事件名称的函数清单// 如果是函数,说明只被监听了一次if(handler&&typeof handler==="function"){this._events.delete(type, fn);}else{let postion;// 如果handler是数组,说明被监听多次要找到对应的函数for(let i=0; i< handler.length; i++){if(handler[i]=== fn){ postion= i;}else{ postion=-1;}}// 如果找到匹配的函数,从数组中清除if(postion!==-1){// 找到数组对应的位置,直接清除此回调 handler.splice(postion,1);// 如果清除后只有一个函数,那么取消数组,以函数形式保存if(handler.length===1){this._events.set(type, handler[0]);}}else{returnthis;}}};
# 5 实现instanceOf
核心要点:原型链的向上查找
functionmyInstanceof(left, right){let proto= Object.getPrototypeOf(left);while(true){if(proto==null)returnfalse;if(proto== right.prototype)returntrue; proto= Object.getPrototypeof(proto);}}
# 6 模拟new
new操作符做了这些事:
- 创建一个全新的对象,这个对象的
__proto__
要指向构造函数的原型对象 - 执行构造函数
- 返回值为object类型则作为new方法的返回值返回,否则返回上述全新对象
functionmyNew(fn,...args){let instance= Object.create(fn.prototype);let res=fn.apply(instance, args);returntypeof res==='object'? res: instance;}
# 7 实现一个call
call做了什么:
- 将函数设为对象的属性
- 执行&删除这个函数
- 指定
this
到函数并传入给定参数执行函数 - 如果不传入参数,默认指向为
window
// 模拟 call bar.mycall(null);//实现一个call方法:Function.prototype.myCall=function(context){//此处没有考虑context非object情况 context.fn=this;let args=[];for(let i=1, len= arguments.length; i< len; i++){ args.push(arguments[i]);} context.fn(...args);let result= context.fn(...args);delete context.fn;return result;};
# 8 实现apply方法
思路: 利用
this
的上下文特性。
//实现apply只要把下一行中的...args换成args即可Function.prototype.myCall=function(context= window,...args){let func=this;let fn=Symbol("fn"); context[fn]= func;let res= context[fn](...args);//重点代码,利用this指向,相当于context.caller(...args)delete context[fn];return res;}
# 9 实现bind
bind 的实现对比其他两个函数略微地复杂了一点,因为 bind 需要返回一个函数,需要判断一些边界问题,以下是 bind 的实现
- bind 返回了一个函数,对于函数来说有两种方式调用,一种是直接调用,一种是通过 new 的方式,我们先来说直接调用的方式
- 对于直接调用来说,这里选择了 apply 的方式实现,但是对于参数需要注意以下情况:因为 bind 可以实现类似这样的代码 f.bind(obj, 1)(2),所以我们需要将两边的参数拼接起来,于是就有了这样的实现 args.concat(...arguments)
- 最后来说通过 new 的方式,在之前的章节中我们学习过如何判断 this,对于 new 的情况来说,不会被任何方式改变 this,所以对于这种情况我们需要忽略传入的 this
Function.prototype.myBind=function(context){if(typeofthis!=='function'){thrownewTypeError('Error')}const _this=thisconst args=[...arguments].slice(1)// 返回一个函数returnfunctionF(){// 因为返回了一个函数,我们可以 new F(),所以需要判断if(thisinstanceofF){returnnew_this(...args,...arguments)}return_this.apply(context, args.concat(...arguments))}}
# 10 模拟Object.create
Object.create()方法创建一个新对象,使用现有的对象来提供新创建的对象的__proto__
// 模拟 Object.createfunctioncreate(proto){functionF(){}F.prototype= proto;returnnewF();}
# 11 实现类的继承-简版
类的继承在几年前是重点内容,有n种继承方式各有优劣,es6普及后越来越不重要,那么多种写法有点『回字有四样写法』的意思,如果还想深入理解的去看红宝书即可,我们目前只实现一种最理想的继承方式。
functionParent(name){this.parent= name}Parent.prototype.say=function(){ console.log(`${this.parent}: 你打篮球的样子像kunkun`)}functionChild(name, parent){// 将父类的构造函数绑定在子类上Parent.call(this, parent)this.child= name}Child.prototype= Object.create(Parent.prototype);Child.prototype.say=function(){ console.log(`${this.parent}好,我是练习时长两年半的${this.child}`);}// 注意记得把子类的构造指向子类本身Child.prototype.constructor= Child;var parent=newParent('father');parent.say()// father: 你打篮球的样子像kunkunvar child=newChild('cxk','father');child.say()// father好,我是练习时长两年半的cxk
# 12 ES5实现继承的那些事-详细
第一种方式是借助call实现继承
functionParent1(){this.name='parent1';}functionChild1(){Parent1.call(this);this.type='child1'}console.log(newChild1);
这样写的时候子类虽然能够拿到父类的属性值,但是问题是父类中一旦存在方法那么子类无法继承。那么引出下面的方法
第二种方式借助原型链实现继承:
functionParent2(){this.name='parent2';this.play=[1,2,3]}functionChild2(){this.type='child2';}Child2.prototype=newParent2(); console.log(newChild2());
看似没有问题,父类的方法和属性都能够访问,但实际上有一个潜在的不足。举个例子:
var s1=newChild2();var s2=newChild2(); s1.play.push(4); console.log(s1.play, s2.play);// [1,2,3,4] [1,2,3,4]
明明我只改变了s1的play属性,为什么s2也跟着变了呢?很简单,因为两个实例使用的是同一个原型对象
第三种方式:将前两种组合:
functionParent3(){this.name='parent3';this.play=[1,2,3];}functionChild3(){Parent3.call(this);this.type='child3';}Child3.prototype=newParent3();var s3=newChild3();var s4=newChild3(); s3.play.push(4); console.log(s3.play, s4.play);// [1,2,3,4] [1,2,3]
之前的问题都得以解决。但是这里又徒增了一个新问题,那就是Parent3的构造函数会多执行了一次(
Child3.prototype = new Parent3()
;)。这是我们不愿看到的。那么如何解决这个问题?
第四种方式: 组合继承的优化1
functionParent4(){this.name='parent4';this.play=[1,2,3];}functionChild4(){Parent4.call(this);this.type='child4';}Child4.prototype=Parent4.prototype;
这里让将父类原型对象直接给到子类,父类构造函数只执行一次,而且父类属性和方法均能访问,但是我们来测试一下
var s3=newChild4();var s4=newChild4(); console.log(s3)
子类实例的构造函数是Parent4,显然这是不对的,应该是Child4。
第五种方式(最推荐使用):优化2
functionParent5(){this.name='parent5';this.play=[1,2,3];}functionChild5(){Parent5.call(this);this.type='child5';}Child5.prototype= Object.create(Parent5.prototype);Child5.prototype.constructor= Child5;
这是最推荐的一种方式,接近完美的继承。
# 13 实现一个JSON.stringify
JSON.stringify(value[, replacer[, space]]):
Boolean | Number| String
类型会自动转换成对应的原始值。undefined
、任意函数以及symbol
,会被忽略(出现在非数组对象的属性值中时),或者被转换成null
(出现在数组中时)。- 不可枚举的属性会被忽略如果一个对象的属性值通过某种间接的方式指回该对象本身,即循环引用,属性也会被忽略
- 如果一个对象的属性值通过某种间接的方式指回该对象本身,即循环引用,属性也会被忽略
functionjsonStringify(obj){let type=typeof obj;if(type!=="object"){if(/string|undefined|function/.test(type)){ obj='"'+ obj+'"';}returnString(obj);}else{let json=[]let arr= Array.isArray(obj)for(let kin obj){let v= obj[k];let type=typeof v;if(/string|undefined|function/.test(type)){ v='"'+ v+'"';}elseif(type==="object"){ v=jsonStringify(v);} json.push((arr?"":'"'+ k+'":')+String(v));}return(arr?"[":"{")+String(json)+(arr?"]":"}")}}jsonStringify({x:5})// "{"x":5}"jsonStringify([1,"false",false])// "[1,"false",false]"jsonStringify({b:undefined})// "{"b":"undefined"}"
# 14 实现一个JSON.parse
JSON.parse(text[, reviver])
用来解析JSON字符串,构造由字符串描述的JavaScript值或对象。提供可选的reviver函数用以在返回之前对所得到的对象执行变换(操作)
第一种:直接调用 eval
functionjsonParse(opt){returneval('('+ opt+')');}jsonParse(jsonStringify({x:5}))// Object { x: 5}jsonParse(jsonStringify([1,"false",false]))// [1, "false", falsr]jsonParse(jsonStringify({b:undefined}))// Object { b: "undefined"}
避免在不必要的情况下使用
eval
,eval()
是一个危险的函数,他执行的代码拥有着执行者的权利。如果你用eval()
运行的字符串代码被恶意方(不怀好意的人)操控修改,您最终可能会在您的网页/扩展程序的权限下,在用户计算机上运行恶意代码。它会执行JS代码,有XSS漏洞。
如果你只想记这个方法,就得对参数json做校验。
var rx_one=/^[\],:{}\s]*$/;var rx_two=/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g;var rx_three=/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g;var rx_four=/(?:^|:|,)(?:\s*\[)+/g;if( rx_one.test( json.replace(rx_two,"@").replace(rx_three,"]").replace(rx_four,""))){var obj=eval("("+json+")");}
第二种:Function
核心:Function与eval有相同的字符串参数特性
var func = new Function(arg1, arg2, ..., functionBody);
在转换JSON的实际应用中,只需要这么做
var jsonStr='{ "age": 20, "name": "jack" }'var json=(newFunction('return '+ jsonStr))();
eval
与Function
都有着动态编译js代码的作用,但是在实际的编程中并不推荐使用
# 15 Promise的简单实现
// 使用var promise=newPromise((resolve,reject)=>{if(操作成功){resolve(value)}else{reject(error)}})promise.then(function(value){// success},function(value){// failure})
functionmyPromise(constructor){let self=this; self.status="pending"// 定义状态改变前的初始状态 self.value=undefined;// 定义状态为resolved的时候的状态 self.reason=undefined;// 定义状态为rejected的时候的状态functionresolve(value){if(self.status==="pending"){ self.value= value; self.status="resolved";}}functionreject(reason){if(self.status==="pending"){ self.reason= reason; self.status="rejected";}}// 捕获构造异常try{constructor(resolve,reject);}catch(e){reject(e);}}
// 添加 then 方法myPromise.prototype.then=function(onFullfilled,onRejected){let self=this;switch(self.status){case"resolved":onFullfilled(self.value);break;case"rejected":onRejected(self.reason);break;default:}}var p=newmyPromise(function(resolve,reject){resolve(1)});p.then(function(x){ console.log(x)// 1})
# 16 解析 URL Params 为对象
let url='http://www.domain.com/?user=anonymous&id=123&id=456&city=%E5%8C%97%E4%BA%AC&enabled';parseParam(url)
functionparseParam(url){const paramsStr=/.+\?(.+)$/.exec(url)[1];// 将 ? 后面的字符串取出来const paramsArr= paramsStr.split('&');// 将字符串以 & 分割后存到数组中let paramsObj={};// 将 params 存到对象中 paramsArr.forEach(param=>{if(/=/.test(param)){// 处理有 value 的参数let[key, val]= param.split('=');// 分割 key 和 value val=decodeURIComponent(val);// 解码 val=/^\d+$/.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;}})return paramsObj;}
# 17 模板引擎实现
let template='我是{{name}},年龄{{age}},性别{{sex}}';let data={ name:'姓名', age:18}render(template, data);// 我是姓名,年龄18,性别undefined
functionrender(template, data){const reg=/\{\{(\w+)\}\}/;// 模板字符串正则if(reg.test(template)){// 判断模板里是否有模板字符串const name= reg.exec(template)[1];// 查找当前模板里第一个模板字符串的字段 template= template.replace(reg, data[name]);// 将第一个模板字符串渲染returnrender(template, data);// 递归的渲染并返回渲染后的结构}return template;// 如果模板没有模板字符串直接返回}
# 18 转化为驼峰命名
var s1="get-element-by-id"// 转化为 getElementByIdvarf=function(s){return s.replace(/-\w/g,function(x){return x.slice(1).toUpperCase();})}
# 19 查找字符串中出现最多的字符和个数
例: abbcccddddd -> 字符最多的是d,出现了5次
let str="abcabcabcbbccccc";let num=0;let char='';// 使其按照一定的次序排列str= str.split('').sort().join('');// "aaabbbbbcccccccc"// 定义正则表达式let re=/(\w)\1+/g;str.replace(re,($0,$1)=>{if(num< $0.length){ num= $0.length; char= $1;}});console.log(`字符最多的是${char},出现了${num}次`);
# 20 字符串查找
请使用最基本的遍历来实现判断字符串 a 是否被包含在字符串 b 中,并返回第一次出现的位置(找不到返回 -1)。
a='34';b='1234567';// 返回 2a='35';b='1234567';// 返回 -1a='355';b='12354355';// 返回 5isContain(a,b);
functionisContain(a, b){for(let iin b){if(a[0]=== b[i]){let tmp=true;for(let jin a){if(a[j]!== b[~~i+~~j]){ tmp=false;}}if(tmp){return i;}}}return-1;}
# 21 实现千位分隔符
// 保留三位小数parseToMoney(1234.56);// return '1,234.56'parseToMoney(123456789);// return '123,456,789'parseToMoney(1087654.321);// return '1,087,654.321'
functionparseToMoney(num){ num=parseFloat(num.toFixed(3));let[integer, decimal]=String.prototype.split.call(num,'.'); integer= integer.replace(/\d(?=(\d{3})+$)/g,'$&,');return integer+'.'+(decimal? decimal:'');}
# 22 判断是否是电话号码
functionisPhone(tel){var regx=/^1[34578]\d{9}$/;return regx.test(tel);}
# 23 验证是否是邮箱
functionisEmail(email){var regx=/^([a-zA-Z0-9_\-])+@([a-zA-Z0-9_\-])+(\.[a-zA-Z0-9_\-])+$/;return regx.test(email);}
# 24 验证是否是身份证
functionisCardNo(number){var regx=/(^\d{15}$)|(^\d{18}$)|(^\d{17}(\d|X|x)$)/;return regx.test(number);}
# 25 用ES5实现数组的map方法
- 回调函数的参数有哪些,返回值如何处理
- 不修改原来的数组
Array.prototype.MyMap=function(fn, context){var arr=Array.prototype.slice.call(this);//由于是ES5所以就不用...展开符了var mappedArr=[];for(var i=0; i< arr.length; i++){ mappedArr.push(fn.call(context, arr[i], i,this));}return mappedArr;}
# 26 用ES5实现数组的reduce方法
- 初始值不传怎么处理
- 回调函数的参数有哪些,返回值如何处理。
Array.prototype.myReduce=function(fn, initialValue){var arr=Array.prototype.slice.call(this);var res, startIndex; res= initialValue? initialValue: arr[0]; startIndex= initialValue?0:1;for(var i= startIndex; i< arr.length; i++){ res=fn.call(null, res, arr[i], i,this);}return res;}
- 对于普通函数,绑定this指向
- 对于构造函数,要保证原函数的原型对象上的属性不能丢失
Function.prototype.bind=function(context,...args){let self=this;//谨记this表示调用bind的函数letfBound=function(){//this instanceof fBound为true表示构造函数的情况。如new func.bind(obj)returnself.apply(thisinstanceoffBound?this: context|| window, args.concat(Array.prototype.slice.call(arguments)));} fBound.prototype= Object.create(this.prototype);//保证原函数的原型对象上的属性不丢失return fBound;}
大家平时说的手写bind,其实就这么简单
# 27 实现单例模式
核心要点: 用闭包和Proxy属性拦截
functionproxy(func){let instance;let handler={constructor(target, args){if(!instance){ instance=Reflect.constructor(fun, args);}return instance;}}returnnewProxy(func, handler);}
# 28 实现数组的flat
需求:多维数组=>一维数组
let ary=[1,[2,[3,[4,5]]],6];let str=JSON.stringify(ary);
//第0种处理:直接的调用arr_flat= arr.flat(Infinity);
//第一种处理ary= str.replace(/(\[|\])/g,'').split(',');
//第二种处理str= str.replace(/(\[\]))/g,'');str='['+ str+']';ary=JSON.parse(str);
//第三种处理:递归处理let result=[];letfn=function(ary){for(let i=0; i< ary.length; i++)}{let item= ary[i];if(Array.isArray(ary[i])){fn(item);}else{ result.push(item);}}}
//第四种处理:用 reduce 实现数组的 flat 方法functionflatten(ary){return ary.reduce((pre, cur)=>{return pre.concat(Array.isArray(cur)?flatten(cur): cur);},[]);}let ary=[1,2,[3,4],[5,[6,7]]]console.log(flatten(ary))
//第五种处理:扩展运算符while(ary.some(Array.isArray)){ ary=[].concat(...ary);}
# 29 请实现一个 add 函数,满足以下功能
add(1); // 1add(1)(2); // 3add(1)(2)(3);// 6add(1)(2,3);// 6add(1,2)(3);// 6add(1,2,3);// 6
functionadd(){let args=[].slice.call(arguments);letfn=function(){let fn_args=[].slice.call(arguments)returnadd.apply(null,args.concat(fn_args))} fn.toString=function(){return args.reduce((a,b)=>a+b)}return fn}
# 30 实现一个 sleep 函数,比如 sleep(1000) 意味着等待1000毫秒
constsleep=(time)=>{returnnewPromise(resolve=>setTimeout(resolve, time))}sleep(1000).then(()=>{// 这里写你的骚操作})
# 31 实现 (5).add(3).minus(2) 功能
例: 5 + 3 - 2,结果为 6
Number.prototype.add=function(n){returnthis.valueOf()+ n;};Number.prototype.minus=function(n){returnthis.valueOf()- n;};
# 32 给定两个数组,写一个方法来计算它们的交集
例如:给定 nums1 = [1, 2, 2, 1],nums2 = [2, 2],返回 [2, 2]。
functionunion(arr1, arr2){return arr1.filter(item=>{ return arr2.indexOf(item)>-1;})}const a=[1,2,2,1];const b=[2,3,2]; console.log(union(a, b));// [2, 2]
# 33 实现一个JS函数柯里化
是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数且返回结果的新函数的技术
通用版
functioncurry(fn, args){var length= fn.length;var args= args||[];returnfunction(){ newArgs= args.concat(Array.prototype.slice.call(arguments));if(newArgs.length< length){returncurry.call(this,fn,newArgs);}else{returnfn.apply(this,newArgs);}}}functionmultiFn(a, b, c){return a* b* c;}var multi=curry(multiFn);multi(2)(3)(4);multi(2,3,4);multi(2)(3,4);multi(2,3)(4)
ES6写法
constcurry=(fn, arr=[])=>(...args)=>(arg=> arg.length=== fn.length?fn(...arg):curry(fn, arg))([...arr,...args])let curryTest=curry((a,b,c,d)=>a+b+c+d)curryTest(1,2,3)(4)//返回10curryTest(1,2)(4)(3)//返回10curryTest(1,2)(3,4)//返回10
# 34 实现一个双向绑定
defineProperty 版本
// 数据const data={ text:'default'};const input= document.getElementById('input');const span= document.getElementById('span');// 数据劫持Object.defineProperty(data,'text',{// 数据变化 --> 修改视图set(newVal){ input.value= newVal; span.innerHTML= newVal;}});// 视图更改 --> 数据变化input.addEventListener('keyup',function(e){ data.text= e.target.value;});
proxy 版本
// 数据const data={ text:'default'};const input= document.getElementById('input');const span= document.getElementById('span');// 数据劫持const handler={set(target, key, value){ target[key]= value;// 数据变化 --> 修改视图 input.value= value; span.innerHTML= value;return value;}};const proxy=newProxy(data, handler);// 视图更改 --> 数据变化input.addEventListener('keyup',function(e){ proxy.text= e.target.value;});
# 35 Array.isArray 实现
Array.myIsArray=function(o){returnObject.prototype.toString.call(Object(o))==='[object Array]';};console.log(Array.myIsArray([]));// true
# 36 对象数组如何去重
根据每个对象的某一个具体属性来进行去重
const responseList=[{ id:1, a:1},{ id:2, a:2},{ id:3, a:3},{ id:1, a:4},];const result= responseList.reduce((acc, cur)=>{const ids= acc.map(item=> item.id);return ids.includes(cur.id)? acc:[...acc, cur];},[]);console.log(result);// -> [ { id: 1, a: 1}, {id: 2, a: 2}, {id: 3, a: 3} ]
# 37 实现一个函数判断数据类型
functiongetType(obj){if(obj===null)returnString(obj);returntypeof obj==='object'?Object.prototype.toString.call(obj).replace('[object ','').replace(']','').toLowerCase():typeof obj;}// 调用getType(null);// -> nullgetType(undefined);// -> undefinedgetType({});// -> objectgetType([]);// -> arraygetType(123);// -> numbergetType(true);// -> booleangetType('123');// -> stringgetType(/123/);// -> regexpgetType(newDate());// -> date
# 38 查找字符串中出现最多的字符和个数
// 例: abbcccddddd -> 字符最多的是d,出现了5次let str="abcabcabcbbccccc";let num=0;let char='';// 使其按照一定的次序排列str= str.split('').sort().join('');// "aaabbbbbcccccccc"// 定义正则表达式let re=/(\w)\1+/g;str.replace(re,($0,$1)=>{if(num< $0.length){ num= $0.length; char= $1;}});console.log(`字符最多的是${char},出现了${num}次`);
# 39 数组去重问题
首先:我知道多少种去重方式
# 双层 for 循环
functiondistinct(arr){for(let i=0, len=arr.length; i<len; i++){for(let j=i+1; j<len; j++){if(arr[i]== arr[j]){ arr.splice(j,1);// splice 会改变数组长度,所以要将数组长度 len 和下标 j 减一 len--; j--;}}}return arr;}
思想: 双重
for
循环是比较笨拙的方法,它实现的原理很简单:先定义一个包含原始数组第一个元素的数组,然后遍历原始数组,将原始数组中的每个元素与新数组中的每个元素进行比对,如果不重复则添加到新数组中,最后返回新数组;因为它的时间复杂度是O(n^2)
,如果数组长度很大,效率会很低
# Array.filter() 加 indexOf/includes
functiondistinct(a, b){let arr= a.concat(b);return arr.filter((item, index)=>{//return arr.indexOf(item) === indexreturn arr.includes(item)})}
思想: 利用
indexOf
检测元素在数组中第一次出现的位置是否和元素现在的位置相等,如果不等则说明该元素是重复元素
# ES6 中的 Set 去重
functiondistinct(array){return Array.from(newSet(array));}
思想: ES6 提供了新的数据结构 Set,Set 结构的一个特性就是成员值都是唯一的,没有重复的值。
# reduce 实现对象数组去重复
var resources=[{ name:"张三", age:"18"},{ name:"张三", age:"19"},{ name:"张三", age:"20"},{ name:"李四", age:"19"},{ name:"王五", age:"20"},{ name:"赵六", age:"21"}]var temp={};resources= resources.reduce((prev, curv)=>{// 如果临时对象中有这个名字,什么都不做if(temp[curv.name]){}else{// 如果临时对象没有就把这个名字加进去,同时把当前的这个对象加入到prev中 temp[curv.name]=true; prev.push(curv);}return prev},[]);console.log("结果", resources);
这种方法是利用高阶函数
reduce
进行去重, 这里只需要注意initialValue
得放一个空数组[],不然没法push