Lodash源码阅读-getSymbolsIn

Lodash 源码阅读-getSymbolsIn

概述

getSymbolsIn 是 Lodash 库中的一个内部工具函数,它的作用是创建一个包含目标对象自身和继承的可枚举 Symbol 属性的数组。与 getSymbols 不同,它会遍历整个原型链,获取对象及其所有原型上的 Symbol 属性。

前置学习

依赖函数

  • nativeGetSymbols :原生的 Object.getOwnPropertySymbols 方法的引用
  • stubArray:一个返回空数组的工具函数,在不支持 Symbol 的环境中作为 fallback
  • arrayPush:用于将一个数组的元素追加到另一个数组中的函数
  • getSymbols:获取对象自身可枚举 Symbol 属性的函数
  • getPrototype :获取对象原型的函数,相当于 Object.getPrototypeOf

技术知识

  • ES6 Symbol:JavaScript 中的原始数据类型,表示唯一的标识符
  • 原型链(Prototype Chain):JavaScript 对象之间通过原型链接起来的继承机制
  • Object.getPrototypeOf:获取对象原型的方法

源码实现

javascript 复制代码
/**
 * Creates an array of the own and inherited enumerable symbols of `object`.
 *
 * @private
 * @param {Object} object The object to query.
 * @returns {Array} Returns the array of symbols.
 */
var getSymbolsIn = !nativeGetSymbols
  ? stubArray
  : function (object) {
      var result = [];
      while (object) {
        arrayPush(result, getSymbols(object));
        object = getPrototype(object);
      }
      return result;
    };

实现思路

getSymbolsIn 函数的实现思路非常清晰:

  1. 首先判断环境是否支持 Symbol(通过检查 nativeGetSymbols 是否存在):

    • 如果不支持,简单返回 stubArray 函数,即返回空数组
    • 如果支持,则定义一个函数,该函数会遍历对象及其原型链
  2. 对于支持 Symbol 的环境,实现逻辑如下:

    • 创建一个空数组 result 用于存储结果
    • 使用 while 循环遍历对象及其原型链
    • 在每次循环中,使用 getSymbols 获取当前对象的可枚举 Symbol 属性,并通过 arrayPush 添加到结果数组中
    • 使用 getPrototype 获取当前对象的原型,作为下一次循环的对象
    • 当原型链遍历完毕(到达 null)时,循环结束
    • 返回收集到的所有 Symbol 属性数组

这种实现方式确保了可以获取对象及其整个原型链上的所有可枚举 Symbol 属性。

源码解析

条件定义部分

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

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

  • 如果 nativeGetSymbols 不存在(值为 falsy),则 getSymbolsIn 被赋值为 stubArray 函数,始终返回空数组
  • 如果 nativeGetSymbols 存在,则 getSymbolsIn 被赋值为一个新定义的函数

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

结果初始化

javascript 复制代码
var result = [];

这行代码创建了一个空数组,用于存储收集到的所有 Symbol 属性。这是必要的,因为函数需要返回一个包含对象及其原型链上所有 Symbol 属性的数组。

原型链遍历

javascript 复制代码
while (object) {
  arrayPush(result, getSymbols(object));
  object = getPrototype(object);
}

这段代码是函数的核心部分,它使用 while 循环来遍历对象及其原型链:

  1. while (object) 检查当前对象是否存在。当遍历到原型链的顶端(null)时,循环结束
  2. arrayPush(result, getSymbols(object)) 调用 getSymbols 获取当前对象的可枚举 Symbol 属性,并使用 arrayPush 将这些属性添加到结果数组中
  3. object = getPrototype(object) 获取当前对象的原型,作为下一次循环的对象

这种循环方式可以有效地遍历整个原型链,收集所有继承的 Symbol 属性。

javascript 复制代码
// arrayPush 函数的实现
function arrayPush(array, values) {
  var index = -1,
    length = values.length,
    offset = array.length;

  while (++index < length) {
    array[offset + index] = values[index];
  }
  return array;
}

// getPrototype 函数的实现 (简化版)
var getPrototype = Object.getPrototypeOf;

返回结果

javascript 复制代码
return result;

函数最后返回收集到的所有 Symbol 属性数组。这个数组包含了对象自身和其原型链上的所有可枚举 Symbol 属性。

与 getSymbols 的比较

getSymbolsIngetSymbols 是两个相似但用途不同的函数:

  1. 属性范围

    • getSymbols 只返回对象自身的可枚举 Symbol 属性
    • getSymbolsIn 返回对象自身和继承的可枚举 Symbol 属性
  2. 实现方式

    • getSymbols 直接使用 Object.getOwnPropertySymbols 并过滤可枚举属性
    • getSymbolsIn 使用循环遍历原型链,并在每个层级上调用 getSymbols
  3. 使用场景

    • getSymbols 适用于只关心对象自身 Symbol 属性的情况
    • getSymbolsIn 适用于需要考虑继承 Symbol 属性的情况

示例:

javascript 复制代码
const parent = {};
const child = Object.create(parent);

// 定义 Symbol 属性
const parentSym = Symbol("parent");
const childSym = Symbol("child");

// 在原型上设置 Symbol 属性
parent[parentSym] = "parent value";
// 在对象自身设置 Symbol 属性
child[childSym] = "child value";

// 使用 getSymbols
console.log(getSymbols(child).length); // 1,只有 childSym

// 使用 getSymbolsIn
console.log(getSymbolsIn(child).length); // 2,包括 childSym 和继承的 parentSym

总结

getSymbolsIn 是 Lodash 中一个重要的内部工具函数,它通过遍历原型链获取对象及其所有原型上的可枚举 Symbol 属性。这个函数在处理需要考虑继承属性的场景中非常有用,如完整的对象克隆和合并操作。

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

  1. 优雅降级:在不支持 Symbol 的环境中提供合理的替代方案
  2. 循环迭代:通过简单的循环实现对原型链的有效遍历
  3. 函数组合 :基于 getSymbolsarrayPushgetPrototype 等基础函数构建更复杂的功能
  4. 职责明确:专注于获取对象及其原型链上的 Symbol 属性这一单一任务

在需要处理 JavaScript 对象继承结构和 Symbol 属性的场景中,getSymbolsIn 提供了一种简洁而有效的解决方案。

相关推荐
小墨宝26 分钟前
js 生成pdf 并上传文件
前端·javascript·pdf
HED41 分钟前
用扣子快速手撸人生中第一个AI智能应用!
前端·人工智能
DN金猿1 小时前
使用npm install或cnpm install报错解决
前端·npm·node.js
丘山子1 小时前
一些鲜为人知的 IP 地址怪异写法
前端·后端·tcp/ip
志存高远661 小时前
Kotlin 的 suspend 关键字
前端
www_pp_1 小时前
# 构建词汇表:自然语言处理中的关键步骤
前端·javascript·自然语言处理·easyui
YuShiYue1 小时前
pnpm monoreop 打包时 node_modules 内部包 typescript 不能推导出类型报错
javascript·vue.js·typescript·pnpm
天天扭码2 小时前
总所周知,JavaScript中有很多函数定义方式,如何“因地制宜”?(ˉ﹃ˉ)
前端·javascript·面试
一个专注写代码的程序媛2 小时前
为什么vue的key值,不用index?
前端·javascript·vue.js
장숙혜2 小时前
ElementUi的Dropdown下拉菜单的详细介绍及使用
前端·javascript·vue.js