普通视图

发现新文章,点击刷新页面。
昨天 — 2025年4月4日首页
昨天以前首页

Lodash源码阅读-cloneTypedArray

作者 好_快
2025年4月3日 08:02

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 函数的实现思路清晰直接:

  1. 根据 isDeep 参数决定如何处理 TypedArray 的底层 ArrayBuffer:
    • 如果是深拷贝(isDeep 为 true),则使用 cloneArrayBuffer 函数创建底层 ArrayBuffer 的独立副本
    • 如果是浅拷贝(isDeep 为 false 或未提供),则直接使用原始 TypedArray 的 buffer 引用
  2. 使用原始 TypedArray 的构造函数创建一个新的 TypedArray 实例,传入处理后的 buffer、原始的字节偏移量和长度
  3. 返回新创建的 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 对象:

  1. 通过 typedArray.constructor 获取原始类型化数组的构造函数(如 Int8Array、Float32Array 等)
  2. 使用该构造函数创建一个新的类型化数组实例,传入三个参数:
    • 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,实现了对各种类型化数组的浅拷贝和深拷贝。

这个函数的实现体现了几个重要的软件设计原则:

  1. 通用性:使用 constructor 属性使函数能够处理所有类型的 TypedArray
  2. 灵活性:通过 isDeep 参数支持浅拷贝和深拷贝两种模式
  3. 组合复用:利用 cloneArrayBuffer 函数处理底层数据的复制,避免代码重复
  4. 单一职责:函数只关注于类型化数组的克隆,遵循"做好一件事"的原则

在处理二进制数据和数值计算的 JavaScript 应用中,cloneTypedArray 提供了一种简洁而强大的方式来创建类型化数组的副本,为数据处理提供了更多的灵活性和安全性。

Lodash源码阅读-cloneDataView

作者 好_快
2025年4月3日 08:02

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 函数的实现思路直接明了:

  1. 根据 isDeep 参数决定是否对 DataView 的底层 ArrayBuffer 进行深拷贝:
    • 如果是深拷贝(isDeep 为 true),则使用 cloneArrayBuffer 函数复制底层的 ArrayBuffer
    • 如果是浅拷贝(isDeep 为 false),则直接使用原始 DataView 的 buffer 引用
  2. 使用 DataView 的构造函数,结合提供的 buffer(原始或克隆的)和偏移量信息,创建一个新的 DataView 对象
  3. 返回新创建的 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 对象:

  1. 使用 dataView.constructor(即 DataView 构造函数)创建一个新的 DataView 实例
  2. 传递三个参数:
    • 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 来支持深拷贝,同时也提供了浅拷贝的选项。

这个函数的实现体现了几个重要的编程原则:

  1. 单一职责:函数只负责一件事情 - 克隆 DataView 对象
  2. 开闭原则:通过参数化 isDeep 实现了功能的扩展,而不需要修改函数内部实现
  3. 组合复用:利用 cloneArrayBuffer 函数处理底层 ArrayBuffer 的复制,体现了"组合优于继承"的思想
  4. 防御性编程:使用 constructor 属性而不是直接引用构造函数,增强了代码的兼容性

在处理二进制数据的应用中,cloneDataView 提供了一种灵活而可靠的方式来创建 DataView 的副本,无论是需要共享底层数据还是完全独立的数据副本。

Lodash源码阅读-cloneArrayBuffer

作者 好_快
2025年4月3日 08:01

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 函数的实现思路非常直接:

  1. 创建一个与源 ArrayBuffer 大小相同的新 ArrayBuffer 对象
  2. 使用 Uint8Array 创建两个视图:一个指向新创建的 ArrayBuffer,另一个指向源 ArrayBuffer
  3. 通过 Uint8Array 的 set 方法,将源数组的所有字节复制到新数组中
  4. 返回完成复制的新 ArrayBuffer 对象

这种实现方式确保了新创建的 ArrayBuffer 与原始对象完全相同但互相独立,任何对新对象的修改都不会影响到原始对象。

源码解析

函数签名

