JS代码案例

196 阅读5分钟

1. 数组去重

const uniqueArray1 = (arr) => Array.from(new Set(arr))

const uniqueArray2 = (arr) => {
    const arrCopy = [...arr]
    let len = arrCopy.length
    for (let i = 0; i < len - 1; ++i) {
        for (let j = i + 1; j < len; ++j) {
            if (arrCopy[i] === arrCopy[j]) {
                arrCopy.splice(j, 1)
                --len
                --j
            }
        }
    }
    return arrCopy
}

const uniqueArray3 = (arr) => {
    const res = []
    arr.forEach(item => {
      if (!res.includes(item))  
        res.push(item)
    })
    return res
}

const uniqueArray4 = (arr) => {
    const res = []
    arr.forEach(item => {
      if (res.indexOf(item) === -1)  
        res.push(item)
    })
    return res
}

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


const uniqueArray6 = (arr) => arr.reduce((pre, cur) => pre.includes(cur) ? pre: [...pre, cur], [])

const uniqueArray7 = (arr) => {
    const res = []
    const obj = {}
    arr.forEach(item => {
        if (!obj[item]){
            res.push(item)
            obj[item] = true
        }
    })
    return res
}

const uniqueArray8 = (arr) => {
    const res = []
    const map = new Map()
    arr.forEach(item => {
        if (!map.has(item)){
            res.push(item)
            map.set(item, true)
        }
    })
    return res
}

2. 手写flat数组拍平方法(对象拍平)

Array.prototype.myFlat = function(deep=1) {
    const inside = (arr, deep) => {
        if (deep <= 0) return arr
        return arr.reduce((pre, cur) => pre.concat(Array.isArray(cur) ? inside(cur, deep - 1) : cur), [])
    }
    return inside(this, deep)   
}

const obj = {
  a: {
    b: 1,
    c: 2,
    d: { e: 5 },
  },
  b: [1, 3, { a: 2, b: 3 }],
  c: 3,
}

// 结果返回如下
// {
//  'a.b': 1,
//  'a.c': 2,
//  'a.d.e': 5,
//  'b[0]': 1,
//  'b[1]': 3,
//  'b[2].a': 2,
//  'b[2].b': 3
//   c: 3
// }
const flatten = (obj) => {
  const res = {}
  const dfs = (obj, str = "") => {
    if (typeof obj !== "object") {
      res[str] = obj
      return
    }
    if (Array.isArray(obj)) {
      for (let i = 0; i < obj.length; ++i) {
        dfs(obj[i], str ? str + `[${i}]` : i)
      }
    } else {
      for (const key in obj) {
        dfs(obj[key], str ? str + `.${key}` : key)
      }
    }
  }
  dfs(obj)
  return res
}
console.log(flatten(obj))

3. 排序算法(快排、归并、插入)

快速排序

const quickSort = (arr, low, high) => {
    if (low < high) {
        const temp = arr[low]
        let i = low
        let j = high
        while (i < j) {
            while(i < j && arr[j] >= temp) --j
            if (i < j) {
                arr[i] = arr[j]
                ++i
            }
            while (i < j && arr[i] <= temp) ++i
            if (i < j) {
                arr[j] = arr[i]
                --j
            }
        }
        arr[i] = temp
        quickSort(arr, low, i - 1)
        quickSort(arr, i + 1, high)
    }
}

归并排序

const mergeSort = (arr) => {
  if (arr.length <= 1) {
    return arr;
  }
  const middle = Math.floor(arr.length / 2);
  const left = arr.slice(0, middle);
  const right = arr.slice(middle);
  return merge(mergeSort(left), mergeSort(right));
};
const merge = (left, right) => {
  let result = [];
  let leftIndex = 0;
  let rightIndex = 0;
  while (leftIndex < left.length && rightIndex < right.length) {
    if (left[leftIndex] < right[rightIndex]) {
      result.push(left[leftIndex]);
      leftIndex++;
    } else {
      result.push(right[rightIndex]);
      rightIndex++;
    }
  }
  return result.concat(left.slice(leftIndex)).concat(right.slice(rightIndex));
};

插入排序

const insertSort = (arr) => {
  for (let i = 1; i < arr.length; i++) {
    let currentVal = arr[i];
    for (let j = i - 1; j >= 0 && arr[j] > currentVal; j--) {
      arr[j + 1] = arr[j];
    }
    arr[j + 1] = currentVal;
  }
  return arr;
};

4. instanceOf

const myInstanceof = (target, type) => {
    if (typeof target !== 'object' || target === null) return false
    let proto = Object.getPrototypeOf(target)
    let prototype = type.prototype
    while (true) {
        if (proto === null) return false
        if (proto === prototype) return true
        proto = Object.getPrototypeOf(proto)
    }
}

5. 用setTimeout实现setInterval

const mySetInterval = (func, milliseconds) => {
    const inside = () => {
        func()
        setTimeout(() => {
            inside()
        }, milliseconds);
    }
    setTimeout(() => {
        inside()
    }, milliseconds);
}

