前端手写函数

159 阅读8分钟

防抖

/**
 * 防抖:
 * 一段时间内若无事件触发,则执行函数,否则延后执行
 * 定时器版本:
 * 实现原理就是利用定时器,函数第一次执行时设定一个定时器,之后调用时发现已经设定过定时器就清空之前的定时器,并重新设定一个新的定时器;
 * 如果存在没有被清空的定时器,当定时器计时结束后触发函数执行。
 * 参考https://github.com/mqyqingfeng/Blog/issues/22
 */
function debounce1(fn, wait) {
  let timer;
  return function (...args) {
    console.log('trigger event');
    if (timer) {
      clearTimeout(timer);
    }
    timer = setTimeout(() => {
      fn.call(this, ...args);
    }, wait);
  };
}

/**
 * 进阶版:
 * 1. immediate 立即执行参数
 *    immediate 表示第一次是否立即执行,如果是 immediate 的情况下,我们立即执行 fn ,并在 wait 时间内锁住 fn 的执行, wait 时间之后再触发,才会重新执行 fn
 * 2. immediate 为ture时的返回值
 * 3. 取消
 */
function debounce2(fn, wait, immediate = true) {
  let timer;
  let result;
  let debounced = function (...args) {
    console.log('trigger event');
    if (timer) {
      clearTimeout(timer);
    }
    if (immediate && !timer) {
      // 返回值
      result = fn.call(this, ...args);
    }
    timer = setTimeout(() => {
      fn.call(this, ...args);
    }, wait);
    return result;
  };
  // 取消
  debounced.cancel = function () {
    clearTimeout(timeout);
    timeout = null;
  };

  return debounced;
}

// 第一次触发执行一次 fn,后续只有在停止触发 1 秒后才执行函数 fn
let debounced = debounce2(() => {
  console.log('fn call');
}, 1000);

let i = 0;
let timer = setInterval(() => {
  debounced();
  if (++i > 5) {
    clearInterval(timer);
  }
}, 600);

节流

高频事件触发,但在 n 秒内只会执行一次,所以节流会稀释函数的执行频率 github.com/mqyqingfeng…

要点:

  • this、入参
  • 返回值
  • 事件触发后是否会立刻执行,事件停止触发后是否再执行
/*
setTimeout版:不立即执行,事件触发后会再执行。
思路:
返回的函数内,若无定时器,设置定时器timer;
定时器回调后,清空timer,执行函数。
*/
function throttle1(fn, timeout) {
  let timer;
  let previous = 0;

  return function () {
    let context = this;
    let args = arguments;
    if (!timer) {
      timer = setTimeout(function () {
        timer = null;
        fn.apply(context, args);
      }, timeout);
    }
  };
}

function throttle(fn, timeout) {
  let timer = null;
  let last;
  let throttledFn = function (...args) {
    let context = this;
    let now = Date.now();
    let duration = last ? now - last : timeout;
    if (timer) {
      clearTimeout(timer);
      timer = null;
    }
    if (duration >= timeout) {
      last = now;
      fn.call(context, ...args);
    } else {
      timer = setTimeout(() => {
        last = now;
        fn.apply(context, args);
      }, timeout - duration);
    }
  };
  return throttledFn;
}

let i = 0;
let throttled = throttle((...args) => {
  console.log('throttled call', ...args);
}, 1000);

let timer = setInterval(() => {
  if (i >= 20) clearInterval(timer);
  throttled(i++);
}, 100);

手写bind

bind()  函数会创建一个新的绑定函数bound function,BF)。

  • 第一个参数绑定为函数this值
  • 其余参数是预置的初始参数,可以应用在偏函数中
  • 返回值是绑定this后的函数
  • 可以通过new调用,但提供的 this 值会被忽略。new 调用时返回值由new的规则决定,需要注意修改原型指向
Function.prototype._bind=function(self,...args1){
    let fn=this
    let result=function(...args2){
        const isNew=this instanceof  result
        const context=isNew?this:self
        return fn.call(context,...args1,...args2)
    }
    result.prototype=Object.create(fn.prototype)
    return   result
}
// 测试
let obj={name:'cn'}
function getName(...args){
    this.args=args
    console.log('fn call',this,...args)
    return '1'
}
getName.prototype={
   group:1
}
let bindFn=getName._bind(obj,1,2,3)
let res=new bindFn(4,5)
console.log('res',res)

Call

Apply

Promise 简单实现

Promise A+ 规范: promisesaplus.cn/

let PADDING = 'PADDING',
  FULFILLED = 'FULFILLED',
  REJECTED = 'REJECTED';
