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

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

1、防抖

function debounce(fn, delay) {  let timer  return function (...args) {    if (timer) {      clearTimeout(timer)
    }
    timer = setTimeout(() => {
      fn.apply(this, args)
    }, delay)
  }
}// 测试function task() {  console.log('run task')
}const debounceTask = debounce(task, 1000)window.addEventListener('scroll', debounceTask)

2、节流

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

3、深拷贝

JSON 方法

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

递归拷贝

function deepClone(obj, cache = new WeakMap()) {  if (obj === null || typeof obj !== 'object') return obj  if (obj instanceof Date) return new Date(obj)  if (obj instanceof RegExp) return new RegExp(obj)  
  if (cache.has(obj)) return cache.get(obj) // 如果出现循环引用,则返回缓存的对象,防止递归进入死循环
  let cloneObj = new obj.constructor() // 使用对象所属的构造函数创建一个新对象
  cache.set(obj, cloneObj) // 缓存对象,用于循环引用的情况

  for (let key in obj) {    if (obj.hasOwnProperty(key)) {
      cloneObj[key] = deepClone(obj[key], cache) // 递归拷贝
    }
  }  return cloneObj
}// 测试const obj = { name: 'Jack', address: { x: 100, y: 200 } }
obj.a = obj // 循环引用const newObj = deepClone(obj)console.log(newObj.address === obj.address) // false

4、手写 Promise

