개체 확산 대 개체 할당


396

options변수가 있고 기본값을 설정하고 싶다고 가정 해 봅시다 .

이 두 대안의 장점 / 단점은 무엇입니까?

객체 확산 사용

options = {...optionsDefault, ...options};

또는 Object.assign 사용

options = Object.assign({}, optionsDefault, options);

이것이 나를 놀라게 한 커밋 입니다.


4
첫 번째는 제안 된 새로운 구문이며 ES6의 일부가 아니므로 준수하려는 표준에 따라 다릅니다.
loganfsmyth

5
" 최고 "를 정의하십시오 (신중하게 의견 기반 질문으로 끝나지 마십시오 :-)
Amit

1
기본 지원없이 환경에서 실행하는 경우 지원 방법에 따라 달라질 수 있습니다. 구문 컴파일 만 가능할 수도 있습니다. 폴리 필해야하는 객체 또는 방법.
JMM

6
호환성 문제 외에도 Object.assign은 유용한 원본 개체를 변경할 수 있습니다. 퍼질 수 없습니다.
pstanton 2012 년

2
@pstanton의 의견을 명확히하기 위해 object.assign은 기존 대상 객체를 수정할 수 있습니다 (다른 속성은 그대로 둔 채 소스에서 속성을 덮어 씁니다). 소스 객체를 건드리지 않습니다 . 나는 먼저 그의 "원래 객체"를 "소스 객체"로 읽었으므로, 그것을 오해하는 다른 사람들을 위해이 메모를 작성합니다. :)
ToolmakerSteve

답변:


328

이것은 반드시 완전한 것은 아닙니다.

확산 구문

options = {...optionsDefault, ...options};

장점 :

  • 기본 지원이없는 환경에서 실행할 코드를 작성하는 경우, polyfill을 사용하는 대신이 구문을 컴파일 할 수 있습니다. (예를 들어, 바벨과 함께)

  • 덜 장황하다.

단점 :

  • 이 답변이 처음 쓰여졌을 때 표준화되지 않은 제안 . 제안서를 사용할 때 지금 코드를 작성하고 표준화를 진행함에 따라 표준화되지 않거나 변경되지 않는 경우 수행 할 작업을 고려하십시오. 이후 ES2018에서 표준화되었습니다.

  • 동적이 아닌 리터럴.


Object.assign()

options = Object.assign({}, optionsDefault, options);

장점 :

  • 표준화.

  • 동적. 예:

    var sources = [{a: "A"}, {b: "B"}, {c: "C"}];
    options = Object.assign.apply(Object, [{}].concat(sources));
    // or
    options = Object.assign({}, ...sources);

단점 :

  • 더 장황하다.
  • 기본 지원이없는 환경에서 실행할 코드를 작성하려면 polyfill해야합니다.

이것이 나를 놀라게 한 커밋입니다.

그것은 당신이 요구하는 것과 직접 관련이 없습니다. 해당 코드는을 사용하지 않았 으며 동일한 작업을 수행하는 Object.assign()사용자 코드 ( object-assign)를 사용하고있었습니다 . 그들은 Babel을 사용하여 코드를 컴파일하고 Webpack과 번들로 묶는 것처럼 보입니다. 그들은 분명히 object-assign빌드에 들어갈 종속성 으로 포함하는 것을 선호했습니다 .


15
오브젝트 레스트 스프레드가 3 단계로 이동 했으므로 향후 twitter.com/sebmarkbage/status/781564713750573056
williaster

11
@JMM "더 자세한 정보 표시"가 확실하지 않습니다. 단점으로.
DiverseAndRemote.com

6
@Omar 잘 나는 당신이 그것이 단지 의견이라는 것을 증명했다고 생각합니다. :) 누군가가 그 이점을 인식하지 못하면, 그래요, 다른 모든 사람들은 평등하게 사용할 수 있습니다 Object.assign(). 또는 수동으로 객체 배열과 대상에 수동으로 할당하는 자체 소품을 반복하여 더 자세하게 만들 수 있습니다. : P
JMM

7
@JMM이 언급했듯이 이제 ES2018 사양 노드에 있습니다 .green
Sebastien H.

2
"모든 바이트 수"thatyz / uglify는 @yzorg에 대한 것입니다
DiverseAndRemote.com

