npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

deepmerge-plus

v4.0.1

Published

用於深度(遞迴)合併 JavaScript 物件的函式庫 / A library for deep (recursive) merging of JavaScript objects

Readme

deepmerge-plus

深度合併兩個 JavaScript 物件的可列舉屬性 / Merge the enumerable attributes of two objects deeply.

pnpm add deepmerge-plus

概述

deepmerge-plus 是一個功能強大的 JavaScript 物件深度合併函式庫,支援:

  • � depth-first 深度合併(遞迴處理巢狀物件)
  • 📦 陣列合併(可自訂合併策略)
  • ⚙️ 靈活的選項配置
  • 🔧 自訂合併邏輯

安裝

# 使用 pnpm(推薦)
pnpm add deepmerge-plus

# 使用 npm
npm install deepmerge-plus

# 使用 yarn
yarn add deepmerge-plus

快速開始

const merge = require('deepmerge-plus');

const target = {
  foo: { bar: 5 },
  array: [{ does: 'work', too: [1, 2, 3] }]
};

const source = {
  foo: { baz: 4 },
  quux: 5,
  array: [{ does: 'work', too: [4, 5, 6] }, { really: 'yes' }]
};

const result = merge(target, source);
console.log(result);
// => { foo: { bar: 5, baz: 4 }, array: [...], quux: 5 }

核心概念:左至右合併

merge(target, source, options) 中:

| 參數 | 術語 | 說明 | |-----|------|------| | target | 左側(left)| 被合併的物件,代表現有值 | | source | 右側(right)| 要合併進來的物件,代表新值 |

預設行為

  • 來源物件的值會覆蓋目標物件的值
  • 合併結果是將「右側」的值合併進「左側」
  • 合併會建立一個新物件,因此 targetsource 都不會被修改
const target = { name: 'Alice', age: 25 };
const source = { name: 'Bob', city: 'Taipei' };

const result = merge(target, source);
// => { name: 'Bob', age: 25, city: 'Taipei' }
// name 被 source 覆蓋,age 保留 target,city 來自 source

API

merge(target, source, [options])

深度合併兩個物件 targetsource,返回一個包含兩者元素的新合併物件。

const result = merge(objectA, objectB, options);

merge.all(arrayOfObjects, [options])

將任意數量的物件合併成單一結果物件。

const result = merge.all([obj1, obj2, obj3], options);

選項說明

1. clone

是否啟用深度複製功能。

  • 類型: boolean
  • 預設值: true
merge(target, source, { clone: true }); // 預設,深度複製物件
merge(target, source, { clone: false }); // 不複製,直接引用

2. arrayMerge

自訂陣列合併函式。

  • 類型: function(target, source, options): array
  • 預設行為: 串接兩個陣列
// 覆寫模式:來源陣列覆蓋目標陣列
function overwriteMerge(destinationArray, sourceArray) {
  return sourceArray;
}

merge([1, 2, 3], [3, 2, 1], { arrayMerge: overwriteMerge });
// => [3, 2, 1]
// 僅保留目標陣列
const keepTarget = (destination) => destination;
merge({ arr: [1,2,3] }, { arr: ['a','b','c'] }, { arrayMerge: keepTarget });
// => { arr: [1, 2, 3] }

3. isMergeableObject

自訂可合併物件判斷函式。

  • 類型: function(value): boolean
const moment = require('moment');

merge(target, source, {
  isMergeableObject(value) {
    if (moment.isMoment(value)) return false;
    return isMergeableObject(value);
  }
});

4. keyValueOrMode(已棄用)

使用 || 運算子邏輯,已由 keyValueUpsertMode 取代。

  • 類型: boolean
  • 狀態: 已棄用,不建議在新程式碼中使用

5. keyValueUpsertMode(推薦)

Upsert 模式,控制何時應該保留目標中的現有值。

  • 類型: booleanfunction
  • 預設值: undefined

true

保留目標中已存在的值(undefined 除外)。

const target = { name: 'Alice', age: undefined, count: 0, email: null };
const source = { name: 'Bob', age: 30, count: 5, email: '[email protected]' };

const result = merge(target, source, { keyValueUpsertMode: true });
// => { name: 'Alice', age: 30, count: 0, email: null }
// name: 保留 'Alice'(不是 undefined)
// age: 使用 30(目標值是 undefined)
// count: 保留 0(不是 undefined)
// email: 保留 null(不是 undefined)

關鍵特性

  • 使用 ?? 運算子,區分 undefined 與其他 falsy 值
  • null0false'' 都會被保留
  • 與 lodash 的 _.defaultsDeep 概念相似

function

自訂函式,根據條件決定是否保留目標的值。

const target = { name: 'Alice', count: 0 };
const source = { name: 'Bob', count: 5 };

