1. 防抖

触发高频事件后n秒内函数只会执行一次,如果n秒内高频事件再次被触发,则重新计算时间

  1. function debounce(func, ms = 1000) {
  2. let timer;
  3. return function (...args) {
  4. if (timer) {
  5. clearTimeout(timer)
  6. }
  7. timer = setTimeout(() => {
  8. func.apply(this, args)
  9. }, ms)
  10. }
  11. }
  12. // 测试
  13. const task = () => { console.log('run task') }
  14. const debounceTask = debounce(task, 1000)
  15. window.addEventListener('scroll', debounceTask)

2. 节流

高频事件触发,但在n秒内只会执行一次,所以节流会稀释函数的执行频率

  1. function throttle(func, ms = 1000) {
  2. let canRun = true
  3. return function (...args) {
  4. if (!canRun) return
  5. canRun = false
  6. setTimeout(() => {
  7. func.apply(this, args)
  8. canRun = true
  9. }, ms)
  10. }
  11. }
  12. // 测试
  13. const task = () => { console.log('run task') }
  14. const throttleTask = throttle(task, 1000)
  15. window.addEventListener('scroll', throttleTask)

3. new

  1. function myNew(Func, ...args) {
  2. // 生成一个新的对象
  3. const instance = {};
  4. if (Func.prototype) {
  5. Object.setPrototypeOf(instance, Func.prototype)
  6. }
  7. const res = Func.apply(instance, args)
  8. if (typeof res === "function" || (typeof res === "object" && res !== null)) {
  9. return res
  10. }
  11. return instance
  12. }
  13. // 测试
  14. function Person(name) {
  15. this.name = name
  16. }
  17. Person.prototype.sayName = function() {
  18. console.log(`My name is ${this.name}`)
  19. }
  20. const me = myNew(Person, 'Jack')
  21. me.sayName()
  22. console.log(me)

4. bind

  1. Function.prototype.myBind = function (context = globalThis) {
  2. const fn = this
  3. const args = Array.from(arguments).slice(1)
  4. const newFunc = function () {
  5. const newArgs = args.concat(...arguments)
  6. if (this instanceof newFunc) {
  7. // 通过 new 调用,绑定 this 为实例对象
  8. fn.apply(this, newArgs)
  9. } else {
  10. // 通过普通函数形式调用,绑定 context
  11. fn.apply(context, newArgs)
  12. }
  13. }
  14. // 支持 new 调用方式
  15. newFunc.prototype = Object.create(fn.prototype)
  16. return newFunc
  17. }
  18. // 测试
  19. const me = { name: 'Jack' }
  20. const other = { name: 'Jackson' }
  21. function say() {
  22. console.log(`My name is ${this.name || 'default'}`);
  23. }
  24. const meSay = say.myBind(me)
  25. meSay()
  26. const otherSay = say.myBind(other)
  27. otherSay()

5. call

  1. Function.prototype.myCall = function (context = globalThis) {
  2. // 关键步骤,在 context 上调用方法,触发 this 绑定为 context,使用 Symbol 防止原有属性的覆盖
  3. const key = Symbol('key')
  4. context[key] = this
  5. // es5 可通过 for 遍历 arguments 得到参数数组
  6. const args = [...arguments].slice(1)
  7. const res = context[key](...args)
  8. delete context[key]
  9. return res
  10. };
  11. // 测试
  12. const me = { name: 'Jack' }
  13. function say() {
  14. console.log(`My name is ${this.name || 'default'}`);
  15. }
  16. say.myCall(me)

6. apply

  1. Function.prototype.myApply = function (context = globalThis) {
  2. // 关键步骤,在 context 上调用方法,触发 this 绑定为 context,使用 Symbol 防止原有属性的覆盖
  3. const key = Symbol('key')
  4. context[key] = this
  5. let res
  6. if (arguments[1]) {
  7. res = context[key](...arguments[1])
  8. } else {
  9. res = context[key]()
  10. }
  11. delete context[key]
  12. return res
  13. }
  14. // 测试
  15. const me = { name: 'Jack' }
  16. function say() {
  17. console.log(`My name is ${this.name || 'default'}`);
  18. }
  19. say.myApply(me)

