- 前言 首先声明一下,这个解读是根据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
}
注意事项:
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
对象只接受字符串作为键名
- new Map参数本身是数组,内部成员也是数组
const map = new Map([
['name', '张三'],
['title', 'Author']
]);
- 可以将对象作为键值,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 () { })