class MyPromise {  constructor(executor) {    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.forEach(fn => fn()) // 调用成功的回调函数
      }
    }    let reject = reason => {      if (this.status === 'pending') {        this.status = 'rejected'
        this.reason = reason        this.onRejectedCallbacks.forEach(fn => fn()) // 调用失败的回调函数
      }
    };    try {      executor(resolve, reject)
    } catch (err) {      reject(err)
    }
  }  then(onFulfilled, onRejected) {    return new MyPromise((resolve, reject) => {      if (this.status === 'fulfilled') {        setTimeout(() => {          const x = onFulfilled(this.value);
          x instanceof MyPromise ? x.then(resolve, reject) : resolve(x)
        })
      }      if (this.status === 'rejected') {        setTimeout(() => {          const x = onRejected(this.reason)
          x instanceof MyPromise ? x.then(resolve, reject) : resolve(x)
        })
      }      if (this.status === 'pending') {        this.onFulfilledCallbacks.push(() => { // 将成功的回调函数放入成功数组
          setTimeout(() => {            const x = onFulfilled(this.value)
            x instanceof MyPromise ? x.then(resolve, reject) : resolve(x)
          })
        })        this.onRejectedCallbacks.push(() => { // 将失败的回调函数放入失败数组
          setTimeout(() => {            const x = onRejected(this.reason)
            x instanceof MyPromise ? x.then(resolve, reject) : resolve(x)
          })
        })
      }
    })
  }
}// 测试function p1() {  return new MyPromise((resolve, reject) => {    setTimeout(resolve, 1000, 1)
  })
}function p2() {  return new MyPromise((resolve, reject) => {    setTimeout(resolve, 1000, 2)
  })
}p1().then(res => {  console.log(res) // 1
  return p2()
}).then(ret => {  console.log(ret) // 2})

5、异步控制并发数

function limitRequest(urls = [], limit = 3) {  return new Promise((resolve, reject) => {    const len = urls.length
    let count = 0

    // 同时启动limit个任务
    while (limit > 0) {      start()
      limit -= 1
    }    function start() {      const url = urls.shift() // 从数组中拿取第一个任务
      if (url) {
        axios.post(url).then(res => {          // todo
        }).catch(err => {          // todo
        }).finally(() => {          if (count == len - 1) {            // 最后一个任务完成
            resolve()
          } else {            // 完成之后,启动下一个任务
            count++            start()
          }
        })
      }
    }

  })
}// 测试limitRequest(['http://xxa', 'http://xxb', 'http://xxc', 'http://xxd', 'http://xxe'])

6、继承

ES5 继承(寄生组合继承)

function Parent(name) {  this.name = name
}Parent.prototype.eat = function () {  console.log(this.name + ' is eating')
}function Child(name, age) {  Parent.call(this, name)  this.age = age
}Child.prototype = Object.create(Parent.prototype)Child.prototype.constructor = Child// 测试let xm = new Child('xiaoming', 12) 
console.log(xm.name) // xiaomingconsole.log(xm.age) // 12xm.eat() // xiaoming is eating

ES6 继承

class Parent {  constructor(name) {    this.name = name
  }  eat() {    console.log(this.name + ' is eating')
  }
}class Child extends Parent {  constructor(name, age) {    super(name)    this.age = age
  }
}// 测试let xm = new Child('xiaoming', 12) 
console.log(xm.name) // xiaomingconsole.log(xm.age) // 12xm.eat() // xiaoming is eating

7、数组排序

sort 排序

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

冒泡排序

function bubbleSort(arr) {  let len = arr.length
  for (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.log(bubbleSort([2, 3, 1, 5, 4])) // [1, 2, 3, 4, 5]

8、数组去重

Set 去重

const newArr = [...new Set(arr)]// 或const newArr = Array.from(new Set(arr))

indexOf 去重

const newArr = arr.filter((item, index) => arr.indexOf(item) === index)

9、获取 url 参数

URLSearchParams 方法

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

split 方法

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

10、发布订阅模式

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, 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://cloud.tencent.com/developer/article/2110950

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


学习编程是顺着互联网的发展潮流,是一件好事。新手如何学习编程?其实不难,不过在学习编程之前你得先了解你的目的是什么?这个很重要,因为目的决定你的发展方向、决定你的发展速度。
IT行业是什么工作做什么?IT行业的工作有:产品策划类、页面设计类、前端与移动、开发与测试、营销推广类、数据运营类、运营维护类、游戏相关类等,根据不同的分类下面有细分了不同的岗位。
女生学Java好就业吗?女生适合学Java编程吗?目前有不少女生学习Java开发,但要结合自身的情况,先了解自己适不适合去学习Java,不要盲目的选择不适合自己的Java培训班进行学习。只要肯下功夫钻研,多看、多想、多练
Can’t connect to local MySQL server through socket \'/var/lib/mysql/mysql.sock问题 1.进入mysql路径
oracle基本命令 一、登录操作 1.管理员登录 # 管理员登录 sqlplus / as sysdba 2.普通用户登录
一、背景 因为项目中需要通北京网络,所以需要连vpn,但是服务器有时候会断掉,所以写个shell脚本每五分钟去判断是否连接,于是就有下面的shell脚本。
BETWEEN 操作符选取介于两个值之间的数据范围内的值。这些值可以是数值、文本或者日期。
假如你已经使用过苹果开发者中心上架app,你肯定知道在苹果开发者中心的web界面,无法直接提交ipa文件,而是需要使用第三方工具,将ipa文件上传到构建版本,开...
下面的 SQL 语句指定了两个别名,一个是 name 列的别名,一个是 country 列的别名。**提示:**如果列名称包含空格,要求使用双引号或方括号:
在使用H5混合开发的app打包后,需要将ipa文件上传到appstore进行发布,就需要去苹果开发者中心进行发布。​
+----+--------------+---------------------------+-------+---------+
数组的声明并不是声明一个个单独的变量,比如 number0、number1、...、number99,而是声明一个数组变量,比如 numbers,然后使用 nu...
第一步:到appuploader官网下载辅助工具和iCloud驱动,使用前面创建的AppID登录。
如需删除表中的列,请使用下面的语法(请注意,某些数据库系统不允许这种在数据库表中删除列的方式):
前不久在制作win11pe,制作了一版,1.26GB,太大了,不满意,想再裁剪下,发现这次dism mount正常,commit或discard巨慢,以前都很快...
赛门铁克各个版本概览:https://knowledge.broadcom.com/external/article?legacyId=tech163829
实测Python 3.6.6用pip 21.3.1,再高就报错了,Python 3.10.7用pip 22.3.1是可以的
Broadcom Corporation (博通公司,股票代号AVGO)是全球领先的有线和无线通信半导体公司。其产品实现向家庭、 办公室和移动环境以及在这些环境...
发现个问题,server2016上安装了c4d这些版本,低版本的正常显示窗格,但红色圈出的高版本c4d打开后不显示窗格,
TAT:https://cloud.tencent.com/document/product/1340