一些js高阶函数的封装及常用优化技巧

发布时间 2023-07-13 15:05:04作者: lix_uan

函数防抖

// 频繁触发、耗时操作,只执行最后一次
const debounce = function (fn, delay = 300) {
  let timer = null
  return function (...args) {
    clearTimeout(timer)
    timer = setTimeout(() => {
      fn.apply(this, args)
    }, delay)
  }
}

// const debounceFn = debounce(fn)

深拷贝以及深拷贝的循环引用问题

const deepClone = val => {
  const cache = new WeakMap()
  const _deepClone = val => {
    if (val === null || typeof val !== 'object') return val
    if (cache.has(val)) return cache.get(val) // 使用缓存解决循环引用问题

    const target = Array.isArray(val) ? [] : {}
    cache.set(val, target)
    for (let key in val) {
      if (val.hasOwnProperty(key)) {
        target[key] = _deepClone(val[key])
      }
    }
    return target
  }

  return _deepClone(val)
}

// test
const obj = {
  arr: [1, 2, 3],
  a: 4
}
obj.sub = obj
obj.arr.push(obj)

console.log(obj)
const obj2 = deepClone(obj)
console.log(obj2.arr !== obj.arr)
console.log(obj2.sub !== obj.sub)
console.log(obj2.arr[3] !== obj)
console.log(obj2.arr[3] === obj2)

避免过多的if..else逻辑

// const Condition  = val => {
//   if (val.startsWith('a')) {
//     console.log('a---' + val)
//   } else if (val.endsWith('b')) {
//     console.log('b---' + val)
//   } else if (val.includes('c')) {
//     console.log('c---' + val)
//   } else {
//     console.log('其他---' + val)
//   }
// }

const Condition2 = val => {
  const map = [
    [() => val.startsWith('a'), () => console.log('a---' + val)],
    [() => val.endsWith('b'), () => console.log('b---' + val)],
    [() => val.includes('c'), () => console.log('c---' + val)]
  ]

  const target = map.find(m => m[0]())
  if (target) {
    target[1]()
  } else {
    console.log('其他---' + val)
  }
}

参数归一化

// 以数据分组为例
const pepole = [
  { name: '张三', age: 20, sex: '女' },
  { name: '李四', age: 20, sex: '男' },
  { name: '王五', age: 22, sex: '男' },
  { name: '赵六', age: 22, sex: '女' },
  { name: '田七', age: 20, sex: '女' },
  { name: '周八', age: 22, sex: '男' }
]

const groupBy = (array, generateKey) => {
  if (typeof generateKey === 'string') {
    const key = generateKey
    generateKey = item => item[key]
  }

  return array.reduce((acc, cur) => {
    const group = generateKey(cur)
    acc[group] = acc[group] || []
    acc[group].push(cur)
    return acc
  }, {})
}

console.log(groupBy(pepole, 'sex'))
console.log(groupBy(pepole, 'age'))
console.log(groupBy(pepole, item => item.sex + ':' + item.age))

js封装进度执行动画

export const animation = (from, to, duration, callback) => {
  const speed = (to - from) / duration
  const start = Date.now()

  const _run = () => {
    const now = Date.now()
    const progress = from + (now - start) * speed
    if (progress > to) {
      callback(to)
      return cancelAnimationFrame(_run)
    }
    requestAnimationFrame(_run)
    callback(parseInt(progress))
  }
  requestAnimationFrame(_run)
}
<template>
  <div>{{ num }}</div>
  <button @click="up">开始</button>
</template>

<script setup>
  import { animation } from './animation'
  const num = ref(100)

  const up = () => {
    animation(num.value, 200, 3000, val => (num.value = val))
  }
</script>

使用 customRef 实现输入框防抖

export const debounceRef = (value, delay = 1000) => {
  let timer
  return customRef((track, trigger) => {
    return {
      get() {
        track()
        return value
      },
      set(val) {
        clearTimeout(timer)
        timer = setTimeout(() => {
          trigger()
          value = val
        }, delay)
      }
    }
  })
}
<template>
  <div class="container">
    <input type="text" v-model="value" />
    <div class="text">{{ value }}</div>
  </div>
</template>

<script setup>
  import { debounceRef } from './debounceRef'
  const value = debounceRef('')
</script>

组件逐帧渲染

// 减少白屏时间,提升用户感知
export const useDefer = (maxCount = 100) => {
  let frameCount = 0
  let refId

  const updateFrameCount = () => {
    refId = requestAnimationFrame(() => {
      frameCount++
      if (frameCount > maxCount) {
        return cancelAnimationFrame(refId)
      }
      updateFrameCount()
    })
  }
  updateFrameCount()

  return i => {
    return frameCount >= i
  }
}
<template>
  <div class="container">
    <div v-for="i in 100" :key="i">
      <heavy-component v-if="defer(i)"></heavy-component>
    </div>
  </div>
</template>

<script setup>
  import { useDefer } from './useDefer'
  const defer = useDefer()
</script>

使用惰性函数

// 避免每次进行函数调用时都要进行if..else判断
// 以文本复制为例
export const creatCopyText = () => {
  if (navigator.clipboard) {
    return text => navigator.clipboard.writeText(text)
  } else {
    return text => {
      const input = document.createElement('input')
      input.setAttribute('value', text)
      document.body.appendChild(input)
      input.select()
      document.execCommand('copy')
      document.body.removeChild(input)
    }
  }
}
<template>
  <div>{{ text }}</div>
  <button @click="CopyText(text)">点击复制</button>
</template>

<script setup>
  import { creatCopyText } from './inhertFn'
  const CopyText = creatCopyText()
  const text = ref('要复制的文本')
</script>

使用分时函数

// 总的执行时间不变,只提前了页面渲染时间,改变了用户感知

export const performChunk = (datas, comsumer) => {
  // 使用了参数的归一化
  if (typeof datas === 'number') {
    datas = new Array(datas)
  }

  if (datas.length === 0) return

  let i = 0
  const _run = () => {
    if (i === datas.length) return
    requestIdleCallback(deadline => {
      while (deadline.timeRemaining() > 0 && i < datas.length) {
        comsumer(datas[i])
        i++
      }
      _run()
    })
  }
  _run()
}
<template>
  <button @click="addNum">点击插入10w个数字</button>
  <div v-for="i in arr" :key="i">{{ i }}</div>
</template>

<script setup>
  import { performChunk } from './performChunk'

  const datas = new Array(100000).fill(0).map((_, i) => i)
  const arr = ref([])

  const addNum = () => {
    performChunk(datas, item => {
      arr.value.push(item)
    })
  }
</script>