Lodash 源码阅读-getSymbols
功能概述
getSymbols
是 Lodash 库中的一个内部工具函数,它的主要作用是创建一个包含目标对象自身可枚举 Symbol 属性的数组。这个函数在处理对象的 Symbol 类型属性时非常有用,特别是在需要获取对象所有键(包括 Symbol 类型)的场景中。
前置学习
依赖关系
getSymbols
函数依赖以下几个函数和变量:
- nativeGetSymbols :原生的
Object.getOwnPropertySymbols
方法的引用 - stubArray:一个返回空数组的工具函数,在不支持 Symbol 的环境中作为 fallback
- arrayFilter:一个数组过滤函数,用于筛选出可枚举的 Symbol
- propertyIsEnumerable :
Object.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
是否存在),然后根据结果采取不同的策略:
- 如果环境不支持 Symbol,则直接使用
stubArray
函数,始终返回空数组 - 如果环境支持 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 [];
}
这段代码检查输入对象是否为 null
或 undefined
(使用 ==
运算符可以同时检查这两种情况)。如果是,则直接返回空数组,避免后续操作出错。
对象转换
javascript
object = Object(object);
这行代码将输入值转换为对象。这是一个安全措施,确保即使传入的是原始值(如数字或字符串),也能正确处理。例如:
Object(42)
会创建一个 Number 对象Object('hello')
会创建一个 String 对象- 如果
object
已经是对象,则不会有变化
获取和过滤 Symbol
javascript
return arrayFilter(nativeGetSymbols(object), function (symbol) {
return propertyIsEnumerable.call(object, symbol);
});
这段代码是函数的核心部分:
nativeGetSymbols(object)
调用原生的Object.getOwnPropertySymbols
方法,获取对象的所有 Symbol 属性(无论是否可枚举)arrayFilter
函数遍历这些 Symbol,并应用一个过滤函数- 过滤函数
function(symbol) { return propertyIsEnumerable.call(object, symbol); }
检查每个 Symbol 是否是对象的可枚举属性 - 最终返回只包含可枚举 Symbol 的数组
这里使用 propertyIsEnumerable.call(object, symbol)
而不是 object.propertyIsEnumerable(symbol)
是为了确保正确的 this
绑定,特别是在处理原始值包装对象的情况下。
stubArray 函数
当环境不支持 Symbol 时,getSymbols
会使用 stubArray
函数:
javascript
function stubArray() {
return [];
}
这个函数非常简单,它只是返回一个空数组。在不支持 Symbol 的环境中,没有 Symbol 属性可以获取,所以返回空数组是合理的行为。
与原生方法的比较
getSymbols
与原生的 Object.getOwnPropertySymbols
方法有一些重要区别:
-
可枚举性过滤:
getSymbols
只返回可枚举的 Symbol 属性Object.getOwnPropertySymbols
返回所有 Symbol 属性,无论是否可枚举
javascriptconst 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
-
兼容性处理:
getSymbols
在不支持 Symbol 的环境中优雅降级,返回空数组Object.getOwnPropertySymbols
在不支持的环境中会抛出错误
-
空值处理:
getSymbols
对 null 和 undefined 返回空数组Object.getOwnPropertySymbols
对 null 和 undefined 会抛出 TypeError
javascripttry { Object.getOwnPropertySymbols(null); // 抛出 TypeError } catch (e) { console.error(e); } console.log(_.getSymbols(null)); // []
总结
getSymbols
是 Lodash 中一个精心设计的内部工具函数,它解决了获取对象可枚举 Symbol 属性的问题,并提供了良好的兼容性和错误处理。
这个函数的设计体现了几个重要的软件工程原则:
- 优雅降级:在不支持新特性的环境中提供合理的替代方案
- 防御性编程:通过检查空值和类型转换,防止运行时错误
- 单一职责:函数只负责一件事情 - 获取对象的可枚举 Symbol 属性
- 组合复用 :通过组合使用其他函数(如
arrayFilter
和stubArray
)来实现功能
在现代 JavaScript 开发中,随着 Symbol 的广泛使用,特别是在库和框架内部,getSymbols
这样的工具函数变得越来越重要。它帮助开发者处理包含 Symbol 的对象,确保在操作对象时不会遗漏这些特殊属性。