前端复习企划14-JS功能与原理实现
JS功能实现
数组去重的方法
双重for循环
外层遍历,内层比较。
function distinct(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); //数组长度变了 len --; j--; } } } }
Array.filter()+indexOf()
合并为一个数组,用fliter遍历数组,结合indexOf排除重复项。
function distinct(arr){ return arr.filter((item,index)=>arr.indexOf(item)===index) }
for...of+includes()
双重for的升级版,外层用for...of替换,内层循环改用includes。
创建一个新数组,如果includes返回false,将该元素push进去。
function distinct(arr){ let result=[]; for(let i of arr){ !result.includes(i)&&result.push(i); } return result; }
Array.sort()+相邻元素比较
先排序,然后比较相邻元素,从而排除重复项。
function distinct(arr){ arr.sort(); let result=[arr[0]]; for(let i=1,len=arr.length;i<len;i++){ arr[i] !==arr[i-1]&&result.push(arr[i]); } return result; }
new Set()
function distinct(arr){ return Array.from(new Set([...arr])); }
for...of+Object对象属性不重复
function distinct(arr){ let result=[]; let obj={}; for(let i of arr){ if(!obj[i]){ result.push(i); obj[i]=1; } } return result; }
数组扁平化
先解释一下意思:就是[1,[2,3,[4,5]]]这样的多维数组,变为一维数组[1,2,3,4,5]
reduce实现
遍历每一项,若值为数组则递归遍历,否则concat
function flatten(arr){ return arr.reduce((result.item)=>{ return result.concat(Array.isArray(item)? flatten(item):item); },[]) }
toString()和split
调用数组的toString()方法,将数组变为字符串,然后再用split分割还原为数组。
split分割后形成的数组每一项值为字符串,所以需要用一个map方法遍历数组将其每一项换为数值型
function flatten(arr){ return arr.toString().split(',').map(function(item){ return Number(item); }) }
join&split
也是将数组转换为字符串
function flatten(arr){ return arr.join(',').split(',').map(function(item){ return parseInt(item); }) }
递归
function flatten(arr){ var res=[]; arr.map(item=>{ if(Array.isArray(item)){ res=res.concat(flatten(item)); }else{ res.push(item); } }); return res; }
扩展运算符
function flatten(arr){ while(arr.some(item=>Array.isArray(item))){ arr=[].concat(...arr); } return arr; }
异步操作对比--实现获取用户信息
先写一个获取用户的方法
function fetchUser() { return new Promise((resolve, reject) => { fetch('https://api.github.com/users/yiichitty') .then((data) => { resolve(data.json()); }, (error) => { reject(error); }); }); }
使用Promise
function getUserByPromise() { fetchUser() .then((data) => { console.log(data); }, (error) => { console.log(error); }) }
Promise 的方式虽然解决了回调地狱的问题,但是如果处理流程复杂的话,整段代码将充满 then()。
它的语义化不明显,代码流程不能很好的表示执行流程。
使用Generator
function* fetchUserByGenerator() { const user = yield fetchUser(); return user; } const g = fetchUserByGenerator(); const result = g.next().value; result.then((v) => { console.log(v); }, (error) => { console.log(error); })
Generator 的方式解决了 Promise 的一些问题,流程更加直观、语义化。但是 Generator 的问题在于,函数的执行需要依靠执行器,每次都需要通过next()的方式去执行。
使用async+await
async function getUserByAsync(){ let user = await fetchUser(); return user; } getUserByAsync() .then(v => console.log(v));
async函数完美的解决了上面两种方式的问题。流程清晰,直观、语义明显。
操作异步流程就如同操作同步流程。同时async函数自带执行器,执行的时候无需手动加载。
数组去重通用API
要求就是写一个数组去重的API,第一个参数是一个数组,第二个参数是一个函数(对象数组去重的规则)。
思路就是当它是数组的时候,直接用这个Set数据结构去重,如果是个对象数据的话,就新建一个Map,按照传入的函数返回的值,存入Map。这里用到了filter,它是用传入的函数测试所有的元素,并且返回所有通过测试的元素。
function fn(arr,rule){ if(!rule){ return Array.from(new Set([...arr])); }else{ const res=new Map(); return arr.filter((a)=>!res.has(rule(a))&&res.set(rule(a),1)); } }
对象数组按规则排序
有一个数组,里面都是对象,现在要针对对象中的某一个key进行排序,顺序是已给定的数组。
比如原数组为[{a:'ww'},{a:'ff'},{a:'pe'}],
顺序是[{ww:1},{pe:3},{hf:2},{oo:4},{ff:5}]
那么输出是 [{a:'ww'},{a:'pe'},{a:'ff'}]
var objarr=[{a:'ww'},{a:'ff'},{a:'pe'}]; var rulearr=[{ww:1},{pe:3},{hf:2},{oo:4},{ff:5}]; //暴力解决 function sortByRule(objarr,key,rulearr){ let ResultArr=[]; rulearr.forEach(item=>{ //获取当前的value和规则的位次 let value=Object.getOwnPropertyNames(item)[0]; let order=item[value]; //找到对应的obj放入对应位次的位置 ResultArr[order]=objarr.find(item=>item[key]===value); }); //去掉那些为空的 return ResultArr.filter(item=>item); } //用sort方法 function sortByRule(objarr,key,rulearr){ //把rulearr处理成一个对象{ww:1,pe:3……} let rule={} rulearr.forEach(item=>rule={...rule,...item}); //对objarr排序 return objarr.sort((a,b)=>{ //取到“a” const akey=Object.keys(a)[0]; const bkey=Object.keys(b)[0]; //按升序排 return rule[a[akey]]-rule[b[bkey]]; } return 0; }) }
实现防抖
//实现按钮防2次点击操作 //在规定时间内再次触发就清除定时后重新设置,直到不触发了 function debounce(fn,delay){ let timer=0; return function(...args){ if (timer) clearTimeout(timer) timer=setTimeout(()=>{func.apply(this,args)},delay); } } function fn(){ console.log('防抖') } addEventListener('scroll',debounce(fn,1000))
实现节流
//节流就是说在一定时间内只会被触发一次 //比如滚动事件啥的 function throttle(fn,delay){ let last;//上次被触发的时间 return function(...args){ let now=+new Date(); if(!last||now>last+delay){ last=now; func.apply(this,args); } } } //使用 function fn(){ console.log('节流'); } addEventListener('scroll',throttle(fn,1000));
实现懒加载
//首先html的img标签设置一个无关的标签比如说data,加载到的时候再替换成src //思路就是到视口区了再替换过去加载 let img=document.querySelectorAll('img'); //可视区大小 let clientHeight=window.innerHeight||document.documentElement.clientHeight||document.body.clientHeight; function lazyload(){ //滚动卷走的高度 let scrollTop=window.pageYOffset||document.documentElement.scrollTop||document.body.scrollTop; for(let i=0;i<imgs.length;i++){ //在可视区冒出的高度 let x=clientHeight+scrollTop-imgs[i].offsetTop; if(x>0&&x<clientHeight+imgs[i].height){ img[i].src=img[i].getAttribute('data'); } } } //addEventListener('scroll',lazyload) //setInterval(lazyload,1000)
hash路由
//hash路由 class Route{ constructor(){ //存储对象 this.routes=[]; //当前hash this.currentHash='' //绑定this.避免监听时this指向改变 this.freshRoute=this.freshRoute.bind(this); //监听 window.addEventListener('load',this.freshRoute,false); window.addEventListener('hashmessage',this.freshRoute,false); } //存储 storeRoute(path,cb){ this.routes[path]=cb||function(){}; } //更新 freshRoute(){ this.currentHash=location.hash.slice(1)||'/' this.routes[this.currentHash](); } }
实现元素拖拽
window.onload=function(){ //drag目标是绝对定位状态 var drag=document.getElementById('box'); drag.onmousedown=function(e){ e=e||window.event; //鼠标与拖拽元素的距离=鼠标与可视区边界的距离-拖拽元素与可视区的距离 let diffX=e.clientX-drag.offsetLeft; let diffY=e.clientY-drag.offsetTop; drag.onmousemove=function(e){ e=e||window.event; //拖拽元素的移动距离=鼠标当前与可视区边界的距离-鼠标与拖拽元素的距离 let left=e.clientX-diffX; let top=e.clientY-diffY; //避免拖出可视区外 if(left<0){ left=0;} else if(left>window.innerWidth-drag.offsetWidth){ //超出了就放在innerWidth的位置 left=window.innerWidth-drag.offsetWidth; } if(top<0) top=0; else if (top>window.innerHeight-drag.offsetHeight){ top=window,innerHeight-drag.offsetHeight; } drag.style.left=left+'px'; drag.style.top=top+'px'; } drag.onmouseup=function(e){ e=e||window.event; this.onmousemove=null; this.onmousedown=null; } } }
构建一个Service Worker
//比如在index.js里注册一个Service Worker if (navigator.serviceWorker){ navigator.serviceWorker.register('xx.js').then( function (registration){ console.log ('注册成功') }).catch(function(e){ console.log('注册失败') }) } //xx.js //监听install事件,缓存所需要的文件 self.addEventListener('install',e=>{ e.wiatUntil( caches.open('my-cache').then(function(cache){ return cache.addAll(['./index.html','./index.js']) }) ) }) //拦截请求 //如果缓存中已经有数据就直接用缓存,否则去请求数据 self.addEventListener('fetch',e=>{ e.respondWith( cache.match(e.request).then(function(response){ if(response){ return response; } console.log('fetch source'); }) ) })
JS原理
使用setTimeout模拟setInterval
//使用setTimeout模拟setInterval //避免因执行时间导致间隔执行时间不一致 setTimeout(function(){ //do something //arguments.callee引用该函数体内当前正在执行的函数 setTimeout(arguments.callee,500); },500)
实现call
////传入一个this 绑定上所有的属性 Function.prototype.myCall=function(context){ if(typeof this !=='function'){ throw new TypeError('error'); } context=context||window; context.fn=this; //除去要绑定的对象,剩下参数应该绑定进去 const args=[...arguments].slice(1); const result=context.fn(...args); delete context.fn; return result; }
实现Apply
//与call的区别是,第二个第二个参数传入的是数组 Function.prototype.apply()=function(context){ if(typeof this !=='function'){ throw new TypeError('error'); } context=context||window; context.fn=this; let result; //判断是否存在数组参数,毕竟是可选参数 if(arguments[1]){ result=context.fn(...arguments[1]); }else{ result=context.fn(); } delete context.fn; return result; }
实现Bind
Function.prototype.myBind=function(context){ if (typeof this !== 'function') { throw new TypeError('error'); } const _this=this; const args=[...arguments].slice(1); return function F(){ // new ,不动this if (this instanceof F){ //链式调用要加上新旧参数 return new _this(...args,...arguments); } return _this.apply(context,args.concat(...arguments)); } }
实现Object.create()
//Object.create()方法创建一个新对象,使用现有的对象来提供新创建的对象的__proto__。 Object.prototype.mycreate=function(obj){ function F(){} F.prototype=obj; return new F(); } //其实也可以这么写 Object.prototype.mycreate=function(obj){ return {'_protp_':obj}; }
实现new
//使用new时:1 内部生成一个obj 2 链接到原型 3 obj绑定this(使用构造函数的this) 4 返回新对象(原始值的话忽略,如果是对象的话就返回这个对象) Function.prototype.myNew()=function(func,...args){ let obj={}; obj._proto_=func.prototype; let result=func.apply(obj,args); return result instanceof Object? result:obj; } //可以用Object.create //以构造函数的原型对象为原型,创建一个空对象;等价于 创建一个新的对象,把他的_proto_指向prototype Function.prototype.myNew()=function(func,...args){ let obj=Object.create(func.prototype); let result=func.apply(obj,args); return result instanceof Object? result:obj; }
Instanceof
//本质是看左边变量的原型链是否含有右边的原型 function myInstanceof(left,right){ //要找的原型 let prototype=right.prototype; left=left._proto_; while(true){ //全部遍历完都没有 false if(left===null||left==="undefined") return false; //匹配上 true if(left===prototype) return true; //找下一个原型链 left=left._proto_; } }
深拷贝
//JSON.parse 解决不了循环引用的问题,会忽略undefined\symbol\函数 let a={} let b=JSON.parse(JSON.stringify(a)); //循环引用是说a.b.d=a.b这样的 //MessageChannel含有内置类型,不包含函数 function structuralClone(obj){ return new Promise(resolve=>{ const{port1,port2}=new MessageChannel(); port2.onmessage=ev=>{resolve(ev.data)} port1.postMessage(obj); }) } //可以处理循环引用对象、也可以处理undefined //不过它是异步的 const test =async()=>{ const clone=await structuralClone(obj); console.log(clone); } test(); //手写一个简单的deepclone function deepClone(obj){ function isObject(o){ return (typeof o==='object'||typeof o==='function')&&o !==null; } if(!isObject(obj)){ throw new Error('Not Object') } let isArray=Array.isArray(obj); let newObj=isArray?[...obj]:{...obj}; Reflect.ownKeys(newObj).forEach(item=>{ newObj[item]=isObject(obj[item])?deepClone(obj[item]):obj[item]; }) return newObj; } //还可以这样写 function deepClone(){ let copy=obj instanceof Array? []:{}; for(let i in obj){ if(obj.hasOwnProperty(i)){ copy[i]=typeof obj[i] ==='object'?deepClone(obj[i]):obj[i]; } } return copy; }
实现Array.map()
Array.prototype.newMap = function(fn) { var newArr = []; for(var i = 0; i<this.length; i++){ newArr.push(fn(this[i],i,this)) } return newArr; }
实现Array.reduce()
Array.prototype.MyReduce = function(fn , prev) { for(let i = 0; i<this.length; i++) { if (typeof prev === 'undefined') { // prev不存在 prev = fn(this[i], this[i+1], i+1, this); i++; } else { prev = fn(prev, this[i], i, this); } } return prev;
原生实现Ajax
//简单流程 //实例化 let xhr=new XMLHttpRequest(); //初始化 xhr.open(methond,url,async); //发送请求 xhr.onreadystatechange=()=>{ if(xhr.readyState===4&&xhr.status===200){ console.log(xhr.responseText); } } //有Promise的实现 function ajax(options){ //地址 const url=options.url; //请求方法 const method=options.methond.toLocalLowerCase()||'get'; //默认为异步true const async=options.async; //请求参数 const data=options.data; //实例化 let xhr=new XMLHttpRequest(); //超时时间 if(options.timeout&&options.timeout>0){ xhr.timeout=options.timeout; } return new Promise((resolve,reject)=>{ xhr.ontimeout=()=>reject&&reject('请求超时'); //状态变化回调 xhr.onreadystatechange=()=>{ if(xhr.readyState==4){ if(xhr.status>=200&&xhr.status<300||xhr.status==304){ resolve && resolve(xhr.responseText) }else{ reject&&reject(); } } } //错误回调 xhr.onerr=err=>reject&&reject(); let paramArr=[]; let encodeData; //处理请求参数 if(data instanceof Object){ for(let key in data){ paramArr.push(encodeURIComponent(key)+"="+encodeURIComponent(data[key])); } encodeData=paramArr.join('&'); } //get请求拼接参数 if(method==='get'){ //检查url中有没有?以及它的位置 const index=url.indexOf('?'); if(index===-1) url+='?'; else if (index !==url.length -1) url+='&'; url += encodeData; } //初始化 xhr.open(method,url,async); if(method ==='get') xhr.send(encodeData); else{ //post设置请求头 xhr.setRequestHeader('Content-Type','application/x-www-form-unlencoded;charset=UTF-8'); xjr.send(encodeData); } }) }
手写实现一个Promise
//n个promise的数组,按要执行的顺序排好 var promiseArr=[new Promise(()=>{console.log('1')}), new Promise(()=>{console.log('2')}), new Promise(()=>{console.log('3')}), new Promise(()=>{console.log('4')}) ]; function* donebyOrder(arr){ let len=arr.length; for(let i=0;i<len;i++){ yield arr[i]; } } let gen=donebyOrder(promiseArr) for(let i=1;i<promiseArr.length;i++){ gen.next(); }
实现Promise.all
//Promise.all //多个Promise任务并行执行,输入是一个数组,最后输出一个新的Promise对象 //1.如果全部成功执行,则以数组的方式返回所有Promise任务的执行结果; //2.如果有一个Promise任务rejected,则只返回 rejected 任务的结果。 function promiseAll(promises){ return new Promise(resolve,reject)=>{ if(!Array.isArray(promises)){ return reject(new Error("arguments must be an array")) } let promisecounter=0, promiseNum=promises.length, //保存结果 resolvedValues=new Array(promisNum); for(let i =0;i<promiseNum;i++){ (function(i){ Promise.resolve(promises[i]).then((value)=>{ promiscounter++; resolvedValues[i]=value; if(promiseCounter==promiseNum) { return resolve(resolvedValues); } }).catch(err=>{reject(err)}) })(i) } }
rem的实现原理
function setRem(){ let doc=document.documentElement; let width=doc.getBoundingClientRect().width; let rem=width/75 doc.style.fontsize=rem+'px'; } addEventListener("resize",setRem);
双向数据绑定实现原理
//双向数据绑定 let obj={}; let input=document.getElementById('input'); let span=document.getElementById('span'); //数据劫持 Object.defineProperty(obj,'text',{ configurable:true, enumerable:true, get(){ console.log('获取数据'); }, set(newVal){ console.log('数据更新'); input.value=newVal; span.innerHTML=newVal; } }) //监听 input.addEventListener('keyup',function(e){ obj.text=e.target.value; })
EventBus组件通信原理
//组件通信 //一个触发 监听的过程 class EventEmitter{ constructor(){ //存储事件 this.events=this.events||new Map(); } addListener(type,fn){ if(!this.events.get(type)){ this.events.set(type,fn); } } emit(type){ let handler=this.events.get(type); handler.apply(this,[...arguments].slice(1)) } } //测试 let emitter=new EventEmitter(); emitter.addListener('ages',age=>{console.log(age)}) emitter.emit('ages',24);