面向面试编程:你要的手写原理大全,这里全都有了

626 阅读5分钟

手写基础原理

手写call

Function.prototype.myCall = function (context) {
  if (typeof this !== "function") {
    throw new Error("类型错误");
  }

  const args = [...arguments].slice(1);

  const currentContext = context || window;

  const fn = Symbol();

  currentContext[fn] = this;

  const res = currentContext[fn](...args);

  Reflect.deleteProperty(currentContext, fn);
  return res;
};

手写apply

Function.prototype.myApply = function (context) {
  if (typeof this !== "function") {
    throw new Error("类型错误");
  }

  const currentContext = context || window;

  const fn = Symbol();

  currentContext[fn] = this;

  let res = null;
  if (arguments[1]) {
    res = currentContext[fn](...arguments[1]);
  } else {
    res = currentContext[fn]();
  }

  Reflect.deleteProperty(currentContext, fn);

  return res;
};

手写bind

Function.prototype.myBind = function (context) {
  if (typeof this !== "function") {
    throw new Error("类型错误");
  }

  const self = this;
  const args = [].slice.apply(arguments, 1);

  const fBind = function () {
    const currentArgs = [].slice.apply(arguments);

    return self.apply(
      this instanceof fBind ? this : context,
      arg.concat(currentArgs)
    );
  };

  fBind.prototype = Object.create(this.prototype);

  return fBind;
};

柯里化函数实现

function curry(fn) {
  if (fn.length <= 1) return fn;
  const generator = (...args) => {
    if (fn.length === args.length) {
      return fn(...args);
    } else {
      return (...args2) => {
        return generator(...args, ...args2);
      };
    }
  };
  return generator;
}

简易版防抖函数

function simpleDebounce(func, wait) {
  let timer = null;
  return function (...args) {
    if (timer) {
      clearTimeout(timer);
    }
    timer = setTimeout(() => {
      func.apply(this, args);
    }, wait);
  };
}

简易版节流函数

function simpleThrottle(func, wait) {
  let canRun = true;
  return function () {
    if (!canRun) return; // 标志不为true则直接返回
    canRun = false; // 修改标志
    setTimeout(() => {
      func.apply(this, arguments);
      canRun = true; // 当完成执行的时候,将标志置为true,表示可以进行下一次执行了,如果没有被执行,则会被直接return,不会进入函数执行
    }, wait);
  };
}

事件委托实现

function delegate(element, eventType, selector, fn) {
  element.addEventListener(
    eventType,
    (e) => {
      let el = e.target;
      while (!el.matches(selector)) {
        if (element === el) {
          el = null;
          break;
        }
        el = el.parentNode;
      }
      el && fn.call(el, e, el);
    },
    true
  );

  return element;
}

手写实现instanceof

function myInstanceof(left, right) {
  let prototype = right.prototype;
  left = left._proto_;
  while (true) {
    if (left === null || left === undefined) {
      return false;
    }
    if (prototype === left) {
      return true;
    }
    left = left._proto_;
  }
}

简易封装JSONP

function jsonp(url, callback, success) {
  let script = document.createElement("script");
  script.src = url;
  script.async = true;
  script.type = "text/javascript";
  window[callback] = function(data) {
    success && success(data);
  };
  document.body.appendChild(script);
}

手写new原理

function myNew(fn, ...args) {
  if (typeof fn !== "function") {
    throw new Error("类型错误");
  }
  const obj = Object.create(fn.prototype);

  const res = fn.apply(obj, args);

  return res instanceof Object ? res : obj;
}

手写Object.create

function create(proto){
  function Fn(){}
  Fn.prototype = proto;
  Fn.prototype.constructor = Fn;

  return new Fn();
}

简易浅拷贝

function shallowClone(obj) {
  function isObject() {
    return typeof o === "object";
  }
  if (!isObject(obj)) return obj;
  let target = {};
  Reflect.ownKeys(obj).forEach((key) => {
    target[key] = obj[key];
  });
}

简易深拷贝

function deepClone(obj) {
  function isObject(o) {
    return typeof o === "object";
  }
  if (!isObject(obj)) return obj;

  let isArray = Array.isArray(obj);
  // 兼容数组
  let newObj = isArray ? [] : {};
  Reflect.ownKeys(newObj).forEach((key) => {
    newObj[key] = isObject(obj[key]) ? deepClone(obj[key]) : obj[key];
  });
  return newObj;
}

手写实现filter方法

const myFilter = function(fn, context) {
  const arr = [].slice.call(this);
  const filterArray = [];
  for (let i = 0; i < arr.length; i++) {
    if (!arr.hasOwnProperty(i)) continue;
    fn.call(context, arr[i], i, this) && filterArray.push(arr[i]);
  }
  return filterArray;
};

手写实现map方法

