前端js手写题大全

480 阅读7分钟

常见题

Function篇

不能使用用箭头函数,会造成this丢失

Function.prototype._call = function (ctx = window, ...args) {
  const fn = Symbol("fn"); // 使用Symbol确保不会和现有属性冲突
  ctx[fn] = this; // this指向调用_call的函数
  const res = ctx[fn](...args);
  delete ctx[fn];
  return res;
};

Function.prototype._apply = function (ctx = window, argsArr = []) {
  const fn = Symbol("fn");
  ctx[fn] = this;
  const res = ctx[fn](...argsArr);
  delete ctx[fn];
  return res;
};

Function.prototype._bind = function (ctx = window, ...args) {
  const _this = this;
  return function () {
    const fn = Symbol("fn");
    ctx[fn] = _this;
    const res = ctx[fn](...args, ...arguments);
    delete ctx[fn];
    return res;
  };
};

Class篇

class Animal {
  constructor(height) {
    this.height = height;
  }
  announce() {
    console.log("announce");
  }
}

class Bird extends Animal {
  constructor(height, size) {
    super(height);
    this.size = size;
  }
  fly() {
    console.log("fly");
  }
}

// function实现
function Animal(height) {
  this.height = height;
}
Animal.prototype.announce = function () {
  console.log("announce");
};

function Bird(height, size) {
  Animal.call(this, height);
  this.size = size;
}
Bird.prototype = Object.create(Animal.prototype);
Bird.prototype.fly = function () {
  console.log("fly");
};

// 继承多个父类
function ParentA(propA) {
  this.propertyA = propA;
}
ParentA.prototype.methodA = function () {
  console.log("Method A");
};
function ParentB(propB) {
  this.propertyB = propB;
}
ParentB.prototype.methodB = function () {
  console.log("Method B");
};

function Child(propA, propB, prop) {
  // 调用父类的构造函数
  ParentA.call(this, propA);
  ParentB.call(this, propB);

  // 添加自身特定的属性或方法
  this.propertyC = prop;
}
Child.prototype.methodC = function () {
  console.log("Method C");
};
// 重点,使用一个对象将多个父类的原型属性copy过来
const FatherPrototype = Object.assign({}, ParentA.prototype, ParentB.prototype);
Object.setPrototypeOf(Child.prototype, FatherPrototype);

Object篇

// 浅拷贝
function copy(obj) {
  return Object.assign({}, obj);
}
function copy(obj) {
  return { ...obj };
}
function copy(obj) {
  const _obj = {};
  Object.keys(obj).forEach((key) => (_obj[key] = obj[key]));
  return _obj;
}

// 深拷贝
// 序列化反序列化
function deepCopy(obj) {
  return JSON.parse(JSON.stringify(obj));  // undefine,symbol,function的属性会被忽略
}
// 递归
function deepCopy(obj) {
  const _obj = Array.isArray(obj) ? {} : [];
  Object.keys(obj).forEach((key) => {
    const val = obj[key];
    if (typeof val === "object") {
      _obj[key] = val === null ? val : deepCopy(val);
    } else {
      _obj[key] = val;
    }
  });
  return _obj;
}
// 非递归,层序遍历
function deepCopy(obj) {
  const _obj = Array.isArray(obj) ? [] : {};
  const node = { val: obj, parent: _obj  };
  const queue = [node];
  let len, root;

  while(len = queue.length) {
    while(len--) {
      const node = queue.shift();
      root = node.parent;
      const { val } = node;
      for(let k in val) {
        if(typeof val[k] === 'object' && val[k] !== null) {
          root[k] = {};
          const _node = { val: val[k], parent: root[k]};
          queue.push(_node);
        } else {
          root[k] = val[k];
        }
      }
    }
  }

  return _obj;
}

function _instanceOf(instance, constructor) {
  let proto = instance.__proto__;
  while (proto) {
    if (proto === constructor.prototype) {
      return true;
    }
    proto = proto.__proto__;
  }
  return false;
}

function _new(constructor, ...args) {
  const obj = Object.create(constructor.prototype);
  const res = constructor.call(obj, ...args);
  return Object.prototype.toString.call(res) === "[object Object]" ? res : obj;
}

