常用的前端手写功能

常见的 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、继承

  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) // xiaoming
  console.log(xm.age) // 12
  xm.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) // xiaoming
  console.log(xm.age) // 12
  xm.eat() // xiaoming is eating

 

6、数组排序

  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]

7、数组去重

  Set 去重

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

  indexOf 去重

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

8、获取 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’ }

9、发布订阅模式

class EventEmitter {
  // 初始化事件对象
  constructor() {
    this.events = {}
  }

  // 事件监听,监听的过程就是订阅,也就是把订阅者收集起来
  on(eventName, callback) {
    // 如果不存在该事件,则进行数组初始化
    if (!this.events[eventName]) {
      this.events[eventName] = []
    }
    // 存在对应的数组继续订阅收集,则把事件推入收集数组
    this.events[eventName].push(callback)
    // 返回自身 方便链式调用
    return this
  }

  // 事件触发,触发的过程就是发布,也就是通知订阅者
  emit(eventName, ...args) {
    // 不存在该事件,则不触发
    if (!this.events[eventName]) {
      return this
    }
    // 存在则对收集的订阅者一一通知(函数一一执行)
    const fns = this.events[eventName]
    // 执行的时候绑定自身this
    fns.forEach(fn => fn.apply(this, args))
    // 返回自身 方便链式调用
    return this
  }

  // 解绑事件,取消订阅,将订阅者从订阅者数组中移除
  remove(eventName, callback) {
    if (!this.events[eventName]) {
      return this;
    }
    // 没有指定解绑事件? 就是没有指定对应的订阅者,那么移除所有订阅者
    if (!callback) {
      this.events[eventName] = null
      return this
    }
    // 否则找到该事件, 就是对应的订阅者,将其移除
    const index = this.events[eventName].indexOf(callback);
    this.events[eventName].splice(index, 1);
    return this;
  }

  // 单次绑定事件,执行完后解绑
  once(eventName, callback) {
    const only = () => {
      callback.apply(this, arguments);
      this.remove(eventName, only);
    };
    this.on(eventName, only);
    return this;
  }
}

// 测试
const emt = new EventEmitter()
// 订阅者1
const listener1 = function (...args) {
  console.log('意林的第一个订阅者', ...args);
}
// 订阅者2
const listener2 = function (...args) {
  console.log('意林的第二个订阅者', ...args);
}
// 收集订阅者,将杂志命名为'yilin'
emt.on('yilin', listener1)
emt.on('yilin', listener2)

// 500ms后订阅者1不想要该杂志了,进行取消订阅
setTimeout(() => {
  emt.remove('yilin', listener1)
}, 500)

// 1秒后意林杂志更新了,进行发布,通知订阅者,这时由于订阅者1取消订阅了,所以订阅者1就不会执行了
setTimeout(() => {
  emt.emit('yilin', 'hello world')
}, 1000)

// 打印结果为:1s后打印 `意林的第二个订阅者 hello world`

 

以上就是工作中最常见的手写功能,欢迎在评论区交流~

posted @ 2022-08-26 15:59  艾莱克  阅读(75)  评论(0编辑  收藏  举报