function deepMerge(first, second, mergeMap = new Map()) {
	let secondMergeMap = mergeMap.get(first);

	if (secondMergeMap) {
		const result = secondMergeMap.get(second);

		if (result) {
			// If this combination of first and second arguments has been already visited, return the previously created result.
			return result;
		}
	} else {
		secondMergeMap = new Map();
		mergeMap.set(first, secondMergeMap);
	}

	/*
	 * First create a result object where properties from the second object
	 * overwrite properties from the first. This sets up a baseline to use
	 * later rather than needing to inspect and change every property
	 * individually.
	 */
	const result = {
		...first,
		...second,
	};

	delete result.__proto__; // eslint-disable-line no-proto -- don't merge own property "__proto__"

	// Store the pending result for this combination of first and second arguments.
	secondMergeMap.set(second, result);

	for (const key of Object.keys(second)) {
		// avoid hairy edge case
		if (
			key === "__proto__" ||
			!Object.prototype.propertyIsEnumerable.call(first, key)
		) {
			continue;
		}

		const firstValue = first[key];
		const secondValue = second[key];

		if (isNonArrayObject(firstValue) && isNonArrayObject(secondValue)) {
			result[key] = deepMerge(firstValue, secondValue, mergeMap);
		} else if (isUndefined(secondValue)) {
			result[key] = firstValue;
		}
	}

	return result;
}
Deeply merges two non-array objects.
@param {Object} first The base object.
@param {Object} second The overrides object.
@param {Map<string, Map<string, Object>>} [mergeMap] Maps the combination of first and second arguments to a merged result.
@returns {Object} An object with properties from both first and second.