Lodash常用方法源码解读

227 阅读4分钟
  • 前言 首先声明一下,这个解读是根据github上某位大佬解读的,我再看的,不是我自己解读的,哈哈哈,慢慢学习。大佬的github在这github.com/yeyuqiudeng…

lodash常用方法源码解读

首先补一下ES6知识点:class

Object.create(null): // 以null为原型创建的空对象,纯净的对象,没有任何其他继承属性

o = Object.create(Object.prototype)//  以Object.prototype为原型创建的空对象,相当于下面这种

var o = new Object()

Hash

将对象来作为缓存对象,键值只能是字符串或者 Symbol 类型

/* 注意点: 
1. 太长的数组变量引用可以拿一个变量进行缓存, 如entries[index][0], entries[index][1], 可以优化成const entries = entries[index] 
2. 在set方法中没有 return this,不写就不能链式调用了。
3. 向一个对象里面取不存在的key值,得到的是undefined
4. 成功删除了才会去减掉对应的size,所以要根据对应的结果来看要不要-1
5. delete obj[key]:删除成功,返回true,否则返回false
*/
const HASH_UNDEFINED = '__lodash_hash_undefined__'
class Hash {
    constructor(entries) {
        const length = entries == null ? 0 : entries.length // 4
        let index = 0
        this.clear()
        while (index < length) {
            // 调用set方法做缓存
            const entry = entries[index]
            this.set(entry[0], entry[1])
            index++
        }
    }
    clear() {
        this.__data__ = Object.create(null)
        this.size = 0
    }
    delete(key) {
        const result = this.has(key) && delete this.__data__[key]
        this.size -= result ? 1 : 0 // 成功删除了才会去减掉对应的size
        return result
    }
    get(key) {
        // const data = this.__data__
        // 1. 这个key可能不存在
        /* let value = (data[key] === HASH_UNDEFINED ? undefined : data[key]) 
        return value */
        const data = this.__data__
        const result = data[key]
        return result === HASH_UNDEFINED ? undefined : result
    }
    // has方法,判断该属性this.__data__[key]的属性值是否等于undefined,
    // 也就是看是否在__data__存在过,是返回false,不是返回true
    //判断某一个key值存不存在,可以看他的属性值是否等于undefined.
    has(key) {
        return this.__data__[key] !== undefined // true: 存在,false:不存在
    }
    set(key, value) {
        // 1. 这个key如果之前存在过,就是修改操作。
        // 2. 这个key对应的属性值如果是undefined,不缓存,或者缓存一个自定义的值
        this.size += this.has(key) ? 0 : 1
        this.__data__[key] = value === undefined ? HASH_UNDEFINED : value
    }
}
export default Hash

assocIndexOf

针对二维数组,作用是找出指定的 key 在数组中的索引值。

var caches = [['test1', 1],['test2',2],['test3',3]]
assocIndexOf(caches, 'test1') // 0
function assocIndexOf(array, key) {
  let { length } = array // 解构赋值出length
  while (length--) {
    if (eq(array[length][0], key)) {
      return length
    }
  }
  return -1
}

注意事项:

  1. while循环里面如果写--length,则第一项,也就是length === 1的话,是不走循环的。

eq

eq 函数用来比较两个值是否相等。遵循的是 SameValueZero 规范。+0 === -0,NaN === NaN,就是肉眼可以看出两个值是相等的。

var obj1 = {test: 1}
var obj2 = {test: 1}
var obj3 = obj1
_.eq(1,1) // true
_.eq(+0, -0) // true
_.eq(obj1, obj3) // true
_.eq(obj1, obj2) // false
_.eq(NaN, NaN) // true

几种相等比较规则

严格相等:+0 === -0,NaN !== NaN

SameValueZero:在严格相等的比较规则上,增加了一种规则,+0 === -0,NaN === NaN 源码:

function eq(value, other){
    value === other || value !== value && other !== other
}
// 或者,只有当Number.isNaN(value),value为NaN时,才为true
function eq(value, other){
    value === other || Number.isNaN(value) && Number.isNaN(other)
}

