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); 
京公网安备 11010502036488号