十个常见的前端手写功能,你全都会吗?

时间:2022-09-24 18:52:02

万丈高楼平地起,地基打的牢,才能永远立于不败之地。今天给大家带来的是10个常见的 JavaScript 手写功能,重要的地方已添加注释。有的是借鉴别人的,有的是自己写的,如有不正确的地方,欢迎多多指正。

十个常见的前端手写功能,你全都会吗?

1、防抖

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

2、节流

  1. function throttle(fn, delay) {
  2. let last = 0 // 上次触发时间
  3. return (...args) => {
  4. const now = Date.now()
  5. if (now - last > delay) {
  6. last = now
  7. fn.apply(this, args)
  8. }
  9. }
  10. }
  11. // 测试
  12. function task() {
  13. console.log('run task')
  14. }
  15. const throttleTask = throttle(task, 1000)
  16. window.addEventListener('scroll', throttleTask)

3、深拷贝

  1. function deepClone(obj, cache = new WeakMap()) {
  2. if (typeof obj !== 'object') return obj // 普通类型,直接返回
  3. if (obj === null) return obj
  4. if (cache.get(obj)) return cache.get(obj) // 防止循环引用,程序进入死循环
  5. if (obj instanceof Date) return new Date(obj)
  6. if (obj instanceof RegExp) return new RegExp(obj)
  7. // 找到所属原型上的constructor,所属原型上的constructor指向当前对象的构造函数
  8. let cloneObj = new obj.constructor()
  9. cache.set(obj, cloneObj) // 缓存拷贝的对象,用于处理循环引用的情况
  10. for (let key in obj) {
  11. if (obj.hasOwnProperty(key)) {
  12. cloneObj[key] = deepClone(obj[key], cache) // 递归拷贝
  13. }
  14. }
  15. return cloneObj
  16. }
  17. // 测试
  18. const obj = { name: 'Jack', address: { x: 100, y: 200 } }
  19. obj.a = obj // 循环引用
  20. const newObj = deepClone(obj)
  21. console.log(newObj.address === obj.address) // false

4、实现 Promise

  1. class MyPromise {
  2. constructor(executor) { // executor执行器
  3. this.status = 'pending' // 等待状态
  4. this.value = null // 成功或失败的参数
  5. this.fulfilledCallbacks = [] // 成功的函数队列
  6. this.rejectedCallbacks = [] // 失败的函数队列
  7. const that = this
  8. function resolve(value) { // 成功的方法
  9. if (that.status === 'pending') {
  10. that.status = 'resolved'
  11. that.value = value
  12. that.fulfilledCallbacks.forEach(myFn => myFn(that.value)) //执行回调方法
  13. }
  14. }
  15. function reject(value) { //失败的方法
  16. if (that.status === 'pending') {
  17. that.status = 'rejected'
  18. that.value = value
  19. that.rejectedCallbacks.forEach(myFn => myFn(that.value)) //执行回调方法
  20. }
  21. }
  22. try {
  23. executor(resolve, reject)
  24. } catch (err) {
  25. reject(err)
  26. }
  27. }
  28. then(onFulfilled, onRejected) {
  29. if (this.status === 'pending') {
  30. // 等待状态,添加回调函数到成功的函数队列
  31. this.fulfilledCallbacks.push(() => {
  32. onFulfilled(this.value)
  33. })
  34. // 等待状态,添加回调函数到失败的函数队列
  35. this.rejectedCallbacks.push(() => {
  36. onRejected(this.value)
  37. })
  38. }
  39. if (this.status === 'resolved') { // 支持同步调用
  40. console.log('this', this)
  41. onFulfilled(this.value)
  42. }
  43. if (this.status === 'rejected') { // 支持同步调用
  44. onRejected(this.value)
  45. }
  46. }
  47. }
  48. // 测试
  49. function fn() {
  50. return new MyPromise((resolve, reject) => {
  51. setTimeout(() => {
  52. if(Math.random() > 0.6) {
  53. resolve(1)
  54. } else {
  55. reject(2)
  56. }
  57. }, 1000)
  58. })
  59. }
  60. fn().then(
  61. res => {
  62. console.log('res', res) // res 1
  63. },
  64. err => {
  65. console.log('err', err) // err 2
  66. })

