Lodash源码阅读-getSymbols

Lodash 源码阅读-getSymbols

功能概述

getSymbols 是 Lodash 库中的一个内部工具函数,它的主要作用是创建一个包含目标对象自身可枚举 Symbol 属性的数组。这个函数在处理对象的 Symbol 类型属性时非常有用,特别是在需要获取对象所有键(包括 Symbol 类型)的场景中。

前置学习

依赖关系

getSymbols 函数依赖以下几个函数和变量:

  • nativeGetSymbols :原生的 Object.getOwnPropertySymbols 方法的引用
  • stubArray:一个返回空数组的工具函数,在不支持 Symbol 的环境中作为 fallback
  • arrayFilter:一个数组过滤函数,用于筛选出可枚举的 Symbol
  • propertyIsEnumerableObject.prototype.propertyIsEnumerable 方法的引用,用于检查属性是否可枚举

技术知识

  • ES6 Symbol:JavaScript 中的原始数据类型,表示唯一的标识符
  • Object.getOwnPropertySymbols:获取对象自身所有 Symbol 属性的方法
  • 可枚举性(Enumerability):JavaScript 属性的一个特性,决定属性是否出现在对象的枚举中
  • 条件定义:根据环境特性动态定义函数的技术

源码实现

javascript 复制代码
/**
 * Creates an array of the own enumerable symbols of `object`.
 *
 * @private
 * @param {Object} object The object to query.
 * @returns {Array} Returns the array of symbols.
 */
var getSymbols = !nativeGetSymbols
  ? stubArray
  : function (object) {
      if (object == null) {
        return [];
      }
      object = Object(object);
      return arrayFilter(nativeGetSymbols(object), function (symbol) {
        return propertyIsEnumerable.call(object, symbol);
      });
    };

实现思路

getSymbols 函数的实现思路非常巧妙,它首先检查环境是否支持 Symbol(通过检查 nativeGetSymbols 是否存在),然后根据结果采取不同的策略:

  1. 如果环境不支持 Symbol,则直接使用 stubArray 函数,始终返回空数组
  2. 如果环境支持 Symbol,则定义一个函数,该函数会:
    • 检查输入对象是否为 null 或 undefined,如果是则返回空数组
    • 将输入转换为对象
    • 使用 nativeGetSymbols 获取所有 Symbol 属性
    • 使用 arrayFilter 过滤出可枚举的 Symbol 属性
    • 返回过滤后的结果

这种实现方式既保证了在所有环境中的兼容性,又提供了在支持 Symbol 的环境中的完整功能。

源码解析

条件定义