171

ECMAScript 2018에서는 참조 오브젝트 레스트 / 스프레드가 4 단계로 마무리됩니다. 제안은 여기 에서 찾을 수 있습니다 .

대부분의 객체 재설정 및 확산 작업은 동일한 방식으로, 주요 차이점은 spread는 속성을 정의하고 Object.assign ()이 속성을 설정 한다는 입니다. 이것은 Object.assign ()이 setter를 트리거한다는 것을 의미합니다.

이것 이외의 오브젝트 레스트 / 스프레드 1 : 1은 Object.assign ()에 매핑되고 배열 (반복 가능) 스프레드에 다르게 작동한다는 것을 기억할 가치가 있습니다. 예를 들어, 배열을 펼칠 때 null 값이 펼칩니다. 그러나 객체 스프레드를 사용하면 null 값이 자동으로 아무 것도 퍼지지 않습니다.

배열 (반복 가능) 스프레드 예

const x = [1, 2, null , 3];
const y = [...x, 4, 5];
const z = null;

console.log(y); // [1, 2, null, 3, 4, 5];
console.log([...z]); // TypeError

개체 확산 예

const x = null;
const y = {a: 1, b: 2};
const z = {...x, ...y};

console.log(z); //{a: 1, b: 2}

이는 Object.assign ()의 작동 방식과 일치하며 오류없이 null 값을 자동으로 제외합니다.

const x = null;
const y = {a: 1, b: 2};
const z = Object.assign({}, x, y);

console.log(z); //{a: 1, b: 2}

10
선택한 답변이어야합니다.
Evan Plaice 2018

4
이것이 답이되어야합니다 ... 지금은 미래입니다.
Zachary Abresch

1
이것이 정답입니다. 가장 큰 차이점은 Object.assign세터를 사용한다는 것입니다. Object.assign({set a(v){this.b=v}, b:2}, {a:4}); // {b: 4}vs.{...{set a(v){this.b=v}, b:2}, ...{a:4}}; // {a: 4, b: 2}
David Boho

1
"미래는 지금이다!" - 조지 알렌 내일은 너무 늦다.
ruffin

1
null이 다르게 처리되는 것은 "사과와 오렌지"입니다. 의미있는 비교는 아닙니다. 배열의 경우 null은 배열의 요소입니다. 스프레드의 경우 전체 객체는 null입니다. x가 가질 대한 올바른 비교가 될 것 널 (null) 속성을 : const x = {c: null};. 어떤 경우에, AFAIK, 우리는 배열과 같은 행동을 보게 될 것입니다 : //{a: 1, b: 2, c: null}.
ToolmakerSteve

39

스프레드 연산자와 Object.assign현재 답변에서 언급되지 않은 것의 큰 차이점 은 스프레드 연산자가 소스 객체의 프로토 타입을 대상 객체에 복사하지 않는다는 것입니다. 객체에 속성을 추가하고 객체의 인스턴스를 변경하지 않으려면을 사용해야 Object.assign합니다. 아래 예제는 이것을 보여 주어야합니다.

const error = new Error();
error instanceof Error // true

const errorExtendedUsingSpread = {
  ...error,
  ...{
    someValue: true
  }
};
errorExtendedUsingSpread instanceof Error; // false

const errorExtendedUsingAssign = Object.assign(error, {
  someValue: true
});
errorExtendedUsingAssign instanceof Error; // true


"시제품은 손상되지 않습니다." -아무것도 수정하지 않기 때문에 확실합니다. 귀하의 예에서 errorExtendedUsingAssign === error, 그러나 errorExtendedUsingSpread새로운 객체입니다 (그리고 프로토 타입은 복사되지 않았습니다).
maaartinus

2
@maaartinus 당신 말이 맞아요, 아마 그렇게 잘못 말했을 것입니다. 프로토 타입이 복사 된 오브젝트에 없다는 것을 의미했습니다. 더 명확하게 편집 할 수 있습니다.
Sean Dawson

다음은 클래스로 객체를 "얕은 복제"하는 방법입니까? let target = Object.create(source); Object.assign(target, source);
ToolmakerSteve