5、异步控制并发数

  1. function limitRequest(urls = [], limit = 3) {
  2. return new Promise((resolve, reject) => {
  3. const len = urls.length
  4. let count = 0 // 当前进行到第几个任务
  5. const start = async () => {
  6. const url = urls.shift() // 从数组中拿取第一个任务
  7. if (url) {
  8. try {
  9. await axios.post(url)
  10. if (count == len - 1) {
  11. // 最后一个任务成功
  12. resolve()
  13. } else {
  14. count++
  15. // 成功,启动下一个任务
  16. start()
  17. }
  18. } catch (e) {
  19. if (count == len - 1) {
  20. // 最后一个任务失败
  21. resolve()
  22. } else {
  23. count++
  24. // 失败,启动下一个任务
  25. start()
  26. }
  27. }
  28. }
  29. }
  30. // 启动limit个任务
  31. while (limit > 0) {
  32. start()
  33. limit -= 1
  34. }
  35. })
  36. }
  37. // 测试
  38. limitRequest(['http://xxa', 'http://xxb', 'http://xxc', 'http://xxd', 'http://xxe'])

6、ES5继承(寄生组合继承)

  1. function Parent(name) {
  2. this.name = name
  3. }
  4. Parent.prototype.eat = function () {
  5. console.log(this.name + ' is eating')
  6. }
  7. function Child(name, age) {
  8. Parent.call(this, name)
  9. this.age = age
  10. }
  11. Child.prototype = Object.create(Parent.prototype)
  12. Child.prototype.contructor = Child
  13. Child.prototype.study = function () {
  14. console.log(this.name + ' is studying')
  15. }
  16. // 测试
  17. let child = new Child('xiaoming', 16)
  18. console.log(child.name) // xiaoming
  19. child.eat() // xiaoming is eating
  20. child.study() // xiaoming is studying

7、数组排序

sort 排序

  1. // 对数字进行排序,简写
  2. const arr = [3, 2, 4, 1, 5]
  3. arr.sort((a, b) => a - b)
  4. console.log(arr) // [1, 2, 3, 4, 5]
  5. // 对字母进行排序,简写
  6. const arr = ['b', 'c', 'a', 'e', 'd']
  7. arr.sort()
  8. console.log(arr) // ['a', 'b', 'c', 'd', 'e']

冒泡排序

  1. function bubbleSort(arr) {
  2. let len = arr.length
  3. for (let i = 0; i < len - 1; i++) {
  4. // 从第一个元素开始,比较相邻的两个元素,前者大就交换位置
  5. for (let j = 0; j < len - 1 - i; j++) {
  6. if (arr[j] > arr[j + 1]) {
  7. let num = arr[j]
  8. arr[j] = arr[j + 1]
  9. arr[j + 1] = num
  10. }
  11. }
  12. // 每次遍历结束,都能找到一个最大值,放在数组最后
  13. }
  14. return arr
  15. }
  16. //测试
  17. console.log(bubbleSort([2, 3, 1, 5, 4])) // [1, 2, 3, 4, 5]

8、数组去重

Set 去重

  1. cosnt newArr = [...new Set(arr)]

Array.from 去重

  1. const newArr = Array.from(new Set(arr))

indexOf 去重

  1. function resetArr(arr) {
  2. let res = []
  3. arr.forEach(item => {
  4. if (res.indexOf(item) === -1) {
  5. res.push(item)
  6. }
  7. })
  8. return res
  9. }
  10. // 测试
  11. const arr = [1, 1, 2, 3, 3]
  12. console.log(resetArr(arr)) // [1, 2, 3]

9、获取 url 参数

URLSearchParams 方法

  1. // 创建一个URLSearchParams实例
  2. const urlSearchParams = new URLSearchParams(window.location.search);
  3. // 把键值对列表转换为一个对象
  4. const params = Object.fromEntries(urlSearchParams.entries());

split 方法

  1. function getParams(url) {
  2. const res = {}
  3. if (url.includes('?')) {
  4. const str = url.split('?')[1]
  5. const arr = str.split('&')
  6. arr.forEach(item => {
  7. const key = item.split('=')[0]
  8. const val = item.split('=')[1]
  9. res[key] = decodeURIComponent(val) // 解码
  10. })
  11. }
  12. return res
  13. }
  14. // 测试
  15. const user = getParams('http://www.baidu.com?user=%E9%98%BF%E9%A3%9E&age=16')
  16. console.log(user) // { user: '阿飞', age: '16' }

10、事件总线 | 发布订阅模式

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

以上就是工作或求职中最常见的手写功能,你是不是全都掌握了呢

原文链接:https://www.toutiao.com/a7042179770611696158/