Object._is = function(a, b) {
    if(a === b) {
        // 判断+0 和 -0
        return a !== 0 || 1/a === 1/b;
    }
    // 判断NaN
    return a !== a && b !== b;
}

Object._assign = function(target, ...args) {
    if(target === null || target === undefined) {
        throw Error('')
    }
    target = Object(target);
    args.forEach(obj => {
        for(let key in obj) {
            obj.hasOwnProperty(key) && (target[key] = obj[key]);
        }
    });
    return target;
}

Array篇

Array.prototype._forEach = function (cb, thisValue = window) {
  for (let i = 0; i < this.length; i++) {
    cb.call(thisValue, this[i], i, this);
  }
};

Array.prototype._map = function (cb, thisValue = window) {
  const res = [];
  for (let i = 0; i < this.length; i++) {
    res.push(cb.call(thisValue, this[i], i, this));
  }
  return res;
};

Array.prototype._filter = function (cb, thisValue = window) {
  const res = [];
  for (let i = 0; i < this.length; i++) {
    cb.call(thisValue, this[i], i, this) && res.push(this[i]);
  }
  return res;
};

Array.prototype._every = function (cb, thisValue = window) {
  for (let i = 0; i < this.length; i++) {
    if (!cb.call(thisValue, this[i], i, this)) {
      return false;
    }
  }
  return true;
};

Array.prototype._some = function (cb, thisValue = window) {
  for (let i = 0; i < this.length; i++) {
    if (cb.call(thisValue, this[i], i, this)) {
      return true;
    }
  }
  return false;
};

Array.prototype._reduce = function (cb, ...args) {
  let start = 1, accumulate = this[0];
  if (args.length) {
    accumulate = args[0];
    start = 0;
  }
  for (let i = start; i < this.length; i++) {
    accumulate = cb(accumulate, this[i], i, this);
  }
  return accumulate;
};

Array.prototype.find = function (cb, thisValue = window) {
  for (let i = 0; i < this.length; i++) {
    if (cb.call(thisValue, this[i], i, this)) {
      return this[i];
    }
  }
};

Array.prototype._findIndex = function (cb, thisValue = window) {
  for (let i = 0; i < this.length; i++) {
    if (cb.call(thisValue, this[i], i, this)) {
      return i;
    }
  }
  return -1;
};

Array.prototype._includes = function (value, start = 0) {
  start = start < 0 ? start + this.length : start;
  for (let i = start; i < this.length; i++) {
    if (Object.is(value, this[i])) {
      return true;
    }
  }
  return false;
};

Array.prototype._join = function (sperator = ",") {
  let res = "";
  for (var i = 0; i < this.length - 1; i++) {
    res += this[i].toString();
    res += sperator;
  }
  res += this[i];
  return res;
};

// 数组拍平
Array.prototype.flatten = function () {
  return this.reduce((pre, item) => {
    return pre.concat(Array.isArray(item) ? item.flatten() : item);
  }, []);
};

// 数组去重
Array.prototype._unique = function () {
  const st = new Set(this);
  return [...st];
};

// 数组转化为树形结构
// 1.使用Map,利用引用类型的特性
function transform(arr) {
  if (!Array.isArray(arr) || !arr.length) return;
  const map = new Map();
  let root;
  arr.forEach((item) => map.set(item.id, item));
  arr.forEach((item) => {
    if (!item.pid) {
      root = item;
    } else {
      const parent = map.get(item.pid);
      parent.children = parent.children || [];
      parent.children.push(item);
    }
  });
  return root;
}
// 2.层序遍历
function transform(arr) {
  if (!Array.isArray(arr) || !arr.length) return;
  const root = arr.find((item) => !item.pid);
  const queue = [root];
  let len;
  while ((len = queue.length)) {
    while (len--) {
      const node = queue.shift();
      arr.forEach((item) => {
        if ((item.pid = node.id)) {
          node.children = node.children || [];
          node.children.push(item);
          queue.push(item);
        }
      });
    }
  }
  return root;
}

Promise篇

