手写lodash工具函数

发布于 2021-12-27  204 次阅读


::: note

Lodash 简介 | Lodash 中文文档 | Lodash 中文网 (lodashjs.com)

Lodash 是一个一致性、模块化、高性能的 JavaScript 实用工具库。

:::


::: tip

因为面试中的很多手写题,都出自lodash,所以今天来简单模拟一下。

:::

1. 数组

简单模拟lodash中的数组方法的实现。

1.1 chunk

/**
 * chunk(array, [size=1])
 * 将数组(array)拆分成多个 size 长度的区块,并将这些区块组成一个新数组。 
 * 如果array 无法被分割成全部等长的区块,那么最后剩余的元素将组成一个区块。
 */
const { log } = console;
function my_chunk(arr=[], size=1){
    const len = arr.length;
    if(len==0 || size<=0 || typeof(size) !='number' || !Array.isArray(arr) ) return [];
    let count = 0;
    let reg = [[]]; 
    let index = 0;
    for(let i=0; i<len; i++){
        reg[index].push(arr[i]);
        count ++ ;
        if(count==size){
            index ++;
            count = 0 ;
            reg.push([])
        }
    }
    // 判断最后一个子数组是否为空
    if( reg[reg.length-1].length == 0){
        reg.pop()
    }
    return reg;
}

// log( Array.isArray([])) // true
// log(typeof([])) // object

const a = [1,2,3,4,5,6,7]
let temp = my_chunk(a,3)
log(a)
log(temp)
log('----------------')
log( my_chunk([],1) )
log( my_chunk([1,2],0) )
log( my_chunk([1,2,3],1))
log( my_chunk([1,2,3], '12'))
log( my_chunk(1,2))
/**
[
  1, 2, 3, 4,
  5, 6, 7
]
[ [ 1, 2, 3 ], [ 4, 5, 6 ], [ 7 ] ]
----------------
[]
[]
[ [ 1 ], [ 2 ], [ 3 ] ]
[]
[]
 */

/**
 * lodash
 *
function chunk(array, size = 1) {
  size = Math.max(toInteger(size), 0)
  const length = array == null ? 0 : array.length
  if (!length || size < 1) {
    return []
  }
  let index = 0
  let resIndex = 0
  const result = new Array(Math.ceil(length / size))

  while (index < length) {
    result[resIndex++] = slice(array, index, (index += size))
  }
  return result
}
 *  
 * */

1.2 compact

/**
 * _.compact(array)
 * 创建一个新数组,包含原数组中所有的非假值元素。例如false, null,0, "", undefined, 和 NaN 都是被认为是“假值”。
 */
const { log } = console;
const my_compact = (arr)=>{
    let reg = arr.filter(i=>i!=false)
    return reg;
}

a = [0,"",[],{},1,2,[1],{'name':'xiaoming'}]
log( my_compact(a) )
/** 
 * [ {}, 1, 2, [ 1 ], { name: 'xiaoming' } ]
 */

/**
 * lodash
function compact(array) {
  let resIndex = 0
  const result = []

  if (array == null) {
    return result
  }

  for (const value of array) {
    if (value) {
      result[resIndex++] = value
    }
  }
  return result
}
 */

1.3 concat

/**
 * _.concat(array, [values])
 * 创建一个新数组,将array与任何数组 或 值连接在一起。
 */
const { log } = console;

const my_concat = (...args)=>{
    const reg = args[0]
    if(!Array.isArray(reg)){
        return []
    }
    console.log(reg)
    for(let i=1, l=args.length; i<l; i++ ){
        let temp = args[i]
        if(Array.isArray(temp)){
            reg.push(...temp)
        } else{
            reg.push(temp)
        }

    }
    return reg;
}

let a = [1]
log( my_concat(a,1,[2],[[3]]))

1.4 diffence