const myMap = function(fn, context) {
  const arr = [].slice.call(this);
  const resultArray = [];
  for (let i = 0; i < arr.length; i++) {
    if (!arr.hasOwnProperty(i)) continue;
    resultArray.push(fn.call(context, arr[i], i, this));
  }
  return resultArray;
};

手写实现reduce

Array.prototype._reduce = function (callback, initVal) {
  let i = 0;
  let result = initVal;
  if (typeof initVal === "undefined") {
    result = this[0];
    i++;
  }

  for (i; i < this.length; i++) {
    result = callback(result, this[i], i);
  }
  return result;
};

通过reduce实现filter

const myReduceFilter = function (fn, context) {
  const arr = [].slice.call(this);
  return arr.reduce((total, cur, index) => {
    return fn.call(context, total, cur, index, this)
      ? [...total, cur]
      : [...total];
  }, []);
};

通过reduce实现flat

const myFlat = function (deep = 1) {
  const arr = [].slice.call(this);
  if (deep === 0) return arr;
  return arr.reduce((total, cur) => {
    if (Array.isArray(cur)) {
      return [...total, ...myFlat.call(cur, deep - 1)];
    } else {
      return [...total, cur];
    }
  }, []);
};

通过reduce实现flat

const myReduceToMap = function (fn, context) {
  const arr = [].slice.call(this);
  return arr.reduce((total, cur, index) => {
    return [...total, fn.call(context, total, cur, index, this)];
  }, []);
};

Promise的简易实现版本

const PENDING = "pending";
const RESOLVED = "resolved";
const REJECTED = "rejected";

// promise主体实现
function MyPromise(fn) {
  const that = this;
  that.state = PENDING;
  that.value = null;
  that.resolvedCallbacks = [];
  that.rejectedCallbacks = [];

  function resolve(value) {
    if (that.state === PENDING) {
      that.state = RESOLVED;
      that.value = value;
      that.resolvedCallbacks.map((cb) => {
        cb(value);
      });
    }
  }
  function reject(value) {
    if (that.state === PENDING) {
      that.state = REJECTED;
      that.value = value;
      that.rejectedCallbacks.map((cb) => {
        cb(value);
      });
    }
  }

  try {
    fn(resolve, reject);
  } catch (e) {
    reject(e);
  }
}

// then的实现
MyPromise.prototype.then = function (onFulfilled, onRejected) {
  const that = this;
  onFulfilled = typeof onFulfilled === "function" ? onFulfilled : (v) => v;
  onRejected =
    typeof onRejected === "function"
      ? onRejected
      : (r) => {
          throw r;
        };
  if (that.state === PENDING) {
    that.resolvedCallbacks.push(onFulfilled);
    that.rejectedCallbacks.push(onRejected);
  }
  if (that.state === RESOLVED) {
    onFulfilled(that.value);
  }
  if (that.state === REJECTED) {
    onRejected(that.value);
  }
};

手写进阶实现

实现一个缓存函数

const memorize = function (fn) {
  const catchs = {};

  return function (...args) {
    const _args = JSON.stringify(args);
    return catchs[_args] || (catchs[_args] = fn.apply(null, args));
  };
};

const add = function (a, b) {
  return a + b;
};

const adder = memorize(add);

adder(2, 6);
// 直接取得缓存
adder(2, 6);

设计实现一个限制最大运行数量的函数

function maxTask(tasks, n) {
  return new Promise((resolve, reject) => {
    let start = 0;
    let index = 0;
    let finish = 0;
    const res = [];

    function processValue(index, value) {
      start -= 1;
      finish += 1;
      res[index] = value;
      runTask();
    }

    function runTask() {
      if (finish === n) {
        resolve(res);
        return;
      }

      while (start < n && index < tasks.length) {
        start += 1;
        let cur = index;
        Promise.resolve(tasks[cur]())
          .then((value) => {
            processValue(cur, value);
          })
          .catch((err) => {
            processValue(cur, err);
          });
      }
    }

    runTask();
  });
}

实现lodash的【get】方法


// var object = { 'a': [{ 'b': { 'c': 3 } }] };

// _.get(object, 'a[0].b.c');
// => 3

function myGet(targetObj, path, defaultValue) {
  if (!path || typeof path !== "string") {
    return defaultValue;
  }
  const transPath = path.replace(/\[(\d+)\]/g, ".$1").split(".");
  let result = targetObj;

  for (const p of transPath) {
    result = Object(result)[p];
    if (result === undefined) {
      return defaultValue;
    }
  }

  return result;
}

实现一个重启函数,(请求失败会重启请求,到达次数上限后抛出错误)

// 模拟api请求
function getMyData() {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      const num = Math.ceil(Math.random() * 20);
      if (num <= 5) {
        console.log("符合条件", num);
        resolve(num);
      }
      reject(`${num}不符合条件`);
    }, 2000);
  });
}

