10个常见的前端手写功能

10个常见的前端手写功能

  • 1、防抖
  • 2、节流
  • 3、深拷贝
    • JSON方法
    • 递归拷贝
  • 4、手写Promise
  • 5、异步控制并发数
  • 6、继承
    • ES5 继承(寄生组合继承)
    • ES6继承
  • 7、数组排序
    • sort排序
    • 冒泡排序
  • 8、数组去重
    • Set去重
    • indexOf去重
  • 9、获取 url 参数
    • URLSearchParams 方法
    • split 方法
  • 10、发布订阅模式

1、防抖

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

function debouncefn, delay) {let timerreturn function ...args) {if timer) {clearTimeouttimer)}timer = setTimeout) => {fn.applythis, args)}, delay)}
}// 测试
function task) {console.log'run task')
}
const debounceTask = debouncetask, 1000)
window.addEventListener'scroll', debounceTask)

2、节流

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

function throttlefn, delay) {let last = 0 // 上次触发时间return function ...args) {const now = Date.now)if now - last > delay) {last = nowfn.applythis, args)}}
}// 测试
function task) {console.log'run task')
}
const throttleTask = throttletask, 1000)
window.addEventListener'scroll', throttleTask)

3、深拷贝

JSON方法

// 不支持值为undefined、函数和循环引用的情况
const cloneObj = JSON.parseJSON.stringifyobj))

递归拷贝

function deepCloneobj, cache = new WeakMap)) {if obj === null || typeof obj !== 'object') return objif obj instanceof Date) return new Dateobj)if obj instanceof RegExp) return new RegExpobj)if cache.hasobj)) return cache.getobj) // 如果出现循环引用,则返回缓存的对象,防止递归进入死循环let cloneObj = new obj.constructor) // 使用对象所属的构造函数创建一个新对象cache.setobj, cloneObj) // 缓存对象,用于循环引用的情况for let key in obj) {if obj.hasOwnPropertykey)) {cloneObj[key] = deepCloneobj[key], cache) // 递归拷贝}}return cloneObj
}// 测试
const obj = { name: 'Jack', address: { x: 100, y: 200 } }
obj.a = obj // 循环引用
const newObj = deepCloneobj)
console.lognewObj.address === obj.address) // false

4、手写Promise

class MyPromise {constructorexecutor) {this.status = 'pending' // 初始状态为等待this.value = null // 成功的值this.reason = null // 失败的原因this.onFulfilledCallbacks = [] // 成功的回调函数数组this.onRejectedCallbacks = [] // 失败的回调函数数组let resolve = value => {if this.status === 'pending') {this.status = 'fulfilled'this.value = value;this.onFulfilledCallbacks.forEachfn => fn)) // 调用成功的回调函数}}let reject = reason => {if this.status === 'pending') {this.status = 'rejected'this.reason = reasonthis.onRejectedCallbacks.forEachfn => fn)) // 调用失败的回调函数}};try {executorresolve, reject)} catch err) {rejecterr)}}thenonFulfilled, onRejected) {return new MyPromiseresolve, reject) => {if this.status === 'fulfilled') {setTimeout) => {const x = onFulfilledthis.value);x instanceof MyPromise ? x.thenresolve, reject) : resolvex)})}if this.status === 'rejected') {setTimeout) => {const x = onRejectedthis.reason)x instanceof MyPromise ? x.thenresolve, reject) : resolvex)})}if this.status === 'pending') {this.onFulfilledCallbacks.push) => { // 将成功的回调函数放入成功数组setTimeout) => {const x = onFulfilledthis.value)x instanceof MyPromise ? x.thenresolve, reject) : resolvex)})})this.onRejectedCallbacks.push) => { // 将失败的回调函数放入失败数组setTimeout) => {const x = onRejectedthis.reason)x instanceof MyPromise ? x.thenresolve, reject) : resolvex)})})}})}
}// 测试
function p1) {return new MyPromiseresolve, reject) => {setTimeoutresolve, 1000, 1)})
}
function p2) {return new MyPromiseresolve, reject) => {setTimeoutresolve, 1000, 2)})
}
p1).thenres => {console.logres) // 1return p2)
}).thenret => {console.logret) // 2
})