6. 手写new

function myNew(func, ...agrs) {
    /**
     * 1. 创建一个空对象obj
     * 2. 将对象与构造函数通过原型链连接起来
     * 3. 将构造函数的this绑定到obj上
     * 4. 根据构造函数的返回类型作判断,如果是原始值,则被忽略,如果是返回对象,则直接将该对象返回给obj
     */
    const obj = {}
    obj.__proto__ = func.prototype
    const res = func.apply(obj, agrs)
    return res instanceof Object? res : obj

}

7. 手写call、apply、bind

//call
Function.prototype.myCall = function(obj, ...args) {
    obj = obj ? Object(obj) : globalThis
    const fn = Symbol('test')
    obj[fn] = this
    const res = obj[fn](...args)
    delete obj[fn]
    return res
}
//apply
Function.prototype.myApply = function(obj, args) {
    obj = obj ? Object(obj) : globalThis
    const fn = Symbol('test')
    obj[fn] = this
    const res = obj[fn](...args)
    delete obj[fn]
    return res
}
//bind
Function.prototype.myBind = function(obj, ...args1) {
    const fn = this
    return function(...args2) {
        return fn.apply(obj, args1.concat(args2))
    }
}

8. 防抖与节流

// 防抖
function debounce(fn, delay) {
  let timer = null;
  return function (...args) {
    let context = this;
    clearTimeout(timer);
    timer = setTimeout(() => {
      fn.apply(context, args);
    }, delay);
  };
}
// 节流
function throttle(func, wait) {
  let timer;
  return function (...args) {
    if (timer) return;
    timer = setTimeout(() => {
      func.apply(this, args);
      timer = null;
    }, wait);
  };
}

9. 手写并发控制

/**
 * @param {string[]} urls 待请求的url数组
 * @param {number} maxNum 最大并发数
 */
function concurRequset(urls, maxNum) {
    return new Promise((resolve) => {
        if (urls.length === 0) {
            resolve([]);
            return;
        }
        const results = [];     //返回的结果,要与urls中一一对应
        let index = 0;
        let count = 0;         // 当前请求完成的数量
        // 发送请求
        async function request() {
            if (index === urls.length) {   //index已经到达末尾,结束请求
                return;
            }
            const i = index;
            const url = urls[index];
            index++;
            try {
                const resp = await fetch(url);
                results[i] = resp;
            } catch (err) {
                // err加入到results中
                results[i] = err;
            } finally {
                // 判断所有请求都已完成
                ++count;
                if (count === urls.length) {
                    resolve(results);
                }
                request();
            }
        }
        const times = Math.min(maxNum, urls.length);
        for (let i = 0; i < times; ++i)
            request();
    })
}

10. 手写promise.all

Promise.prototype.myAll = function (proms) {
    return new Promise((resolve, reject) => {
        if (!Array.isArray(proms)) {
            reject(new Error('params is not array'))
        }
        let count = 0
        const result = []
        for (let i = 0; i < proms.length; ++i) {
            Promise.resolve(proms[i]).then(value => {
                ++count
                result[i] = value
                if (count === proms.length) {
                    resolve(result)
                }
            }).catch(e => {
                reject(e)
            })
        }
    })
}

11. reduce

Array.prototype.myReduce = function (callback, initVal) {
    const arr = this
    const len = arr.length
    if (typeof callback !== 'function') {
        throw new TypeError(callback + 'is not a function')
    }
    if (len === 0 && !initVal) {
        throw new TypeError('Reduce of empty array with no initial value')
    }
    let curIndex, prevVal
    if (initVal) {
        prevVal = initVal
        curIndex = 0
    } else {
        prevVal = arr[0]
        curIndex = 1
    }
    for (let i = curIndex; i < len; i++) {
        prevVal = callback(prevVal, arr[i], i, arr)
    }
    return prevVal
}

12. 深拷贝

const cloneDeep = (obj, hash=new WeakMap()) => {
  if (obj === null) return obj
  if (obj instanceof Date) return new Date(obj)
  if (obj instanceof RegExp) return new RegExp(obj)
  if (typeof obj !== 'object') return obj 
  if (hash.has(obj)) return hash.get(obj)
  const newObj = new obj.constructor
  hash.set(obj, newObj)
  for (let k in obj) {
    newObj[k] = cloneDeep(obj[k], hash)
  }
  return newObj
}

13. EventBus(可以精简一点)