@ToolmakerSteve 예, 모든 객체의 "자신의 속성"을 복사하여 사실상 얕은 복제본이됩니다. 참조 : stackoverflow.com/questions/33692912/...를
숀 도슨

12

다른 사람들이 언급했듯이,이 글을 쓰는 순간에 Object.assign()폴리 필이 ...필요 하고 객체 스프레드 에는 작동하기 위해 약간의 트랜스 필링 (그리고 아마도 폴리 필도 필요)이 필요합니다.

이 코드를 고려하십시오.

// Babel wont touch this really, it will simply fail if Object.assign() is not supported in browser.
const objAss = { message: 'Hello you!' };
const newObjAss = Object.assign(objAss, { dev: true });
console.log(newObjAss);

// Babel will transpile with use to a helper function that first attempts to use Object.assign() and then falls back.
const objSpread = { message: 'Hello you!' };
const newObjSpread = {...objSpread, dev: true };
console.log(newObjSpread);

둘 다 동일한 출력을 생성합니다.

다음은 Babel에서 ES5 로의 출력입니다.

var objAss = { message: 'Hello you!' };
var newObjAss = Object.assign(objAss, { dev: true });
console.log(newObjAss);

var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };

var objSpread = { message: 'Hello you!' };
var newObjSpread = _extends({}, objSpread, { dev: true });
console.log(newObjSpread);

이것은 지금까지 나의 이해입니다. Object.assign()객체 확산 ...이 아직없는 곳에서 실제로 표준화되었습니다 . 유일한 문제는 전자에 대한 브라우저 지원과 미래에 대한 브라우저 지원입니다.

여기 코드를 가지고 놀아

도움이 되었기를 바랍니다.


1
감사합니다! 귀하의 코드 샘플은 내 상황에 따라 결정을 정말 쉽게 만듭니다. 트랜스 파일러 (바벨 또는 타이프 스크립트)는 인라인 코드에 pollyfill을 포함시켜 스프레드 연산자를 브라우저에 더 적합하게 만듭니다. 흥미롭게도 TypeScript 번역 된 버전은 Babel과 거의 동일합니다 : typescriptlang.org/play/…
Mark Whitfeld

2
흠 ... 두 사례 가 같은 결과를 얻지 못 할까요? 첫 번째 경우에는 한 객체에서 다른 객체로 속성을 복사하고 다른 경우에는 새 객체를 만듭니다. Object.assign은 대상을 반환하므로 첫 번째 경우 objAss와 newObjAss가 동일합니다.
케빈 B

새로운 첫 번째 매개 변수를 추가 {}하면 불일치를 수정해야합니다.
케빈 B

11

객체 스프레드 연산자 (...)는 브라우저에서 작동하지 않습니다. 아직 ES 사양의 일부가 아니며 제안 일뿐입니다. 유일한 옵션은 Babel (또는 이와 유사한 것)로 컴파일하는 것입니다.

보다시피 Object.assign ({})에 대한 구문 설탕 일뿐입니다.

내가 볼 수있는 한, 이것들은 중요한 차이점입니다.

  • Object.assign은 대부분의 브라우저에서 작동합니다 (컴파일없이).
  • ... 개체가 표준화되지 않았습니다
  • ... 실수로 오브젝트를 변경하지 않도록 보호
  • ... 브라우저없이 Object.assign을 polyfill합니다.
  • ... 같은 생각을 표현하기 위해 더 적은 코드가 필요하다

34
그것은의 하지 에 대한 문법 설탕 Object.assign확산 연산자는 항상 당신에게 새로운 객체를 줄 것 같은.
MaxArt

4
사실, 나는 다른 사람들이 가변성 차이를 더 강조하지 않는 것에 놀랐습니다. 개발자 시간의 모든 생각은 Object.assign와 우발적 인 돌연변이를 디버깅 손실
deepelement

이것은 현재 대부분의 최신 브라우저에서 지원됩니다 (다른 ES6 에서처럼) : developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
akmalhakimi1991

11

"스프레드 오브젝트 병합"ES 기능, 브라우저 및 생태계에서 도구를 통해 상태를 요약하고 싶습니다.

투기

