Mik*_*ike 289 javascript spread-syntax
无论Object.assign和对象传播只能做一浅合并.
问题的一个例子:
// No object nesting
const x = { a: 1 }
const y = { b: 1 }
const z = { ...x, ...y } // { a: 1, b: 1 }
Run Code Online (Sandbox Code Playgroud)
输出是你所期望的.但是如果我试试这个:
// Object nesting
const x = { a: { a: 1 } }
const y = { a: { b: 1 } }
const z = { ...x, ...y } // { a: { b: 1 } }
Run Code Online (Sandbox Code Playgroud)
代替
{ a: { a: 1, b: 1 } }
Run Code Online (Sandbox Code Playgroud)
你得到
{ a: { b: 1 } }
Run Code Online (Sandbox Code Playgroud)
x被完全覆盖,因为扩展语法只有一个深度.这是一样的Object.assign().
有没有办法做到这一点?
小智 305
有人知道ES6/ES7规范中是否存在深度合并?
不,不是的.
Sal*_*kar 154
我知道这是一个老问题,但ES2015/ES6中我能想到的最简单的解决方案实际上非常简单,使用Object.assign(),
希望这有助于:
/**
* Simple object check.
* @param item
* @returns {boolean}
*/
export function isObject(item) {
return (item && typeof item === 'object' && !Array.isArray(item));
}
/**
* Deep merge two objects.
* @param target
* @param ...sources
*/
export function mergeDeep(target, ...sources) {
if (!sources.length) return target;
const source = sources.shift();
if (isObject(target) && isObject(source)) {
for (const key in source) {
if (isObject(source[key])) {
if (!target[key]) Object.assign(target, { [key]: {} });
mergeDeep(target[key], source[key]);
} else {
Object.assign(target, { [key]: source[key] });
}
}
}
return mergeDeep(target, ...sources);
}
Run Code Online (Sandbox Code Playgroud)
用法示例:
mergeDeep(this, { a: { b: { c: 123 } } });
// or
const merged = mergeDeep({a: 1}, { b : { c: { d: { e: 12345}}}});
console.dir(merged); // { a: 1, b: { c: { d: [Object] } } }
Run Code Online (Sandbox Code Playgroud)
您将在下面的答案中找到不可变版本.
请注意,这将导致循环引用的无限递归.如果您认为自己面临这个问题,那么有关如何检测循环引用的一些很好的答案.
the*_*472 95
当涉及到宿主对象或任何比一包价值更复杂的对象时,问题就不那么重要了
另外要记住的是:包含循环的对象图.处理通常并不困难 - 只需保留Set已经访问过的源对象 - 但经常被遗忘.
您可能应该编写一个深度合并函数,它只需要原始值和简单对象 - 最多是结构化克隆算法可以处理的那些类型- 作为合并源.抛出如果它遇到任何它无法处理或只是通过引用分配而不是深度合并.
换句话说,没有一个适合所有人的算法,你要么自己动手,要么寻找恰好涵盖你的用例的库方法.
And*_*son 95
你可以使用Lodash合并:
var object = {
'a': [{ 'b': 2 }, { 'd': 4 }]
};
var other = {
'a': [{ 'c': 3 }, { 'e': 5 }]
};
_.merge(object, other);
// => { 'a': [{ 'b': 2, 'c': 3 }, { 'd': 4, 'e': 5 }] }
Run Code Online (Sandbox Code Playgroud)
CpI*_*ILL 56
这是@ Salakar答案的不可变(不修改输入)版本.如果你正在做函数式编程类型的东西很有用.
export function isObject(item) {
return (item && typeof item === 'object' && !Array.isArray(item));
}
export default function mergeDeep(target, source) {
let output = Object.assign({}, target);
if (isObject(target) && isObject(source)) {
Object.keys(source).forEach(key => {
if (isObject(source[key])) {
if (!(key in target))
Object.assign(output, { [key]: source[key] });
else
output[key] = mergeDeep(target[key], source[key]);
} else {
Object.assign(output, { [key]: source[key] });
}
});
}
return output;
}
Run Code Online (Sandbox Code Playgroud)
jhi*_*dle 31
由于此问题仍然有效,这是另一种方法:
/**
* Performs a deep merge of objects and returns new object. Does not modify
* objects (immutable) and merges arrays via concatenation.
*
* @param {...object} objects - Objects to merge
* @returns {object} New object with merged key/values
*/
function mergeDeep(...objects) {
const isObject = obj => obj && typeof obj === 'object';
return objects.reduce((prev, obj) => {
Object.keys(obj).forEach(key => {
const pVal = prev[key];
const oVal = obj[key];
if (Array.isArray(pVal) && Array.isArray(oVal)) {
prev[key] = pVal.concat(...oVal);
}
else if (isObject(pVal) && isObject(oVal)) {
prev[key] = mergeDeep(pVal, oVal);
}
else {
prev[key] = oVal;
}
});
return prev;
}, {});
}
// Test objects
const obj1 = {
a: 1,
b: 1,
c: { x: 1, y: 1 },
d: [ 1, 1 ]
}
const obj2 = {
b: 2,
c: { y: 2, z: 2 },
d: [ 2, 2 ],
e: 2
}
const obj3 = mergeDeep(obj1, obj2);
// Out
console.log(obj3);Run Code Online (Sandbox Code Playgroud)
Rap*_*Mex 25
我知道已经有很多答案,并且有很多评论认为它们不起作用.唯一的共识是它很复杂,没有人为它制定标准.然而,SO中大多数公认的答案暴露了广泛使用的"简单技巧".因此,对于像我这样没有专家但想通过掌握更多关于javascript复杂性来编写更安全代码的所有人,我会试着解释一下.
在弄清楚之前,让我澄清2点:
Object.assign.for..in或Object.keys有误导性制作一份深刻的副本似乎是如此基本和通用的做法,我们期望通过简单的递归找到一个单行或至少快速获胜.我们不希望我们需要一个库或编写100行的自定义函数.
当我第一次读到Salakar的回答,我真的以为我可以做的更好,更简单的(你可以用比较Object.assign上x={a:1}, y={a:{b:1}}).然后我读了8472的回答,我想......没有那么容易逃脱,改善已经给出的答案不会让我们走得太远.
让我们快速复制和递归.只考虑人们如何(错误地)解析属性来复制一个非常简单的对象.
const y = Object.create(
{ proto : 1 },
{ a: { enumerable: true, value: 1},
[Symbol('b')] : { enumerable: true, value: 1} } )
Object.assign({},y)
> { 'a': 1, Symbol(b): 1 } // All (enumerable) properties are copied
((x,y) => Object.keys(y).reduce((acc,k) => Object.assign(acc, { [k]: y[k] }), x))({},y)
> { 'a': 1 } // Missing a property!
((x,y) => {for (let k in y) x[k]=y[k];return x})({},y)
> { 'a': 1, 'proto': 1 } // Missing a property! Prototype's property is copied too!
Run Code Online (Sandbox Code Playgroud)
Object.keys将省略自己的非可枚举属性,拥有符号键控属性和所有原型的属性.如果您的对象没有任何这些对象可能没问题.但要记住,Object.assign处理自己的符号键可枚举属性.所以你的自定义副本失去了它的绽放
for..in将提供源,其原型和完整原型链的属性,而无需您(或知道它).您的目标最终可能会有太多属性,混合原型属性和自己的属性.
如果你正在写一个通用的功能,你不使用Object.getOwnPropertyDescriptors,Object.getOwnPropertyNames,Object.getOwnPropertySymbols或Object.getPrototypeOf,你很可能就错了.
首先,确保您了解Javascript对象是什么.在Javascript中,对象由其自己的属性和(父)原型对象组成.原型对象又由它自己的属性和原型对象组成.等等,定义原型链.
属性是一对键(string或symbol)和描述符(value或get/ set访问器,以及属性enumerable).
最后,有许多类型的对象.您可能希望以不同的方式处理来自对象Date或对象Function的对象Object.
所以,写你的深层副本,你应该至少回答这些问题:
对于我的例子,我认为只有object Objects 很深,因为其他构造函数创建的其他对象可能不适合深入查看.从这个SO定制.
function toType(a) {
// Get fine type (object, array, function, null, error, date ...)
return ({}).toString.call(a).match(/([a-z]+)(:?\])/i)[1];
}
function isDeepObject(obj) {
return "Object" === toType(obj);
}
Run Code Online (Sandbox Code Playgroud)
我做了一个options对象来选择要复制的内容(用于演示目的).
const options = {nonEnum:true, symbols:true, descriptors: true, proto:true};
Run Code Online (Sandbox Code Playgroud)
你可以在这个plunker中测试它.
function deepAssign(options) {
return function deepAssignWithOptions (target, ...sources) {
sources.forEach( (source) => {
if (!isDeepObject(source) || !isDeepObject(target))
return;
// Copy source's own properties into target's own properties
function copyProperty(property) {
const descriptor = Object.getOwnPropertyDescriptor(source, property);
//default: omit non-enumerable properties
if (descriptor.enumerable || options.nonEnum) {
// Copy in-depth first
if (isDeepObject(source[property]) && isDeepObject(target[property]))
descriptor.value = deepAssign(options)(target[property], source[property]);
//default: omit descriptors
if (options.descriptors)
Object.defineProperty(target, property, descriptor); // shallow copy descriptor
else
target[property] = descriptor.value; // shallow copy value only
}
}
// Copy string-keyed properties
Object.getOwnPropertyNames(source).forEach(copyProperty);
//default: omit symbol-keyed properties
if (options.symbols)
Object.getOwnPropertySymbols(source).forEach(copyProperty);
//default: omit prototype's own properties
if (options.proto)
// Copy souce prototype's own properties into target prototype's own properties
deepAssign(Object.assign({},options,{proto:false})) (// Prevent deeper copy of the prototype chain
Object.getPrototypeOf(target),
Object.getPrototypeOf(source)
);
});
return target;
}
}
Run Code Online (Sandbox Code Playgroud)
这可以像这样使用:
const x = { a: { a: 1 } },
y = { a: { b: 1 } };
deepAssign(options)(x,y); // { a: { a: 1, b: 1 } }
Run Code Online (Sandbox Code Playgroud)
mod*_*diX 25
如果您想拥有一个单行而不需要像 lodash 这样的庞大库,我建议您使用deepmerge。( npm install deepmerge)
然后,你可以做
deepmerge({ a: 1, b: 2, c: 3 }, { a: 2, d: 3 });
Run Code Online (Sandbox Code Playgroud)
要得到
{ a: 2, b: 2, c: 3, d: 3 }
Run Code Online (Sandbox Code Playgroud)
这适用于复杂的对象和数组。好消息是它立即带有 TypeScript 的类型。这是一个真正的全能解决方案。
Jef*_*ian 10
我用lodash:
import _ = require('lodash');
value = _.merge(value1, value2);
Run Code Online (Sandbox Code Playgroud)
per*_*mon 10
在这里,直截了当;
一个简单的解决方案,就像Object.assigndeep 一样,适用于数组,无需任何修改。
function deepAssign(target, ...sources) {
for (source of sources) {
for (let k in source) {
let vs = source[k], vt = target[k]
if (Object(vs) == vs && Object(vt) === vt) {
target[k] = deepAssign(vt, vs)
continue
}
target[k] = source[k]
}
}
return target
}
x = { a: { a: 1 }, b: [1,2] }
y = { a: { b: 1 }, b: [3] }
z = { c: 3, b: [,,,4] }
x = deepAssign(x, y, z)
console.log(JSON.stringify(x) === JSON.stringify({
"a": {
"a": 1,
"b": 1
},
"b": [ 1, 2, null, 4 ],
"c": 3
}))Run Code Online (Sandbox Code Playgroud)
很多答案使用了几十行代码,或者需要在项目中添加一个新的库,但是如果你使用递归,这只是4行代码。
function merge(current, updates) {
for (key of Object.keys(updates)) {
if (!current.hasOwnProperty(key) || typeof updates[key] !== 'object') current[key] = updates[key];
else merge(current[key], updates[key]);
}
return current;
}
console.log(merge({ a: { a: 1 } }, { a: { b: 1 } }));Run Code Online (Sandbox Code Playgroud)
数组处理:以上版本用新的数组覆盖旧的数组值。如果您希望它保留旧的数组值并添加新的值,只需else if (current[key] instanceof Array && updates[key] instanceof Array) current[key] = current[key].concat(updates[key])在elsestatament上方添加一个块即可。
这是TypeScript实现:
export const mergeObjects = <T extends object = object>(target: T, ...sources: T[]): T => {
if (!sources.length) {
return target;
}
const source = sources.shift();
if (source === undefined) {
return target;
}
if (isMergebleObject(target) && isMergebleObject(source)) {
Object.keys(source).forEach(function(key: string) {
if (isMergebleObject(source[key])) {
if (!target[key]) {
target[key] = {};
}
mergeObjects(target[key], source[key]);
} else {
target[key] = source[key];
}
});
}
return mergeObjects(target, ...sources);
};
const isObject = (item: any): boolean => {
return item !== null && typeof item === 'object';
};
const isMergebleObject = (item): boolean => {
return isObject(item) && !Array.isArray(item);
};
Run Code Online (Sandbox Code Playgroud)
和单元测试:
describe('merge', () => {
it('should merge Objects and all nested Ones', () => {
const obj1 = { a: { a1: 'A1'}, c: 'C', d: {} };
const obj2 = { a: { a2: 'A2'}, b: { b1: 'B1'}, d: null };
const obj3 = { a: { a1: 'A1', a2: 'A2'}, b: { b1: 'B1'}, c: 'C', d: null};
expect(mergeObjects({}, obj1, obj2)).toEqual(obj3);
});
it('should behave like Object.assign on the top level', () => {
const obj1 = { a: { a1: 'A1'}, c: 'C'};
const obj2 = { a: undefined, b: { b1: 'B1'}};
expect(mergeObjects({}, obj1, obj2)).toEqual(Object.assign({}, obj1, obj2));
});
it('should not merge array values, just override', () => {
const obj1 = {a: ['A', 'B']};
const obj2 = {a: ['C'], b: ['D']};
expect(mergeObjects({}, obj1, obj2)).toEqual({a: ['C'], b: ['D']});
});
it('typed merge', () => {
expect(mergeObjects<TestPosition>(new TestPosition(0, 0), new TestPosition(1, 1)))
.toEqual(new TestPosition(1, 1));
});
});
class TestPosition {
constructor(public x: number = 0, public y: number = 0) {/*empty*/}
}
Run Code Online (Sandbox Code Playgroud)
如果您使用的是ImmutableJS,您可以使用mergeDeep:
fromJS(options).mergeDeep(options2).toJS();
Run Code Online (Sandbox Code Playgroud)
我想提出一个非常简单的ES5替代方案。该函数获取2个参数- target并且source必须为“对象”类型。Target将成为结果对象。Target保留其所有原始属性,但是可以修改其值。
function deepMerge(target, source) {
if(typeof target !== 'object' || typeof source !== 'object') return false; // target or source or both ain't objects, merging doesn't make sense
for(var prop in source) {
if(!source.hasOwnProperty(prop)) continue; // take into consideration only object's own properties.
if(prop in target) { // handling merging of two properties with equal names
if(typeof target[prop] !== 'object') {
target[prop] = source[prop];
} else {
if(typeof source[prop] !== 'object') {
target[prop] = source[prop];
} else {
if(target[prop].concat && source[prop].concat) { // two arrays get concatenated
target[prop] = target[prop].concat(source[prop]);
} else { // two objects get merged recursively
target[prop] = deepMerge(target[prop], source[prop]);
}
}
}
} else { // new properties get added to target
target[prop] = source[prop];
}
}
return target;
}
Run Code Online (Sandbox Code Playgroud)
情况:
target没有source财产,target得到它;target确实有一个source属性,并且target&source都不是两个对象(4个案例中有3个),则target的属性会被覆盖;target确实具有一个source属性并且它们都是对象/数组(剩下1种情况),则合并两个对象(或两个数组的串联)时会发生递归;还考虑以下几点:
它是可预测的,支持基本类型以及数组和对象。另外,由于我们可以合并2个对象,因此我认为可以通过reduce函数合并2个以上的对象。
看一个例子(如果需要,可以尝试一下):
function deepMerge(target, source) {
if(typeof target !== 'object' || typeof source !== 'object') return false; // target or source or both ain't objects, merging doesn't make sense
for(var prop in source) {
if(!source.hasOwnProperty(prop)) continue; // take into consideration only object's own properties.
if(prop in target) { // handling merging of two properties with equal names
if(typeof target[prop] !== 'object') {
target[prop] = source[prop];
} else {
if(typeof source[prop] !== 'object') {
target[prop] = source[prop];
} else {
if(target[prop].concat && source[prop].concat) { // two arrays get concatenated
target[prop] = target[prop].concat(source[prop]);
} else { // two objects get merged recursively
target[prop] = deepMerge(target[prop], source[prop]);
}
}
}
} else { // new properties get added to target
target[prop] = source[prop];
}
}
return target;
}
Run Code Online (Sandbox Code Playgroud)
有一个限制-浏览器的调用堆栈长度。现代浏览器会在某些真正深层次的递归(例如成千上万的嵌套调用)上引发错误。另外,您还可以通过添加新条件和类型检查来随意处理数组+对象等情况。
这是另一个ES6解决方案,适用于对象和数组.
function deepMerge(...sources) {
let acc = {}
for (const source of sources) {
if (source instanceof Array) {
if (!(acc instanceof Array)) {
acc = []
}
acc = [...acc, ...source]
} else if (source instanceof Object) {
for (let [key, value] of Object.entries(source)) {
if (value instanceof Object && key in acc) {
value = deepMerge(acc[key], value)
}
acc = { ...acc, [key]: value }
}
}
}
return acc
}
Run Code Online (Sandbox Code Playgroud)
这里的大多数示例似乎太复杂了,我在我创建的 TypeScript 中使用了一个,我认为它应该涵盖大多数情况(我将数组作为常规数据处理,只是替换它们)。
const isObject = (item: any) => typeof item === 'object' && !Array.isArray(item);
export const merge = <A = Object, B = Object>(target: A, source: B): A & B => {
const isDeep = (prop: string) =>
isObject(source[prop]) && target.hasOwnProperty(prop) && isObject(target[prop]);
const replaced = Object.getOwnPropertyNames(source)
.map(prop => ({ [prop]: isDeep(prop) ? merge(target[prop], source[prop]) : source[prop] }))
.reduce((a, b) => ({ ...a, ...b }), {});
return {
...(target as Object),
...(replaced as Object)
} as A & B;
};
Run Code Online (Sandbox Code Playgroud)
在普通 JS 中也是如此,以防万一:
const isObject = item => typeof item === 'object' && !Array.isArray(item);
const merge = (target, source) => {
const isDeep = prop =>
isObject(source[prop]) && target.hasOwnProperty(prop) && isObject(target[prop]);
const replaced = Object.getOwnPropertyNames(source)
.map(prop => ({ [prop]: isDeep(prop) ? merge(target[prop], source[prop]) : source[prop] }))
.reduce((a, b) => ({ ...a, ...b }), {});
return {
...target,
...replaced
};
};
Run Code Online (Sandbox Code Playgroud)
这是我的测试用例来展示如何使用它
describe('merge', () => {
context('shallow merges', () => {
it('merges objects', () => {
const a = { a: 'discard' };
const b = { a: 'test' };
expect(merge(a, b)).to.deep.equal({ a: 'test' });
});
it('extends objects', () => {
const a = { a: 'test' };
const b = { b: 'test' };
expect(merge(a, b)).to.deep.equal({ a: 'test', b: 'test' });
});
it('extends a property with an object', () => {
const a = { a: 'test' };
const b = { b: { c: 'test' } };
expect(merge(a, b)).to.deep.equal({ a: 'test', b: { c: 'test' } });
});
it('replaces a property with an object', () => {
const a = { b: 'whatever', a: 'test' };
const b = { b: { c: 'test' } };
expect(merge(a, b)).to.deep.equal({ a: 'test', b: { c: 'test' } });
});
});
context('deep merges', () => {
it('merges objects', () => {
const a = { test: { a: 'discard', b: 'test' } };
const b = { test: { a: 'test' } } ;
expect(merge(a, b)).to.deep.equal({ test: { a: 'test', b: 'test' } });
});
it('extends objects', () => {
const a = { test: { a: 'test' } };
const b = { test: { b: 'test' } };
expect(merge(a, b)).to.deep.equal({ test: { a: 'test', b: 'test' } });
});
it('extends a property with an object', () => {
const a = { test: { a: 'test' } };
const b = { test: { b: { c: 'test' } } };
expect(merge(a, b)).to.deep.equal({ test: { a: 'test', b: { c: 'test' } } });
});
it('replaces a property with an object', () => {
const a = { test: { b: 'whatever', a: 'test' } };
const b = { test: { b: { c: 'test' } } };
expect(merge(a, b)).to.deep.equal({ test: { a: 'test', b: { c: 'test' } } });
});
});
});
Run Code Online (Sandbox Code Playgroud)
如果您认为我缺少某些功能,请告诉我。
我们可以使用$.extend(true,object1,object2)进行深度合并。值true表示递归合并两个对象,修改第一个。
以下函数对对象进行了深层复制,它涵盖了复制基元,数组以及对象的过程
function mergeDeep (target, source) {
if (typeof target == "object" && typeof source == "object") {
for (const key in source) {
if (source[key] === null && (target[key] === undefined || target[key] === null)) {
target[key] = null;
} else if (source[key] instanceof Array) {
if (!target[key]) target[key] = [];
//concatenate arrays
target[key] = target[key].concat(source[key]);
} else if (typeof source[key] == "object") {
if (!target[key]) target[key] = {};
this.mergeDeep(target[key], source[key]);
} else {
target[key] = source[key];
}
}
}
return target;
}
Run Code Online (Sandbox Code Playgroud)
使用ES5的简单解决方案(覆盖现有值):
function merge(current, update) {
Object.keys(update).forEach(function(key) {
// if update[key] exist, and it's not a string or array,
// we go in one level deeper
if (current.hasOwnProperty(key)
&& typeof current[key] === 'object'
&& !(current[key] instanceof Array)) {
merge(current[key], update[key]);
// if update[key] doesn't exist in current, or it's a string
// or array, then assign/overwrite current[key] to update[key]
} else {
current[key] = update[key];
}
});
return current;
}
var x = { a: { a: 1 } }
var y = { a: { b: 1 } }
console.log(merge(x, y));Run Code Online (Sandbox Code Playgroud)
有没有办法做到这一点?
如果可以将npm库用作解决方案,那么您真正使用的对象合并高级功能将允许您深度合并对象,并使用熟悉的回调函数自定义/覆盖每个合并操作。它的主要思想不只是深度合并-当两个键相同时,值会发生什么?这个库可以解决这个问题-当两个键发生冲突时,object-merge-advanced权衡类型,目的是在合并后保留尽可能多的数据:
第一个自变量的键标记为#1,第二个自变量的键标记为#2。根据每种类型,选择一个作为结果键的值。在图中,“对象”是指普通对象(不是数组等)。
当按键不冲突时,它们都会输入结果。
在示例代码段中,如果您曾经object-merge-advanced合并代码段,请执行以下操作:
const mergeObj = require("object-merge-advanced");
const x = { a: { a: 1 } };
const y = { a: { b: 1 } };
const res = console.log(mergeObj(x, y));
// => res = {
// a: {
// a: 1,
// b: 1
// }
// }
Run Code Online (Sandbox Code Playgroud)
该算法递归地遍历所有输入对象键,进行比较并生成并返回新的合并结果。
与减少
export const merge = (objFrom, objTo) => Object.keys(objFrom)
.reduce(
(merged, key) => {
merged[key] = objFrom[key] instanceof Object && !Array.isArray(objFrom[key])
? merge(objFrom[key], merged[key] ?? {})
: objFrom[key]
return merged
}, { ...objTo }
)
Run Code Online (Sandbox Code Playgroud)
test('merge', async () => {
const obj1 = { par1: -1, par2: { par2_1: -21, par2_5: -25 }, arr: [0,1,2] }
const obj2 = { par1: 1, par2: { par2_1: 21 }, par3: 3, arr: [3,4,5] }
const obj3 = merge3(obj1, obj2)
expect(obj3).toEqual(
{ par1: -1, par2: { par2_1: -21, par2_5: -25 }, par3: 3, arr: [0,1,2] }
)
})
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
139720 次 |
| 最近记录: |