Promise._all = function (promises) {
  const resArr = new Array(promises.length);
  let count = 0;
  return new Promise((resolve, reject) => {
    promises.forEach((item, index) => {
      Promise.resolve(item).then(
        (value) => {
          resArr[index] = value;
          count++;
          if (count >= promises.length) {
            resolve(resArr);
          }
        },
        (rej) => {
          reject(rej);
        }
      );
    });
  });
};

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

Promise._allSettled = function (promises) {
  const resArr = new Array(promises.length);
  let count = 0;
  // 这个promise只会决议为fulfilled
  return new Promise(function (resolve, reject) {
    const addData = (status, value, index) => {
      if (status === "fulfilled") {
        resArr[index] = {
          status,
          value,
        };
      } else {
        resArr[index] = {
          status,
          reason: value,
        };
      }
      count++;
      if (count === promises.length) {
        resolve(resArr);
      }
    };
    promises.forEach((promise, index) => {
      Promise.resolve(promise).then(
        (res) => {
          addData("fulfilled", res, index);
        },
        (rej) => {
          addData("rejected", rej, index);
        }
      );
    });
  });
};

Promise._any = function (promises) {
  let count = 0;
  return new Promise((resolve, reject) => {
    promises.forEach((item) => {
      Promise.resolve(item).then(
        (res) => {
          resolve(res);
        },
        () => {
          count++;
          if (count === promises.length) {
            reject("All promises were rejected");
          }
        }
      );
    });
  });
};

手写Promise

const STATE = {
  PENDING: "pending",
  FULFILLED: "fulfilled",
  REJECTED: "rejected",
};

class MyPromise {
  constructor(fn) {
    this.state = STATE.PENDING;
    this.value = null;
    this.fulfilledSubs = [];
    this.rejectedSubs = [];
    try {
      fn(this.resolve.bind(this), this.reject.bind(this));
    } catch (error) {
      this.reject(error);
    }
  }
  resolve(result) {
    if (this.state === STATE.PENDING) {
      this.state = STATE.FULFILLED;
      this.value = result;
      setTimeout(() => {
        this.fulfilledSubs.forEach((sub) => {
          sub(result);
        });
      }, 0);
    }
  }
  reject(reason) {
    if (this.state === STATE.PENDING) {
      this.state = STATE.REJECTED;
      this.value = reason;
      setTimeout(() => {
        this.rejectedSubs.forEach((sub) => {
          sub(reason);
        });
      }, 0);
    }
  }
  then(onFulfilled, onRejected) {
    onFulfilled = onFulfilled || ((val) => val);
    onRejected =
      onRejected ||
      ((rej) => {
        throw Error(rej);
      });
    if (this.state === STATE.PENDING) {
      this.fulfilledSubs.push(onFulfilled);
      this.rejectedSubs.push(onRejected);
    } else if (this.state === STATE.FULFILLED) {
      setTimeout(() => {
        onFulfilled(this.value);
      }, 0);
    } else {
      setTimeout(() => {
        onRejected(this.value);
      }, 0);
    }
  }
  finally(callback) {
    return this.then((data) => {
    // 让函数执行 内部会调用方法,如果方法是promise,需要等待它完成
    // 如果当前promise执行时失败了,会把err传递到err的回调函数中
      return Promise.resolve(callback()).then(() => data); // data 上一个promise的成功态
    }, err => {
      return Promise.resolve(callback()).then(() => {
        throw err; // 把之前的失败的err,抛出去
        });
    })
  }
}

节流、防抖

function debounce(fn, delay = 200) {
  let timer;
  return function (...args) {
    if (timer) {
      clearTimeout(timer);
      timer = null;
    }
    timer = setTimeout(() => {
      fn(...args);
      timer = null;
    }, delay);
  };
}

function throttle(fn, delay = 200) {
  let timer;
  return function (...args) {
    if (timer) return;
    timer = setTimeout(() => {
      fn(...args);
      timer = null;
    }, delay);
  };
}

异步

并行执行异步任务

function foo() {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve("p1");
    }, 2000);
  });
}

function bar() {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve("p2");
    }, 2000);
  });
}

// 大约四秒
async function serial() {
  console.time("serial");
  const p1 = await foo();
  const p2 = await bar();
  console.log("res", p1, p2);
  console.timeEnd("serial");
}

