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
函数的实现思路非常清晰:
-
首先判断环境是否支持 Symbol(通过检查
nativeGetSymbols
是否存在):- 如果不支持,简单返回
stubArray
函数,即返回空数组 - 如果支持,则定义一个函数,该函数会遍历对象及其原型链
- 如果不支持,简单返回
-
对于支持 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
循环来遍历对象及其原型链:
while (object)
检查当前对象是否存在。当遍历到原型链的顶端(null
)时,循环结束arrayPush(result, getSymbols(object))
调用getSymbols
获取当前对象的可枚举 Symbol 属性,并使用arrayPush
将这些属性添加到结果数组中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 的比较
getSymbolsIn
和 getSymbols
是两个相似但用途不同的函数:
-
属性范围:
getSymbols
只返回对象自身的可枚举 Symbol 属性getSymbolsIn
返回对象自身和继承的可枚举 Symbol 属性
-
实现方式:
getSymbols
直接使用Object.getOwnPropertySymbols
并过滤可枚举属性getSymbolsIn
使用循环遍历原型链,并在每个层级上调用getSymbols
-
使用场景:
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 属性。这个函数在处理需要考虑继承属性的场景中非常有用,如完整的对象克隆和合并操作。
函数的实现体现了几个重要的软件工程原则:
- 优雅降级:在不支持 Symbol 的环境中提供合理的替代方案
- 循环迭代:通过简单的循环实现对原型链的有效遍历
- 函数组合 :基于
getSymbols
、arrayPush
和getPrototype
等基础函数构建更复杂的功能 - 职责明确:专注于获取对象及其原型链上的 Symbol 属性这一单一任务
在需要处理 JavaScript 对象继承结构和 Symbol 属性的场景中,getSymbolsIn
提供了一种简洁而有效的解决方案。