前端JS常见手写

103 阅读3分钟

防抖和节流

防抖

function debounce(fn,delay) {
    let timer = null;
    return (...args)=>{
        clearTimeout(timer);
        timer = setTimeout(() => {
            //改正this,event
            fn.apply(this,args)
        }, delay);
    }
}

节流

//时间戳
const throttle = (fn, delay) => {
    let startTime = Date.now();
    return (...args)=>{
        let curTime = Date.now();
        if(curTime-startTime>delay){
            fn.apply(this,args);
            startTime = curTime;
        }
    }
}
//定时器
const throttle = (fn, delay) => {
    let timer = null;
    return (...args)=>{
        if(!timer){
            timer=setTimeout(()=>{
                fn.apply(this,args);
                timer=null
            },delay)
        }
    }
}

图片懒加载

我们会把没有出现在视窗内的图片,用一张占位符图片来代替。 <img src='./loading.png' data-src='./真正的图片地址'>

const images = document.querySelectorAll("img");
const observer = new IntersectionObserver((images, observer) => {
  entries.forEach((entry) => {
    if (entry.isIntersecting) {//出现在视口内
      entry.target.src = entry.target.dataset.src;
      observer.unobserve(entry.target);
    }
  });
});
images.forEach((image) => {
  observer.observe(image);
});

数组

快排

const quickSort = (arr) => {
    if(arr.length<2) return arr;
    let mid = Math.floor(arr.length/2);
    let val = arr.splice(mid,1);
    let left=[],right=[];
    for(let i of arr){
        if(i<val){
            left.push(i);
        }else{
            right.push(i);
        }
    };
    return quickSort(left).concat(val).concat(quickSort(right))
}

数组扁平化

const flatten = (arr) => {
    return arr.reduce((pre,cur)=>{
        return pre.concat(Array.isArray(cur)?flatten(cur):cur)
    },[])
}

数组去重

const unique = arr => [...new Set(arr)]

object的键名必须是string类型或者symbol,map则都可

function unique(arr){
    return arr.filter((item,index,arr)=>{
        return arr.indexOf(item)==index
    })
}

reduce

//不能使用箭头函数
Array.prototype.myreduce = function(fn, initial){
    const arr = this;
    let total = initial || arr[0];
    for (let index = initial ? 0 : 1; index < arr.length; index++) {
        total = fn(total, arr[index], index, arr);
    };
    return total;
};

forEach

遍历需要.call

Array.prototype._forEach = function(fn, thisArg){
    const arr = this;
    for(let i = 0;i < arr.length;i++){
        fn.call(thisArg,arr[i],i,arr);
    }
};

map

Array.prototype._map = function(fn, thisArg){
    const arr = this;
    let res = [];
    for(let i = 0;i < arr.length;i++){
        res[i]=fn.call(thisArg,arr[i],i,arr);
    };
    return res;
};

filter

Array.prototype._Filter = function (fn, thisArg) {
  if (typeof fn !== 'function') {
      throw new Error(`${fn} 不是一个函数`)
  }
  const arr = this
  const filterArr = [] // 没有符合条件的返回空数组
  for (let i = 0; i < arr.length; i++) {
      const res = fn.call(thisArg, arr[i], i, arr)
      if (res) {
          filterArr.push(arr[i])
      }
  }
  return filterArr
}

函数

call

Function.prototype.myCall = function(context,...args){
    context = context || window;//没有输入就是window
    let fn = Symbol('fn');//确保fn唯一性,防止属性覆盖
    context[fn] = this;//绑定this
    const res = context[fn](...args);
    delete context[fn];//删除fn防止污染
    return res;//调用函数
}

apply

Function.prototype.myApply=function(context,args){//这里不一样
    context = context || window;
    let fn = Symbol('fn');
    context[fn] = this;
    let res = context[fn](...args);
    delete context[fn];
    return res;
}

bind

返回一个新的函数,但是不会立即执行该函数

Function.prototype._bind = function (context, ...args) {
    if (typeof this !== 'function') {
        throw new Error("Type Error");
    }
    // 保存this的值
    const _this = this;
    return function F() {
        // 考虑new的情况
        if (this instanceof F) {
            return new _this(...args, ...arguments)
        }
        return _this.apply(context, [...args, ...arguments])
    }
}

函数柯里化

function add(a, b, c) {
            return a + b + c
        }
function curry(fn) {
    let judge = (...args) => {
        if(args.length == fn.length) return fn(...args);
        return (...arg)=>judge(...args,...arg)
    };
    return judge
}
let addCurry = curry(add)
const res1 = addCurry(1, 2)(3)
const res2 = addCurry(1)(2)(3)

instanceOf

const _instanceOf = (left, right) => {
    let proto = left.__proto__;
    let prototype = right.prototype;
    while (true) {
        if (proto == null) return false;
        if (proto === prototype) return true;
        proto = proto.__proto__;
    }
}

发布与订阅

        class Observer{
            constructor(){
                this.message = {}
            }
            $on(type,callback){
                if(!this.message[type]){
                    this.message[type] = [callback];
                }else{
                    this.message[type].push(callback)
                }
            }
            $off(type,callback){
                if(!this.message[type]) return;
                if(!callback){
                    this.message[type] = undefined;
                    return;
                };
                this.message[type] = this.message[type].filter(item=>item !== callback);
            };
            $emit(type){
                if(!this.message[type]) return;
                this.message[type].forEach(item => {
                    item()
                });
            }
        }

