普通视图
Lodash源码阅读-initCloneByTag
Lodash源码阅读-cloneSymbol
Lodash源码阅读-cloneRegExp
Lodash源码阅读-cloneTypedArray
Lodash 源码阅读-cloneTypedArray
概述
cloneTypedArray
是 Lodash 库中的一个内部工具函数,主要用于创建 TypedArray(类型化数组)对象的克隆。TypedArray 是处理二进制数据的专用数组类型,如 Int8Array、Uint8Array、Float32Array 等,这个函数在深度克隆操作中确保了对这类特殊数组的正确处理,支持浅拷贝和深拷贝两种模式。
前置学习
依赖函数
- cloneArrayBuffer:用于克隆 TypedArray 底层的 ArrayBuffer 对象,在深拷贝模式下使用
技术知识
- TypedArray:JavaScript 中的类型化数组,包括 Int8Array、Uint8Array、Float32Array 等多种类型
- ArrayBuffer:类型化数组底层的二进制数据缓冲区
-
构造函数属性:JavaScript 对象的
constructor
属性,指向创建该对象的构造函数 - 浅拷贝与深拷贝:对象复制的两种模式及其在处理引用类型时的区别
源码实现
/**
* Creates a clone of `typedArray`.
*
* @private
* @param {Object} typedArray The typed array to clone.
* @param {boolean} [isDeep] Specify a deep clone.
* @returns {Object} Returns the cloned typed array.
*/
function cloneTypedArray(typedArray, isDeep) {
var buffer = isDeep ? cloneArrayBuffer(typedArray.buffer) : typedArray.buffer;
return new typedArray.constructor(
buffer,
typedArray.byteOffset,
typedArray.length
);
}
实现思路
cloneTypedArray
函数的实现思路清晰直接:
- 根据
isDeep
参数决定如何处理 TypedArray 的底层 ArrayBuffer:- 如果是深拷贝(
isDeep
为 true),则使用cloneArrayBuffer
函数创建底层 ArrayBuffer 的独立副本 - 如果是浅拷贝(
isDeep
为 false 或未提供),则直接使用原始 TypedArray 的 buffer 引用
- 如果是深拷贝(
- 使用原始 TypedArray 的构造函数创建一个新的 TypedArray 实例,传入处理后的 buffer、原始的字节偏移量和长度
- 返回新创建的 TypedArray 对象
这种实现方式既可以创建共享底层数据的浅拷贝,也可以创建完全独立的深拷贝,满足不同场景的需求。
源码解析
函数签名
function cloneTypedArray(typedArray, isDeep) {
函数接收两个参数:
-
typedArray
:要克隆的类型化数组对象(如 Int8Array、Uint8Array 等) -
isDeep
:可选的布尔值,指定是否进行深拷贝
处理 buffer
var buffer = isDeep ? cloneArrayBuffer(typedArray.buffer) : typedArray.buffer;
这行代码是函数的核心,它根据 isDeep
参数决定如何处理底层的 ArrayBuffer:
- 如果
isDeep
为 true,调用cloneArrayBuffer
函数创建typedArray.buffer
的深拷贝 - 如果
isDeep
为 false 或未提供,直接使用原始的typedArray.buffer
引用
在深拷贝模式下,新创建的 TypedArray 将拥有自己独立的底层数据存储,不会与原始数组共享数据。而在浅拷贝模式下,新旧数组共享同一个底层 ArrayBuffer,对一个数组的修改会影响另一个。
创建新的 TypedArray
return new typedArray.constructor(
buffer,
typedArray.byteOffset,
typedArray.length
);
这行代码创建并返回一个新的 TypedArray 对象:
- 通过
typedArray.constructor
获取原始类型化数组的构造函数(如 Int8Array、Float32Array 等) - 使用该构造函数创建一个新的类型化数组实例,传入三个参数:
-
buffer
:上一步获取或创建的 ArrayBuffer -
typedArray.byteOffset
:原始类型化数组的字节偏移量 -
typedArray.length
:原始类型化数组的长度(元素个数)
-
使用 constructor
属性而不是硬编码特定的构造函数,使得这个函数可以处理所有类型的 TypedArray,保持了代码的通用性和扩展性。
不同类型的 TypedArray
JavaScript 提供了多种类型化数组,cloneTypedArray
可以处理所有这些类型:
- Int8Array:8 位有符号整数数组
- Uint8Array:8 位无符号整数数组
- Uint8ClampedArray:8 位无符号整数数组(固定范围)
- Int16Array:16 位有符号整数数组
- Uint16Array:16 位无符号整数数组
- Int32Array:32 位有符号整数数组
- Uint32Array:32 位无符号整数数组
- Float32Array:32 位浮点数数组
- Float64Array:64 位浮点数数组
无论哪种类型,cloneTypedArray
都能通过 constructor
属性正确地创建对应类型的克隆。
浅拷贝与深拷贝的区别
// 浅拷贝 - 共享同一个底层 ArrayBuffer
var shallowClone = cloneTypedArray(originalArray, false);
// 深拷贝 - 创建底层 ArrayBuffer 的独立副本
var deepClone = cloneTypedArray(originalArray, true);
// 修改测试
shallowClone[0] = 99; // 也会修改 originalArray[0]
deepClone[0] = 99; // 不会影响 originalArray[0]
浅拷贝和深拷贝的关键区别在于对底层 ArrayBuffer 的处理:
- 浅拷贝创建的新数组与原始数组共享同一个底层 ArrayBuffer,任何一方的修改都会影响另一方
- 深拷贝通过复制底层 ArrayBuffer 创建完全独立的新数组,两个数组之间的修改互不影响
总结
cloneTypedArray
是 Lodash 中一个专门用于克隆类型化数组的工具函数,它通过灵活处理底层 ArrayBuffer,实现了对各种类型化数组的浅拷贝和深拷贝。
这个函数的实现体现了几个重要的软件设计原则:
-
通用性:使用
constructor
属性使函数能够处理所有类型的 TypedArray -
灵活性:通过
isDeep
参数支持浅拷贝和深拷贝两种模式 -
组合复用:利用
cloneArrayBuffer
函数处理底层数据的复制,避免代码重复 - 单一职责:函数只关注于类型化数组的克隆,遵循"做好一件事"的原则
在处理二进制数据和数值计算的 JavaScript 应用中,cloneTypedArray
提供了一种简洁而强大的方式来创建类型化数组的副本,为数据处理提供了更多的灵活性和安全性。
Lodash源码阅读-cloneDataView
Lodash 源码阅读-cloneDataView
概述
cloneDataView
是 Lodash 库中的一个内部工具函数,主要用于创建 DataView 对象的克隆。DataView 提供了对二进制数据缓冲区的低级读写接口,这个函数确保了在克隆复杂对象时能够正确处理 DataView 类型,支持浅拷贝和深拷贝两种模式。
前置学习
依赖函数
- cloneArrayBuffer:用于克隆 DataView 底层的 ArrayBuffer 对象,实现深拷贝时使用
技术知识
- DataView:JavaScript 中用于读写二进制数据缓冲区的底层接口
- ArrayBuffer:表示通用的、固定长度的原始二进制数据缓冲区
-
构造函数属性:JavaScript 对象的
constructor
属性,指向创建该对象的构造函数 - 浅拷贝与深拷贝:对象复制的两种模式及其区别
源码实现
/**
* Creates a clone of `dataView`.
*
* @private
* @param {Object} dataView The data view to clone.
* @param {boolean} [isDeep] Specify a deep clone.
* @returns {Object} Returns the cloned data view.
*/
function cloneDataView(dataView, isDeep) {
var buffer = isDeep ? cloneArrayBuffer(dataView.buffer) : dataView.buffer;
return new dataView.constructor(
buffer,
dataView.byteOffset,
dataView.byteLength
);
}
实现思路
cloneDataView
函数的实现思路直接明了:
- 根据
isDeep
参数决定是否对 DataView 的底层 ArrayBuffer 进行深拷贝:- 如果是深拷贝(
isDeep
为 true),则使用cloneArrayBuffer
函数复制底层的 ArrayBuffer - 如果是浅拷贝(
isDeep
为 false),则直接使用原始 DataView 的 buffer 引用
- 如果是深拷贝(
- 使用 DataView 的构造函数,结合提供的 buffer(原始或克隆的)和偏移量信息,创建一个新的 DataView 对象
- 返回新创建的 DataView 对象
这种实现方式灵活地支持了浅拷贝和深拷贝两种需求,使得函数可以在不同的克隆场景中使用。
源码解析
函数签名
function cloneDataView(dataView, isDeep) {
函数接收两个参数:
-
dataView
:要克隆的 DataView 对象 -
isDeep
:可选的布尔值,指定是否进行深拷贝
处理 buffer
var buffer = isDeep ? cloneArrayBuffer(dataView.buffer) : dataView.buffer;
这行代码根据 isDeep
参数决定如何处理 DataView 的底层 ArrayBuffer:
- 如果
isDeep
为 true,调用cloneArrayBuffer
函数创建dataView.buffer
的深拷贝 - 如果
isDeep
为 false 或未提供,直接使用原始dataView.buffer
的引用
深拷贝确保了新创建的 DataView 有自己独立的底层 ArrayBuffer,任何对新 DataView 的修改都不会影响原始对象。而浅拷贝则保持了对同一底层 ArrayBuffer 的引用,修改会相互影响。
创建新的 DataView
return new dataView.constructor(
buffer,
dataView.byteOffset,
dataView.byteLength
);
这行代码创建并返回一个新的 DataView 对象:
- 使用
dataView.constructor
(即 DataView 构造函数)创建一个新的 DataView 实例 - 传递三个参数:
-
buffer
:上一步获取或创建的 ArrayBuffer -
dataView.byteOffset
:原始 DataView 的字节偏移量 -
dataView.byteLength
:原始 DataView 的字节长度
-
使用 constructor
属性而不是直接使用 DataView
构造函数,是一种防御性编程方法,确保代码在各种环境中的兼容性。
浅拷贝与深拷贝的区别
在 cloneDataView
函数中,浅拷贝和深拷贝的区别在于对底层 ArrayBuffer 的处理:
// 浅拷贝 - 共享同一个底层 ArrayBuffer
var shallowClone = cloneDataView(originalDataView, false);
// 深拷贝 - 创建底层 ArrayBuffer 的独立副本
var deepClone = cloneDataView(originalDataView, true);
- 浅拷贝:新旧 DataView 指向同一个 ArrayBuffer,一个 DataView 的修改会影响另一个
- 深拷贝:新的 DataView 指向一个独立的 ArrayBuffer 副本,相互之间的修改不会影响对方
总结
cloneDataView
是 Lodash 中一个专门用于克隆 DataView 对象的工具函数,它巧妙地利用了 cloneArrayBuffer
来支持深拷贝,同时也提供了浅拷贝的选项。
这个函数的实现体现了几个重要的编程原则:
- 单一职责:函数只负责一件事情 - 克隆 DataView 对象
-
开闭原则:通过参数化
isDeep
实现了功能的扩展,而不需要修改函数内部实现 -
组合复用:利用
cloneArrayBuffer
函数处理底层 ArrayBuffer 的复制,体现了"组合优于继承"的思想 -
防御性编程:使用
constructor
属性而不是直接引用构造函数,增强了代码的兼容性
在处理二进制数据的应用中,cloneDataView
提供了一种灵活而可靠的方式来创建 DataView 的副本,无论是需要共享底层数据还是完全独立的数据副本。
Lodash源码阅读-cloneArrayBuffer
Lodash 源码阅读-cloneArrayBuffer
概述
cloneArrayBuffer
是 Lodash 库中的一个内部工具函数,主要用于创建 ArrayBuffer 对象的克隆。在处理二进制数据时,这个函数确保了对 ArrayBuffer 的深度复制,而不是简单的引用复制,这对于避免副作用和保持数据独立性至关重要。
前置学习
依赖函数
cloneArrayBuffer
是一个相对独立的函数,不依赖其他 Lodash 工具函数。
技术知识
- ArrayBuffer:表示通用的、固定长度的原始二进制数据缓冲区
- TypedArray:如 Uint8Array,是访问二进制数据缓冲区的视图
-
构造函数属性:JavaScript 对象的
constructor
属性,指向创建该对象的构造函数 - 二进制数据操作:在 JavaScript 中处理二进制数据的方法
源码实现
/**
* Creates a clone of `arrayBuffer`.
*
* @private
* @param {ArrayBuffer} arrayBuffer The array buffer to clone.
* @returns {ArrayBuffer} Returns the cloned array buffer.
*/
function cloneArrayBuffer(arrayBuffer) {
var result = new arrayBuffer.constructor(arrayBuffer.byteLength);
new Uint8Array(result).set(new Uint8Array(arrayBuffer));
return result;
}
实现思路
cloneArrayBuffer
函数的实现思路非常直接:
- 创建一个与源 ArrayBuffer 大小相同的新 ArrayBuffer 对象
- 使用 Uint8Array 创建两个视图:一个指向新创建的 ArrayBuffer,另一个指向源 ArrayBuffer
- 通过 Uint8Array 的
set
方法,将源数组的所有字节复制到新数组中 - 返回完成复制的新 ArrayBuffer 对象
这种实现方式确保了新创建的 ArrayBuffer 与原始对象完全相同但互相独立,任何对新对象的修改都不会影响到原始对象。
源码解析
函数签名
function cloneArrayBuffer(arrayBuffer) {
函数接收一个参数:
-
arrayBuffer
:要克隆的 ArrayBuffer 对象
创建新的 ArrayBuffer
var result = new arrayBuffer.constructor(arrayBuffer.byteLength);
这行代码做了两件事:
- 通过
arrayBuffer.constructor
获取原始 ArrayBuffer 的构造函数(即 ArrayBuffer) - 使用这个构造函数创建一个新的 ArrayBuffer 实例,大小与原始对象相同(
arrayBuffer.byteLength
)
使用 constructor
属性而不是直接使用 ArrayBuffer
构造函数,是一种防御性编程方法,确保即使在未来 ArrayBuffer 被子类化的情况下代码仍能正确工作。
复制数据
new Uint8Array(result).set(new Uint8Array(arrayBuffer));
这行代码是函数的核心,它执行了实际的数据复制:
-
new Uint8Array(arrayBuffer)
创建一个视图,指向原始 ArrayBuffer 中的所有字节 -
new Uint8Array(result)
创建一个视图,指向新创建的 ArrayBuffer -
.set()
方法复制一个数组的所有元素到另一个数组,这里是将原始 ArrayBuffer 中的所有字节复制到新的 ArrayBuffer 中
Uint8Array 是以 8 位无符号整数格式查看 ArrayBuffer 的一种视图,使用它可以逐字节地操作 ArrayBuffer。
返回结果
return result;
函数返回复制完成的新 ArrayBuffer 对象。
总结
cloneArrayBuffer
是 Lodash 中一个看似简单但非常实用的工具函数,它解决了 JavaScript 中二进制数据深度复制的问题。通过利用 TypedArray 的能力,它实现了对 ArrayBuffer 数据的高效复制。
这个函数的实现体现了几个关键的编程原则:
- 不可变性:函数不修改输入参数,而是返回一个新的对象
- 封装复杂性:将二进制数据复制的细节封装在简单的函数接口后面
- 组合复用:作为其他克隆函数的基础组件,遵循了"做好一件事"的设计原则
-
防御性编程:使用
constructor
属性而不是直接引用构造函数,增强代码的鲁棒性
在处理二进制数据的 JavaScript 应用中,cloneArrayBuffer
提供了一种简洁可靠的方式来创建数据的深度副本,是二进制数据操作中不可或缺的工具。
Lodash源码阅读-copySymbolsIn
Lodash 源码阅读-copySymbolsIn
概述
copySymbolsIn
是 Lodash 库中的一个内部工具函数,主要用于将源对象的自身和继承的可枚举 Symbol 属性复制到目标对象中。它是对象复制操作中处理 Symbol 类型属性的重要组成部分,特别是在需要考虑继承属性的深度克隆中发挥作用。
前置学习
依赖函数
- copyObject:一个复制对象属性的基础函数,将源对象的属性复制到目标对象
- getSymbolsIn:获取对象自身和继承的可枚举 Symbol 属性的函数,会遍历整个原型链
- Object 构造器:在未提供目标对象时用于创建空对象
技术知识
- ES6 Symbol:JavaScript 中的原始数据类型,用作对象属性的唯一标识符
- 原型链(Prototype Chain):JavaScript 对象继承机制,对象可以从原型链继承属性和方法
- 对象复制:JavaScript 中对象的浅拷贝和深拷贝概念与实现方式
- 属性描述符:JavaScript 中对象属性的特性,如可枚举性、可写性等
源码实现
/**
* Copies own and inherited symbols of `source` to `object`.
*
* @private
* @param {Object} source The object to copy symbols from.
* @param {Object} [object={}] The object to copy symbols to.
* @returns {Object} Returns `object`.
*/
function copySymbolsIn(source, object) {
return copyObject(source, getSymbolsIn(source), object);
}
实现思路
copySymbolsIn
函数的实现思路非常直接明了:
- 调用
getSymbolsIn(source)
获取源对象及其原型链上的所有可枚举 Symbol 属性 - 将这些 Symbol 属性作为要复制的属性列表,传递给
copyObject
函数 -
copyObject
函数负责将这些 Symbol 属性从源对象复制到目标对象 - 最终返回修改后的目标对象
这种实现方式利用了函数组合的思想,将获取 Symbol 和复制属性的功能分离,使代码更简洁清晰。
源码解析
函数签名
function copySymbolsIn(source, object) {
函数接收两个参数:
-
source
:源对象,要从中复制 Symbol 属性的对象 -
object
:目标对象,要将 Symbol 属性复制到的对象(可选)
如果不提供 object
参数,copyObject
函数会创建一个新的空对象。
函数体
return copyObject(source, getSymbolsIn(source), object);
这行代码包含了函数的全部逻辑,可以分解为以下步骤:
-
getSymbolsIn(source)
:调用getSymbolsIn
函数,获取源对象及其原型链上的所有可枚举 Symbol 属性数组 -
copyObject(source, symbolsArray, object)
:调用copyObject
函数,将获取到的 Symbol 属性从源对象复制到目标对象-
source
:源对象,提供属性值 -
symbolsArray
:要复制的属性数组,这里是所有 Symbol 属性 -
object
:目标对象,接收复制的属性
-
copyObject
函数的工作方式是遍历提供的属性数组(这里是 Symbol 属性数组),将每个属性从源对象复制到目标对象,并返回修改后的目标对象。
与 copySymbols 的对比
copySymbolsIn
与 copySymbols
的主要区别在于:
// copySymbols 仅复制对象自身的 Symbol 属性
function copySymbols(source, object) {
return copyObject(source, getSymbols(source), object);
}
// copySymbolsIn 复制对象自身和继承的 Symbol 属性
function copySymbolsIn(source, object) {
return copyObject(source, getSymbolsIn(source), object);
}
区别仅在于使用 getSymbols
还是 getSymbolsIn
来获取 Symbol 属性:
-
getSymbols
只获取对象自身的可枚举 Symbol 属性 -
getSymbolsIn
获取对象自身和继承的可枚举 Symbol 属性
这种区别对应了 JavaScript 中浅拷贝和深拷贝的不同需求。
总结
copySymbolsIn
函数是 Lodash 中一个短小精悍但功能强大的工具函数,它通过组合使用 getSymbolsIn
和 copyObject
两个函数,实现了对象自身和继承的 Symbol 属性的复制。
这个函数的实现体现了几个软件设计原则:
- 单一职责原则:函数只负责一件事——复制 Symbol 属性,而获取 Symbol 和实际的复制操作则委托给其他专门的函数
-
函数组合:通过组合
getSymbolsIn
和copyObject
这两个基础函数来构建更复杂的功能 - 抽象合理:提供了适当级别的抽象,使调用者不需要关心 Symbol 属性的获取和复制细节
-
接口一致性:与
copySymbols
函数保持一致的接口,方便开发者在不同场景下选择合适的工具
在现代 JavaScript 开发中,随着 Symbol 的广泛使用,特别是在框架和库的内部实现中,copySymbolsIn
这样的工具函数变得越来越重要,它确保了在处理对象时不会遗漏这些特殊的属性,包括那些来自原型链的属性。
Lodash源码阅读-copySymbols
Lodash 源码阅读-copySymbols
概述
copySymbols
是 Lodash 库中的一个内部工具函数,主要用于将源对象的自身可枚举 Symbol 属性复制到目标对象中。在 JavaScript 中处理对象复制时,它确保了 Symbol 类型的属性也能被正确地复制,这在标准的对象复制方法中容易被忽略。
前置学习
依赖函数
- copyObject:一个基础的对象属性复制函数,负责将属性从源对象复制到目标对象
- getSymbols:获取对象自身可枚举 Symbol 属性的函数,不包括继承的属性
- Object 构造器:在未提供目标对象时用于创建空对象
技术知识
- ES6 Symbol:JavaScript 中的原始数据类型,用作对象属性的唯一标识符
- 可枚举属性:JavaScript 对象属性的特性之一,决定该属性是否会出现在对象的枚举中
- 对象复制:JavaScript 中对象的浅拷贝和属性转移的概念与实现
- 函数组合:通过组合多个单一功能的函数构建更复杂功能的编程模式
源码实现
/**
* Copies own symbols of `source` to `object`.
*
* @private
* @param {Object} source The object to copy symbols from.
* @param {Object} [object={}] The object to copy symbols to.
* @returns {Object} Returns `object`.
*/
function copySymbols(source, object) {
return copyObject(source, getSymbols(source), object);
}
实现思路
copySymbols
函数的实现思路十分清晰简洁:
- 调用
getSymbols(source)
获取源对象中所有自身的可枚举 Symbol 属性 - 将这些 Symbol 属性作为要复制的属性列表,传递给
copyObject
函数 -
copyObject
函数负责将这些 Symbol 属性从源对象复制到目标对象 - 最终返回修改后的目标对象
这种实现方式体现了"组合胜于继承"的设计理念,通过组合使用 getSymbols
和 copyObject
这两个专门的函数,实现了 Symbol 属性的复制功能。
源码解析
函数签名
function copySymbols(source, object) {
函数接收两个参数:
-
source
:源对象,要从中复制 Symbol 属性的对象 -
object
:目标对象,要将 Symbol 属性复制到的对象(可选参数)
如果调用时不提供 object
参数,copyObject
函数会创建一个新的空对象作为目标对象。
函数体
return copyObject(source, getSymbols(source), object);
这行代码体现了函数的全部逻辑,可以分解为以下几个步骤:
-
getSymbols(source)
:调用getSymbols
函数,获取源对象的所有自身可枚举 Symbol 属性,返回一个数组 -
copyObject(source, symbolsArray, object)
:调用copyObject
函数,将获取到的 Symbol 属性从源对象复制到目标对象-
source
:源对象,提供属性值的来源 -
symbolsArray
:要复制的属性列表,这里是所有 Symbol 属性 -
object
:目标对象,接收复制的属性
-
copyObject
函数的工作原理是遍历提供的属性数组(这里是 Symbol 属性数组),将每个属性从源对象复制到目标对象,并最终返回修改后的目标对象。
与 copySymbolsIn 的比较
copySymbols
与 copySymbolsIn
函数的主要区别在于处理范围:
// copySymbols 只复制对象自身的 Symbol 属性
function copySymbols(source, object) {
return copyObject(source, getSymbols(source), object);
}
// copySymbolsIn 复制对象自身和继承的 Symbol 属性
function copySymbolsIn(source, object) {
return copyObject(source, getSymbolsIn(source), object);
}
两者的区别仅在于使用的获取 Symbol 属性的函数不同:
-
getSymbols
只获取对象自身的可枚举 Symbol 属性 -
getSymbolsIn
获取对象自身和继承的可枚举 Symbol 属性(遍历整个原型链)
这种区别对应了不同的使用场景,特别是在需要决定是否复制继承属性时。
总结
copySymbols
是 Lodash 中一个简洁但非常实用的工具函数,它解决了 JavaScript 对象复制中容易被忽略的 Symbol 属性处理问题。通过组合 getSymbols
和 copyObject
这两个基础函数,提供了一种优雅的方式来复制对象的 Symbol 属性。
这个函数的实现体现了几个重要的软件设计原则:
- 单一职责原则:函数只专注于一件事——复制 Symbol 属性,而获取 Symbol 和实际复制操作分别委托给专门的函数
- 模块化设计:将复杂操作分解为更小、更专注的函数,使代码更容易理解和维护
- 函数组合:通过组合多个简单函数实现复杂功能,减少代码重复
-
一致性接口:与
copySymbolsIn
保持相似的接口,方便开发者根据需要选择合适的工具
在现代 JavaScript 开发中,随着 Symbol 的广泛应用,特别是在库和框架的内部实现中,copySymbols
这样的工具函数变得越来越重要,它确保了在处理对象复制时不会遗漏这些特殊的属性类型。
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:获取对象原型的方法
源码实现
/**
* 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 属性。
源码解析
条件定义部分
var getSymbolsIn = !nativeGetSymbols ? stubArray : function(object) {
这行代码使用条件运算符来定义 getSymbolsIn
函数。它检查 nativeGetSymbols
(即 Object.getOwnPropertySymbols
)是否存在:
- 如果
nativeGetSymbols
不存在(值为 falsy),则getSymbolsIn
被赋值为stubArray
函数,始终返回空数组 - 如果
nativeGetSymbols
存在,则getSymbolsIn
被赋值为一个新定义的函数
这种模式在 Lodash 中很常见,用于处理不同环境的兼容性问题。在不支持 Symbol 的环境中,提供一个简单的替代实现。
结果初始化
var result = [];
这行代码创建了一个空数组,用于存储收集到的所有 Symbol 属性。这是必要的,因为函数需要返回一个包含对象及其原型链上所有 Symbol 属性的数组。
原型链遍历
while (object) {
arrayPush(result, getSymbols(object));
object = getPrototype(object);
}
这段代码是函数的核心部分,它使用 while
循环来遍历对象及其原型链:
-
while (object)
检查当前对象是否存在。当遍历到原型链的顶端(null
)时,循环结束 -
arrayPush(result, getSymbols(object))
调用getSymbols
获取当前对象的可枚举 Symbol 属性,并使用arrayPush
将这些属性添加到结果数组中 -
object = getPrototype(object)
获取当前对象的原型,作为下一次循环的对象
这种循环方式可以有效地遍历整个原型链,收集所有继承的 Symbol 属性。
// 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;
返回结果
return result;
函数最后返回收集到的所有 Symbol 属性数组。这个数组包含了对象自身和其原型链上的所有可枚举 Symbol 属性。
与 getSymbols 的比较
getSymbolsIn
和 getSymbols
是两个相似但用途不同的函数:
-
属性范围:
-
getSymbols
只返回对象自身的可枚举 Symbol 属性 -
getSymbolsIn
返回对象自身和继承的可枚举 Symbol 属性
-
-
实现方式:
-
getSymbols
直接使用Object.getOwnPropertySymbols
并过滤可枚举属性 -
getSymbolsIn
使用循环遍历原型链,并在每个层级上调用getSymbols
-
-
使用场景:
-
getSymbols
适用于只关心对象自身 Symbol 属性的情况 -
getSymbolsIn
适用于需要考虑继承 Symbol 属性的情况
-
示例:
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
提供了一种简洁而有效的解决方案。
Lodash源码阅读-getSymbols
Lodash 源码阅读-getSymbols
概述
getSymbols
是 Lodash 库中的一个内部工具函数,它的主要作用是创建一个包含目标对象自身可枚举 Symbol 属性的数组。这个函数在处理对象的 Symbol 类型属性时非常有用,特别是在获取对象所有键(包括 Symbol 类型)的场景中。
前置学习
依赖函数
-
nativeGetSymbols:原生的
Object.getOwnPropertySymbols
方法的引用 - stubArray:一个返回空数组的工具函数,在不支持 Symbol 的环境中作为 fallback
- arrayFilter:一个数组过滤函数,用于筛选出可枚举的 Symbol
-
propertyIsEnumerable:
Object.prototype.propertyIsEnumerable
方法的引用,用于检查属性是否可枚举
技术知识
- ES6 Symbol:JavaScript 中的原始数据类型,表示唯一的标识符
- Object.getOwnPropertySymbols:获取对象自身所有 Symbol 属性的方法
- 可枚举性(Enumerability):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 的环境中的完整功能。
源码解析
条件定义部分
var getSymbols = !nativeGetSymbols ? stubArray : function(object) {
这行代码使用了条件运算符来定义 getSymbols
函数。它检查 nativeGetSymbols
(即 Object.getOwnPropertySymbols
)是否存在:
- 如果
nativeGetSymbols
不存在(值为 falsy),则getSymbols
被赋值为stubArray
函数 - 如果
nativeGetSymbols
存在,则getSymbols
被赋值为一个新定义的函数
这种模式在 Lodash 中很常见,用于处理不同环境的兼容性问题。在不支持某些新特性的旧环境中,提供一个简单的替代实现。
// stubArray 函数的实现
function stubArray() {
return [];
}
这个函数非常简单,它只是返回一个空数组。在不支持 Symbol 的环境中,没有 Symbol 属性可以获取,所以返回空数组是合理的行为。
空值处理
if (object == null) {
return [];
}
这段代码检查输入对象是否为 null
或 undefined
(使用 ==
运算符可以同时检查这两种情况)。如果是,则直接返回空数组,避免后续操作出错。
例如:
getSymbols(null); // []
getSymbols(undefined); // []
而原生的 Object.getOwnPropertySymbols
方法在接收 null 或 undefined 时会抛出错误:
try {
Object.getOwnPropertySymbols(null); // 抛出 TypeError
} catch (e) {
console.error(e); // TypeError: Cannot convert undefined or null to object
}
对象转换
object = Object(object);
这行代码将输入值转换为对象。这是一个安全措施,确保即使传入的是原始值(如数字或字符串),也能正确处理。例如:
-
Object(42)
会创建一个 Number 对象 -
Object('hello')
会创建一个 String 对象 - 如果
object
已经是对象,则不会有变化
这样做的好处是函数可以统一处理各种类型的输入:
getSymbols(42); // [] (因为数字没有 Symbol 属性)
getSymbols("hello"); // [] (因为字符串没有 Symbol 属性)
获取和过滤 Symbol
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
绑定,特别是在处理原始值包装对象的情况下。
// arrayFilter 函数的实现
function arrayFilter(array, predicate) {
var index = -1,
length = array == null ? 0 : array.length,
resIndex = 0,
result = [];
while (++index < length) {
var value = array[index];
if (predicate(value, index, array)) {
result[resIndex++] = value;
}
}
return result;
}
总结
getSymbols
是 Lodash 中一个精心设计的内部工具函数,它解决了获取对象可枚举 Symbol 属性的问题,并提供了良好的兼容性和错误处理。
这个函数的设计体现了几个重要的软件工程原则:
- 优雅降级:在不支持新特性的环境中提供合理的替代方案
- 防御性编程:通过检查空值和类型转换,防止运行时错误
- 单一职责:函数只负责一件事情 - 获取对象的可枚举 Symbol 属性
-
组合复用:通过组合使用其他函数(如
arrayFilter
和stubArray
)来实现功能
在现代 JavaScript 开发中,随着 Symbol 的广泛使用,getSymbols
这样的工具函数变得越来越重要。它帮助开发者处理包含 Symbol 的对象,确保在操作对象时不会遗漏这些特殊属性。
Lodash源码阅读-copyObject
Lodash 源码阅读-copyObject
功能概述
copyObject
是 Lodash 内部的一个工具函数,主要用于将源对象的特定属性复制到目标对象上。它支持自定义复制行为,能处理新对象创建和现有对象更新两种情况,是 Lodash 实现对象操作相关方法的基础函数。
前置学习
依赖函数
- baseAssignValue:底层的属性赋值实现,不检查现有值,直接赋值
- assignValue:智能属性赋值,会检查属性是否需要更新
-
Object 构造器:用于在
isNew
为 true 且未提供 object 时创建空对象
技术知识
- JavaScript 对象遍历:通过索引和长度进行属性遍历
- 对象属性赋值:了解 JavaScript 中对象属性的赋值机制
- 函数回调模式:理解如何使用自定义函数(customizer)来控制复制行为
- 引用类型和值类型:理解 JavaScript 中的值传递和引用传递
源码实现
function copyObject(source, props, object, customizer) {
var isNew = !object;
object || (object = {});
var index = -1,
length = props.length;
while (++index < length) {
var key = props[index];
var newValue = customizer
? customizer(object[key], source[key], key, object, source)
: undefined;
if (newValue === undefined) {
newValue = source[key];
}
if (isNew) {
baseAssignValue(object, key, newValue);
} else {
assignValue(object, key, newValue);
}
}
return object;
}
实现思路
copyObject
的实现思路可以归纳为以下几点:
- 确定目标对象:如果没有提供目标对象(
object
),则创建一个新的空对象 - 遍历要复制的属性列表(
props
) - 对于每个属性:
- 如果提供了自定义处理函数(
customizer
),则使用它计算新值 - 如果没有提供自定义处理函数或其返回
undefined
,则直接使用源对象的属性值
- 如果提供了自定义处理函数(
- 根据是新建对象还是更新对象,选择不同的赋值方法:
- 新建对象:使用
baseAssignValue
直接赋值(更高效) - 更新对象:使用
assignValue
智能赋值(避免不必要的更新)
- 新建对象:使用
- 最后返回复制后的对象
这种实现既灵活又高效,能处理多种对象复制场景。
源码解析
让我们逐行解析 copyObject
函数的实现:
function copyObject(source, props, object, customizer) {
函数定义,接收四个参数:
-
source
:源对象,要从中复制属性的对象 -
props
:要复制的属性列表(数组形式) -
object
:目标对象,要将属性复制到的对象(可选) -
customizer
:自定义处理函数,用于自定义属性复制行为(可选)
var isNew = !object;
object || (object = {});
这两行代码处理目标对象:
-
isNew
标记是否为新对象:如果未提供object
(值为 null、undefined 或其他假值),则isNew
为 true - 如果未提供
object
,则创建一个新的空对象作为目标对象
var index = -1,
length = props.length;
while (++index < length) {
设置循环变量,准备遍历属性列表。
var key = props[index];
获取当前要复制的属性名。
var newValue = customizer
? customizer(object[key], source[key], key, object, source)
: undefined;
这行代码处理自定义复制行为:
- 如果提供了
customizer
函数,则调用它来计算新值 - 传递给
customizer
函数的参数依次为:目标对象的当前值、源对象的当前值、属性名、目标对象、源对象 - 如果没有提供
customizer
,则newValue
为undefined
if (newValue === undefined) {
newValue = source[key];
}
这是一个巧妙的设计:如果 customizer
返回 undefined
(或者没有提供 customizer
),则使用源对象的原始值。这样,自定义函数可以选择性地处理某些属性,而对于返回 undefined
的情况,会回退到默认复制行为。
if (isNew) {
baseAssignValue(object, key, newValue);
} else {
assignValue(object, key, newValue);
}
根据目标对象是新建的还是已存在的,选择不同的赋值方法:
- 如果是新对象(
isNew
为 true),使用baseAssignValue
直接赋值,因为新对象上肯定没有现有属性,不需要做额外检查 - 如果是现有对象,使用
assignValue
进行智能赋值,会先检查是否需要更新(避免不必要的操作)
}
return object;
}
完成所有属性的复制后,返回目标对象。
baseAssignValue 和 assignValue 的选择逻辑
copyObject
在赋值时选择了两个不同的函数,这是一个重要的性能优化:
-
对于新对象:使用
baseAssignValue
- 新对象上肯定没有现有属性,所以不需要进行
assignValue
中的值比较 -
baseAssignValue
是直接赋值,少了条件判断,性能更高
- 新对象上肯定没有现有属性,所以不需要进行
-
对于现有对象:使用
assignValue
- 现有对象可能已有同名属性,需要先检查是否值相同
- 如果值相同则跳过赋值,避免不必要的操作(特别是对于有 getter/setter 的属性)
这种区分处理的方式可以在保持功能完整的同时提高性能。
总结
copyObject
是 Lodash 中一个设计精巧的内部工具函数,它通过以下几个特点实现了高效灵活的对象属性复制:
- 灵活性:支持自定义处理函数,可以控制每个属性的复制行为
- 效率优化:根据目标对象是新建还是已存在,选择最优的赋值方法
- 功能完备:能处理多种属性复制场景,是 Lodash 对象操作方法的基础
这个函数体现了几个重要的设计原则:
- 单一职责原则:函数只负责对象属性的复制,不关心属性来源和选择逻辑
- 开放/封闭原则:通过 customizer 参数扩展功能,而不需要修改基础代码
- 性能优化:在保证功能的前提下,针对不同场景使用最优实现
- 合成复用:借助 baseAssignValue 和 assignValue 等基础函数构建更复杂的功能
Lodash源码阅读-baseAssignValue
Lodash 源码阅读-baseAssignValue
功能概述
baseAssignValue
是 Lodash 中的一个内部工具函数,用于将值赋给对象的属性,是 assignValue
的基础实现版本。它的特点是直接赋值,不进行任何相等性检查,同时对 __proto__
属性有特殊处理,以防止原型污染攻击。
前置学习
依赖函数
-
defineProperty:对象属性定义工具,用于安全地处理
__proto__
属性
技术知识
- 对象属性赋值:JavaScript 中对象属性的赋值机制
-
原型污染攻击:通过修改
__proto__
属性导致的安全问题 - Object.defineProperty:JavaScript 中定义或修改对象属性的 API
源码实现
function baseAssignValue(object, key, value) {
if (key == "__proto__" && defineProperty) {
defineProperty(object, key, {
configurable: true,
enumerable: true,
value: value,
writable: true,
});
} else {
object[key] = value;
}
}
实现思路
baseAssignValue
的实现思路非常直接:
-
首先检查要赋值的键名是否为
__proto__
,这是一个特殊的键,需要特殊处理- 如果是
__proto__
且存在defineProperty
方法(即环境支持Object.defineProperty
),则使用defineProperty
安全地设置该属性 - 这么做是为了防止原型链污染攻击,保证操作的安全性
- 如果是
-
如果不是
__proto__
或环境不支持defineProperty
,就直接使用赋值运算符=
设置属性- 这是 JavaScript 中最基本的属性赋值方式
源码解析
让我们逐行分析 baseAssignValue
函数的实现:
function baseAssignValue(object, key, value) {
函数定义,接收三个参数:
-
object
:要修改的目标对象 -
key
:要设置的属性名 -
value
:要赋的值
if (key == '__proto__' && defineProperty) {
第一个条件检查,判断:
- 属性名是否为
__proto__
(注意这里使用的是宽松相等==
,所以会做类型转换) -
defineProperty
是否可用(在 Lodash 内部,defineProperty
是对Object.defineProperty
的引用或自定义实现)
这个检查很重要,因为 __proto__
是一个特殊属性,直接设置它可能导致原型链污染,产生安全风险。
defineProperty(object, key, {
configurable: true,
enumerable: true,
value: value,
writable: true,
});
如果条件满足,使用 defineProperty
安全地定义该属性,并设置属性描述符:
-
configurable: true
:允许属性被删除或重新配置 -
enumerable: true
:允许属性在对象的属性枚举中出现 -
value: value
:设置属性的值为传入的value
-
writable: true
:允许属性的值被修改
这种方式比直接赋值更安全,特别是对于 __proto__
这样的特殊属性。
} else {
object[key] = value;
}
如果不是 __proto__
属性或环境不支持 defineProperty
,则直接使用标准的属性赋值语法。这是处理常规属性的快速路径。
原型污染问题
最新版的 Chrome 浏览器中暂时没有用代码验证出来
与 assignValue 的关系
baseAssignValue
是 assignValue
的基础实现。assignValue
在调用 baseAssignValue
前会进行额外的检查:
function assignValue(object, key, value) {
var objValue = object[key];
if (
!(hasOwnProperty.call(object, key) && eq(objValue, value)) ||
(value === undefined && !(key in object))
) {
baseAssignValue(object, key, value);
}
}
assignValue
会:
- 检查对象是否已有同名且值相等的属性(使用
eq
判断) - 检查值是否为
undefined
且对象上不存在该属性
只有当需要更新值时,才会调用 baseAssignValue
进行实际赋值操作。
总结
baseAssignValue
是 Lodash 内部的一个基础工具函数,用于安全地为对象设置属性值。
Lodash源码阅读-initCloneObject
Lodash源码阅读-keysIn
Lodash源码阅读-baseKeysIn
Lodash源码阅读-nativeKeysIn
Lodash源码阅读-baseGetAllKeys
Lodash源码阅读-baseAssign
Lodash 源码阅读-baseAssign
功能概述
baseAssign
是 Lodash 内部的一个工具函数,主要用于将源对象的所有自身可枚举属性(不包括继承的属性)复制到目标对象上。它是实现 _.assign
等方法的基础,特点是只复制源对象上的自有属性,不复制原型链上的属性。
前置学习
依赖函数
- copyObject:负责将属性从源对象复制到目标对象的工具函数
- keys:获取对象的所有自身可枚举属性名的函数(不包括继承属性)
技术知识
- JavaScript 对象属性:理解对象自身属性与继承属性的区别
- 可枚举属性:了解 JavaScript 中属性的可枚举性概念
- 对象复制:浅拷贝和属性赋值的基本知识
源码实现
function baseAssign(object, source) {
return object && copyObject(source, keys(source), object);
}
实现思路
baseAssign
的实现思路非常简洁明了:
- 首先检查目标对象(
object
)是否存在(不是 null 或 undefined) - 如果目标对象存在,则:
- 使用
keys
获取源对象的所有自身可枚举属性名(不包括继承的属性) - 通过
copyObject
将这些属性从源对象复制到目标对象
- 使用
- 最后返回处理后的目标对象
这个函数的关键特点是它使用了 keys
而不是 keysIn
,这使得它只复制源对象的自有属性,而不复制原型链上的属性。
源码解析
让我们逐步分析 baseAssign
函数的实现:
function baseAssign(object, source) {
函数定义,接收两个参数:
-
object
:目标对象,要将属性复制到的对象 -
source
:源对象,要从中复制属性的对象
return object && copyObject(source, keys(source), object);
}
这一行代码包含了函数的全部逻辑,可以分解为两个部分:
-
object &&
:这是一个短路逻辑与操作,确保目标对象存在(不是 null 或 undefined)。如果object
是 falsy 值,函数会直接返回该值(一般是 undefined、null 等),不进行后续操作。 -
copyObject(source, keys(source), object)
:这是函数的核心逻辑,分解来看:-
keys(source)
:获取源对象的所有自身可枚举属性名(不包括继承的属性)。keys
函数等同于Object.keys
,只返回对象自身的可枚举属性。 -
copyObject(source, keys(source), object)
:调用copyObject
函数,将source
对象中由keys(source)
返回的所有自身属性复制到object
对象上。
-
整个函数同样巧妙地利用了短路评估和函数组合,用极简的代码实现了完整的功能。
baseAssign 与 baseAssignIn 的区别
baseAssign
和 baseAssignIn
是两个相似但有重要区别的函数:
-
baseAssign:
function baseAssign(object, source) { return object && copyObject(source, keys(source), object); }
使用
keys(source)
只获取源对象自身的可枚举属性。 -
baseAssignIn:
function baseAssignIn(object, source) { return object && copyObject(source, keysIn(source), object); }
使用
keysIn(source)
获取源对象自身和继承的所有可枚举属性。
这个区别决定了这两个函数处理原型链上属性的不同行为,也是它们各自对应的公开 API(_.assign
和 _.assignIn
)功能差异的根本原因。
总结
baseAssign
是 Lodash 中一个设计精巧的内部工具函数,它通过结合 copyObject
和 keys
函数,高效地实现了对象自有属性的复制,排除了继承的属性。设计体现了以下编程原则:
- 单一职责原则:函数只负责一件事情,即复制对象自身的可枚举属性
-
组合优于继承:通过组合使用
copyObject
和keys
实现功能,展示了函数组合的强大 - 防御性编程:通过短路逻辑检查参数有效性,增强代码健壮性
理解 baseAssign
的实现有助于我们更好地掌握 JavaScript 中对象属性复制的处理方法,尤其是在只需要处理对象自有属性的场景中。它也展示了如何通过选择合适的依赖函数(这里是 keys
vs keysIn
)来实现不同的功能行为。