5、异步控制并发数

function limitRequesturls = [], limit = 3) {return new Promiseresolve, reject) => {const len = urls.lengthlet count = 0// 同时启动limit个任务while limit > 0) {start)limit -= 1}function start) {const url = urls.shift) // 从数组中拿取第一个任务if url) {axios.posturl).thenres => {// todo}).catcherr => {// todo}).finally) => {if count == len - 1) {// 最后一个任务完成resolve)} else {// 完成之后,启动下一个任务count++start)}})}}})
}// 测试
limitRequest['http://xxa', 'http://xxb', 'http://xxc', 'http://xxd', 'http://xxe'])

6、继承

ES5 继承(寄生组合继承)

function Parentname) {this.name = name
}
Parent.prototype.eat = function ) {console.logthis.name + ' is eating')
}function Childname, age) {Parent.callthis, name)this.age = age
}
Child.prototype = Object.createParent.prototype)
Child.prototype.constructor = Child// 测试
let xm = new Child'xiaoming', 12) 
console.logxm.name) // xiaoming
console.logxm.age) // 12
xm.eat) // xiaoming is eating

ES6继承

class Parent {constructorname) {this.name = name}eat) {console.logthis.name + ' is eating')}
}class Child extends Parent {constructorname, age) {supername)this.age = age}
}// 测试
let xm = new Child'xiaoming', 12) 
console.logxm.name) // xiaoming
console.logxm.age) // 12
xm.eat) // xiaoming is eating

7、数组排序

sort排序

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

冒泡排序

function bubbleSortarr) {let len = arr.lengthfor let i = 0; i < len - 1; i++) {// 从第一个元素开始,比较相邻的两个元素,前者大就交换位置for let j = 0; j < len - 1 - i; j++) {if arr[j] > arr[j + 1]) {let num = arr[j]arr[j] = arr[j + 1]arr[j + 1] = num}}// 每次遍历结束,都能找到一个最大值,放在数组最后}return arr
}//测试
console.logbubbleSort[2, 3, 1, 5, 4])) // [1, 2, 3, 4, 5]

8、数组去重

Set去重

const newArr = [...new Setarr)]
// 或
const newArr = Array.fromnew Setarr))

indexOf去重

const newArr = arr.filteritem, index) => arr.indexOfitem) === index)

9、获取 url 参数

URLSearchParams 方法

// 创建一个URLSearchParams实例
const urlSearchParams = new URLSearchParamswindow.location.search);
// 把键值对列表转换为一个对象
const params = Object.fromEntriesurlSearchParams.entries));

split 方法

function getParamsurl) {const res = {}if url.includes'?')) {const str = url.split'?')[1]const arr = str.split'&')arr.forEachitem => {const key = item.split'=')[0]const val = item.split'=')[1]res[key] = decodeURIComponentval) // 解码})}return res
}// 测试
const user = getParams'http://www.baidu.com?user=%E9%98%BF%E9%A3%9E&age=16')
console.loguser) // { user: '阿飞', age: '16' }

10、发布订阅模式

class EventEmitter {constructor) {this.cache = {}}onname, fn) {if this.cache[name]) {this.cache[name].pushfn)} else {this.cache[name] = [fn]}}offname, fn) {const tasks = this.cache[name]if tasks) {const index = tasks.findIndexf) => f === fn || f.callback === fn)if index >= 0) {tasks.spliceindex, 1)}}}emitname, 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)
eventBus.off'task', task1)
setTimeout) => {eventBus.emit'task') // task2
}, 1000)

转载于:https://juejin.cn/post/7031322059414175774

Published by

风君子

独自遨游何稽首 揭天掀地慰生平

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注