Lodash源码阅读-size

126 阅读3分钟

Lodash 源码阅读-size

概述

size函数用于获取集合(collection)的大小,适用于多种数据类型,包括数组、字符串、对象、Map 和 Set。它通过不同的方法准确计算各种集合类型的元素数量,为开发者提供统一的 API 接口确定集合大小,而无需关心具体集合类型。

前置学习

依赖函数

  • isArrayLike: 检查值是否类数组,判断对象是否有 length 属性且是有效的数组长度
  • isString: 检查值是否为字符串类型
  • stringSize: 计算字符串的实际字符数量
  • getTag: 获取值的内部[[Class]]标签,用于精确判断数据类型
  • baseKeys: 获取对象的所有自身可枚举属性键名数组

技术知识

  • JavaScript 数据类型判断方法
  • 集合类型(Array、String、Object、Map、Set)的大小计算方式
  • Object.prototype.toString.call()方法和[object Type]标记
  • Symbol.toStringTag 及 ES6 引入的内置对象类型识别机制

源码实现

function size(collection) {
  if (collection == null) {
    return 0;
  }
  if (isArrayLike(collection)) {
    return isString(collection) ? stringSize(collection) : collection.length;
  }
  var tag = getTag(collection);
  if (tag == mapTag || tag == setTag) {
    return collection.size;
  }
  return baseKeys(collection).length;
}

实现思路

  • 首先处理空值情况,若集合为 null 或 undefined 则直接返回 0
  • 判断集合是否为类数组,若是则进一步区分字符串和普通数组,采用不同方式获取长度
  • 对于非类数组集合,通过 getTag 获取其内部标签类型
  • 针对 Map 和 Set 类型,直接使用其内置的 size 属性
  • 对于普通对象,则通过 baseKeys 获取所有键名然后计算数组长度

源码解析

1. 空值处理

if (collection == null) {
  return 0;
}

这段代码使用宽松相等操作符==检查collection是否为nullundefined。在 JavaScript 中,只有这两个值与null进行宽松比较时会返回true。当集合为空时,函数直接返回 0 作为集合大小,避免后续操作出现错误。

例如:

_.size(null); // => 0
_.size(undefined); // => 0

2. 类数组和字符串处理

if (isArrayLike(collection)) {
  return isString(collection) ? stringSize(collection) : collection.length;
}

当集合是类数组对象时,函数进一步判断是否为字符串类型:

  • 若为字符串,则调用stringSize函数计算字符串的实际字符数
  • 若为其他类数组对象(如数组、arguments 等),则直接返回其length属性值

对字符串使用特殊处理是因为 JavaScript 中字符串的length属性只计算代码单元(code units)数量,而不是实际 Unicode 字符数量,尤其对于包含 emoji 或 surrogate pairs 的字符串,stringSize能更准确地计算。

例如:

_.size([1, 2, 3]); // => 3
_.size("abc"); // => 3

3. Map、Set 和普通对象处理

var tag = getTag(collection);
if (tag == mapTag || tag == setTag) {
  return collection.size;
}
return baseKeys(collection).length;

对于非类数组的集合,函数首先通过getTag获取其内部[[Class]]标签:

  • 若集合是 Map 或 Set 类型,则直接返回其内置的size属性值
  • 对于其他对象类型(如普通对象),则调用baseKeys获取所有自身可枚举属性的键名数组,然后返回该数组的长度

getTag函数通过Object.prototype.toString.call()实现更准确的类型检测,能识别 ES6 引入的新集合类型。

例如:

_.size(
  new Map([
    ["a", 1],
    ["b", 2],
  ])
); // => 2
_.size(new Set([1, 2, 3, 3])); // => 3 (Set自动去重)
_.size({ a: 1, b: 2 }); // => 2

总结

  • 使用宽松相等(==)检查 null/undefined,减少代码冗余
  • 采用类型检测分派策略,对不同集合类型使用最优的大小计算方法
  • 特殊处理字符串,确保 Unicode 字符计数准确性
  • 利用 ES6 集合类型的内置属性,避免不必要的转换操作
  • 结合Object.keyslength实现对普通对象大小的优雅计算