// 重启函数
function myGetData(fn, times = 5, delay = 1000) {
  return new Promise((resolve, reject) => {
    function attempt() {
      fn().then(resolve).catch((err) => {
        console.log(`还有${times}次重启机会`);
        if (times === 0) {
          reject(err);
        } else {
          times -= 1;
          setTimeout(attempt, delay);
        }
      });
    }
    attempt();
  });
}

实现以给定前缀的累加函数

//   输入:getUniqId('my')
//   输出:my_1
//   输入:getUniqId('my')
//   输出:my_2
//   输入:getUniqId('he')
//   输出:he_1
const getUniqId = (() => {
  const keyMap = new Map();

  return function (currentStr) {
    if (typeof currentStr !== "string") {
      return "";
    }
    if (keyMap.get(currentStr)) {
      const value = keyMap.get(currentStr);
      keyMap.set(currentStr, value + 1);
      return `${currentStr}_${value + 1}`;
    } else {
      keyMap.set(currentStr, 1);
      return `${currentStr}_1`;
    }
  };
})();

实现一个检验对象是否是循环引用的函数

const isCyclic = (obj) => {
  try {
    JSON.stringify(obj);
    return false;
  } catch {
    return true;
  }
};

实现一个以给定数组【去重】并输出从大到小的【货币格式】

const nums = [7, 8, 3, 5, 1, 2, 4, 3, 1];
// 排序
const sortNums = nums.sort((a, b) => a - b);
// 去重
const targetNums = [...new Set(sortNums)];
// 转换字符串
const targetStr = targetNums.join("");
// 转换方法
const dealNumber = (money) => {
  if (!money) {
    return "";
  }
  const temp = money.match(/(\d{1,3})/g);
  return temp.join(",").split("").reverse().join("");
};

实现一个按顺序加载img的函数

// const imgs = ['url1', 'url2', 'url3', ...];
// 按照图片数组顺序队列加载图片(注:加载完一张再加载下一张)

// 加载img的异步函数
const loadImg = (url) => {
  return new Promise((resolve, reject) => {
    let img = new Image();
    img.src = url;
    img.onload = () => {
      resolve(img);
    };
    img.onerror = () => {
      reject("error");
    };
  });
};

// 图片处理函数
const dealWithImgs = (imgs) => {
  const imgQueue = [];
  for (let i = 0; i < imgs.length; i += 1) {
    imgQueue.push(loadImg(imgs[i]));
  }
  Promise.all(imgQueue)
    .then((item) => {})
    .catch((err) => {});
};

dealWithImgs(imgs);

订阅-发布模式实现

// 订阅-发布模式
class EvevtEmitter {
  constructor() {
    // map,存储事件与回调之间的关系
    this.handlers = {};
  }

  // 安装事件监听,接受事件名和回调函数作为参数
  on(eventName, cb) {
    if (!this.handlers[eventName]) {
      // 初始化一个监听函数队列
      this.handlers[eventName] = [];
    }
    this.handlers[eventName].push(cb);
  }

  // emit用于触发目标事件,接受事件名和监听函数的参数作为参数
  emit(eventName, ...args) {
    if (this.handlers[eventName]) {
      this.handlers[eventName].forEach((cb) => {
        cb(...args);
      });
    }
  }

  // 移除某个事件回调队列中的回调函数
  off(eventName, cb) {
    const callbacks = this.handlers[eventName];
    const index = callbacks.indexOf(cb);
    if (index !== -1) {
      this.handlers[eventName].splice(index, 1);
    }
  }

  // 为事件注册单次监听器
  once(eventName, cb) {
    const wrapper = (...args) => {
      cb.apply(null, args);
      this.off(eventName, wrapper);
    };
    this.on(eventName, wrapper);
  }
}

高频算法题

实现LRU

// 这里利用JS的map实现的,标准实现一般是链表,但是对于前端来说,能用map写好,也是没问题的

class LRU {
  constructor(max) {
    this.max = max;
    this.map = new Map();
  }

  get(key) {
    const value = this.map.get(key);
    if (value === undefined) {
      return -1;
    }
    this.map.delete(key);
    this.map.set(key, value);
    return value;
  }

  set(key, value) {
    if (this.map.get(key)) {
      this.map.delete(key);
    }
    this.map.set(key, value);
    const keys = this.map.keys();
    while (this.map.size > this.max) {
      this.map.delete(keys.next().value);
    }
  }
}

青蛙跳台阶

/**
 * 一只青蛙一次可以跳上1级台阶,也可以跳上2级台阶。求该青蛙跳上一个 n 级的台阶总共有多少种跳法。
 * 答案需要取模 1e9+7(1000000007),如计算初始结果为:1000000008,请返回 1。
 */

