手撕前端常用代码

142 阅读3分钟

6ece4aa4-57a4-4f84-8c41-293f77d07ef1.png

作者: 云峰 github: github.com/ihtml5

一、模拟new

function createNew(constructor, ...args) {
   const obj = Object.create(null);
   obj.__proto__ = constructor.prototype;
   const result = constructor.apply(obj, args);
   return typeof result === 'object' && result ? result : obj;
}

实现过程解析

  • 创建一个空对象
  • 将空对象的原型指向构造函数原型上
  • 通过apply方法执行构造函数,并将构造函数中this指向obj,传递参数
  • 如果返回值是对象或者数组,返回该返回值,否则返回obj

二、Object.create实现

Object.create = Object.create || function(target) {
   const fNop = function() {};
   fNop.prototype = target.prototype;
   return new fNop();
}

实现过程解析

  • 检查是否已经有原生的Object.create方法存在,如果存在直接返回
  • 创建一个空函数fNop
  • 将空函数fNop的原型指向target的原型
  • 通过new返回一个基于target的实例

三、bind/apply/call

Function.prototype.bind = Function.prototype.bind || function(context, ...args) {
    if (typeof this !== 'function') {
       throw new Error('Function.prototype.bind is not Function');
    }
    const self = this;
    const fNop = function() {};
    const fBound = function(...rest) {
      return self.apply(this instanceof fBound ? this : context, [...args,...rest]);
    };
    if (this.prototype) {
       fNop.prototype = this.prototype;
    }
    fBound.prototype = new fNop();
    
    return fBound;
    
}
Function.prototype.call = Function.prototype.call || function(context, ...args) {
    if (typeof this !== 'function') {
       throw new Error('Function.prototype.call is not Function');
    }
    const ctx = context || window;
    ctx.fn = this;
    const result = ctx.fn(...args);
    delete ctx.fn;
    return result;
}
Function.prototype.apply = Function.prototype.apply || function(context, ...args) {
    if (typeof this !== 'function') {
       throw new Error('Function.prototype.apply is not Function');
    }
    const ctx = context || window;
    ctx.fn = this;
    let result;
    if (args.length > 0) {
      result = ctx.fn(...args[0]);
    } else {
      result = ctx.fn();
    }
    delete ctx.fn;
    return result;
}

四、debounce

防抖的原理就是:频繁触发事件,如果在n秒内继续有事件触发,则会以新触发的时间点为起点,延迟到n秒后执行,常用于搜索关键词查找。总之,就是要等你触发事件,n秒内不再触发事件。类比电梯,如果在n秒内,如果有人一直进来,就会一直等。

function debounce(fn, wait, immediate) {
  let timeout;
  let result;
  function debounced(...args) {
    const context = this;
    if (timeout) {
      clearTimeout(timeout);
    }
    if (immediate) {
      const callNow = !timeout;
      timeout = setTimeout(() => {
        timeout = null;
      }, wait);
      if (callNow) {
        result = fn.apply(context, args);
      }
      return result;
    } else {
      timeout = setTimeout(() => {
        fn.apply(context, args);
      }, wait);
    }
  } 
  debounced.cancel = function() {
     clearTimeout(timeout);
     timeout = null;
  }
  return debounced;
}

五、throttle

节流的原理就是:每隔n秒触发一次,n秒内如果有频繁事件进入,不触发

function throttle(fn, wait, options = {}) {
   let timeout;
   let context;
   let args;
   let previous = 0;
   function later() {
     previous = options.leading === false ? 0 : new Date.getTime();
     timeout = null;
     fn.apply(context, args);
     if (!timeout) {
       context = null;
       args = null;
     }
   }
   function throttled(...rest) {
      context = this;
      args = [...rest];
      const now = new Date().getTime();
      if (!previous && options.leading === false) {
        previous = now;
      }
      const remaing = wait - (now - previous);
      
      if (remaing <=0 && remaing > wait) {
        if (timeout) {
          clearTimeout(timeout);
        }
        fn.apply(context, args);
        if (!timeout) {
           context = null;
           args = null;
        }
      } else if (!timeout && options.trailing !== false) {
        timeout = setTimout(later, remaing);
      }
   }
   throttled.cancel = function() {
     clearTimeout(timeout);
     timeout = null;
     previous = 0;
   }
}

六、compose

1.同步版本

const compose = (...fns) => (value) => {
  let len = fns.length;
  while (len > 0 ) {
    const fn = fns.pop();
    value = fn(value);
    len--;
  }
  return value;
}

2.异步版本

// promise
const compose = (...fns) => (...args) => {
  const initFn = args.pop();
  args.reverse().reduce((prev, cur) => {
    return prev.then((result) => {
      return cur.call(null, result);
    });
  }, Promise.resolve(initFn.apply(null, args));
}

七、flatten

const flatten = (array, maxDepth = 3) => {
  if (maxDepth === 0) {
    return array;
  }
  return array.reduce((a,b) => a.concat(Array.isArray(b)
  ? flatten(b, maxDepth - 1) : b), []);
}

flatten([1, [2, 3, [5, 6, [7,8]]]], 3); // [1, 2, 3, 5, 6, 7, 8]

八、reduce

Array.prototype.reduce = Array.prototype.reduce || function(fn, initialVal) {
   if (!Array.isArray(this)) {
     throw new Error('Array.prototype.reduce is used for array');
   }
   const len = this.length;
   const isUsedInitailVal = typeof initialVal !== 'undefined';
   let base = isUsedInitailVal ? initialVal : this[0];
   const start = isUsedInitailVal ? 0 : 1;
   
   for (let i = start; i < len; i++) {
      base = fn(base, this[i]);
   }
   
   return base;
}

九、并发控制

常用于请求和逻辑调度

class Scheduler {
  constructor(maxLimit = 2) {
     this.maxLimit = maxLimit; // 并发控制
     this.runingTask = 0; // 正在执行中的任务数
     this.tasks = []; // 当前队列中的任务
     this.cache = new Map();// 缓存promise回调
  }
  
  add(promiseCreator) {
     this.tasks.push(promiseCreator); // 追加到队列尾部
     return new Promise((resolve, reject) => {
        this.cache.set(promiseCreator, resolve); // 缓存resove等真正执行的时候再resovle
        this.run(); // 执行任务队列
     }).catch((error) => console.error(`add promiseCreator ${error}`));
  }
  
  run() {
    // 当任务队列不为空,且当前执行队列数不超过最大数据时,依次从队列中取出
    while (this.runingTask < this.maxLimit && this.tasks.length > 0) {
      const fn = this.tasks.shift(); // 从队列头部取出执行任务
      this.runingTask++; // 当前执行任务加一
      fn().then(() => {
        this.runingTask--; // 当前执行任务减一
        if (this.cache.has(fn)) { // 判断当前执行任务是否有回调
           const resolve = this.cache.get(fn); 
           resolve(); // 执行当前回调函数
           this.cache.delete(fn); // 从缓存中删除已经执行过的会调
        }
        this.run(); // 继续轮训
      });
    }
  }
}

测试用例

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