7. deepCopy

  1. function deepCopy(obj, cache = new WeakMap()) {
  2. if (!obj instanceof Object) return obj
  3. // 防止循环引用
  4. if (cache.get(obj)) return cache.get(obj)
  5. // 支持函数
  6. if (obj instanceof Function) {
  7. return function () {
  8. return obj.apply(this, arguments)
  9. }
  10. }
  11. // 支持日期
  12. if (obj instanceof Date) return new Date(obj)
  13. // 支持正则对象
  14. if (obj instanceof RegExp) return new RegExp(obj.source, obj.flags)
  15. // 还可以增加其他对象,比如:Map, Set等,根据情况判断增加即可,面试点到为止就可以了
  16. // 数组是 key 为数字素银的特殊对象
  17. const res = Array.isArray(obj) ? [] : {}
  18. // 缓存 copy 的对象,用于处理循环引用的情况
  19. cache.set(obj, res)
  20. // Object.keys() 方法会返回一个由一个给定对象的自身可枚举属性组成的数组,数组中属性名的排列顺序和正常循环遍历该对象时返回的顺序一致 。
  21. Object.keys(obj).forEach((key) => {
  22. if (obj[key] instanceof Object) {
  23. res[key] = deepCopy(obj[key], cache)
  24. } else {
  25. res[key] = obj[key]
  26. }
  27. });
  28. return res
  29. }
  30. // 测试
  31. const source = {
  32. name: 'Jack',
  33. meta: {
  34. age: 12,
  35. birth: new Date('1997-10-10'),
  36. ary: [1, 2, { a: 1 }],
  37. say() {
  38. console.log('Hello');
  39. }
  40. }
  41. }
  42. source.source = source
  43. const newObj = deepCopy(source)
  44. console.log(newObj.meta.ary[2] === source.meta.ary[2]); // false
  45. console.log(newObj.meta.birth === source.meta.birth); // false

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

  1. // 8. 事件总线 | 发布订阅模式
  2. class EventEmitter {
  3. constructor() {
  4. this.cache = {}
  5. }
  6. on(name, fn) {
  7. if (this.cache[name]) {
  8. this.cache[name].push(fn)
  9. } else {
  10. this.cache[name] = [fn]
  11. }
  12. }
  13. off(name, fn) {
  14. const tasks = this.cache[name]
  15. if (tasks) {
  16. const index = tasks.findIndex((f) => f === fn || f.callback === fn)
  17. if (index >= 0) {
  18. tasks.splice(index, 1)
  19. }
  20. }
  21. }
  22. emit(name) {
  23. if (this.cache[name]) {
  24. // 创建副本,如果回调函数内继续注册相同事件,会造成死循环
  25. const tasks = this.cache[name].slice()
  26. for (let fn of tasks) {
  27. fn();
  28. }
  29. }
  30. }
  31. emit(name, once = false) {
  32. if (this.cache[name]) {
  33. // 创建副本,如果回调函数内继续注册相同事件,会造成死循环
  34. const tasks = this.cache[name].slice()
  35. for (let fn of tasks) {
  36. fn();
  37. }
  38. if (once) {
  39. delete this.cache[name]
  40. }
  41. }
  42. }
  43. }
  44. // 测试
  45. const eventBus = new EventEmitter()
  46. const task1 = () => { console.log('task1'); }
  47. const task2 = () => { console.log('task2'); }
  48. eventBus.on('task', task1)
  49. eventBus.on('task', task2)
  50. setTimeout(() => {
  51. eventBus.emit('task')
  52. }, 1000)

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

  1. function curry(func) {
  2. return function curried(...args) {
  3. // 关键知识点:function.length 用来获取函数的形参个数
  4. // 补充:arguments.length 获取的是实参个数
  5. if (args.length >= func.length) {
  6. return func.apply(this, args)
  7. }
  8. return function (...args2) {
  9. return curried.apply(this, args.concat(args2))
  10. }
  11. }
  12. }
  13. // 测试
  14. function sum (a, b, c) {
  15. return a + b + c
  16. }
  17. const curriedSum = curry(sum)
  18. console.log(curriedSum(1, 2, 3))
  19. console.log(curriedSum(1)(2,3))
  20. console.log(curriedSum(1)(2)(3))

11. instanceof

  1. function isInstanceOf(instance, klass) {
  2. let proto = instance.__proto__
  3. let prototype = klass.prototype
  4. while (true) {
  5. if (proto === null) return false
  6. if (proto === prototype) return true
  7. proto = proto.__proto__
  8. }
  9. }
  10. // 测试
  11. class Parent {}
  12. class Child extends Parent {}
  13. const child = new Child()
  14. console.log(isInstanceOf(child, Parent), isInstanceOf(child, Child), isInstanceOf(child, Array));