function cloneArrayBuffer(arrayBuffer) {

函数接收一个参数:

  • arrayBuffer:要克隆的 ArrayBuffer 对象

创建新的 ArrayBuffer

var result = new arrayBuffer.constructor(arrayBuffer.byteLength);

这行代码做了两件事:

  1. 通过 arrayBuffer.constructor 获取原始 ArrayBuffer 的构造函数(即 ArrayBuffer)
  2. 使用这个构造函数创建一个新的 ArrayBuffer 实例,大小与原始对象相同(arrayBuffer.byteLength

使用 constructor 属性而不是直接使用 ArrayBuffer 构造函数,是一种防御性编程方法,确保即使在未来 ArrayBuffer 被子类化的情况下代码仍能正确工作。

复制数据

new Uint8Array(result).set(new Uint8Array(arrayBuffer));

这行代码是函数的核心,它执行了实际的数据复制:

  1. new Uint8Array(arrayBuffer) 创建一个视图,指向原始 ArrayBuffer 中的所有字节
  2. new Uint8Array(result) 创建一个视图,指向新创建的 ArrayBuffer
  3. .set() 方法复制一个数组的所有元素到另一个数组,这里是将原始 ArrayBuffer 中的所有字节复制到新的 ArrayBuffer 中

Uint8Array 是以 8 位无符号整数格式查看 ArrayBuffer 的一种视图,使用它可以逐字节地操作 ArrayBuffer。

返回结果

return result;

函数返回复制完成的新 ArrayBuffer 对象。

总结

cloneArrayBuffer 是 Lodash 中一个看似简单但非常实用的工具函数,它解决了 JavaScript 中二进制数据深度复制的问题。通过利用 TypedArray 的能力,它实现了对 ArrayBuffer 数据的高效复制。

这个函数的实现体现了几个关键的编程原则:

  1. 不可变性:函数不修改输入参数,而是返回一个新的对象
  2. 封装复杂性:将二进制数据复制的细节封装在简单的函数接口后面
  3. 组合复用:作为其他克隆函数的基础组件,遵循了"做好一件事"的设计原则
  4. 防御性编程:使用 constructor 属性而不是直接引用构造函数,增强代码的鲁棒性

在处理二进制数据的 JavaScript 应用中,cloneArrayBuffer 提供了一种简洁可靠的方式来创建数据的深度副本,是二进制数据操作中不可或缺的工具。

Lodash源码阅读-copySymbolsIn

作者 好_快
2025年4月3日 08:01

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 函数的实现思路非常直接明了:

  1. 调用 getSymbolsIn(source) 获取源对象及其原型链上的所有可枚举 Symbol 属性
  2. 将这些 Symbol 属性作为要复制的属性列表,传递给 copyObject 函数
  3. copyObject 函数负责将这些 Symbol 属性从源对象复制到目标对象
  4. 最终返回修改后的目标对象

这种实现方式利用了函数组合的思想,将获取 Symbol 和复制属性的功能分离,使代码更简洁清晰。

源码解析

函数签名

function copySymbolsIn(source, object) {

函数接收两个参数:

  • source:源对象,要从中复制 Symbol 属性的对象
  • object:目标对象,要将 Symbol 属性复制到的对象(可选)

如果不提供 object 参数,copyObject 函数会创建一个新的空对象。

函数体

return copyObject(source, getSymbolsIn(source), object);

这行代码包含了函数的全部逻辑,可以分解为以下步骤:

  1. getSymbolsIn(source):调用 getSymbolsIn 函数,获取源对象及其原型链上的所有可枚举 Symbol 属性数组
  2. copyObject(source, symbolsArray, object):调用 copyObject 函数,将获取到的 Symbol 属性从源对象复制到目标对象
    • source:源对象,提供属性值
    • symbolsArray:要复制的属性数组,这里是所有 Symbol 属性
    • object:目标对象,接收复制的属性

copyObject 函数的工作方式是遍历提供的属性数组(这里是 Symbol 属性数组),将每个属性从源对象复制到目标对象,并返回修改后的目标对象。

与 copySymbols 的对比

copySymbolsIncopySymbols 的主要区别在于:

// 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 中一个短小精悍但功能强大的工具函数,它通过组合使用 getSymbolsIncopyObject 两个函数,实现了对象自身和继承的 Symbol 属性的复制。

这个函数的实现体现了几个软件设计原则:

  1. 单一职责原则:函数只负责一件事——复制 Symbol 属性,而获取 Symbol 和实际的复制操作则委托给其他专门的函数
  2. 函数组合:通过组合 getSymbolsIncopyObject 这两个基础函数来构建更复杂的功能
  3. 抽象合理:提供了适当级别的抽象,使调用者不需要关心 Symbol 属性的获取和复制细节
  4. 接口一致性:与 copySymbols 函数保持一致的接口,方便开发者在不同场景下选择合适的工具

在现代 JavaScript 开发中,随着 Symbol 的广泛使用,特别是在框架和库的内部实现中,copySymbolsIn 这样的工具函数变得越来越重要,它确保了在处理对象时不会遗漏这些特殊的属性,包括那些来自原型链的属性。

Lodash源码阅读-copySymbols

作者 好_快
2025年4月3日 08:01

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 函数的实现思路十分清晰简洁:

  1. 调用 getSymbols(source) 获取源对象中所有自身的可枚举 Symbol 属性
  2. 将这些 Symbol 属性作为要复制的属性列表,传递给 copyObject 函数
  3. copyObject 函数负责将这些 Symbol 属性从源对象复制到目标对象
  4. 最终返回修改后的目标对象

这种实现方式体现了"组合胜于继承"的设计理念,通过组合使用 getSymbolscopyObject 这两个专门的函数,实现了 Symbol 属性的复制功能。

源码解析

函数签名

function copySymbols(source, object) {

函数接收两个参数:

  • source:源对象,要从中复制 Symbol 属性的对象
  • object:目标对象,要将 Symbol 属性复制到的对象(可选参数)

如果调用时不提供 object 参数,copyObject 函数会创建一个新的空对象作为目标对象。

函数体

return copyObject(source, getSymbols(source), object);

这行代码体现了函数的全部逻辑,可以分解为以下几个步骤:

  1. getSymbols(source):调用 getSymbols 函数,获取源对象的所有自身可枚举 Symbol 属性,返回一个数组
  2. copyObject(source, symbolsArray, object):调用 copyObject 函数,将获取到的 Symbol 属性从源对象复制到目标对象
    • source:源对象,提供属性值的来源
    • symbolsArray:要复制的属性列表,这里是所有 Symbol 属性
    • object:目标对象,接收复制的属性

copyObject 函数的工作原理是遍历提供的属性数组(这里是 Symbol 属性数组),将每个属性从源对象复制到目标对象,并最终返回修改后的目标对象。

与 copySymbolsIn 的比较

copySymbolscopySymbolsIn 函数的主要区别在于处理范围:

// 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 属性处理问题。通过组合 getSymbolscopyObject 这两个基础函数,提供了一种优雅的方式来复制对象的 Symbol 属性。

这个函数的实现体现了几个重要的软件设计原则:

  1. 单一职责原则:函数只专注于一件事——复制 Symbol 属性,而获取 Symbol 和实际复制操作分别委托给专门的函数
  2. 模块化设计:将复杂操作分解为更小、更专注的函数,使代码更容易理解和维护
  3. 函数组合:通过组合多个简单函数实现复杂功能,减少代码重复
  4. 一致性接口:与 copySymbolsIn 保持相似的接口,方便开发者根据需要选择合适的工具

在现代 JavaScript 开发中,随着 Symbol 的广泛应用,特别是在库和框架的内部实现中,copySymbols 这样的工具函数变得越来越重要,它确保了在处理对象复制时不会遗漏这些特殊的属性类型。

Lodash源码阅读-getSymbolsIn

作者 好_快
2025年4月2日 07:29

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 函数的实现思路非常清晰:

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

    • 如果不支持,简单返回 stubArray 函数,即返回空数组
    • 如果支持,则定义一个函数,该函数会遍历对象及其原型链
  2. 对于支持 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 循环来遍历对象及其原型链:

  1. while (object) 检查当前对象是否存在。当遍历到原型链的顶端(null)时,循环结束
  2. arrayPush(result, getSymbols(object)) 调用 getSymbols 获取当前对象的可枚举 Symbol 属性,并使用 arrayPush 将这些属性添加到结果数组中
  3. 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 的比较

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

  1. 属性范围

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

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

    • 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 属性。这个函数在处理需要考虑继承属性的场景中非常有用,如完整的对象克隆和合并操作。

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

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

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

Lodash源码阅读-getSymbols

作者 好_快
2025年4月2日 07:28

Lodash 源码阅读-getSymbols

概述

getSymbols 是 Lodash 库中的一个内部工具函数,它的主要作用是创建一个包含目标对象自身可枚举 Symbol 属性的数组。这个函数在处理对象的 Symbol 类型属性时非常有用,特别是在获取对象所有键(包括 Symbol 类型)的场景中。

前置学习

依赖函数

  • nativeGetSymbols:原生的 Object.getOwnPropertySymbols 方法的引用
  • stubArray:一个返回空数组的工具函数,在不支持 Symbol 的环境中作为 fallback
  • arrayFilter:一个数组过滤函数,用于筛选出可枚举的 Symbol
  • propertyIsEnumerableObject.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 是否存在),然后根据结果采取不同的策略:

  1. 如果环境不支持 Symbol,则直接使用 stubArray 函数,始终返回空数组
  2. 如果环境支持 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 [];
}

这段代码检查输入对象是否为 nullundefined(使用 == 运算符可以同时检查这两种情况)。如果是,则直接返回空数组,避免后续操作出错。

例如:

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);
});