class EventBus {
    constructor() {
        this.eventBus = {}
    }
    // 事件监听
    $on(eventName, eventCallback, thisArg) {
        // 判断事件名是否是string类型
        if (typeof eventName !== 'string') {
            throw TypeError('传入的事件名数据类型需要设为string类型')
        }
        // 判断事件函数是否为function类型
        if (typeof eventCallback !== 'function') {
            throw TypeError('需要传入的回调函数')
        }
        // 从eventBus中获取对应的事件数据
        if (!this.eventBus[eventName]) {
            this.eventBus[eventName] = []
        }
        this.eventBus[eventName].push({ eventCallback, thisArg })
    }
    // 事件触发
    $emit(eventName, ...args) {
        if (typeof eventName !== 'string') {
            throw TypeError('传入的事件名数据类型需为string类型')
        }
        // 从eventBus中获取对应的事件数据
        const handlers = this.eventBus[eventName] || []
        handlers.forEach(handler => {
            // 触发事件
            handler.eventCallback.apply(handler.thisArg, args)
        })
    }
    // 移除事件监听
    $off(eventName, eventCallback) {
        // 判断事件类型是否是string类型
        if (typeof eventName !== 'string') {
            throw TypeError('传入的事件名数据类型需要设为string类型')
        }
        // 判断事件函数是否为function类型
        if (!eventCallback) {
            this.eventBus[eventName] = undefined
            return
        }
        if (typeof eventCallback !== 'function') {
            throw TypeError('需要传入的回调函数')
        }
        // 从eventBus中获取对应的事件数据
        const handlers = this.eventBus[eventName] || []
        // 如果eventName在eventBus中存在则进行操作
        if (handlers.length) {
            if (eventCallback) {
                for (let i = 0; i < handlers.length; ++i) {
                    if (handlers[i].eventCallback === eventCallback) {
                        handlers.splice(i, 1)
                    }
                }
            }
        }
        if (handlers.length === 0) {
            delete this.eventBus[eventName]
        }
    }
    // 单次监听
    $once(eventName, eventCallback, ...thisArg) {
        // 判断事件类型是否是string类型
        if (typeof eventName !== 'string') {
            throw TypeError('传入的事件名数据类型需要设为string类型')
        }
        // 判断事件函数是否为function类型
        if (typeof eventCallback !== 'function') {
            throw TypeError('需要传入的回调函数')
        }
        const handlers = this.eventBus[eventName]
        if (handlers.length) {
            for (let i = 0; i < handlers.length; ++i) {
                if (handlers[i].eventCallback === eventCallback) {
                    eventCallback.apply(handlers[i].thisArg, thisArg)
                    this.$off(eventName, eventCallback)
                }
            }
        }
    }
}

14. 数字千分位

const reg = /\B(?=(\d{3})+(?!\d))/g
console.log('123456789'.replace(reg, ','))

15. 手写lodash.get

示例:

const obj = {
    a: {
        b: [0, 1, 2],
        c: 2,
    }
}
const path = 'a.b[1]'

lodashGet(obj, path)  // 输出1
const lodashGet = (obj, path) =>  path.replace(/\[(\d+)\]/g, '.$1').split('.').reduce((o, k) => o[k], obj)

16. LRU

class LRUCache {
  constructor(capacity) {
    this.capacity = capacity;
    this.cache = new Map();
  }
  get(key) {
    if (!this.cache.has(key)) {
      return -1;
    }
    const value = this.cache.get(key);
    this.cache.delete(key);
    this.cache.set(key, value);
    return value;
  }
  put(key, value) {
    if (this.cache.has(key)) {
      this.cache.delete(key);
    } else if (this.cache.size >= this.capacity) {
      this.cache.delete(this.cache.keys().next().value);
    }
    this.cache.set(key, value);
  }
}

17. Promise实现失败重传

const retry = (promiseFn, times) => {
  return new Promise((resolve, reject) => {
    const inside = async (times) => {
      try {
        const res = await promiseFn()
        console.log(`成功, ${res}`)
        resolve(res)
      } catch (err) {
        if (times > 0) {
          console.log(`倒数第${times}次重试, ${err}`)
          --times
          inside(times)
        } else {
          console.log('End');
          reject(err)
        }
      }
    }
    inside(times)
  })
}


const test = () => {
  return new Promise((res, rej) => {
    const num = Math.random() * 10
    num < 5 ? res(`success,${num}`) : rej(`fail,${num}`)
  })
}

retry(test, 4).then(res => {
  console.log(`res: ${res}`);
}).catch(err => {
  console.log(`err: ${err}`);
})

带定时器的失败重传

const retryFn = (task, time, timeout) => {
  return Promise.race([retry(task, time), timeEnd(timeout)])
}

const retry = (task, time) => {
  return new Promise((resolve, reject) => {
    const inside = async (time) => {
      try {
        const res = await task()
        resolve(res)
      } catch (error) {
        if (time <= 1) {
          reject(error)
        } else {
          inside(time - 1)
        }
      }
    }
    inside(time)
  })
}

const timeEnd = (timeout) => {
  return new Promise(( _ , reject) => {
    setTimeout(() => {
      reject("timeout error")
    }, timeout)
  })
}

18 图片懒加载

const images = document.querySelectorAll('img')
const callback = entries => {
    entries.forEach(item => {
        if (item.isIntersecting) {
            const image = item.target
            const dataSrc = image.getAttribute('data-src')
            image.setAttribute('src', dataSrc)
            observer.unobserve(item.target)
        }
    })
}
const observer = new IntersectionObserver(callback)
images.forEach(image => {
    observer.observe(image)
})