브라우저 : Chrome, SF, Firefox 곧 (버전 60, IIUC)

  • Chrome 60에서 제공되는 '스프레드 속성'에 대한 브라우저 지원이 시나리오를 포함하여 .
  • 이 시나리오에 대한 지원은 현재 Firefox (59)에서 작동하지 않지만 DOES는 Firefox Developer Edition에서 작동합니다. 그래서 나는 그것이 Firefox 60에 제공 될 것이라고 믿습니다.
  • Safari : 테스트되지 않았지만 Kangax는 Desktop Safari 11.1에서는 작동하지만 SF 11에서는 작동하지 않는다고 말합니다.
  • iOS Safari : teseted는 아니지만 Kangax는 iOS 11.3에서는 작동하지만 iOS 11에서는 작동하지 않는다고 말합니다.
  • 아직 가장자리에 없습니다

도구 : 노드 8.7, TS 2.1

연결

코드 샘플 (호환성 테스트의 두 배)

var x = { a: 1, b: 2 };
var y = { c: 3, d: 4, a: 5 };
var z = {...x, ...y};
console.log(z); // { a: 5, b: 2, c: 3, d: 4 }

다시 :이 샘플을 작성할 때이 샘플은 Chrome (60+), Firefox Developer Edition (Firefox 60의 미리보기) 및 노드 (8.7+)에서 변환없이 작동합니다.

왜 대답해야합니까?

나는 원래 질문 후 2.5 을 쓰고 있습니다. 그러나 나는 똑같은 질문을했고 이것이 Google이 보낸 곳입니다. 나는 긴 꼬리를 향상시키는 SO의 사명의 노예입니다.

이것이 "배열 확산"구문의 확장이기 때문에 Google에 매우 어려워 호환성 테이블에서 찾기가 어렵다는 것을 알았습니다. 내가 찾을 수있는 가장 가까운 것은 Kangax "속성 확산"입니다 이지만,이 테스트에는 같은 식으로 두 개의 스프레드가 없습니다 (병합 아님). 또한 제안서 / 초안 / 브라우저 상태 페이지의 이름은 모두 "속성 스프레드"를 사용하지만 커뮤니티가 제안서 다음에 "개체 병합"에 대한 스프레드 구문을 사용하기 위해 도착한 "첫 번째 주체"인 것 같습니다. (Google이 왜 그렇게 어려운지 설명 할 수 있습니다.) 따라서 다른 사람들이이 특정 기능에 대한 링크를보고 업데이트하고 컴파일 할 수 있도록 여기에 찾은 결과를 문서화합니다. 나는 그것이 붙잡기를 바란다. 사양 및 브라우저에 상륙한다는 소식을 알리십시오.

마지막으로이 정보를 주석으로 추가했지만 저자의 원래 의도를 깨뜨리지 않고는 정보를 편집 할 수 없었습니다. 특히 @RichardSchulte를 수정하려는 의도를 잃지 않으면 @ChillyPenguin의 의견을 편집 할 수 없습니다. 그러나 몇 년 후 Richard는 (내 의견으로는) 옳았습니다. 그래서 나는이 답변을 씁니다. 오래된 답변에 결국 견인력을 갖기를 기대합니다 (몇 년이 걸릴 수 있지만 결국 긴 꼬리 효과가 있습니다).


3
"응답하는 이유"섹션은 아마 필요하지 않습니다
LocustHorde

@LocustHorde 어쩌면 두 번째 단락 (이 주제가 Google에 너무 어려운 이유)을 자체 섹션으로 옮길 수 있습니다. 그런 다음 나머지는 주석에 맞을 수 있습니다.
yzorg

8

참고 : 확산은 Object.assign 주위의 구문 설탕이 아닙니다. 그들은 배후에서 크게 다르게 작동합니다.

Object.assign은 setter를 새 객체에 적용하지만 Spread는 적용하지 않습니다. 또한 객체는 반복 가능해야합니다.

복사이 시점에서 개체의 값이 필요하고 해당 값이 개체의 다른 소유자가 변경 한 내용을 반영하지 않게하려면이 옵션을 사용하십시오.

변경 불가능한 버전은 변경 불가능한 속성으로 전달 될 수 있으므로 복사는 항상 변경 불가능한 오브젝트를 처리 할 수 ​​있도록하기 위해 항상 변경 불가능한 특성을 복사하도록 오브젝트의 단순 사본을 작성하는 데 사용하십시오.