注意: value !== value && other !== other,自己与自己严格不相等,这个值只能是NaN.

Object.is():

Object.is(NaN,NaN) ==> true
Object.is(+0,-0) ==> false

Number.isNaN():只有当里面的值是NaN时,才为true,所以,

Map

对象只接受字符串作为键名

  1. new Map参数本身是数组,内部成员也是数组
const map = new Map([
  ['name', '张三'],
  ['title', 'Author']
]);
  1. 可以将对象作为键值,set到map对象中。
const m = new Map();
const o = {p: 'Hello World'};

m.set(o, 'content')
m.get(o) // "content"

Map构造函数接受数组作为参数,实际上执行的是下面的算法。

const items = [
  ['name', '张三'],
  ['title', 'Author']
];

const map = new Map();

items.forEach(
  ([key, value]) => map.set(key, value)
);

注意:只有对同一个对象的引用,Map 结构才将其视为同一个键。这一点要非常小心。

const map = new Map();

map.set(['a'], 555);
map.get(['a']) // undefined

两个不同引用类型的key值,会当做两个key来处理。

const map = new Map();

const k1 = ['a'];
const k2 = ['a'];

map
.set(k1, 111)
.set(k2, 222);

map.get(k1) // 111
map.get(k2) // 222

var map = new Map(); 
map.set('我','前端'); 
map.set('是','技术'); 
map.set('谁','江湖'); 
map.set('作者','zz_jesse'); 
var [d,e]=map; // 数据解构,[["我", "前端"] ["是", "技术"],...]
console.log(d,e); //["我", "前端"] ["是", "技术"]

\
作者:zz_jesse\
链接:https://juejin.cn/post/6844904000131694605\
来源:稀土掘金\
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

ListCache

模拟Map,在没有Map的情况下,lodash提供了ListCache方法来存储键值对。

new ListCache([
  [{key: 'An Object Key'}, 1],
  [['An Array Key'],2],
  [function(){console.log('A Function Key')},3]
])

返回的结果如下:

{
  size: 3,
  __data__: [
    [{key: 'An Object Key'}, 1],
    [['An Array Key'],2],
    [function(){console.log('A Function Key')},3]
  ]
}
import assocIndexOf from './assocIndexOf.js'
// 1. get方法没考虑到没找到的情况,导致代码报错
// 2. delete返回值为 true or false,这是设计模式
// 3. 先把异常情况找出直接返回, 后续逻辑就不用走了
// 4. 删除元素,可以考虑删除的选项是否在最后一项,在最后一项,可以用数组的pop(),节省性能。
class ListCache {
    constructor(entries) {
        const length = entries.length
        let index = 0
        this.clear()
        while (index < length) {
            const entry = entries[index]
            index++;
            console.log('entry', entry)
            this.set(entry[0], entry[1])
        }
    }
    // 判断是否有这个key,重复的key则替换=>不替换,符合map的规则
    // 返回索引
    has(key) {
        const data = this.__data__
        return assocIndexOf(data, key) // 是否有这个key
    }
    // 置空
    clear() {
        this.size = 0
        this.__data__ = [] // 创建一个空数组
    }
    delete(key) {
        // 先把异常情况找出直接返回,后续逻辑就不用走了
        const data = this.__data__
        const index = assocIndexOf(data, key)
        const lastIndex = data.length
        if (index < 0) {
            return false // 未找到
        }
        if (index === lastIndex) {
            // 是最后一项
            data.pop()
        } else if (index > -1) {
            data.splice(index, 1)
        }
        this.size--
        return true
    }
    get(key) {
        const data = this.__data__
        const index = assocIndexOf(data, key)
        // 要考虑到没找到的情况,否则data[-1][1] === undefined[1]会报错
        index > -1 ? data[index][1] : undefined
    }
    set(key, value) {
        const index = this.has(key)
        const data = this.__data__
        if (index > -1) {
            data[index][1] = value
        } else {
            this.size++
            data.push([key, value])
        }

    }
}
export default ListCache