这段代码是函数的核心部分:

  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 绑定,特别是在处理原始值包装对象的情况下。

// 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 属性的问题,并提供了良好的兼容性和错误处理。

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

  1. 优雅降级:在不支持新特性的环境中提供合理的替代方案
  2. 防御性编程:通过检查空值和类型转换,防止运行时错误
  3. 单一职责:函数只负责一件事情 - 获取对象的可枚举 Symbol 属性
  4. 组合复用:通过组合使用其他函数(如 arrayFilterstubArray)来实现功能

在现代 JavaScript 开发中,随着 Symbol 的广泛使用,getSymbols 这样的工具函数变得越来越重要。它帮助开发者处理包含 Symbol 的对象,确保在操作对象时不会遗漏这些特殊属性。

Lodash源码阅读-copyObject

作者 好_快
2025年4月2日 07:28

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 的实现思路可以归纳为以下几点:

  1. 确定目标对象:如果没有提供目标对象(object),则创建一个新的空对象
  2. 遍历要复制的属性列表(props
  3. 对于每个属性:
    • 如果提供了自定义处理函数(customizer),则使用它计算新值
    • 如果没有提供自定义处理函数或其返回 undefined,则直接使用源对象的属性值
  4. 根据是新建对象还是更新对象,选择不同的赋值方法:
    • 新建对象:使用 baseAssignValue 直接赋值(更高效)
    • 更新对象:使用 assignValue 智能赋值(避免不必要的更新)
  5. 最后返回复制后的对象

这种实现既灵活又高效,能处理多种对象复制场景。

源码解析

让我们逐行解析 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,则 newValueundefined
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 在赋值时选择了两个不同的函数,这是一个重要的性能优化:

  1. 对于新对象:使用 baseAssignValue

    • 新对象上肯定没有现有属性,所以不需要进行 assignValue 中的值比较
    • baseAssignValue 是直接赋值,少了条件判断,性能更高
  2. 对于现有对象:使用 assignValue

    • 现有对象可能已有同名属性,需要先检查是否值相同
    • 如果值相同则跳过赋值,避免不必要的操作(特别是对于有 getter/setter 的属性)

这种区分处理的方式可以在保持功能完整的同时提高性能。

总结

copyObject 是 Lodash 中一个设计精巧的内部工具函数,它通过以下几个特点实现了高效灵活的对象属性复制:

  1. 灵活性:支持自定义处理函数,可以控制每个属性的复制行为
  2. 效率优化:根据目标对象是新建还是已存在,选择最优的赋值方法
  3. 功能完备:能处理多种属性复制场景,是 Lodash 对象操作方法的基础

这个函数体现了几个重要的设计原则:

  1. 单一职责原则:函数只负责对象属性的复制,不关心属性来源和选择逻辑
  2. 开放/封闭原则:通过 customizer 参数扩展功能,而不需要修改基础代码
  3. 性能优化:在保证功能的前提下,针对不同场景使用最优实现
  4. 合成复用:借助 baseAssignValue 和 assignValue 等基础函数构建更复杂的功能

Lodash源码阅读-baseAssignValue

作者 好_快
2025年4月2日 07:28

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 的实现思路非常直接:

  1. 首先检查要赋值的键名是否为 __proto__,这是一个特殊的键,需要特殊处理

    • 如果是 __proto__ 且存在 defineProperty 方法(即环境支持 Object.defineProperty),则使用 defineProperty 安全地设置该属性
    • 这么做是为了防止原型链污染攻击,保证操作的安全性
  2. 如果不是 __proto__ 或环境不支持 defineProperty,就直接使用赋值运算符 = 设置属性

    • 这是 JavaScript 中最基本的属性赋值方式

源码解析

让我们逐行分析 baseAssignValue 函数的实现:

function baseAssignValue(object, key, value) {

函数定义,接收三个参数:

  • object:要修改的目标对象
  • key:要设置的属性名
  • value:要赋的值
  if (key == '__proto__' && defineProperty) {

第一个条件检查,判断:

  1. 属性名是否为 __proto__(注意这里使用的是宽松相等 ==,所以会做类型转换)
  2. 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 的关系

baseAssignValueassignValue 的基础实现。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 会:

  1. 检查对象是否已有同名且值相等的属性(使用 eq 判断)
  2. 检查值是否为 undefined 且对象上不存在该属性

只有当需要更新值时,才会调用 baseAssignValue 进行实际赋值操作。

总结

baseAssignValue 是 Lodash 内部的一个基础工具函数,用于安全地为对象设置属性值。

Lodash源码阅读-baseAssign

作者 好_快
2025年3月30日 08:42

Lodash 源码阅读-baseAssign

功能概述

baseAssign 是 Lodash 内部的一个工具函数,主要用于将源对象的所有自身可枚举属性(不包括继承的属性)复制到目标对象上。它是实现 _.assign 等方法的基础,特点是只复制源对象上的自有属性,不复制原型链上的属性。

前置学习

依赖函数

  • copyObject:负责将属性从源对象复制到目标对象的工具函数
  • keys:获取对象的所有自身可枚举属性名的函数(不包括继承属性)

技术知识

  • JavaScript 对象属性:理解对象自身属性与继承属性的区别
  • 可枚举属性:了解 JavaScript 中属性的可枚举性概念
  • 对象复制:浅拷贝和属性赋值的基本知识

源码实现

function baseAssign(object, source) {
  return object && copyObject(source, keys(source), object);
}

实现思路

baseAssign 的实现思路非常简洁明了:

  1. 首先检查目标对象(object)是否存在(不是 null 或 undefined)
  2. 如果目标对象存在,则:
    • 使用 keys 获取源对象的所有自身可枚举属性名(不包括继承的属性)
    • 通过 copyObject 将这些属性从源对象复制到目标对象
  3. 最后返回处理后的目标对象

这个函数的关键特点是它使用了 keys 而不是 keysIn,这使得它只复制源对象的自有属性,而不复制原型链上的属性。

源码解析

让我们逐步分析 baseAssign 函数的实现:

function baseAssign(object, source) {

函数定义,接收两个参数:

  • object:目标对象,要将属性复制到的对象
  • source:源对象,要从中复制属性的对象
  return object && copyObject(source, keys(source), object);
}

这一行代码包含了函数的全部逻辑,可以分解为两个部分:

  1. object &&:这是一个短路逻辑与操作,确保目标对象存在(不是 null 或 undefined)。如果 object 是 falsy 值,函数会直接返回该值(一般是 undefined、null 等),不进行后续操作。

  2. copyObject(source, keys(source), object):这是函数的核心逻辑,分解来看:

    • keys(source):获取源对象的所有自身可枚举属性名(不包括继承的属性)。keys 函数等同于 Object.keys,只返回对象自身的可枚举属性。
    • copyObject(source, keys(source), object):调用 copyObject 函数,将 source 对象中由 keys(source) 返回的所有自身属性复制到 object 对象上。

整个函数同样巧妙地利用了短路评估和函数组合,用极简的代码实现了完整的功能。

baseAssign 与 baseAssignIn 的区别

baseAssignbaseAssignIn 是两个相似但有重要区别的函数:

  1. baseAssign

    function baseAssign(object, source) {
      return object && copyObject(source, keys(source), object);
    }
    

    使用 keys(source) 只获取源对象自身的可枚举属性。

  2. baseAssignIn

    function baseAssignIn(object, source) {
      return object && copyObject(source, keysIn(source), object);
    }
    

    使用 keysIn(source) 获取源对象自身和继承的所有可枚举属性。

这个区别决定了这两个函数处理原型链上属性的不同行为,也是它们各自对应的公开 API(_.assign_.assignIn)功能差异的根本原因。

总结

baseAssign 是 Lodash 中一个设计精巧的内部工具函数,它通过结合 copyObjectkeys 函数,高效地实现了对象自有属性的复制,排除了继承的属性。设计体现了以下编程原则:

  1. 单一职责原则:函数只负责一件事情,即复制对象自身的可枚举属性
  2. 组合优于继承:通过组合使用 copyObjectkeys 实现功能,展示了函数组合的强大
  3. 防御性编程:通过短路逻辑检查参数有效性,增强代码健壮性

理解 baseAssign 的实现有助于我们更好地掌握 JavaScript 中对象属性复制的处理方法,尤其是在只需要处理对象自有属性的场景中。它也展示了如何通过选择合适的依赖函数(这里是 keys vs keysIn)来实现不同的功能行为。

❌
❌