class Promise {
  static status;
  result;
  resolvedCallbacks = [];
  rejectedCallbacks = [];
  constructor(fn) {
    this.status = PADDING;

    try {
      fn(this.resolve.bind(this), this.reject.bind(this));
    } catch (e) {
      this.reject(e);
    }
  }
  // 当promise处于pending时 promise可以转为fulfilled或rejected状态
  resolve(res) {
    setTimeout(() => {
      if (this.status === PADDING) {
        this.status = FULFILLED;
        this.result = res;
        this.resolvedCallbacks.forEach((fn) => {
          fn(res);
        });
      }
    });
  }
  reject(err) {
    setTimeout(() => {
      if (this.status === PADDING) {
        this.status = REJECTED;
        this.result = err;
        this.rejectedCallbacks.forEach((fn) => {
          fn(err);
        });
      }
    });
  }
  then(onFulfilled, onRejected) {
    return new Promise((resolve, reject) => {
      if (this.status === PADDING) {
        this.resolvedCallbacks.push(onFulfilled);
        this.rejectedCallbacks.push(onRejected);
      } else if (this.status === FULFILLED) {
        setTimeout(() => {
          try {
            this.result = onFulfilled();
          } catch (e) {
            onRejected(e);
          }
        });
      } else if (this.status === REJECTED) {
        setTimeout(() => {
          onRejected(this.result);
        });
      }
    });
  }
}
let p = new Promise((resolve, reject) => {
  console.log('created promise');
  setTimeout(() => {
    resolve('1');
    console.log('setTimeout called');
  });
});
p.then(
  (res) => {
    console.log('then resolved', res);
  },
  (err) => {
    console.log('then rejected', err);
  }
);
console.log('executed');

Promise.finally

Promise.prototype.finally = function (callback) {
  let P = this.constructor;
  return this.then(
    value  => P.resolve(callback()).then(() => value, () => value),
    reason => P.resolve(callback()).then(() => { throw reason }, () => {throw reason})
  );
};

手写 Promise.all

方法返回一个Promise实例,此实例在 promise 都完成(resolved)时回调完成(resolve);如果参数中 promise有一个失败(rejected),此实例回调失败(reject),失败的原因是第一个失败promise的结果。

Promise.all = function(arr) {
  let list = []
  let len = 0
  let hasErr = false
  return new Promise((resolve, reject) => {
    for(let i = 0; i < arr.length; i++) {
      arr[i].then( data=> {
        list[i] = data
        len++
        len === arr.length && resolve(list)
      }, error => {
        !hasErr && reject(error)
        hasErr = true
      })
    }
  })
}

Promise.race

方法返回一个Promise实例,一旦迭代器中的某个 promise 完成(resolved)或失败(rejected),返回的 promise 就会 resolve 或 reject

Promise.race = function(arr) {
  let hasValue = false
  let hasError = false
  return new Promise1((resolve, reject) => {
    for(let i = 0; i < arr.length; i++) {
      arr[i].then(data => {
        !hasValue && !hasError && resolve(data) 
        hasValue = true
      }, error => {
        !hasValue && !hasError &&reject(error)
        hasError = true
      })
    }
  })
}

Promise 任务调度

juejin.cn/post/684490…

class Scheduler {
    constructor() {
        this.tasks = [], // 待运行的任务
        this.runing = [] // 正在运行的任务
        this.limit=2
    }
    // promiseCreator 是一个异步函数,return Promise
    add(promiseCreator) {
      // 返回promsie用于任务等待
      return  new Promise((resolve,reject)=>{
             // 绑定fn和对应的resolve
            let task={fn:promiseCreator,resolve}
            //console.log('runing:',this.runing)
            if (this.runing.length <this.limit) {
                this.run(task)
            } else {
                this.tasks.push(task)
            }
        })
       
    }
    run(task) {
       this.runing.push(task)
       task.fn().then((res)=>{
           task.resolve(res)
           this.runing=this.runing.filter(v=>v!=task)
           if(this.limit<=2 && this.tasks.length){
               this.run(this.tasks.shift())
           }
       })
    }
}
const timeout = (time) => new Promise(resolve => {
    setTimeout(resolve, time)
})
const scheduler = new Scheduler()
const addTask = (time, order) => {
    scheduler.add(() => timeout(time)).then(() => console.log(order,new Date().getTime()))
}
addTask(400, 4)
addTask(200, 2)
addTask(300, 3)
addTask(100, 1)

Redux