// 大约两秒
async function parallel() {
  console.time("parallel");
  const p1 = foo();
  const p2 = bar();
  const res1 = await p1;
  const res2 = await p2;
  console.log("res", res1, res2);
  console.timeEnd("parallel");
}

// 大约两秒
async function promiseall() {
  console.time("promiseall");
  const res = await Promise.all([foo(), bar()]);
  console.log("res", res);
  console.timeEnd("promiseall");
}

serial();
parallel();
promiseall();

基于Generator函数实现async/await

把生成器函数看做异步函数,yield当作await,可看作是执行async函数

function _async(gen) {
    const it = gen();
    return Promise.resolve().then(function handleNext(value) {
        // 递归,自动迭代执行生成器
        const next = it.next(value);
        if(next.done) {
            return next.value;
        } else {
            // yield返回的next.value是个promise
            return Promise.resolve(next.value).then(handleNext)
        }
    })
}

asyncFunc(function* () {
    const data1 = yield request('a.js');
    console.log('data1', data1);
    const data2 = yield request(data1 + 'b.js');
    console.log('data2', data2);
    return data2;
}).then(res =>{
    console.log('res', res);
});

异步调度器控制最大并发执行数

class Schedule {
  constructor(maxLimit) {
    this.maxLimit = maxLimit;
    this.count = 0;
    this.cache = [];
  }
  add(task) {
    if (this.count < this.maxLimit) {
      this.run(task);
    } else {
      this.cache.push(task);
    }
  }
  run(task) {
    this.count++;
    task()
      .then(
        (res) => {
          console.timeLog("complish");
          console.log("res", res);
        },
        (rej) => {
          console.timeLog("complish");
          console.log("rej", rej);
        }
      )
      .finally(() => {
        this.count--;
        if (this.cache.length) {
          const top = this.cache.shift();
          this.run(top);
        }
      });
  }
}

const createTask = (timeout, res, rej) => () =>
  new Promise(function (resolve, reject) {
    if (res) {
      setTimeout(() => {
        resolve(res);
      }, timeout);
    } else {
      setTimeout(() => {
        reject(rej);
      });
    }
  });

const p1 = createTask(1000, 'p1')
const p2 = createTask(2000, 'p2')
const p3 = createTask(1000, 'p3')
const p4 = createTask(3000, 'p4')
const p5 = createTask(4000, 'p5')

const schedule = new Schedule(2);
console.time('complish');
schedule.add(p1);
schedule.add(p2);
schedule.add(p3);
schedule.add(p4);
schedule.add(p5);

实现一个请求池并发控制请求数量,给定url数组要获得返回结果

function requestLimit(urls = [], maxLimit = 2) {
  let count = 0;
  const result = new Array(urls.length).fill(false);
  return new Promise((resolve, reject) => {
    function request(url) {
      const current = count++;
      if (current >= urls.length && !result.includes(false)) {
        resolve(result);
      }
      const url = urls[current];
      fetch(url)
        .then(
          (res) => {
            result[current] = res;
          },
          (rej) => {
            result[current] = rej;
          }
        )
        .finally(() => {
          if (current < urls.length) {
            request();
          }
        });
    }

    while (count < maxLimit) {
      request();
    }
  });
}

function requestPool(urls, limit = 3) {
  const cache = [];
  const result = new Array(urls.length);
  let count = 0;
  return new Promise((resolve, reject) => {
    function request(url, index) {
      if (count < limit) {
        count++;
        fetch(url)
          .then(
            (res) => {
              result[index] = res;
            },
            (rej) => {
              result[index] = rej;
            }
          )
          .finally(() => {
            count--;
            if (cache.length) {
              const { url, index } = cache.shift();
              request(url, index);
            } else if (count === 0) {
              resolve(result);
            }
          });
      } else {
        cache.push({ url, index });
      }
    }
    urls.forEach((url, index) => {
      request(url, index);
    });
  });
}

手写一个发布订阅

