前端面试-常见手写题总结3

手写题部分

理解每个手写题的思路很重要,不需要死记硬背。

// es5手写call
Function.prototype.call = function (context) {
  var context = context || window;
  context.fn = this;
  var args = [];
  for (var i = 1, len = arguments.length; i < len; i++) {
    args.push("arguments[" + i + "]");
  }
  var result = eval("context.fn(" + args + ")");
  delete context.fn;
  return result;
};
// es5手写apply
Function.prototype.apply = function (context, arr) {
  var context = context || window;
  context.fn = this;
  var result;
  if (!arr) {
    result = context.fn();
  } else {
    var args = [];
    for (var i = 0, len = arr.length; i < len; i++) {
      args.push("arr[" + i + "]");
    }
    result = eval("context.fn(" + args + ")");
  }
  delete context.fn;
  return result;
};

Function.prototype.myCall = function (context = window) {
  context.fn = this;
  let args = [...arguments].slice(1);
  let result = context.fn(...args);
  delete context.fn;
  return result;
};
Function.prototype.myApply = function (context = window) {
  context.fn = this;
  let result;
  if (arguments[1]) {
    result = context.fn(...arguments[1]);
  } else {
    result = context.fn();
  }
  delete context.fn;
  return result;
};

Function.prototype.bind = function (context) {
  // 1、返回一个函数
  // 2、里外两个函数均可拼接参数
  // 3、bind 返回的函数作为构造函数的时候,bind 时指定的 this 值会失效,但传入的参数依然生效。
  // 4、判断传入参数类型
  if (typeof this !== "function") {
    throw new Error(
      "Function.prototype.bind - what is trying to be bound is not callable"
    );
  }
  var self = this;
  var args = Array.prototype.slice.call(arguments, 1);
  var fNOP = function () {};
  var fBound = function () {
    var bindArgs = Array.prototype.slice.call(arguments);
    return self.apply(
      this instanceof fNOP ? this : context,
      args.concat(bindArgs)
    );
  };
  fNOP.prototype = this.prototype;
  fBound.prototype = new fNOP();
  return fBound;
};

// 手写promise
const PENDING = "PENDING";
const FULFILLED = "FULFILLED";
const REJECTED = "REJECTED";
class Promise {
  constructor(executor) {
    this.status = PENDING;
    this.value = undefined;
    this.reason = undefined;
    // 存放成功的回调
    this.onResolvedCallback = [];
    // 存放失败的回调
    this.onRejectedCallback = [];

    let resolve = (value) => {
      if (this.status === PENDING) {
        this.status = FULFILLED;
        this.value = value;
        // 依次将对应的函数执行
        this.onResolvedCallback.forEach((fn) => fn());
      }
    };
    let reject = (reason) => {
      if (this.status === PENDING) {
        this.status = REJECTED;
        this.reason = reason;
        // 依次将对应的函数执行
        this.onRejectedCallback.map((fn) => fn());
      }
    };

    try {
      executor(resolve, reject);
    } catch (error) {
      reject(error);
    }
  }

  then(onFulfilled, onRejected) {
    if (this.status === FULFILLED) {
      onFulfilled(this.value);
    }

    if (this.status === REJECTED) {
      onRejected(this.reason);
    }

    if (this.status === PENDING) {
      // 如果promise的状态是 pending,需要将 onFulfilled 和 onRejected 函数存放起来,等待状态确定后,再依次将对应的函数执行
      this.onResolvedCallback.push(() => {
        onFulfilled(this.value);
      });

      // 如果promise的状态是 pending,需要将 onFulfilled 和 onRejected 函数存放起来,等待状态确定后,再依次将对应的函数执行
      this.onRejectedCallback.push(() => {
        onRejected(this.reason);
      });
    }
  }
}

Promise.prototype.all = function (arr) {
  // 1、返回的是个promise对象
  // 2、把传入的promise数组全部执行,并将结果存起来
  // 3、等全部执行完以后,返回结果
  let result = [];
  return new Promise((resolve, reject) => {
    for (let i in arr) {
      Promise.resolve(arr[i])
        .then((data) => {
          result[i] = data;
          if (result.length === arr.length) resolve(result);
        })
        .catch((e) => {
          reject(e);
        });
    }
  });
};