javascript 复制代码
var getSymbols = !nativeGetSymbols ? stubArray : function(object) {

这行代码使用了条件运算符来定义 getSymbols 函数。它检查 nativeGetSymbols(即 Object.getOwnPropertySymbols)是否存在:

  • 如果 nativeGetSymbols 不存在(值为 falsy),则 getSymbols 被赋值为 stubArray 函数
  • 如果 nativeGetSymbols 存在,则 getSymbols 被赋值为一个新定义的函数

这种模式在 Lodash 中很常见,用于处理不同环境的兼容性问题。在不支持某些新特性的旧环境中,提供一个简单的替代实现。

空值处理

javascript 复制代码
if (object == null) {
  return [];
}

这段代码检查输入对象是否为 nullundefined(使用 == 运算符可以同时检查这两种情况)。如果是,则直接返回空数组,避免后续操作出错。

对象转换

javascript 复制代码
object = Object(object);

这行代码将输入值转换为对象。这是一个安全措施,确保即使传入的是原始值(如数字或字符串),也能正确处理。例如:

  • Object(42) 会创建一个 Number 对象
  • Object('hello') 会创建一个 String 对象
  • 如果 object 已经是对象,则不会有变化

获取和过滤 Symbol

javascript 复制代码
return arrayFilter(nativeGetSymbols(object), function (symbol) {
  return propertyIsEnumerable.call(object, symbol);
});

这段代码是函数的核心部分:

  1. nativeGetSymbols(object) 调用原生的 Object.getOwnPropertySymbols 方法,获取对象的所有 Symbol 属性(无论是否可枚举)
  2. arrayFilter 函数遍历这些 Symbol,并应用一个过滤函数
  3. 过滤函数 function(symbol) { return propertyIsEnumerable.call(object, symbol); } 检查每个 Symbol 是否是对象的可枚举属性
  4. 最终返回只包含可枚举 Symbol 的数组

这里使用 propertyIsEnumerable.call(object, symbol) 而不是 object.propertyIsEnumerable(symbol) 是为了确保正确的 this 绑定,特别是在处理原始值包装对象的情况下。

stubArray 函数

当环境不支持 Symbol 时,getSymbols 会使用 stubArray 函数:

javascript 复制代码
function stubArray() {
  return [];
}

这个函数非常简单,它只是返回一个空数组。在不支持 Symbol 的环境中,没有 Symbol 属性可以获取,所以返回空数组是合理的行为。

与原生方法的比较

getSymbols 与原生的 Object.getOwnPropertySymbols 方法有一些重要区别:

  1. 可枚举性过滤

    • getSymbols 只返回可枚举的 Symbol 属性
    • Object.getOwnPropertySymbols 返回所有 Symbol 属性,无论是否可枚举
    javascript 复制代码
    const obj = {};
    const sym1 = Symbol("enumerable");
    const sym2 = Symbol("non-enumerable");
    
    Object.defineProperty(obj, sym1, { enumerable: true, value: "value1" });
    Object.defineProperty(obj, sym2, { enumerable: false, value: "value2" });
    
    console.log(Object.getOwnPropertySymbols(obj).length); // 2
    console.log(_.getSymbols(obj).length); // 1
  2. 兼容性处理

    • getSymbols 在不支持 Symbol 的环境中优雅降级,返回空数组
    • Object.getOwnPropertySymbols 在不支持的环境中会抛出错误
  3. 空值处理

    • getSymbols 对 null 和 undefined 返回空数组
    • Object.getOwnPropertySymbols 对 null 和 undefined 会抛出 TypeError
    javascript 复制代码
    try {
      Object.getOwnPropertySymbols(null); // 抛出 TypeError
    } catch (e) {
      console.error(e);
    }
    
    console.log(_.getSymbols(null)); // []

总结

getSymbols 是 Lodash 中一个精心设计的内部工具函数,它解决了获取对象可枚举 Symbol 属性的问题,并提供了良好的兼容性和错误处理。

这个函数的设计体现了几个重要的软件工程原则:

  1. 优雅降级:在不支持新特性的环境中提供合理的替代方案
  2. 防御性编程:通过检查空值和类型转换,防止运行时错误
  3. 单一职责:函数只负责一件事情 - 获取对象的可枚举 Symbol 属性
  4. 组合复用 :通过组合使用其他函数(如 arrayFilterstubArray)来实现功能

在现代 JavaScript 开发中,随着 Symbol 的广泛使用,特别是在库和框架内部,getSymbols 这样的工具函数变得越来越重要。它帮助开发者处理包含 Symbol 的对象,确保在操作对象时不会遗漏这些特殊属性。

相关推荐
gnip2 分钟前
项目开发流程之技术调用流程
前端·javascript
答案—answer2 分钟前
three.js编辑器2.0版本
javascript·three.js·three.js 编辑器·three.js性能优化·three.js模型编辑·three.js 粒子特效·three.js加载模型
转转技术团队16 分钟前
多代理混战?用 PAC(Proxy Auto-Config) 优雅切换代理场景
前端·后端·面试
南囝coding17 分钟前
这几个 Vibe Coding 经验,真的建议学!
前端·后端
gnip31 分钟前
SSE技术介绍
前端·javascript
yinke小琪1 小时前
JavaScript DOM节点操作(增删改)常用方法
前端·javascript
枣把儿1 小时前
Vercel 收购 NuxtLabs!Nuxt UI Pro 即将免费!
前端·vue.js·nuxt.js
望获linux1 小时前
【Linux基础知识系列】第四十三篇 - 基础正则表达式与 grep/sed
linux·运维·服务器·开发语言·前端·操作系统·嵌入式软件
爱编程的喵1 小时前
从XMLHttpRequest到Fetch:前端异步请求的演进之路
前端·javascript
喜欢吃豆1 小时前
深入企业内部的MCP知识(三):FastMCP工具转换(Tool Transformation)全解析:从适配到增强的工具进化指南
java·前端·人工智能·大模型·github·mcp