前端面試常見的手寫功能

  1. 防抖
function debounce(func, ms = 500) {
  let timer;
  return function (...args) {
    if (timer) {
      clearTimeout(timer);
    }
    timer = setTimeout(() => {
      func.apply(this, args);
    }, ms);
  };
}
  1. 節(jié)流
function throttle(func, ms) {
  let canRun = true;
  return function (...args) {
    if (!canRun) return;
    canRun = false;
    setTimeout(() => {
      func.apply(this, args);
      canRun = true;
    }, ms);
  };
}
  1. new
function myNew(Func) {
  const instance = {};
  if (Func.prototype) {
    Object.setPrototypeOf(instance, Func.prototype);
  }
  const res = Func.apply(instance, [].slice.call(arguments, 1));
  if (typeof res === "function" || (typeof res === "object" && res !== null)) {
    return res;
  }
  return instance;
}
  1. bind
Function.prototype.myBind = function (context = globalThis) {
  const fn = this;
  const args = Array.from(arguments).slice(1);
  const newFunc = function () {
    if (this instanceof newFunc) {
      // 通過 new 調(diào)用,綁定 this 為實例對象
      fn.apply(this, args);
    } else {
      // 通過普通函數(shù)形式調(diào)用,綁定 context
      fn.apply(context, args);
    }
  };
  // 支持 new 調(diào)用方式
  newFunc.prototype = fn.prototype;
  return newFunc;
};
  1. call
Function.prototype.myCall = function (context = globalThis) {
  // 關(guān)鍵步驟,在 context 上調(diào)用方法,觸發(fā) this 綁定為 context
  context.fn = this;
  let args = [].slice.call(arguments, 1);
  let res = context.fn(...args);
  delete context.fn;
  return res;
};
  1. apply
Function.prototype.myApply = function (context = globalThis) {
  // 關(guān)鍵步驟,在 context 上調(diào)用方法,觸發(fā) this 綁定為 context
  context.fn = this;
  let res;
  if (arguments[1]) {
    res = context.fn(...arguments[1]);
  } else {
    res = context.fn();
  }
  delete context.fn;
  return res;
};
  1. deepCopy
function deepCopy(obj, cache = new WeakMap()) {
  if (!obj instanceof Object) return obj;
  // 防止循環(huán)引用
  if (cache.get(obj)) return cache.get(obj);
  // 支持函數(shù)
  if (obj instanceof Function) {
    return function () {
      obj.apply(this, arguments);
    };
  }
  // 支持日期
  if (obj instanceof Date) return new Date(obj);
  // 支持正則對象
  if (obj instanceof RegExp) return new RegExp(obj.source, obj.flags);
  // 還可以增加其他對象,比如:Map, Set等,根據(jù)情況判斷增加即可,面試點到為止就可以了

  // 數(shù)組是 key 為數(shù)字素銀的特殊對象
  const res = Array.isArray(obj) ? [] : {};
  // 緩存 copy 的對象,用于出來循環(huán)引用的情況
  cache.set(obj, res);

  Object.keys(obj).forEach((key) => {
    if (obj[key] instanceof Object) {
      res[key] = deepCopy(obj[key], cache);
    } else {
      res[key] = obj[key];
    }
  });
  return res;
}
  1. 事件總線 | 發(fā)布訂閱模式
class EventEmitter {
  constructor() {
    this.cache = {};
  }

  on(name, fn) {
    if (this.cache[name]) {
      this.cache[name].push(fn);
    } else {
      this.cache[name] = [fn];
    }
  }

  off(name, fn) {
    const tasks = this.cache[name];
    if (tasks) {
      const index = tasks.findIndex((f) => f === fn || f.callback === fn);
      if (index >= 0) {
        tasks.splice(index, 1);
      }
    }
  }

  emit(name) {
    if (this.cache[name]) {
      for (let fn of this.cache[name]) {
        fn();
      }
    }
  }