Promise.prototype.race = function (arr) {
  return new Promise((resolve, reject) => {
    for (let i in arr) {
      Promise.resolve(arr[i])
        .then((data) => {
          resolve(data);
        })
        .catch((e) => {
          reject(e);
        });
    }
  });
};

// 调用
const promise = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve("成功");
  }, 1000);
}).then(
  (data) => {
    console.log("success", data);
  },
  (err) => {
    console.log("faild", err);
  }
);

// 柯里化
// 函数科里化, 对函数的一种转换,例如:fn(a, b, c)可转化为fn(a)(b)(c)调用,本质上上一种函数转换
// 简单柯里化
function add(a, b) {
  console.log(a + b);
}
let adds = curry(add);
adds(3)(4);

const easyCurry = function (fn) {
  return function (a) {
    return function (b) {
      return fn(a, b);
    };
  };
};

// 复杂柯里化
function sum(a, b, c) {
  return a + b + c;
}
let curriedSum = curry(sum);
alert(curriedSum(1, 2, 3)); // 6,仍然可以被正常调用
alert(curriedSum(1)(2, 3)); // 6,对第一个参数的柯里化
alert(curriedSum(1)(2)(3)); // 6,全柯里化

const curry = function (fn) {
  return function curried(...args) {
    // 如果传入参数数大于函数可接受的参数数
    // 原封不动输出
    if (args.length >= fn.length) {
      // 最终还是返回fn 参数动态拼在一起
      return fn.apply(this, args);
    }
    // 否则
    else {
      // 把之前的参数拼在一起去调用curried
      return function (...args2) {
        return curried.apply(this, args.concat(args2));
      };
    }
  };
};

// 二叉树前序遍历 last
let result = [];
function loopTree(tree) {
  if (tree) {
    result.push(tree.value); //前中后序遍历只需调整此行代码即可
    loopTree(tree.left);
    loopTree(tree.right);
  }
}

// 防抖
// 定义:在事件被触发n秒后再执行回调,如果在这n秒内又被触发,则重新计时。
// 应用:输入框实时搜索,按钮防重复点击
function debounce(fn, delay, immediate) {
  let timer;
  return function () {
    if (timer) clearTimeout(timer);
    if (immediate) {
      fn.apply(this, arguments);
    } else {
      timer = setTimeout(() => {
        fn.apply(this, arguments);
      }, delay);
    }
  };
}

// 节流
// 定义:规定在一个单位时间内,只能触发一次函数。如果这个单位时间内触发多次函数,只有一次生效。
// 摁一下,发射一个子弹。最快每秒1发,如果1秒内触发了3次,还是只会发射一发。
// 监控浏览器resize
function throttle(fn, delay) {
  let prev = new Date();
  return function () {
    let now = new Date();
    if (now - prev > delay) {
      fn.apply(this, arguments);
      prev = new Date();
    }
  };
}

// 深拷贝
function isObject(target) {
  return target !== null && typeof target === "object";
}
// 简易版深拷贝
function deepClone(target) {
  // 不是对象直接返回
  if (!isObject(target)) return target;
  // 判断数组
  let result = Array.isArray(target) ? [] : {};//如果需要解决相同引用和循环引用,在此处前后使用map去存储和获取
  let keys = Object.keys(target);
  // 迭代目标对象的key来递归完成复制
  for (let i = 0, len = keys.length; i < len; i++) {
    result[keys[i]] = deepClone(target[keys[i]]);
  }
  return result;
}

/**
 * @description: 解决相同引用和循环引用的深拷贝版本
 */
function deepClone(target) {
  let visitedMap = new Map();
  function baseClone(target) {
    if (!isObject(target)) return target;
    // 先获取map中是否存在当前target,如果存在则直接返回
    if (visitedMap.get(target)) return visitedMap.get(target);
    let result = Array.isArray(target) ? [] : {};
    visitedMap.set(target, result);
    let keys = Object.keys(target);
    for (let i = 0, len = keys.length; i < len; i++) {
      result[keys[i]] = baseClone(target[keys[i]]);
    }
    return result;
  }
  return baseClone(target);
}

