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 的对象,确保在操作对象时不会遗漏这些特殊属性。

相关推荐
子兮曰1 天前
jev-ultrafast 深度解析:7 秒订机票的浏览器 Agent 是如何炼成的
前端·后端·agent
子兮曰1 天前
Jev 爆发一周:7 秒 Agent 背后的 System One 生态与三场争议
前端·后端·ai编程
前端小万1 天前
写公众号赚了 3000 块后,我做了一款叫 "一键成稿" 的软件
前端·微信小程序
爱勇宝1 天前
ZCode 开源 24 小时:一份没有历史的账本,回答不了"有没有偷代码"
前端·后端·chatglm (智谱)
三十而立洋1 天前
Cookie 详解:从产生到安全,一次讲透
前端·javascript
卡布鲁1 天前
把一个 Vite + Vue3 应用塞进 qiankun (React + Umi3) 主站:十个坑的复盘
前端·javascript·react.js
李少兄1 天前
JavaScript 隐式全局变量解析
javascript
汉堡大王95272 天前
Jev:不是聊天机器人, 而是一个智能 if 语句
前端·人工智能·后端
梦想很大很大2 天前
从运行事实到回归证据:Workrun 的 Telemetry 与 Evaluation 实践
前端·人工智能·后端
计算机魔术师2 天前
Meta Muse agent 接入 Shopify 的 Shop Pay 实现代理式购物
前端