할당 할당은 복사와 반대입니다. Assign은 값을 복사하거나 유지하지 않고 인스턴스 변수에 직접 값을 지정하는 setter를 생성합니다. assign 속성의 getter를 호출하면 실제 데이터에 대한 참조가 반환됩니다.


3
왜 "복사"라고 표시됩니까? 이 대담한 제목은 무엇입니까? 이 글을 읽을 때 어떤 맥락을 놓친 것 같습니다 ...
ADJenks

2

다른 답변은 오래되었으므로 좋은 답변을 얻을 수 없습니다.
아래 예제는 객체 리터럴을위한 것으로, 서로 보완 할 수있는 방법과 서로 보완 할 수없는 방법 (따라서 차이)에 도움이됩니다.

var obj1 = { a: 1,  b: { b1: 1, b2: 'b2value', b3: 'b3value' } };

// overwrite parts of b key
var obj2 = {
      b: {
        ...obj1.b,
        b1: 2
      }
};
var res2 = Object.assign({}, obj1, obj2); // b2,b3 keys still exist
document.write('res2: ', JSON.stringify (res2), '<br>');
// Output:
// res2: {"a":1,"b":{"b1":2,"b2":"b2value","b3":"b3value"}}  // NOTE: b2,b3 still exists

// overwrite whole of b key
var obj3 = {
      b: {
        b1: 2
      }
};
var res3 = Object.assign({}, obj1, obj3); // b2,b3 keys are lost
document.write('res3: ', JSON.stringify (res3), '<br>');
// Output:
  // res3: {"a":1,"b":{"b1":2}}  // NOTE: b2,b3 values are lost

배열 및 객체에 대한 몇 가지 더 작은 예제 :
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax


1

이것은 현재 ES6의 일부이므로 표준화되어 있으며 MDN에도 문서화되어 있습니다. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_operator

사용하는 것이 매우 편리하고 객체 파괴와 함께 많은 의미가 있습니다.

위에 나열된 나머지 장점 중 하나는 Object.assign ()의 동적 기능이지만, 리터럴 객체 내부에 배열을 확산시키는 것만 큼 쉽습니다. 컴파일 된 babel 출력에서 ​​Object.assign ()으로 보여지는 것을 정확하게 사용합니다.

정답은 이제 표준화되고 널리 사용되며 (반응, 리덕스 등 참조) 사용하기 쉽고 Object.assign ()의 모든 기능을 가지고 있기 때문에 객체 확산을 사용하는 것입니다.


2
아니요, ES6의 일부가 아닙니다. 제공 한 링크는 스프레드 연산자가 배열 에서만 사용됨을 나타냅니다 . JMM의 답변에 설명 된 것처럼 객체 에서 스프레드 연산자를 사용하는 것은 현재 2 단계 (초안) 제안입니다.
ChillyPenguin

1
나는 거짓 정보에 전적으로 답을 본 적이 없다고 생각합니다. 1 년이 지난 후에도 ES 사양의 일부가 아니며 대부분의 환경에서 지원되지 않습니다.
3ocene

Chilly와 3o는 틀린 것으로 밝혀졌습니다. Richard 씨. 브라우저 지원 및 도구 지원은 모두 상륙하지만 Richard의 답변 후 1.5 년이 걸렸습니다. 2018 년 3 월 현재 지원 요약에 대한 새로운 답변을 참조하십시오.
yzorg

0

Object.assign을 사용해야 할 때이 간단한 예제를 추가하고 싶습니다.

class SomeClass {
  constructor() {
    this.someValue = 'some value';
  }

  someMethod() {
    console.log('some action');
  }
}


const objectAssign = Object.assign(new SomeClass(), {});
objectAssign.someValue; // ok
objectAssign.someMethod(); // ok

const spread = {...new SomeClass()};
spread.someValue; // ok
spread.someMethod(); // there is no methods of SomeClass!

JavaScript를 사용할 때는 명확하지 않습니다. 그러나 TypeScript를 사용하면 일부 클래스의 인스턴스를 만들고 싶다면 더 쉽습니다.

const spread: SomeClass = {...new SomeClass()} // Error
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.