function createStore(reducer) {
  // 创建一个 store 对象
  let state; // 状态对象
  let listeners = []; // 监听器

  // 订阅器
  function subscribe(callback) {
    listeners.push(callback); // 每订阅一个,就为监听器添加一个回调函数
  }

  // 更新 state
  function dispatch(action) {
    //  用户编写的reducer,形式为 (state, action) => state 的纯函数。
    state = reducer(state, action); // 更新 state
    listeners.forEach((i) => {
      // 通知所有订阅者
      i();
    });
  }

  // 获取 state
  function getState() {
    return state;
  }

  // 返回 store 对象
  return {
    subscribe,
    dispatch,
    getState
  };
}

参考:

jelly.jd.com/article/61b…

排序

快速排序

算法思想:

  • 在数据集之中,选择一个元素作为"基准"(pivot)。

  • 所有小于"基准"的元素,都移到"基准"的左边;所有大于"基准"的元素,都移到"基准"的右边。

  • 对"基准"左边和右边的两个子集,不断重复第一步和第二步,直到所有子集只剩下一个元素为止。

递归实现:

let quickSort = (arr)=> {
    let mid= arr.splice(Math.floor(arr.length/2),1)[0];
    let left =[],right=[];
    arr.forEach((v,i)=>{
        if(v>mid){
            right.push(v)
        }else{
            left.push(v)
        }
    })
    if(left.length>1) left = quickSort(left)
    if(right.length>1) right = quickSort(right)
    return [...left,mid,...right]
};
quickSort([3,5,0,2,4,8,1,9,7,6,2])

时间复杂度: O(nlogn)

插入排序

算法步骤

将第一待排序序列第一个元素看做一个有序序列,把第二个元素到最后一个元素当成是未排序序列。

从头到尾依次扫描未排序序列,将扫描到的每个元素插入有序序列的适当位置。(如果待插入的元素与有序序列中的某个元素相等,则将待插入元素插入到相等元素的后面。)

function insertionSort(arr) {
    var len = arr.length;
    var preIndex, current;
    for (var i = 1; i < len; i++) {
        preIndex = i - 1;
        current = arr[i];
        while(preIndex >= 0 && arr[preIndex] > current) {
            arr[preIndex+1] = arr[preIndex];
            preIndex--;
        }
        arr[preIndex+1] = current;
    }
    return arr;
}

Array.sort

sort方法基本使用:arr.sort([compareFunction])

如果不传入 compareFunction,则元素按照转换为字符串的各个字符的 Unicode 位点进行排序

如果指明了 compareFunction 参数 ,那么数组会按照调用该函数的返回值排序,即 a 和 b 是两个将要被比较的元素:

  • compareFunction(a, b)< 0,a 会被排列到 b 之前
  • compareFunction(a, b)=== 0,a 和 b 的相对位置不变
  • compareFunction(a, b)> 0,b 会被排列到 a 之前

参考: blog.csdn.net/xuyangxinle… juejin.cn/post/697798…

ParseInt

function _parseInt(str, radix) {
 let str_type = typeof str;
 let res = 0;
 if (str_type !== 'string' && str_type !== 'number') {
  // 如果类型不是 string 或 number 类型返回NaN
  return NaN
 }

 // 字符串处理
 str = String(str).trim().split('.')[0]
 let length = str.length;
 if (!length) {
  // 如果为空则返回 NaN
  return NaN
 }

 if (!radix) {
  // 如果 radix 为0 null undefined
  // 则转化为 10
  radix = 10;
 }
 if (typeof radix !== 'number' || radix < 2 || radix > 36) {
  return NaN
 }

 for (let i = 0; i < length; i++) {
  let arr = str.split('').reverse().join('');
  res += Math.floor(arr[i]) * Math.pow(radix, i)
 }

 return res;
}

大整数相加

function addBig(a, b) {
    // 补齐位数
    if(a.length>b.length) [a,b]=[b,a]
    a=Array(b.length-a.length).fill(0).join('')+a
    
    let sign = 0;//标记 是否进位
    let newVal = [];//用于存储最后的结果
    for(let i = a.length-1;i>=0;i--){
    let val = a[i]*1+b[i]*1+sign;
        if(val>=10){
            sign = 1;
            newVal.unshift(val%10)//这里用unshift而不是push是因为可以省了使用reverse
        }else{
            sign = 0;
            newVal.unshift(val)
        }
    }
    return newVal.join('')
  }

字符串相乘

/**
 * @param {string} num1
 * @param {string} num2
 * @return {string}
 */
