Skip to content

挑战看200 个npm模块源码:第6个

目录:

一、介绍

  1、原始类型

  2、复合类型

  3、具有自定义哈希函数的复合类型

二、个人手写

  1、个人未看源码思路

  2、个人手写代码

    2.1:类型区分

    2.2:原始数据类型去重

    2.3:数组对象类型去重

    2.4:具有自定义哈希函数的复合类型

    2.5测试

三、源码分析

  1、源码思路

四、总结

字数:大约900字

一、介绍

名称:dedupe

地址:https://github.com/seriousManual/dedupe.git

今天写的是,去重,面试中也经常遇到。

示例:

1、原始类型

js
import dedupe from 'dedupe'

const a = [1, 2, 2, 3]
const b = dedupe(a)
console.log(b)

//result: [1, 2, 3]

2、复合类型

js
import dedupe from 'dedupe'

const aa = [{a: 2}, {a: 1}, {a: 1}, {a: 1}]
const bb = dedupe(aa)
console.log(bb)

//result: [{a: 2}, {a: 1}]

3、具有自定义哈希函数的复合类型

js
const aaa = [
  {a: 2, b: 1}, 
  {a: 1, b: 2}, 
  {a: 1, b: 3}, 
  {a: 1, b: 4}
]
const bbb = dedupe(aaa, value => value.a)
console.log(bbb)

//result: [{a: 2, b: 1}, {a: 1,b: 2}]

二、个人手写

看了上面的示例,可以发现并不是简单的原始数据去重,刚好,可以稍微提升一点点难度。继续走。

1、个人未看源码思路

1、根据不同数据类型来针对性处理。

2、判断数组类型、复合类型、基本类型三种类型

3、对于原始数组类型--> 用ES6 Set()

4、数组对象类型--> reduce+JSON.stringify去重

5、复合类型--> Set 哈希方式去重

2、个人手写代码

2.1:类型区分

js
const deduplicate = (data, fn) => {
  if (!Array.isArray(data) || !data.length) throw new Error("list is not a Array or length === 0!");
    if (fn) {
      // 复合数据类型去重
    } else if (typeof data[0] === 'object') {
      // 数组对象类型去重
    } else {
      // 原始类型去重
    }
}

2.2:原始数据类型去重

js
const deduplicate = (data, fn) => {
  if (!Array.isArray(data) || !data.length) throw new Error("list is not a Array or length === 0!");
    if (fn) {
      // 复合数据类型去重
    } else if (typeof data[0] === 'object') {
      // 数组对象类型去重
    } else {
      // 原始类型去重
      return Array.from(new Set(data));
    }
}

2.3:数组对象类型去重

js
const deduplicate = (data, fn) => {
  if (!Array.isArray(data) || !data.length) throw new Error("list is not a Array or length === 0!");
    if (fn) {
      // 复合数据类型去重
    } else if (typeof data[0] === 'object') {
      // 数组对象类型去重
      return data.reduce((prev, curr) => {
        if (JSON.stringify(prev).indexOf(JSON.stringify(curr)) === -1) {
          prev.push(curr);
        }
        return prev;
      }, [])
    } else {
      // 原始类型去重
      return Array.from(new Set(data));
    }
}

2.4:具有自定义哈希函数的复合类型

js
const deduplicate = (data, fn) => {
  if (!Array.isArray(data) || !data.length) throw new Error("list is not a Array or length === 0!");
    if (fn) {
      // 复合数据类型去重
      const hashSet = new Set();
      return data.filter((item) => {
        const hash = getHash(item,fn);
        return !hashSet.has(hash) ? hashSet.add(hash) : false;
      });
    } else if (typeof data[0] === 'object') {
      // 数组对象类型去重
      return data.reduce((prev, curr) => {
        if (JSON.stringify(prev).indexOf(JSON.stringify(curr)) === -1) {
          prev.push(curr);
        }
        return prev;
      }, [])
    } else {
      // 原始类型去重
      return Array.from(new Set(data));
    }
}
const  getHash = (obj,fn) => {
  const _v = fn(obj);
  const hash = _v || null;
  return hash;
}

2.5测试

js
const aaa = [1, 2, 2, 3]
deduplicate(aaa); // [1,2,3]


const aaa = [{a: 2}, {a: 1}, {a: 1}, {a: 1}]
deduplicate(aaa); // [{"a": 2},{"a": 1}]


const aaa = [
  {a: 2, b: 1}, 
  {a: 1, b: 2}, 
  {a: 1, b: 3}, 
  {a: 1, b: 4}
]
deduplicate(aaa, obj => obj.a)
// [{"a": 2,"b": 1},
//  {"a": 1,"b": 2}]

🍺🍺🍺

三、源码分析

ts
type Hasher<T> = (input: T) => string

function dedupe <T>(list: T[], hasher: Hasher<T> = JSON.stringify) {
    const clone: T[] = []
    const lookup: Record<string, boolean> = {}

    for (let i = 0; i < list.length; i++) {
        let entry = list[i]
        let hashed = hasher(entry)

        if (!lookup[hashed]) {
            clone.push(entry)
            lookup[hashed] = true
        }
    }

    return clone
}

export default dedupe

3.1:源码思路

此源码没有区分数组具体类型,直接都是使用hash+JSON.stringify来实现。相比起来,他的代码量很少,很好理解,而我们的是区分来去处理,所以代码量有点多。但是都用JSON.stringify会影响性能。那我们来看看性能区别呢。

性能对比

1、原始数据数组 手写vs源码: 1ms vs 4ms

2、数据对象数组 手写vs源码: 1ms vs 1ms

3、具有自定义哈希函数的复合类型 手写vs源码: 1.5ms vs 1ms

性能方面综合而来,我们手写的胜出。 但是代码可读性,他的更佳。

四、总结

1、数组对象的 indexOf 方法使用的是全等比较(===)

2、尽可能的少用if else

3、多情况条件去重,用高阶函数(学了就要用,前面刚好学完)实现是个好的方式。

4、善于使用默认参数