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

相关推荐
阿珊和她的猫31 分钟前
v-scale-scree: 根据屏幕尺寸缩放内容
开发语言·前端·javascript
加班是不可能的,除非双倍日工资5 小时前
css预编译器实现星空背景图
前端·css·vue3
wyiyiyi5 小时前
【Web后端】Django、flask及其场景——以构建系统原型为例
前端·数据库·后端·python·django·flask
gnip6 小时前
vite和webpack打包结构控制
前端·javascript
excel6 小时前
在二维 Canvas 中模拟三角形绕 X、Y 轴旋转
前端
阿华的代码王国6 小时前
【Android】RecyclerView复用CheckBox的异常状态
android·xml·java·前端·后端
一条上岸小咸鱼6 小时前
Kotlin 基本数据类型(三):Booleans、Characters
android·前端·kotlin
Jimmy6 小时前
AI 代理是什么,其有助于我们实现更智能编程
前端·后端·ai编程
ZXT7 小时前
promise & async await总结
前端
Jerry说前后端7 小时前
RecyclerView 性能优化:从原理到实践的深度优化方案
android·前端·性能优化