const multiply = function(num1, num2) {
    if(num1==0 || num2==0) return "0"
    const res=[];// 结果集
    for(let i=0;i<num1.length;i++){
        let tmp1=num1[num1.length-1-i]; // num1尾元素
        for(let j=0;j<num2.length;j++){
            let tmp2 = num2[num2.length-1-j]; // num2尾元素
            let pos = res[i+j] ? res[i+j]+tmp1*tmp2 : tmp1*tmp2;// 目标值 ==》三元表达式,判断结果集索引位置是否有值
            res[i+j]=pos%10; // 赋值给当前索引位置
            // 目标值是否大于10 ==》是否进位 这样简化res去除不必要的"0"
            if(pos >=10){
                res[i+j+1]=res[i+j+1] ? res[i+j+1]+Math.floor(pos/10) : Math.floor(pos/10)
            }
        }
    }
    return res.reverse().join("");
};

LRC

// https://github.com/sisterAn/JavaScript-Algorithms/issues/7
var LRUCache = function(capacity) {
    this.keys = []
    this.cache = Object.create(null)
    this.capacity = capacity
};

LRUCache.prototype.get = function(key) {
    if(this.cache[key]) {
        // 调整位置
        remove(this.keys, key)
        this.keys.push(key)
        return this.cache[key]
    }
    return -1
};

LRUCache.prototype.put = function(key, value) {
    if(this.cache[key]) {
        // 存在即更新
        this.cache[key] = value
        remove(this.keys, key)
        this.keys.push(key)
    } else {
        // 不存在即加入
        this.keys.push(key)
        this.cache[key] = value
        // 判断缓存是否已超过最大值
        if(this.keys.length > this.capacity) {
            removeCache(this.cache, this.keys, this.keys[0])
        }
    }
};

// 移除 key
function remove(arr, key) {
    if (arr.length) {
        const index = arr.indexOf(key)
        if (index > -1) {
            return arr.splice(index, 1)
        }
    }
}

// 移除缓存中 key
function removeCache(cache, keys, key) {
    cache[key] = null
    remove(keys, key)
}

cloneDeep

// 1.数组处理
// 2.循环引用
// 3.Symbol key
// 4. 破解递归爆栈-BFS
function cloneDeep3(source, hash = new WeakMap()) {

    if (!isObject(source)) return source; 
    if (hash.has(source)) return hash.get(source); // 新增代码,查哈希表
      
    var target = Array.isArray(source) ? [] : {};
    hash.set(source, target); // 新增代码,哈希表设值
 
    // Symbol key
    let symKeys = Object.getOwnPropertySymbols(source); // 查找
    if (symKeys.length) { // 查找成功	
        symKeys.forEach(symKey => {
            if (isObject(source[symKey])) {
                target[symKey] = cloneDeep4(source[symKey], hash); 
            } else {
                target[symKey] = source[symKey];
            }    
        });
    }

    for(var key in source) {
        if (Object.prototype.hasOwnProperty.call(source, key)) {
            if (isObject(source[key])) {
                target[key] = cloneDeep3(source[key], hash); // 新增代码,传入哈希表
            } else {
                target[key] = source[key];
            }
        }
    }
    return target;
}

参考: github.com/yygmind/blo…

获取URL参数

function getQueryString(url,name) {
    let reg = new RegExp("(^|&)" + name + "=([^&]*)(&|$)", "i");
    let r = url.split('?')[1].split('#')[0].match(reg);
    if (r != null) {
        return decodeURIComponent(r[2]);
    };
    return ‘’;
 }
 
 var url = `http://m.meijiabang.cn/index.html?key0=0&key1=1&key2=2#/index`
getQueryString(url,'key2')

柯里化

只传递给函数一部分参数来调用它,让它返回一个函数去处理剩下的参数,如将fn(x,y)转化为fn(x)fn(y)

const curry = (fn, ...args) => 
    // 函数的参数个数可以直接通过函数数的.length属性来访问
    args.length >= fn.length // 这个判断很关键!!!
    // 传入的参数大于等于原始函数fn的参数个数,则直接执行该函数
    ? fn(...args)
    /**
     * 传入的参数小于原始函数fn的参数个数时
     * 则继续对当前函数进行柯里化,返回一个接受所有参数(当前参数和剩余参数) 的函数
    */
    : (..._args) => curry(fn, ...args, ..._args);

function add1(x, y, z) {
    return x + y + z;
}
const add = curry(add1);
console.log(add(1, 2, 3));
console.log(add(1)(2)(3));
console.log(add(1, 2)(3));
console.log(add(1)(2, 3));