  emit(name, once = false) {
    if (this.cache[name]) {
      // 創(chuàng)建事件副本,如果回調(diào)函數(shù)內(nèi)繼續(xù)注冊相同事件,觸發(fā)時,會造成死循環(huán)
      const tasks = this.cache[name].slice()
      for (let fn of tasks) {
        fn();
      }
      if (once) {
        delete this.cache[name]
      }
    }
  }
}
  1. 柯里化:只傳遞給函數(shù)一部分參數(shù)來調(diào)用它,讓它返回一個函數(shù)去處理剩下的參數(shù)
function curry(func) {
  return function curried(...args) {
    if (args.length >= func.length) {
      func.apply(this, args);
    } else {
      return function (...args2) {
        curried.apply(this, args.concat(args2));
      };
    }
  };
}
  1. es5 實現(xiàn)繼承
function create(proto) {
  function F() {}
  F.prototype = proto;
  return new F();
}

// Parent
function Parent(name) {
  this.name = name;
}

Parent.prototype.say = function () {
  console.log(this.name);
};

// Child
function Child(age, name) {
  Parent.call(this, name);
  this.age = age;
}
Child.prototype = create(Parent.prototype);
Child.prototype.constructor = Child;

Child.prototype.say = function () {
  console.log(this.age);
};
  1. instanceof
function instanceOf(instance, klass) {
  let proto = instance.__proto__;
  let prototype = klass.prototype;
  while (true) {
    if (proto === null) return false;
    if (proto === prototype) return true;
    proto = proto.__proto__;
  }
}
  1. 異步并發(fā)數(shù)限制
/**
 * 關(guān)鍵點說明
 * 1. new promise 一經(jīng)創(chuàng)建,立即執(zhí)行
 * 2. 使用 Promise.resolve().then 可以把任務(wù)加到微任務(wù)隊列,防止立即執(zhí)行迭代方法
 * 3. 微任務(wù)處理過程中,產(chǎn)生的新的微任務(wù),會在同一事件循環(huán)內(nèi),追加到微任務(wù)隊列里
 * 4. 使用 race 在某個任務(wù)完成時,繼續(xù)添加任務(wù),保持任務(wù)按照最大并發(fā)數(shù)進行執(zhí)行
 * 5. 任務(wù)完成后,需要從 doingTasks 中移出
 */
function limit(count, array, iterateFunc) {
  const tasks = [];
  const doingTasks = [];
  let i = 0;
  const enqueue = () => {
    if (i === array.length) {
      return Promise.resolve();
    }
    const task = Promise.resolve().then(() => iterateFunc(array[i++]));
    tasks.push(task);
    const doing = task.then(() => doingTasks.splice(doingTasks.indexOf(doing), 1));
    doingTasks.push(doing);
    const res = doingTasks.length >= count ? Promise.race(doingTasks) : Promise.resolve();
    return res.then(enqueue);
  };
  return enqueue().then(() => Promise.all(tasks));
}

// test
const timeout = i => new Promise(resolve => setTimeout(() => resolve(i), i));
limit(4, [1000, 1000, 1000, 1000], timeout).then((res) => {
  console.log(res);
})
  1. 異步串行 | 異步并行
// 大廠面試題,實現(xiàn)一個異步加法
function asyncAdd(a, b, callback) {
  setTimeout(function () {
    callback(null, a + b);
  }, 1000);
}

// 0. promisify
const promiseAdd = (a, b) => new Promise((resolve, reject) => {
  asyncAdd(a, b, (err, res) => {
    if (err) {
      reject(err)
    } else {
      resolve(res)
    }
  })
})

// 1. 串行處理
async function serialSum(...args) {
  return args.reduce((task, now) => task.then(res => promiseAdd(res, now)), Promise.resolve(0))
}