const result = merge(target, source, {
  keyValueUpsertMode: (value, options, tmpRuntimeTarget) => {
    const targetValue = tmpRuntimeTarget.target?.[tmpRuntimeTarget.key];
    return targetValue === 0; // 只有當目標值為 0 時保留
  }
});
// => { name: 'Bob', count: 0 }
// name: targetValue='Alice' !== 0,回傳 false,使用 source 的 'Bob'
// count: targetValue=0 === 0,回傳 true,保留 target 的 0

函式參數說明

| 參數 | 類型 | 說明 | |-----|------|------| | value | unknown | 來源物件中該鍵的值 | | optionsRuntime | IOptions | 目前的合併選項 | | tmpRuntimeTarget | ICache | 包含 targetsourcedestinationkey 等資訊 | | tmpRuntimeData | ITmpRuntimeData | 包含 levelpathsrootparent 等資訊 |

進一步閱讀

如需了解 keyValueUpsertMode 與陣列合併策略的詳細組合應用,請參閱:


實際應用場景

1. 配置合併(保留使用者設定)

// 預設配置
const defaultConfig = {
  theme: 'light',
  language: 'en',
  notifications: { email: true, sms: false },
  timeout: 3000
};

// 使用者自訂配置
const userConfig = {
  theme: 'dark',
  notifications: { email: false }
};

// 合併:保留使用者設定,填充預設值
const finalConfig = merge(defaultConfig, userConfig, {
  keyValueUpsertMode: true
});
// => { theme: 'dark', language: 'en', notifications: { email: false }, timeout: 3000 }

對比 lodash

  • _.defaults(userConfig, defaultConfig) - 填充缺失的鍵
  • merge(defaultConfig, userConfig, { keyValueUpsertMode: true }) - 保留使用者設定

2. 表單資料合併

// 預設表單值
const defaultValues = {
  username: '',
  password: '',
  rememberMe: false,
  preferences: { newsletter: true, language: 'en' }
};

// 使用者已輸入的資料
const userInput = {
  username: 'alice',
  preferences: { language: 'zh-TW' }
};

// 合併:保留使用者已輸入的資料
const formData = merge(defaultValues, userInput, {
  keyValueUpsertMode: true
});
// => { username: 'alice', password: '', rememberMe: false, preferences: { newsletter: true, language: 'zh-TW' } }

3. 環境變數覆蓋

// 基礎環境配置
const baseConfig = {
  API_URL: 'https://api.example.com',
  DEBUG: false,
  CACHE: { enabled: true, ttl: 3600 }
};

// 開發環境配置
const devConfig = {
  DEBUG: true,
  API_URL: 'https://dev-api.example.com'
};

// 生產環境配置
const prodConfig = {
  DEBUG: false
};

// 合併環境配置(保留最基礎的值)
const envConfig = merge(baseConfig, devConfig, {
  keyValueUpsertMode: true
});
// => { API_URL: 'https://dev-api.example.com', DEBUG: true, CACHE: { enabled: true, ttl: 3600 } }

4. 深度合併(類似 _.merge)

const target = { user: { name: 'Alice', age: 25 }, role: 'admin' };
const source = { user: { name: 'Bob', city: 'Taipei' }, role: 'user' };

// 預設深度合併(source 覆蓋 target)
const result1 = merge(target, source);
// => { user: { name: 'Bob', age: 25, city: 'Taipei' }, role: 'user' }

// 使用 keyValueUpsertMode 保留 target 的值
const result2 = merge(target, source, { keyValueUpsertMode: true });
// => { user: { name: 'Alice', age: 25, city: 'Taipei' }, role: 'admin' }

與 lodash 函式對比

| lodash 函式 | deepmerge-plus 實作 | 說明 | |------------|-------------------|------| | _.defaults | merge(target, source, { keyValueUpsertMode: true }) | 淺層預設值(需正確參數順序) | | _.defaultsDeep | merge(target, source, { keyValueUpsertMode: true }) | 深度預設值 | | _.merge | merge(target, source) | 預設深度合併 |

重要語義差異

  • _.defaults / _.defaultsDeep:填充缺失的鍵(fill missing)
  • keyValueUpsertMode: true:有值則保留(preserve if exists)

類型定義

interface IOptions {
  /** 是否啟用深度複製,預設 true */
  clone?: boolean;

  /** 自訂陣列合併函式 */
  arrayMerge?: <T extends any[]>(target: T, source: any[], options?: IOptions) => T[];

  /** 自訂可合併物件判斷函式 */
  isMergeableObject?: (value: any) => boolean;

  /** 已棄用:使用 keyValueUpsertMode 取代 */
  keyValueOrMode?: boolean;

  /** Upsert 模式:true 或自訂函式 */
  keyValueUpsertMode?:
    | boolean
    | ((value: unknown, optionsRuntime?: IOptions, tmpRuntimeTarget?: ICache) => boolean);
}

測試

pnpm test

授權

MIT