深拷贝和浅拷贝

浅拷贝

  1. Object.assign()
  2. 展开运算符...
  3. Array.prototype.slice()

深拷贝

  1. JSON.parse(JSON.stringify(demo))

    有缺点:会忽略undefined/Symbol/function,不能处理正则、new Date(),循环引用对象

  2. 手撕

function myclone(target,hash = new WeakMap()) {
    if(typeof(target)!=='object'||target==null){
        return target;
    };
    if(hash.has(target)) return hash.get(target);

    let res = Array.isArray(target)?[]:{}
    hash.set(target,res)
    for(let key in target){
        res[key]=myclone(target[key],hash)
    };
    return res
}

promise

promise.resolve和reject

Promise.resolve(value)可以将任何值转成值为value&状态是resolve的Promise,但如果传入的Value是promise,则会直接返回。

Promise._resolve = (value)=>{
    if(value instanceof Promise){
        return value
    };
    return new Promise(resolve=>resolve(value))
};

Promise.reject(value)也会实例化一个reject状态的promise对象,但是与resolve不同的是,如果给reject传入一个promise对象,这个对象会成为新的Promise的值。

Promise._reject = (value)=>{
    return new Promise((resolve,reject)=>reject(value))
};

promise.all

接收一组异步任务,并行执行异步任务,并且在所有异步操作执行完后才执行回调。使用场景:素材较多的应用,打开网页时,预先加载需要用到的各种资源,所有的都加载完成后在进行页面的初始化。Promise.all().then结果中数组的顺序和接收到数组顺序一致。

  • 传入的所有 Promsie 都是 resolve,则返回由他们的值组成的,状态为 fulfilled 的新 Promise;
  • 只要有一个 Promise 是 rejected,则返回 rejected 状态的新 Promsie,且它的值是第一个 rejected 的 Promise 的值;
  • 只要有一个 Promise 是 pending,则返回一个 pending 状态的新 Promise;
Promise._all=(promises)=>{
    //返回一个promise的变量
    return new Promise((resolve, reject) => {
        // 该方法的参数需为一个可迭代对象
        if (promises == null || typeof promises[Symbol.iterator] !== 'function') {
            throw new error(`${promises} is not a iterable`)
        }
        //声明变量
        let count = 0;
        let arr = [];
        //遍历
        promises.forEach((item,index)=>{
            Promise.resolve(item).then((res)=>{
                count++;
                arr[index] = res;
                if(count===promises.length) resolve(arr)
            }).catch(reject)
        })
    })
}

promise.race

接收一组异步任务,并行执行异步任务,只保留第一个执行完成的异步操作结果,其他方法仍在执行,不过结果会被抛弃。all和race传入的数组中如果有抛出异常的任务,只有最先抛出的错误会被(.then的第二个参数或者catch)捕获,但并不会影响数组中其他的异步任务的执行。

Promise._race=(promises)=>{
    return new Promise((resolve,reject)=>{
        Promise.forEach((item)=>{
            Promise.resolve(item).then(res=>{
                resolve(res)
            }).catch(reject)
        })
    })
}

promise.allSettled

所有的promise状态都变化了,则返回一个状态是fulfilled的promise,值是一个数组(按照输入顺序)。如果有一个是pending的promise,则返回一个状态是pending的实例。

Promise.MyAllSettled = function (promises) {
  let arr = [],count = 0;
  return new Promise((resolve, reject) => {
  
    const processResult = (res, index, status) => {
      arr[index] = { status: status, val: res }
      count += 1
      if (count === promises.length) resolve(arr)
    }

    promises.forEach((item, i) => {
      Promise.resolve(item).then(res => {
        processResult(res, i, 'fulfilled')
      }, err => {
        processResult(err, i, 'rejected')
      })
    })
  })
}

列表转树

let arr = [
    { id: 1, name: '部门1', pid: 0 },
    { id: 2, name: '部门2', pid: 1 },
    { id: 3, name: '部门3', pid: 1 },
    { id: 4, name: '部门4', pid: 3 },
    { id: 5, name: '部门5', pid: 4 },
    { id: 6, name: '部门6', pid: 0 },
]
function get_tree(arr) {
    const list = []
  
    arr.forEach(element => {
      const chiildren_arr = arr.filter(ele => {
        return element.id === ele.pid
      })
  
      if (chiildren_arr.length > 0) {
        element.chiildren = chiildren_arr
      }
  
      if (element.pid === 0) {
        list.push(element)
      }
    });

树转列表

const data = [
    {
        id: 1,
        text: '节点1',
        parentId: 0,
        children: [
            {
                id: 2,
                text: '节点1_1',
                parentId: 1
            }
        ]
    }
]
function treeToList(data) {
    let res = [];
    const dfs = (tree) => {
        tree.forEach((item) => {
            if (item.children) {
                dfs(item.children);
                delete item.children;
            }
            res.push(item);
        });
    };
    dfs(data);
    return res;
}