/**
 * 创建一个具有唯一array值的数组,每个值不包含在其他给定的数组中。(注:即创建一个新数组,这个数组中的值,为第一个数字(array 参数)排除了给定数组中的值。)该方法使用SameValueZero做相等比较。结果值的顺序是由第一个数组中的顺序确定。
    注意: 不像_.pullAll,这个方法会返回一个新数组。
    _.difference([3, 2, 1], [4, 2]);
// => [3, 1]

 */
const { log } = console;

const my_difference = (arr, dif)=>{
   if( !Array.isArray(arr) || !Array.isArray(dif)) return []
   let reg = arr.filter(item=>{
      for(val of dif){
         if(val == item){
            return false;
         }
      }
      return true;
   })
   return reg
}

const my_difference1 = (arr, dif)=>{
   if( !Array.isArray(arr) || !Array.isArray(dif)) return []
   let reg = arr.filter(item=> !dif.includes(item))
   return reg
}

a = [4,2]

log(my_difference([4,3,2,1], [2]))
// [ 4, 3, 1 ]

log( my_difference([,1,2,3,4,5,3], [2,3]) )
// [ 1, 4, 5 ]

log(my_difference1([4,3,2,1], [2]))
// [ 4, 3, 1 ]

log( my_difference1([,1,2,3,4,5,3], [2,3]) )
// [ 1, 4, 5 ]

1.5 flatten

const { log } = console
const arr = [99, [100,101], [1,2,3], [3,4,5,6], [2,3,[7,8,7]]]
// 得到一个去重且升序的数组

// 方法1
const s = new Set()
const f = (arr)=>{
    if(!Array.isArray(arr)) return;
    for(let i=0, l = arr.length; i<l; ++i){
        if(Array.isArray(arr[i])){
            f(arr[i])
        }else{
            s.add(arr[i])
        }
    }
}
f(arr)
const res = Array.from(s).sort((a,b)=>a-b)
log(res)

log('-------\r\n', arr, '\r\n--------')

// 方法2
const res1 = Array.from(new Set(arr.flat(Infinity))).sort((a,b)=> a-b)
log(res1)

/**
var newArray = arr.flat([depth])
depth 可选
    指定要提取嵌套数组的结构深度,默认值为 1。
    使用 Infinity,可展开任意深度的嵌套数组
返回值
    一个包含将数组与子数组中所有元素的新数组。

 */

1.6 zip

/**
 * 创建一个分组元素的数组,
 * 数组的第一个元素包含所有给定数组的第一个元素,
 * 数组的第二个元素包含所有给定数组的第二个元素,以此类推。
 * 
 * _.zip(['fred', 'barney'], [30, 40], [true, false]);
// => [['fred', 30, true], ['barney', 40, false]]

 */

const my_zip = (...args)=>{
    const reg = []
    let index = 0;
    const len = args.length;
    let maxLen = 0;
    if( len===0 ) return reg;
    for( val of args ){
        maxLen = val.length > maxLen ? maxLen = val.length : maxLen;
    }
    // 初始化分组数组
    for(let i=0; i<maxLen; i++) {
        reg.push([]);
        for(val of args){
            if(i<val.length){
                reg[i].push(val[i])
            }
        }
    }
    return reg;
}

const { log } = console;
log( my_zip( ['fred', 'barney'], [30, 40], [true, false] ))
// [ [ 'fred', 30, true ], [ 'barney', 40, false ] ]

log( my_zip( ['fred', 'barney'], [30, 40], [true, false] ,['hello'],['a','b','c','d']))
/**
[
  [ 'fred', 30, true, 'hello', 'a' ],
  [ 'barney', 40, false, 'b' ],
  [ 'c' ],
  [ 'd' ]
]
 * 
 */

1.7 zipObjectDeep

/**
_.zipObjectDeep(['a.b[0].c', 'a.b[1].d'], [1, 2]);
// => { 'a': { 'b': [{ 'c': 1 }, { 'd': 2 }] } }

 */

