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);