class EventBus {
  constructor() {
    this.clients = {};
  }
  $on(eventName, fn) {
    this.clients[eventName] = this.clients[eventName] || [];
    this.clients[eventName].push(fn);
  }
  $off(eventName, fn) {
    if (!this.clients[eventName]) return;
    if (!fn) {
      this.clients[eventName] = [];
    } else {
      const subs = this.clients[eventName];
      for (let i = subs.length - 1; i >= 0; i--) {
        if (fn === subs[i]) {
          subs.splice(i, 1);
        }
      }
    }
  }
  $emit(eventName, ...args) {
    const subs = this.clients[eventName];
    if (subs && subs.length) {
      subs.forEach((cb) => cb(...args));
    }
  }
}

高级题

函数柯里化

// 多参数柯里化;
// 将fn(a, b, c, b)转换为fn(a)(b)(c)(d);
// 需要明确fn的输入参数个数
const fn = (x, y, z, a) => x + y + z + a;
const curry = function (fn) {
  return function curriedFn(...args) {
    // 如果输入的参数个数少于fn接收的参数个数,继续进行递归,返回一个函数
    if (args.length < fn.length) {
      return function () {
        // 将每次的参数收集起来,存在args数组中,最后一次性计算
        return curriedFn(...args, ...arguments);
      };
    }
    return fn.apply(this, args);
  };
};

const myfn = curry(fn);
console.log(myfn(1)(2)(3)(1));
console.log(myfn(1)(2, 3)(2));

// 函数柯里化,部分求值
const fn = function (...args) {
  let res = 0;
  args.forEach((el) => (res += el));
  return res;
};
const _curry = (fn) => {
  const cache = [];
  return function _curriedFn(...args) {
    if (args && args.length) {
      cache.push(...args);
      return _curriedFn;
    }
    const res = fn(...cache); // this指向全局对象
    cache.length = 0;
    return res;
  };
};

const myfn = _curry(fn);
console.log(myfn(1)(2)(3)(1)());
console.log(myfn(1)(2, 3)(2)());
console.log(myfn(1)(2)(10)());

Koa2中间件原理compose函数

function compose(middlewares) {
  return function (ctx, next) {
    let index = -1;
    function dispatch(i) {
      index = i;
      if (index === middlewares.length) {
        return Promise.resolve();
      }
      const fn = middlewares[i];
      try {
        return Promise.resolve(fn(ctx, dispatch.bind(null, i + 1)));
      } catch (error) {
        return Promise.reject(error);
      }
    }

    return dispatch(0);
  };
}

async function fn1(ctx, next) {
  console.log("fn1");
  await next();
  console.log("end fn1");
}
async function fn2(ctx, next) {
  console.log("fn2");
  await next();
  console.log("end fn2");
}
function fn3(ctx, next) {
  console.log("fn3");
}

const finallyFn = compose([fn1, fn2, fn3]);
finallyFn(window);

生成器

function foo() {
  setTimeout(() => {
    it.next("foo");
  }, 1000);
}

function bar() {
  setTimeout(() => {
    it.next("next");
  }, 1000);
}

function* main() {
  const res1 = yield foo();
  const res2 = yield bar();
  console.log("res1", res1);
  console.log("res2", res2);
}

const it = main();
it.next();

function run(generator) {
  const it = generator();
  return Promise.resolve().then(function handleResult(value) {
    const nextTick = it.next(value);
    if (nextTick.done) {
      return nextTick.value;
    } else {
      return Promise.resolve(nextTick.value).then(
        handleResult,
        function handleError(err) {
          return Promise.reject(it.throw(err));
        }
      );
    }
  });
}

请求失败重试,超时重传

function retryRequest(url, time = 3, delay = 3000) {
  return new Promise((resolve, reject) => {
    let timer;
    const request = (count, start, lastError) => {
      if (count <= 0) {
        clearTimeout(timer);
        reject(lastError);
        return;
      }

      timer = setTimeout(() => {
        request(count - 1, Date.now(), "超时");
      }, delay);

      fetch(url)
        .then((res) => {
          const end = Date.now();
          if (end - start < delay) {
            clearTimeout(timer);
            resolve(res);
          }
        })
        .catch((error) => {
          const end = Date.now();
          if (end - start < delay) {
            clearTimeout(timer);
            request(count - 1, Date.now(), error);
          }
        });
    };

    request(time, Date.now());
  });
}