12. 异步并发数限制

  1. /**
  2. * 关键点
  3. * 1. new promise 一经创建,立即执行
  4. * 2. 使用 Promise.resolve().then 可以把任务加到微任务队列,防止立即执行迭代方法
  5. * 3. 微任务处理过程中,产生的新的微任务,会在同一事件循环内,追加到微任务队列里
  6. * 4. 使用 race 在某个任务完成时,继续添加任务,保持任务按照最大并发数进行执行
  7. * 5. 任务完成后,需要从 doingTasks 中移出
  8. */
  9. function limit(count, array, iterateFunc) {
  10. const tasks = []
  11. const doingTasks = []
  12. let i = 0
  13. const enqueue = () => {
  14. if (i === array.length) {
  15. return Promise.resolve()
  16. }
  17. const task = Promise.resolve().then(() => iterateFunc(array[i++]))
  18. tasks.push(task)
  19. const doing = task.then(() => doingTasks.splice(doingTasks.indexOf(doing), 1))
  20. doingTasks.push(doing)
  21. const res = doingTasks.length >= count ? Promise.race(doingTasks) : Promise.resolve()
  22. return res.then(enqueue)
  23. };
  24. return enqueue().then(() => Promise.all(tasks))
  25. }
  26. // test
  27. const timeout = i => new Promise(resolve => setTimeout(() => resolve(i), i))
  28. limit(2, [1000, 1000, 1000, 1000], timeout).then((res) => {
  29. console.log(res)
  30. })

13. 异步串行 | 异步并行

  1. // 字节面试题,实现一个异步加法
  2. function asyncAdd(a, b, callback) {
  3. setTimeout(function () {
  4. callback(null, a + b);
  5. }, 500);
  6. }
  7. // 解决方案
  8. // 1. promisify
  9. const promiseAdd = (a, b) => new Promise((resolve, reject) => {
  10. asyncAdd(a, b, (err, res) => {
  11. if (err) {
  12. reject(err)
  13. } else {
  14. resolve(res)
  15. }
  16. })
  17. })
  18. // 2. 串行处理
  19. async function serialSum(...args) {
  20. return args.reduce((task, now) => task.then(res => promiseAdd(res, now)), Promise.resolve(0))
  21. }
  22. // 3. 并行处理
  23. async function parallelSum(...args) {
  24. if (args.length === 1) return args[0]
  25. const tasks = []
  26. for (let i = 0; i < args.length; i += 2) {
  27. tasks.push(promiseAdd(args[i], args[i + 1] || 0))
  28. }
  29. const results = await Promise.all(tasks)
  30. return parallelSum(...results)
  31. }
  32. // 测试
  33. (async () => {
  34. console.log('Running...');
  35. const res1 = await serialSum(1, 2, 3, 4, 5, 8, 9, 10, 11, 12)
  36. console.log(res1)
  37. const res2 = await parallelSum(1, 2, 3, 4, 5, 8, 9, 10, 11, 12)
  38. console.log(res2)
  39. console.log('Done');
  40. })()

14. vue reactive

  1. // Dep module
  2. class Dep {
  3. static stack = []
  4. static target = null
  5. deps = null
  6. constructor() {
  7. this.deps = new Set()
  8. }
  9. depend() {
  10. if (Dep.target) {
  11. this.deps.add(Dep.target)
  12. }
  13. }
  14. notify() {
  15. this.deps.forEach(w => w.update())
  16. }
  17. static pushTarget(t) {
  18. if (this.target) {
  19. this.stack.push(this.target)
  20. }
  21. this.target = t
  22. }
  23. static popTarget() {
  24. this.target = this.stack.pop()
  25. }
  26. }
  27. // reactive
  28. function reactive(o) {
  29. if (o && typeof o === 'object') {
  30. Object.keys(o).forEach(k => {
  31. defineReactive(o, k, o[k])
  32. })
  33. }
  34. return o
  35. }
  36. function defineReactive(obj, k, val) {
  37. let dep = new Dep()
  38. Object.defineProperty(obj, k, {
  39. get() {
  40. dep.depend()
  41. return val
  42. },
  43. set(newVal) {
  44. val = newVal
  45. dep.notify()
  46. }
  47. })
  48. if (val && typeof val === 'object') {
  49. reactive(val)
  50. }
  51. }
  52. // watcher
  53. class Watcher {
  54. constructor(effect) {
  55. this.effect = effect
  56. this.update()
  57. }
  58. update() {
  59. Dep.pushTarget(this)
  60. this.value = this.effect()
  61. Dep.popTarget()
  62. return this.value
  63. }
  64. }
  65. // 测试代码
  66. const data = reactive({
  67. msg: 'aaa'
  68. })
  69. new Watcher(() => {
  70. console.log('===> effect', data.msg);
  71. })
  72. setTimeout(() => {
  73. data.msg = 'hello'
  74. }, 1000)

