乐趣区

关于前端:前端面试中常见的手写功能

1. 防抖

function debounce(func, ms = 1000) {
  let timer;
  return function (...args) {if (timer) {clearTimeout(timer)
    }
    timer = setTimeout(() => {func.apply(this, args)
    }, ms)
  }
}

// 测试
const task = () => { console.log('run task') }
const debounceTask = debounce(task, 1000)
window.addEventListener('scroll', debounceTask)

2. 节流

function throttle(func, ms = 1000) {
  let canRun = true
  return function (...args) {if (!canRun) return
    canRun = false
    setTimeout(() => {func.apply(this, args)
      canRun = true
    }, ms)
  }
}

// 测试
const task = () => { console.log('run task') }
const throttleTask = throttle(task, 1000)
window.addEventListener('scroll', throttleTask)

3. new

function myNew(Func, ...args) {const instance = {};
  if (Func.prototype) {Object.setPrototypeOf(instance, Func.prototype)
  }
  const res = Func.apply(instance, args)
  if (typeof res === "function" || (typeof res === "object" && res !== null)) {return res}
  return instance
}

// 测试
function Person(name) {this.name = name}
Person.prototype.sayName = function() {console.log(`My name is ${this.name}`)
}
const me = myNew(Person, 'Jack')
me.sayName()
console.log(me)

4. bind

Function.prototype.myBind = function (context = globalThis) {
  const fn = this
  const args = Array.from(arguments).slice(1)
  const newFunc = function () {const newArgs = args.concat(...arguments)
    if (this instanceof newFunc) {
      // 通过 new 调用,绑定 this 为实例对象
      fn.apply(this, newArgs)
    } else {
      // 通过一般函数模式调用,绑定 context
      fn.apply(context, newArgs)
    }
  }
  // 反对 new 调用形式
  newFunc.prototype = Object.create(fn.prototype)
  return newFunc
}

// 测试
const me = {name: 'Jack'}
const other = {name: 'Jackson'}
function say() {console.log(`My name is ${this.name || 'default'}`);
}
const meSay = say.bind(me)
meSay()
const otherSay = say.bind(other)
otherSay()

5. call

Function.prototype.myCall = function (context = globalThis) {
  // 关键步骤,在 context 上调用办法,触发 this 绑定为 context,应用 Symbol 避免原有属性的笼罩
  const key = Symbol('key')
  context[key] = this
  let args = [].slice.call(arguments, 1)
  let res = context[key](...args)
  delete context[key]
  return res
};

// 测试
const me = {name: 'Jack'}
function say() {console.log(`My name is ${this.name || 'default'}`);
}
say.myCall(me)

6. apply

Function.prototype.myApply = function (context = globalThis) {
  // 关键步骤,在 context 上调用办法,触发 this 绑定为 context,应用 Symbol 避免原有属性的笼罩
  const key = Symbol('key')
  context[key] = this
  let res
  if (arguments[1]) {res = context[key](...arguments[1])
  } else {res = context[key]()}
  delete context[key]
  return res
}

// 测试
const me = {name: 'Jack'}
function say() {console.log(`My name is ${this.name || 'default'}`);
}
say.myApply(me)

7. deepCopy

function deepCopy(obj, cache = new WeakMap()) {if (!obj instanceof Object) return obj
  // 避免循环援用
  if (cache.get(obj)) return cache.get(obj)
  // 反对函数
  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 等,依据状况判断减少即可,面试点到为止就能够了

  // 数组是 key 为数字素银的非凡对象
  const res = Array.isArray(obj) ? [] : {}
  // 缓存 copy 的对象,用于解决循环援用的状况
  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
}

// 测试
const source = {
  name: 'Jack',
  meta: {
    age: 12,
    birth: new Date('1997-10-10'),
    ary: [1, 2, { a: 1}],
    say() {console.log('Hello');
    }
  }
}
source.source = source
const newObj = deepCopy(source)
console.log(newObj.meta.ary[2] === source.meta.ary[2]);

8. 事件总线 | 公布订阅模式

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]) {
      // 创立正本,如果回调函数内持续注册雷同事件,会造成死循环
      const tasks = this.cache[name].slice()
      for (let fn of tasks) {fn();
      }
    }
  }

  emit(name, once = false) {if (this.cache[name]) {
      // 创立正本,如果回调函数内持续注册雷同事件,会造成死循环
      const tasks = this.cache[name].slice()
      for (let fn of tasks) {fn();
      }
      if (once) {delete this.cache[name]
      }
    }
  }
}

// 测试
const eventBus = new EventEmitter()
const task1 = () => { console.log('task1'); }
const task2 = () => { console.log('task2'); }
eventBus.on('task', task1)
eventBus.on('task', task2)