strictIndexOf

找出数组的某一项然后返回下标,从下标第几项开始,包括当前项。找不出NaN

function strictIndexOf(array, value, fromindex) {
    const {
        length
    } = array
    let index = fromindex - 1
    while (++index < length) {
        if (array[index] === value) {
            return index
        }
    }
    return -1
}
console.log(strictIndexOf([3, 2, 1], 1, 0)) // 2
console.log(strictIndexOf([3, 2, 1], 4, 0)) // -1
console.log(strictIndexOf([3, 2, 1, NaN], NaN, 0)) // -1

baseIndexOf

indexOf()采用的是严格相等,所以找不出NaN,因为NaN找不出与它相等的数,所以baseIndexOf()会来处理这种情况。

arrayIncludes

判断数组中是否包含某个元素,从第0项开始,包含返回true,否则返回false。判断条件可以用上面的baseIndexOf方法。

function arrayIncludes(array,value){
    const length = array == null? 0: array.length
    return !!length && baseIndexOf(array,value,0) > -1
}

找到就返回索引,> -1返回true,未找到就返回-1,返回false。

arrayIncludesWith

与数组中的每一项逐个比较,比较策略由自己定义的函数决定

function arrayIncludesWith(array, target, comparator) {
  if (array == null) {
    return false
  }

  for (const value of array) {
    if (comparator(target, value)) {
      return true
    }
  }
  return false
}

注:comparator是自定义的函数

map

数组可以自定义,处理的方法也可以自定义

    function map(array, func) {
      let index = -1
      const length = array == null ? 0 : array.length
      const result = new Array(length) // 创建以length为大小的数组
      while (++index < length) {
        result[index] = func(array[index])
      }
      return result
    }
    // func可以自定义
    function func(value) {
      return value * 2
    }
    const array = [1, 2, 3, 4]
    const result = map(array, func)
    console.log(result) // [2,4,6,8]

isFlattenable

用来判断某个值是否可以被展平(展平?与其他数组可以进行拼接?)普通数组,类数组,或者将某个对象的属性设置成true,也就可以展平了。

const spreadableSymbol = Symbol.isConcatSpreadable

function isFlattenable(value) {
  return Array.isArray(value) || isArguments(value) ||
    !!(spreadableSymbol && value && value[spreadableSymbol])
}

isArguments(value):判断类数组的方法

baseFlatten

用来展平数组,可以指定展平的深度。(数组的内层数组层数,最外层不算)

/* 
    array:需要展平的数组
    depth:展平的深度
    predicate:每次迭代时都会调用的检查函数,回调参数为每次迭代的值,默认为 isFlattenable 函数
    isStrict:是否严格模式,在严格模式下,迭代的值必须要经过 predicate 函数的检查才存入结果数组中
    result:结果数组 
    */
    function baseFlatten(array, depth, predicate = isFlattenable, isStrict, result = []) {
      /*  predicate || (predicate = isFlattenable)
       result || (result = []) */
      if (array == null) {
        return result
      }
      for (const value of array) {
        if (depth > 0 && predicate(value)) {
          if (depth > 1) {
            baseFlatten(value, depth - 1, predicate, isStrict, result)
          } else {
            // depth === 1:一维数组,此时value是一维数组
            // 不使用严格模式,则depth === 0或者predicate(value)为false的都可以放进去
            result.push(...value)
          }
        } else if (!isStrict) {
          //在当前位置追加值
          result[result.length] = value
        }
      }
      return result
    }
    function test(...values) {
      baseFlatten(values, 2, null, true)
    }
    test([[1, 2], [3]], [[4], [5, 6]], function () { })

扩展

    function aaa(...values) {
      console.log('values', values) 
      // values:实参集合[[1,2,3],[4,5,6],function(){}],深度为1
    }
    aaa([1, 2, 3], [4, 5, 6], function () { })