// 2. 并行處理
async function parallelSum(...args) {
  if (args.length === 1) return args[0]
  const tasks = []
  for (let i = 0; i < args.length; i += 2) {
    tasks.push(promiseAdd(args[i], args[i + 1] || 0))
  }
  const results = await Promise.all(tasks)
  return parallelSum(...results)
}

// 測試
(async () => {
  const res1 = await serialSum(1, 2, 3, 4, 5, 8, 9, 10, 11, 12)
  console.log(res1)
  const res2 = await parallelSum(1, 2, 3, 4, 5, 8, 9, 10, 11, 12)
  console.log(res2)
})()
  1. vue reactive
// Dep module
class Dep {
  static stack = []
  static target = null
  deps = null
  
  constructor() {
    this.deps = new Set()
  }

  depend() {
    if (Dep.target) {
      this.deps.add(Dep.target)
    }
  }

  notify() {
    this.deps.forEach(w => w.update())
  }

  static pushTarget(t) {
    if (this.target) {
      this.stack.push(this.target)
    }
    this.target = t
  }

  static popTarget() {
    this.target = this.stack.pop()
  }
}

// reactive
function reactive(o) {
  if (o && typeof o === 'object') {
    Object.keys(o).forEach(k => {
      defineReactive(o, k, o[k])
    })
  }
  return o
}

function defineReactive(obj, k, val) {
  let dep = new Dep()
  Object.defineProperty(obj, k, {
    get() {
      dep.depend()
      return val
    },
    set(newVal) {
      val = newVal
      dep.notify()
    }
  })
  if (isObj(val)) {
    reactive(val)
  }
}

// watcher
class Watcher {
  constructor(effect) {
    this.effect = effect
    this.update()
  }

  update() {
    Dep.pushTarget(this)
    this.value = this.effect()
    Dep.popTarget()
    return this.value
  }
}

// 測試代碼
const data = reactive({
  msg: 'aaa'
})

new Watcher(() => {
  console.log('===> effect', data.msg);
})

setTimeout(() => {
  data.msg = 'hello'
}, 1000)
  1. promise
// 建議閱讀 [Promises/A+ 標(biāo)準(zhǔn)](https://promisesaplus.com/)
class MyPromise {
  constructor(func) {
    this.status = 'pending'
    this.value = null
    this.resolvedTasks = []
    this.rejectedTasks = []
    this._resolve = this._resolve.bind(this)
    this._reject = this._reject.bind(this)
    try {
      func(this._resolve, this._reject)
    } catch (error) {
      this._reject(error)
    }
  }

  _resolve(value) {
    setTimeout(() => {
      this.status = 'fulfilled'
      this.value = value
      this.resolvedTasks.forEach(t => t(value))
    })
  }

  _reject(reason) {
    setTimeout(() => {
      this.status = 'reject'
      this.value = reason
      this.rejectedTasks.forEach(t => t(reason))
    })
  }

  then(onFulfilled, onRejected) {
    return new MyPromise((resolve, reject) => {
      this.resolvedTasks.push((value) => {
        try {
          const res = onFulfilled(value)
          if (res instanceof MyPromise) {
            res.then(resolve, reject)
          } else {
            resolve(res)
          }
        } catch (error) {
          reject(error)
        }
      })
      this.rejectedTasks.push((value) => {
        try {
          const res = onRejected(value)
          if (res instanceof MyPromise) {
            res.then(resolve, reject)
          } else {
            reject(res)
          }
        } catch (error) {
          reject(error)
        }
      })
    })
  }

  catch(onRejected) {
    return this.then(null, onRejected);
  }
}

// 測試
new MyPromise((resolve) => {
  setTimeout(() => {
    resolve(1);
  }, 500);
})
  .then((res) => {
    console.log(res);
    return new MyPromise((resolve) => {
      setTimeout(() => {
        resolve(2);
      }, 500);
    });
  })
  .then((res) => {
    console.log(res);
  }, err => {
    console.log('==>', err);
  });
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容