setTimeout(() => {eventBus.emit('task')
}, 1000)

9. 柯里化:只传递给函数一部分参数来调用它,让它返回一个函数去解决剩下的参数

function curry(func) {return function curried(...args) {
    // 要害知识点:function.length 用来获取函数的形参个数
    // 补充:arguments.length 获取的是实参个数
    if (args.length >= func.length) {return func.apply(this, args)
    }
    return function (...args2) {return curried.apply(this, args.concat(args2))
    }
  }
}

// 测试
function sum (a, b, c) {return a + b + c}
const curriedSum = curry(sum)
console.log(curriedSum(1, 2, 3))
console.log(curriedSum(1)(2,3))
console.log(curriedSum(1)(2)(3))

10. es5 实现继承

function create(proto) {function F() {}
  F.prototype = proto;
  return new F();}

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

Parent.prototype.sayName = 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.sayAge = function () {console.log(this.age)
}

// 测试
const child = new Child(18, 'Jack')
child.sayName()
child.sayAge()

11. instanceof

function isInstanceOf(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__
  }
}

// 测试
class Parent {}
class Child extends Parent {}
const child = new Child()
console.log(isInstanceOf(child, Parent), isInstanceOf(child, Child), isInstanceOf(child, Array));

12. 异步并发数限度

/**
 * 关键点
 * 1. new promise 一经创立,立刻执行
 * 2. 应用 Promise.resolve().then 能够把工作加到微工作队列,避免立刻执行迭代办法
 * 3. 微工作处理过程中,产生的新的微工作,会在同一事件循环内,追加到微工作队列里
 * 4. 应用 race 在某个工作实现时,持续增加工作,放弃工作依照最大并发数进行执行
 * 5. 工作实现后,须要从 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(2, [1000, 1000, 1000, 1000], timeout).then((res) => {console.log(res)
})

13. 异步串行 | 异步并行

// 字节面试题,实现一个异步加法
function asyncAdd(a, b, callback) {setTimeout(function () {callback(null, a + b);
  }, 500);
}

// 解决方案
// 1. promisify
const promiseAdd = (a, b) => new Promise((resolve, reject) => {asyncAdd(a, b, (err, res) => {if (err) {reject(err)
    } else {resolve(res)
    }
  })
})

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

// 3. 并行处理
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 () => {console.log('Running...');
  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)
  console.log('Done');
})()

14. 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 (val && typeof val === 'object') {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)

15. promise

// 倡议浏览 [Promises/A+ 规范](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);
    throw new Error('a error')
  }).catch((err) => {console.log('==>', err);
  })

16. 数组扁平化

// 计划 1
function recursionFlat(ary = []) {const res = []
  ary.forEach(item => {if (Array.isArray(item)) {res.push(...recursionFlat(item))
    } else {res.push(item)
    }
  })
  return res
}
// 计划 2
function reduceFlat(ary = []) {return ary.reduce((res, item) => res.concat(Array.isArray(item) ? reduceFlat(item) : item), [])
}

// 测试
const source = [1, 2, [3, 4, [5, 6]], '7']
console.log(recursionFlat(source))
console.log(reduceFlat(source))

17. 对象扁平化

function objectFlat(obj = {}) {const res = {}
  function flat(item, preKey = '') {Object.entries(item).forEach(([key, val]) => {const newKey = preKey ? `${preKey}.${key}` : key
      if (val && typeof val === 'object') {flat(val, newKey)
      } else {res[newKey] = val
      }
    })
  }
  flat(obj)
  return res
}

// 测试
const source = {a: { b: { c: 1, d: 2}, e: 3 }, f: {g: 2} }
console.log(objectFlat(source));

18. 图片懒加载

// <img src="default.png" data-src="https://xxxx/real.png">
function isVisible(el) {const position = el.getBoundingClientRect()
  const windowHeight = document.documentElement.clientHeight
  // 顶部边缘可见
  const topVisible = position.top > 0 && position.top < windowHeight;
  // 底部边缘可见
  const bottomVisible = position.bottom < windowHeight && position.bottom > 0;
  return topVisible || bottomVisible;
}

function imageLazyLoad() {const images = document.querySelectorAll('img')
  for (let img of images) {
    const realSrc = img.dataset.src
    if (!realSrc) continue
    if (isVisible(img)) {
      img.src = realSrc
      img.dataset.src = ''
    }
  }
}

// 测试
window.addEventListener('load', imageLazyLoad)
window.addEventListener('scroll', imageLazyLoad)
// or
window.addEventListener('scroll', throttle(imageLazyLoad, 1000))

欢送一起补充 ~ Github 地址

退出移动版