UNPKG

@leapond/utilities

Version:

A set of JavaScript utilities for on-demand using.

162 lines (148 loc) 5.42 kB
import deepCopy from "./deepCopy"; import {getMergeType} from "./common"; const INNER_MARK = Symbol(''/*<DEV*/ + 'INNER'/*DEV>*/) /** * @property ARRAY_NORMAL - write by deepMerge * @property ARRAY_NORMAL_FIXED - existed item will be readonly, but can increase new item * @property ARRAY_CONCAT - concat to target array * @property ARRAY_CONCAT_UNIQ - concat to target array, but skip existed item * @property ARRAY_REPLACE - replace whole array * @property ARRAY_SEAL - fixed length * @property ARRAY_FREEZE - ignore source */ const arrayMergePolicies = { ARRAY_NORMAL: 1, ARRAY_NORMAL_FIXED: 1.1, ARRAY_CONCAT: 2, ARRAY_CONCAT_UNIQ: 2.1, ARRAY_REPLACE: 3, ARRAY_SEAL: 4, ARRAY_FREEZE: 5 } const optionsDefault = { [INNER_MARK]: 1, clone: -Infinity, unEnumerableInclude: false, arrayPolicy: arrayMergePolicies.ARRAY_NORMAL, // (a, b) => a.push(b) && a arrayMerge: undefined, deepMap: true } /** * @typedef mergeOptions * @property {boolean|number} [clone=-Infinity] - if clone target. <br/>> true for all, false for none, <br/>> number<0 for reuse depth, number>0 for clone depth * @property {boolean} [unEnumerableInclude=false] - if include keys not enumerable(and Symbol keys) * @property {arrayMergePolicies} [arrayPolicy=ARRAY_NORMAL] - array merge policy of build-in * @property {function} [arrayMerge] - custom array merger, (a, b) => result * @property {boolean} [deepMap=true] - if dig in Map items */ /** * @param target * @param source * @param {mergeOptions} [options] * @return {*|{}|[]|[]} */ function deepMerge(target, source, options) { let isBatch = arguments[4], isRoot = !options || !options[INNER_MARK], aLoops, depthCurrent = 0, typeTarget, typeSource, typeIncoming if (isBatch) (isRoot = true) && ([typeTarget, typeSource, typeIncoming] = isBatch); else { typeTarget = getMergeType(target) typeSource = getMergeType(source) } // clone source while target type is not same with source if (!typeTarget) return source if (typeTarget !== typeSource) return isBatch && typeIncoming && !typeSource ? target : source // init loop circle and depth recorder if (isRoot) { // init options options = parseOptions(options) // create new loops aLoops = [source] depthCurrent = aLoops.depthCurrent = 1 } else { // concat loops const aLoopsPrev = arguments[3] aLoops = [...aLoopsPrev, source] // detect circled loops if (aLoopsPrev.includes(source)) return source // increase current depth depthCurrent = aLoopsPrev.depthCurrent + 1 } // clone source while target is empty if (!target) return source // store current depth on loops array property aLoops.depthCurrent = depthCurrent // clone target current level if ((options.clone > 0 && depthCurrent <= options.clone) || (options.clone < 0 && depthCurrent > -options.clone)) target = deepCopy(target, 1) switch (typeSource) { case 1: // Object let keys if (options.unEnumerableInclude) keys = [...Object.getOwnPropertyNames(source), ...Object.getOwnPropertySymbols(source)]; else keys = Object.keys(source) keys.forEach(key => { target[key] = deepMerge(target[key], source[key], options, aLoops) }) break case 2: // Array if (options.arrayMerge) return options.arrayMerge(target, source) switch (options.arrayPolicy) { case arrayMergePolicies.ARRAY_FREEZE: return target case arrayMergePolicies.ARRAY_SEAL: case arrayMergePolicies.ARRAY_NORMAL: case arrayMergePolicies.ARRAY_NORMAL_FIXED: Object.keys(source).forEach(key => { if (key in target) { if (options.arrayPolicy === arrayMergePolicies.ARRAY_NORMAL_FIXED) return } else if (options.arrayPolicy === arrayMergePolicies.ARRAY_SEAL) return target[key] = deepMerge(target[key], source[key], options, aLoops) }) break; case arrayMergePolicies.ARRAY_REPLACE: return source case arrayMergePolicies.ARRAY_CONCAT: source.forEach((v, i) => { target.push(deepMerge(target[i], v, options, aLoops)) }) break case arrayMergePolicies.ARRAY_CONCAT_UNIQ: source.forEach((v, i) => { !target.includes(v) && target.push(deepMerge(target[i], v, options, aLoops)) }) } break case 3: source.forEach(v => target.add(v)) break case 4: source.forEach((v, k) => target.set(k, options.deepMap ? deepMerge(target.get(k), v, options, aLoops) : v)) break } return target } /** * * @param {Object[]} aTargets * @param {mergeOptions} [options] * @return {*} */ deepMerge.batch = function (aTargets, options) { let target, aTypes options = parseOptions(options) target = aTargets.shift() aTypes = aTargets.map(getMergeType) aTargets.forEach((v, index) => target = deepMerge( target, v, options, null, [getMergeType(target), aTypes[index], aTypes.find((t, i) => i > index && t)] )) return target } function parseOptions(options) { options = Object.assign({}, optionsDefault, options) if (options.clone === true) options.clone = Infinity; else if (!(options.clone >= -Infinity)) options.clone = optionsDefault.clone return options } Object.assign(deepMerge, arrayMergePolicies) export default deepMerge