15. promise

  1. // 建议阅读 [Promises/A+ 标准](https://promisesaplus.com/)
  2. class MyPromise {
  3. constructor(func) {
  4. this.status = 'pending'
  5. this.value = null
  6. this.resolvedTasks = []
  7. this.rejectedTasks = []
  8. this._resolve = this._resolve.bind(this)
  9. this._reject = this._reject.bind(this)
  10. try {
  11. func(this._resolve, this._reject)
  12. } catch (error) {
  13. this._reject(error)
  14. }
  15. }
  16. _resolve(value) {
  17. setTimeout(() => {
  18. this.status = 'fulfilled'
  19. this.value = value
  20. this.resolvedTasks.forEach(t => t(value))
  21. })
  22. }
  23. _reject(reason) {
  24. setTimeout(() => {
  25. this.status = 'reject'
  26. this.value = reason
  27. this.rejectedTasks.forEach(t => t(reason))
  28. })
  29. }
  30. then(onFulfilled, onRejected) {
  31. return new MyPromise((resolve, reject) => {
  32. this.resolvedTasks.push((value) => {
  33. try {
  34. const res = onFulfilled(value)
  35. if (res instanceof MyPromise) {
  36. res.then(resolve, reject)
  37. } else {
  38. resolve(res)
  39. }
  40. } catch (error) {
  41. reject(error)
  42. }
  43. })
  44. this.rejectedTasks.push((value) => {
  45. try {
  46. const res = onRejected(value)
  47. if (res instanceof MyPromise) {
  48. res.then(resolve, reject)
  49. } else {
  50. reject(res)
  51. }
  52. } catch (error) {
  53. reject(error)
  54. }
  55. })
  56. })
  57. }
  58. catch(onRejected) {
  59. return this.then(null, onRejected);
  60. }
  61. }
  62. // 测试
  63. new MyPromise((resolve) => {
  64. setTimeout(() => {
  65. resolve(1);
  66. }, 500);
  67. }).then((res) => {
  68. console.log(res);
  69. return new MyPromise((resolve) => {
  70. setTimeout(() => {
  71. resolve(2);
  72. }, 500);
  73. });
  74. }).then((res) => {
  75. console.log(res);
  76. throw new Error('a error')
  77. }).catch((err) => {
  78. console.log('==>', err);
  79. })

16. 数组扁平化

  1. // 方案 1
  2. function recursionFlat(ary = []) {
  3. const res = []
  4. ary.forEach(item => {
  5. if (Array.isArray(item)) {
  6. res.push(...recursionFlat(item))
  7. } else {
  8. res.push(item)
  9. }
  10. })
  11. return res
  12. }
  13. // 方案 2
  14. function reduceFlat(ary = []) {
  15. return ary.reduce((res, item) => res.concat(Array.isArray(item) ? reduceFlat(item) : item), [])
  16. }
  17. // 测试
  18. const source = [1, 2, [3, 4, [5, 6]], '7']
  19. console.log(recursionFlat(source))
  20. console.log(reduceFlat(source))

17. 对象扁平化

  1. function objectFlat(obj = {}) {
  2. const res = {}
  3. function flat(item, preKey = '') {
  4. Object.entries(item).forEach(([key, val]) => {
  5. const newKey = preKey ? `${preKey}.${key}` : key
  6. if (val && typeof val === 'object') {
  7. flat(val, newKey)
  8. } else {
  9. res[newKey] = val
  10. }
  11. })
  12. }
  13. flat(obj)
  14. return res
  15. }
  16. // 测试
  17. const source = { a: { b: { c: 1, d: 2 }, e: 3 }, f: { g: 2 } }
  18. console.log(objectFlat(source));

18. 图片懒加载

  1. // <img src="default.png" data-src="https://xxxx/real.png">
  2. function isVisible(el) {
  3. const position = el.getBoundingClientRect()
  4. const windowHeight = document.documentElement.clientHeight
  5. // 顶部边缘可见
  6. const topVisible = position.top > 0 && position.top < windowHeight;
  7. // 底部边缘可见
  8. const bottomVisible = position.bottom < windowHeight && position.bottom > 0;
  9. return topVisible || bottomVisible;
  10. }
  11. function imageLazyLoad() {
  12. const images = document.querySelectorAll('img')
  13. for (let img of images) {
  14. const realSrc = img.dataset.src
  15. if (!realSrc) continue
  16. if (isVisible(img)) {
  17. img.src = realSrc
  18. img.dataset.src = ''
  19. }
  20. }
  21. }
  22. // 测试
  23. window.addEventListener('load', imageLazyLoad)
  24. window.addEventListener('scroll', imageLazyLoad)
  25. // or
  26. window.addEventListener('scroll', throttle(imageLazyLoad, 1000))