/**
 * 整体思路,广度优先设置当前n-1的key,最后特殊处理n
 */
const { log } = console;
let ans = {}

const reverseFlat = (key,value)=>{
    let cur = ans
    const keys = key.split('.')
    let i = 0; 
    const len = keys.length;
    // 先处理前面n-1个key
    for( i=0; i<len-1; i++ ){
        let k = keys[i]
        // 判断是否是数组
        let r = k.match( /\[(.*)\]/g )
        let index = 0
        if(r){
            k = k.split('[')[0]
            r = r[0]
            index = Number( r.substring(1, r.length-1) )
            if( !cur[k] ){
                cur[k] = []
            }
            cur[k][index] = {}
            // log('识别到是数组',cur[k][index])
            cur = cur[k][index]
            // cur[k][index] = {}

        }else{
            if( !cur[k] ){
                cur[k] = {}
            }
            cur = cur[k]
        }
    }
    // 处理最后一个key
    let k = keys[len-1]
    // 判断最后一个元素是数组还是key
    let r = k.match( /\[(.*)\]/g )
    let index = 0
    if(r){
        k = k.split('[')[0]
        r = r[0]
        index = Number( r.substring(1, r.length-1) )
        if( !cur[k] ){
            cur[k] = []
        }
        cur[k][index] = value  
    }else{
        if( !cur[k] ){
            cur[k] = {}
        }
        cur[ k ] = value
    }

}

const my_zipObjectDeep = (a,b)=>{
    for(let i = 0, l =b.length; i<l; i++ ){
        reverseFlat(a[i], b[i])
    }
}

let k = ['a.b[0].c', 'a.b[1].d', 'e.f.g[0]', 'x.y', 'e.f.g[1]', 'arr[1].gg.g[2]']
let v = [1, 2, 3, 4, 5,6]

my_zipObjectDeep(k,v)
log( ans )
log(ans['a']['b'], ans['e']['f']['g'])

/**
{
  a: { b: [ [Object], [Object] ] },
  e: { f: { g: [Array] } },
  x: { y: 4 },
  arr: [ <1 empty item>, { gg: [Object] } ]
}
[ { c: 1 }, { d: 2 } ] [ 3, 5 ]
 * 
 */

2.集合

比较有意思的集合的lodash函数

2.1 countBy

/**
 * _.countBy(collection, [iteratee=_.identity])
 * 创建一个组成对象,
 * key(键)是经过 iteratee(迭代函数) 
 * 执行处理collection中每个元素后返回的结果,
 * 每个key(键)对应的值是 iteratee(迭代函数)返回该key(键)的次数(注:迭代次数)。 iteratee 调用一个参数:(value)。
    参数
collection (Array|Object): 一个用来迭代的集合。
[iteratee=_.identity] (Array|Function|Object|string): 一个迭代函数,用来转换key(键)。

    _.countBy([6.1, 4.2, 6.3], Math.floor);
// => { '4': 1, '6': 2 }

// The `_.property` iteratee shorthand.
_.countBy(['one', 'two', 'three'], 'length');
// => { '3': 2, '5': 1 }

iteratee 是Object和Array 没想到怎么用,就不管了吧
 */

const my_countBy = (collection, iterate=None)=>{
    let ans = {}
    if(!iterate ) return ans;
    const f = (k)=>{
        if( !ans[k] ){
                ans[k] = 1
        } else {
            ans[k] ++
        }
    }
    if( typeof iterate === 'function'){
        for(let v of collection){
            let reg = iterate(v)
            f(reg)
        }

    } else if(typeof(iterate) === 'string') {
        for( let v of collection){
            let reg = v[iterate]
            f(reg)
        }
    }
    return ans;
}

const { log } = console;
log( my_countBy(
    [6.1, 4.2, 6.3],
    Math.floor
) )

log( my_countBy(
    ['one', 'two','three'],
    'length'
))

3. 函数

4. 对象

5. 字符串

6. 实用函数


见其广知其深