const numWays = (n) => {
  if (n === 1) {
    return 1;
  }
  const dp = [];
  dp[0] = 1;
  dp[1] = 1;

  const max = 1e9 + 7;
  for (let i = 2; i <= n; i += 1) {
    dp[i] = (dp[i - 1] + dp[i - 2]) % max;
  }

  return dp[n];
};

二分矩阵搜索

/*
写一个有效的算法完成矩阵搜索,这个矩阵有如下特点:
  1) 矩阵中的每行数字都是经过排序的,从左到右依次变大。
  2) 每行的第一个数字都比上一行的最后一个数字大
例如:
[
  [2,   4,  8,  9],
  [10, 13, 15, 21],
  [23, 31, 33, 51]
]
实现一个函数,搜索这个数组
输入:4,返回:true
输入:3,返回:false
*/

// 矩阵搜索函数
const searchFunc = (matrix, target) => {
  if (matrix.length === 0 || matrix[0].length === 0 || matrix[0][0] > target) {
    return false;
  }
  let i = matrix.length - 1;
  let j = 0;
  while (i >= 0 && j < matrix[0].length) {
    if (matrix[i][j] === target) {
      return true;
    }
    if (matrix[i][j] < target) {
      j += 1;
    } else {
      i -= 1;
    }
  }
  return false;
};

反转链表

const reverseList = (head) => {
  // 前驱节点
  let pre = null;
  // 当前节点
  let cur = head;

  while (cur.next) {
    // 先记录next节点
    let next = cur.next;
    // 反转当前节点
    cur.next = pre;
    // pre节点前进
    pre = cur;
    // cur节点前进
    cur = next;
  }
  return pre;
};

三数求和

const threeSum = function (sendSums, target = 0) {
  // 结果数组
  const res = [];
  if (Array.isArray(sendSums)) {
    const nums = sendSums.sort((a, b) => {
      return a - b;
    });
    // 遍历到倒数第三个数即可,指针会指向到最后两个数,并计算
    for (let i = 0; i < nums.length - 2; i += 1) {
      // 左指针
      let left = i + 1;
      // 右指针
      let right = nums.length - 1;
      // 如果遇到相同的数字,就跳过
      if (i > 0 && nums[i] === nums[i - 1]) {
        continue;
      }

      // 左指针必须小于右指针,这样才是三个数
      while (left < right) {
        // 相加小于目标值,左指针移动
        if (nums[i] + nums[left] + nums[right] < target) {
          left += 1;
          // 处理左指针相同的情况,如果相同,则继续往前推,这样可以避免出现重复的数组元素
          while (left < right && nums[left] === nums[left - 1]) {
            left += 1;
          }
        } else if (nums[i] + nums[left] + nums[right] > target) {
          right -= 1;
          while (left < right && nums[right] === nums[right + 1]) {
            right -= 1;
          }
        } else {
          res.push([nums[i], nums[left], nums[right]]);

          // 左右指针同时前进
          left += 1;
          right -= 1;

          while (left < right && nums[left] === nums[left - 1]) {
            left += 1;
          }
          while (left < right && nums[right] === nums[right + 1]) {
            right -= 1;
          }
        }
      }
    }
  }
  return res;
};

模拟栈算法实现

/** 
  模拟栈操作
  push(x) —— 将元素 x 推入栈中。
  pop() —— 删除栈顶的元素。
  top() —— 获取栈顶元素。
  getMin() —— 检索栈中的最小元素。 
*/

class Mystack {
  constructor() {
    // 主功能栈
    this.stack = [];
    // 辅助栈,用于记录栈中最小的元素
    this.minStack = []
  }

  push(x) {
    this.stack.push(x);
    if (!this.minStack.length || this.minStack[this.minStack.length - 1] >= x) {
      this.minStack.push(x);
    }
  }

  pop() {
    const popValue = this.stack.pop();
    if (popValue === this.minStack[this.minStack.length - 1]) {
      this.minStack.pop();
    }
  }

  top() {
    return this.stack[this.stack.length - 1];
  }

  getMin() {
    return this.minStack[this.minStack.length - 1];
  }
}

数组转树结构

const listToTree = (arr) => {
  const map = {};
  let node;
  const tree = [];
  for (let i = 0; i < arr.length; i += 1) {
    map[arr[i].id] = arr[i];
    arr[i].children = [];
  }

  for (let i = 0; i < arr.length; i += 1) {
    node = arr[i];
    if (node.pid !== "0") {
      map[node.pid].children.push(node);
    } else {
      tree.push(node);
    }
  }
  return tree;
};

树转扁平化数组

const treeToList = (tree) => {
  let queen = [].concat(tree);
  const out = [];
  while (queen.length) {
    let first = queen.shift();
    if (first.children) {
      queen = queen.concat(first.children);
      Reflect.deleteProperty(first, "children");
    }
    out.push(first);
  }
  return out;
};