class EventEmitter {
  constructor() {
    // 维护事件及监听者
    this.listeners = {};
  }
  /**
   * 注册事件监听者
   * @param {String} key 事件类型
   * @param {Function} fn 回调函数
   */
  on(key, fn) {
    if (!this.listeners[key]) {
      this.listeners[key] = [];
    }
    this.listeners[key].push(fn);
  }
  /**
   * 发布事件
   * @param {String} key 事件类型
   * @param  {...any} arg 参数列表,把emit传递的参数赋给回调函数
   */
  emit(key, ...arg) {
    if (this.listeners[key]) {
      this.listeners[key].forEach((fn) => {
        fn(...arg);
      });
    }
  }
  /**
   * 移除某个事件的一个监听者
   * @param {String} key 事件类型
   * @param {Function} fn 回调函数
   */
  off(key, fn) {
    if (this.listeners[key]) {
      const targetIndex = this.listeners[key].findIndex((item) => item === fn);
      if (targetIndex !== -1) {
        this.listeners[key].splice(targetIndex, 1);
      }
      if (this.listeners[key].length === 0) {
        delete this.listeners[key];
      }
    }
  }
  /**
   * 移除某个事件的所有监听者
   * @param {String} key 事件类型
   */
  offAll(key) {
    if (this.listeners[key]) {
      delete this.listeners[key];
    }
  }
}
// 创建事件管理器实例
const eve = new EventEmitter();
// 注册一个chifan事件监听者
eve.on("eat", function () {
  console.log("吃饭了,我们走!");
});
// 发布事件chifan
eve.emit("eat");
// 也可以emit传递参数
eve.on("eat", function (address, food) {
  console.log(`吃饭了,我们去${address}${food}!`);
});
eve.emit("eat", "三食堂", "铁板饭"); // 此时会打印两条信息,因为前面注册了两个chifan事件的监听者
// 测试移除事件监听
const toBeRemovedListener = function () {
  console.log("我是一个可以被移除的监听者");
};
eve.on("testoff", toBeRemovedListener);
eve.emit("testoff");
eve.off("testoff", toBeRemovedListener);
eve.emit("testoff"); // 此时事件监听已经被移除,不会再有console.log打印出来了
// 测试移除chifan的所有事件监听
eve.offAll("eat");
console.log(eve); // 此时可以看到eve.listeners已经变成空对象了,再emit发送chifan事件也不会有反应了

// 斐波那契数列
// 输入正整数n,输出0,1,1,2,3,5,8,13...
function fibonacci(n) {
  if (n <= 1) return n;
  return fibonacci(n - 1) + fibonacci(n - 2);
}

// 动态规划
function dynFib(n) {
  if (n === 0) return n;
  if (n <= 2) return 1;
  let result = [];
  for (let i = 0; i <= n; i++) {
    result[i] = 0;
  }
  result[1] = 1;
  result[2] = 1;
  for (let i = 3; i <= n; i++) {
    result[i] = result[i - 1] + result[i - 2];
  }
  return result[n];
}

// 冒泡排序
function bubbleSort(arr) {
  for (let i = 0, len = arr.length; i < len - 1; i++) {
    for (let j = 0; j < len - i - 1; j++) {
      if (arr[j] > arr[j + 1]) {
        [arr[j], arr[j + 1]] = [arr[j + 1], arr[j]];
      }
    }
  }
  return arr;
}
let arr = [5, 4, 3, 2, 1];
bubbleSort(arr);

// 插入排序
// 快速排序
function quickSort(arr){
  // 1、从数组arr中拿出中间项mid
  // 2、声明左右两个数组,循环arr,比mid小push到左数组,否则push到右数组
  // 3、对左右两个数组进行递归处理,把左数组,中间值,右数组拼在一起
  // 4、如果数组里面只有一项或者没有,那么原封不动返回数组
  if(arr.length<=1) return arr
  let middleIndex = parseInt(arr.length/2)
  let middleValue = arr.splice(middleIndex, 1)[0]
  let arrLeft = []
  let arrRight = []
  for(let i=0;i<arr.length;i++) {
    let item = arr[i];
    item<middleValue ? arrLeft.push(item) : arrRight.push(item)
  }
  return quickSort(arrLeft).concat(middleValue, quickSort(arrRight))
}