정보실

웹학교

정보실

javascript JavaScript의 객체 레스트 / 스프레드 속성에 대한 쉬운 안내서

본문

여러 JavaScript 객체를 병합하는 것은 빈번한 작업입니다. 불행히도 JavaScript는 병합을 수행하는 편리한 구문을 제공하는 데 어려움이 있습니다. 적어도 지금까지는


ES5에서 솔루션은 Lodash (또는 다른 대안)의 _.extend (target, [sources])이며 ES2015는 Object.assign (target, [sources])을 소개합니다.


https://dmitripavlutin.com/object-rest-spread-properties-javascript/ 


운좋게도 객체 확산 구문 (3 단계의 ECMAScript 제안)은 객체를 조작하는 방법을 한 단계 발전시켜 짧고 이해하기 쉬운 구문을 제공합니다.

const cat = {
  legs: 4,
  sound: 'meow'
};
const dog = {
  ...cat,
  sound: 'woof'
};

console.log(dog); // => { legs: 4, sounds: 'woof' }

위의 예에서 ... cat은 cat의 속성을 새 개체 도그에 복사합니다. .sound 속성은 최종 값 'woof'를 받습니다.


이 기사는 객체 확산 및 휴식 구문을 안내합니다. 객체 확산이 객체 복제, 병합, 속성 재정의 등과 같은 레시피를 구현하는 방법을 포함합니다.


다음은 열거 가능한 속성에 대한 간단한 요약과 상속 된 속성과 자신을 구별하는 방법입니다. 이것들은 물체 확산과 휴식이 어떻게 작동하는지 이해하기 위해 필요한 기본 사항입니다.


1. 번호 및 자체 속성 


JavaScript의 객체는 키와 값 사이의 연결입니다.


키 유형은 일반적으로 문자열 또는 기호입니다. 값은 기본 유형 (문자열, 부울, 숫자, 정의되지 않은 또는 널), 오브젝트 또는 함수일 수 있습니다.


다음 예제는 객체 리터럴 (일명 객체 이니셜 라이저)을 사용하여 객체를 만듭니다.

const person = {
  name: 'Dave',
  surname: 'Bowman'
};

person 객체는 사람의 이름과 성을 설명합니다.


1.1 열거 가능한 속성 


속성에는 값, 쓰기 가능, 열거 가능 및 구성 가능 상태를 설명하는 몇 가지 속성이 있습니다. 자세한 내용은 JavaScript의 객체 속성을 참조하십시오.


열거 가능한 속성은 객체의 속성이 열거 될 때 속성에 액세스 할 수 있는지 여부를 나타내는 부울입니다.


Object.keys() (자체 및 열거 가능한 속성에 액세스), for..in 문 (모든 열거 가능한 속성에 액세스) 등을 사용하여 객체 속성을 열거 할 수 있습니다.


객체 리터럴 {prop1 : 'val1', prop2 : 'val2'}에서 명시 적으로 선언 된 속성은 열거 할 수 있습니다. person 객체에 열거 가능한 속성이 무엇인지 살펴 보겠습니다.

const keys = Object.keys(person);
console.log(keys); // => ['name', 'surname']

.name 및 .surname은 person 객체의 열거 가능한 속성입니다.


여기 흥미로운 부분이 있습니다. 소스 열거 가능 특성에서 오브젝트 스프레드 사본 :

console.log({ ...person };// => { name: 'Dave', surname: 'Bowman' }

이제 사람 개체에 열거 할 수 없는 속성 .age를 만들어 봅시다. 그런 다음 스프레드가 어떻게 작동하는지 확인하십시오.

Object.defineProperty(person, 'age', {
  enumerable: false, // Make the property non-enumerable  value: 25
});
console.log(person['age']); // => 25

const clone = {
  ...person
};
console.log(clone); // => { name: 'Dave', surname: 'Bowman' }


.name 및 .surname 열거 가능 특성은 소스 오브젝트 person에서 clone으로 복사 됩니다. 그러나 열거 할 수 없는 .age는 무시됩니다.


1.2 자신의 속성 


JavaScript는 프로토 타입 상속을 포함합니다. 따라서 객체 속성은 소유하거나 상속 받을 수 있습니다.


객체 리터럴에 명시적으로 선언 된 속성은 자신의 속성입니다. 그러나 객체가 프로토 타입에서 받는 속성은 상속됩니다.


personB 오브젝트를 작성하고 프로토 타입을 person으로 설정하십시오.


const personB = Object.create(person, {  
  profession: {
    value: 'Astronaut',
    enumerable: true
  }
});

console.log(personB.hasOwnProperty('profession')); // => true
console.log(personB.hasOwnProperty('name'));       // => false
console.log(personB.hasOwnProperty('surname'));    // => false

personB 객체에는 고유 한 속성 .profession이 있으며 프로토 타입 사람의 .name 및 .surname 속성을 상속합니다.


상속 된 속성을 무시하고 소스 자체 속성에서 객체 확산 사본 :

const cloneB = {
  ...personB
};
console.log(cloneB); // => { profession: 'Astronaut' }


오브젝트 spread ... personB는 소스 오브젝트 personB의 .profession 자신의 특성에서만 복사합니다. 상속 된 .name 및 .surname은 무시됩니다.


객체 스프레드 구문은 소스 객체 자체의 열거 가능 속성에서 복사됩니다. Object.keys() 함수에서 반환 한 것과 동일합니다. 


2. 객체 확산 속성 

객체 리터럴 내의 객체 확산 구문은 소스 객체 자체 및 열거 가능한 속성에서 추출하여 대상 객체에 복사합니다.

const targetObject = {
  ...sourceObject,
  property: 'Value'
};


참고로, 여러 가지 방법으로 객체 확산 구문은 Object.assign ()과 같습니다. 위의 코드는 다음과 같이 구현할 수도 있습니다.

const targetObject = Object.assign(
  { }, 
  sourceObject,
  { property: 'Value' }
);

객체 리터럴은 일반 속성 선언과 조합하여 여러 객체 스프레드를 가질 수 있습니다.

const targetObject = {
  ...sourceObject1,
  property1: 'Value 1',
  ...sourceObject2,
  ...sourceObject3,
  property2: 'Value 2'
};

2.1 물체 보급 규칙 : 후자의 재산이 이긴다 


여러 객체가 분산 되고 일부 속성에 동일한 키가 있는 경우 최종 최종 값 세트는 어떻게 계산됩니까? 규칙은 간단합니다.


후기 스프레드 속성은 동일한 키를 가진 이전 속성을 덮어 씁니다. 


몇 가지 예를 계속합시다. 다음 객체 리터럴은 고양이를 인스턴스화 합니다.

const cat = {
  sound: 'meow',
  legs: 4
};

프랑켄슈타인 박사와 함께 이 고양이를 개로 바꿔 봅시다. .sound 속성 값에 주의 하십시오.

const dog = {
  ...cat,
  ...{
    sound: 'woof' // <----- Overwrites cat.sound  }
};
console.log(dog); // => { sound: 'woof', legs: 4 }

후자의 값인 'woof'는 이전 값인 'meow'(고양이 소스 개체에서 온 것)를 덮어 씁니다. 이는 후자의 속성이 동일한 키로 가장 빠른 속성을 덮어 쓰는 규칙과 일치합니다.


객체 이니셜 라이저의 일반 속성에도 동일한 규칙이 적용됩니다.

const anotherDog = {
  ...cat,
  sound: 'woof' // <---- Overwrites cat.sound};
console.log(anotherDog); // => { sound: 'woof', legs: 4 }


일반적인 속성 사운드 : 'woof'가 최신이기 때문에 이깁니다.


이제 스프레드 객체를 상대 위치로 바꾸면 결과가 다릅니다.

const stillCat = {
  ...{
    sound: 'woof' // <---- Is overwritten by cat.sound  },
  ...cat
};
console.log(stillCat); // => { sound: 'meow', legs: 4 }


고양이는 고양이로 남아 있습니다. 첫 번째 소스 객체는 값이 'woof'인 .sound 속성을 제공하지만 cat spread 속성의 후자 'meow'값으로 덮어 씁니다.


개체 스프레드와 규칙적인 속성의 상대적 위치가 중요합니다. 확산 구문의 이러한 효과는 객체 복제, 객체 병합, 기본값으로 채우기와 같은 레시피를 구현할 수 있습니다.


이 레시피에 대해 자세히 알아 보겠습니다.


2.2 객체 복제 


스프레드 구문을 사용하여 객체를 집어 넣는 것은 짧고 표현력이 있습니다. 다음 예제는 bird 객체의 복제본을 만듭니다.

const bird = {
  type: 'pigeon',
  color: 'white'
};

const birdClone = {
  ...bird
};

console.log(birdClone); // => { type: 'pigeon', color: 'white' }
console.log(bird === birdClone); // => false


문자 그대로의 새는 대상 birdClone에 고유하고 열거 가능한 새 속성을 복사합니다. 결과적으로 birdClone은 새의 복제본입니다.


복제는 첫눈에 단순 해 보이지만 알아 두어야 할 몇 가지 뉘앙스가 있습니다.


얕은 사본 


개체 확산은 개체의 얕은 복사본을 수행합니다. 개체 자체 만 복제되고 중첩 된 인스턴스는 복제되지 않습니다.


노트북에는 중첩 된 개체 laptop.screen이 있습니다. 랩탑을 복제하고 중첩 된 객체에 어떤 영향을 미치는지 살펴 보겠습니다.

const laptop = {
  name: 'MacBook Pro',
  screen: {
    size: 17,
    isRetina: true
  }
};
const laptopClone = {
  ...laptop
};

console.log(laptop === laptopClone);               // => false
console.log(laptop.screen === laptopClone.screen); // => true


첫 번째 비교 랩탑 === laptopClone은 false입니다. 주 개체가 올바르게 복제되었습니다.


그러나 laptop.screen === laptopClone.screen은 true로 평가됩니다. laptop.screen과 laptopClone.screen은 복사 되지 않은 동일한 중첩 객체를 참조합니다.


좋은 소식은 어떤 수준에서든 속성을 전파 할 수 있다는 것입니다. 적은 노력으로도 중첩 된 객체를 복제하십시오.

const laptopDeepClone = {
  ...laptop,
  screen: {
     ...laptop.screen
  }
};

console.log(laptop === laptopDeepClone);               // => false
console.log(laptop.screen === laptopDeepClone.screen); // => false

추가 스프레드 ... laptop.screen을 사용하면 중첩 된 객체도 복제됩니다. Nice, now laptopDeepClone은 랩탑 객체의 전체 복제입니다.


프로토 타입이 손실되었습니다 


아래의 코드 스 니펫은 Game 클래스를 선언하고 이 클래스 운명의 인스턴스를 만듭니다.

class Game {
  constructor(name) {
    this.name = name;
  }

  getMessage() {
    return `I like ${this.name}!`;
  }
}

const doom = new Game('Doom');
console.log(doom instanceof Game); // => true
console.log(doom.name);            // => "Doom"
console.log(doom.getMessage());    // => "I like Doom!"

이제 생성자 호출에서 생성 된 운명 인스턴스를 복제하겠습니다. 이것은 놀라움으로 이어질 수 있습니다.

const doomClone = {
  ...doom
};

console.log(doomClone instanceof Game); // => false
console.log(doomClone.name);            // => "Doom"
console.log(doomClone.getMessage());
// TypeError: doomClone.getMessage is not a function

... doom은 자체 속성 .name을 doomClone에 복사합니다. 그리고 더 이상 아무것도 없습니다.


doomClone은 프로토 타입이 Object.prototype이지만 일반 Game.prototype이 아닌 일반 JavaScript 객체입니다. 오브젝트 스프레드는 소스 오브젝트의 프로토 타입을 보존하지 않습니다.


따라서 doomClone은 getMessage() 메소드를 상속하지 않으므로 doomClone.getMessage()를 호출하면 TypeError가 발생합니다.


누락 된 프로토 타입을 수정하려면 __proto__를 사용하여 수동으로 표시하십시오.

const doomFullClone = {
  ...doom,
  __proto__: Game.prototype};

console.log(doomFullClone instanceof Game); // => true
console.log(doomFullClone.name);            // => "Doom"
console.log(doomFullClone.getMessage());    // => "I like Doom!"


객체 리터럴 내부의 __proto__는 doomFullClone에 필요한 프로토 타입 Game.prototype이 있는지 확인합니다.


집에서 시도하지 마십시오 : __proto__는 더 이상 사용되지 않습니다. 데모 용으로 만 사용하고 있습니다.


프로토 타입을 유지하지 않기 때문에 생성자 호출로 생성 된 인스턴스에서 오브젝트 스프레드가 지연됩니다. 의도는 자신의 열거 가능한 속성을 얕게 퍼뜨리는 것이므로 프로토 타입을 무시하는 접근법은 합리적입니다.


참고로 Object.assign()을 사용하여 운명을 복제하는 더 합리적인 방법이 있습니다.

const doomFullClone = Object.assign(new Game(), doom);

console.log(doomFullClone instanceof Game); // => true
console.log(doomFullClone.name);            // => "Doom"
console.log(doomFullClone.getMessage());    // => "I like Doom!"

좋습니다. 프로토 타입으로 충분합니다. 약속합니다.


2.3 불변 개체 업데이트 


동일한 개체가 응용 프로그램의 여러 곳에서 공유되면 직접 수정하면 예기치 않은 부작용이 발생할 수 있습니다. 그러한 수정을 추적하는 것은 지루한 작업입니다.


더 나은 방법은 작업을 변경할 수 없게 만드는 것입니다. 불변성은 제어 객체의 수정을 더 잘 유지하고 순수한 함수 작성을 선호합니다. 복잡한 시나리오에서도 데이터가 한 방향으로 흐르기 때문에 개체 업데이트의 원인과 이유를 더 쉽게 확인할 수 있습니다.


객체 확산은 불변의 방식으로 객체를 수정하는 데 편리합니다. 책의 판을 설명하는 물건이 있다고 가정 해보십시오.


const book = {
  name: 'JavaScript: The Definitive Guide',
  author: 'David Flanagan',
  edition: 5,
  year: 2008
};


그런 다음 새로운 6 판이 나옵니다. 개체 확산 :이 시나리오를 변경할 수 없는 방식으로 프로그래밍 해 보겠습니다.

const newerBook = {
  ...book,
  edition: 6,  // <----- Overwrites book.edition  year: 2011   // <----- Overwrites book.year};

console.log(newerBook);
/*
{
  name: 'JavaScript: The Definitive Guide',
  author: 'David Flanagan',
  edition: 6,
  year: 2011
}
*/


... 리터럴 안의 책은 책 객체의 속성을 확산 시킵니다. 수동으로 열거 된 특성 에디션 : 6 및 연도 : 2011은 업데이트 된 특성 값을 설정합니다.


중요한 특성 값은 마지막 특성 값이 동일한 키로 이전 값을 겹쳐 쓰는 스프레드 규칙과 일치하도록 끝에 지정됩니다.


newerBook은 업데이트 된 속성을 가진 새로운 객체입니다. 그 동안 원본 책은 그대로 남아 있습니다. 불변성이 만족됩니다.


2.4 객체 병합 


여러 객체의 속성을 분산 시킬 수 있으므로 병합이 간단합니다.


합성물을 만들기 위해 3 개의 객체를 병합합시다.

const part1 = {
  color: 'white'
};
const part2 = {
  model: 'Honda'
};
const part3 = {
  year: 2005
};

const car = {
  ...part1,
  ...part2,
  ...part3
};
console.log(car); // { color: 'white', model: 'Honda', year: 2005 }


car 객체는 part1, part2 및 part3의 세 객체를 병합하여 생성됩니다.


후자의 속성 승 규칙을 잊지 마십시오. 동일한 키를 가진 여러 객체를 병합하는 데 대한 추론을 제공합니다.


앞의 예를 조금 바꿔 봅시다. 이제 part1과 part3에는 새로운 속성 .configuration이 있습니다.

const part1 = {
  color: 'white',
  configuration: 'sedan'
};
const part2 = {
  model: 'Honda'
};
const part3 = {
  year: 2005,
  configuration: 'hatchback'
};

const car = {
  ...part1,
  ...part2,
  ...part3 // <--- part3.configuration overwrites part1.configuration};
console.log(car); 
/*
{ 
  color: 'white', 
  model: 'Honda', 
  year: 2005,
  configuration: 'hatchback'  <--- part3.configuration
}
*/


첫 번째 오브젝트 스프레드 ... part1은 .configuration의 값을 'sedan'으로 설정합니다. 그럼에도 불구하고 후자의 객체 스프레드 ... part3은 이전 .configuration 값을 덮어 써서 마침내 '해치백'합니다.


2.5 기본값으로 객체 채우기 


객체는 런타임에 다른 속성 집합을 가질 수 있습니다. 일부 속성이 설정되었거나 다른 속성이 누락되었을 수 있습니다.


이러한 시나리오는 구성 오브젝트의 경우에 발생할 수 있습니다. 사용자는 구성의 중요한 속성 만 지정할 수 있지만 지정되지 않은 속성은 기본값에서 가져옵니다.


주어진 너비에서 str을 여러 줄로 나누는 multiline (str, config) 함수를 구현해 봅시다.


구성 오브젝트는 다음 선택적 매개 변수를 승인합니다.

  • width : 구분할 문자 수입니다. 기본값은 10입니다.
  • newLine : 줄 끝에 추가 할 문자열입니다. 기본적으로 \ n;
  • indent : 줄을 의도하는 문자열. 기본값은 빈 문자열 ''입니다.

multiline() 작동 방식에 대한 몇 가지 예 :

multiline('Hello World!');
// => 'Hello Worl\nd!'

multiline('Hello World!', { width: 6 });
// => 'Hello \nWorld!'

multiline('Hello World!', { width: 6, newLine: '*' });
// => 'Hello *World!'

multiline('Hello World!', { width: 6, newLine: '*', indent: '_' });
// => '_Hello *_World!'


config 인수는 다른 속성 집합을 허용합니다. 1, 2 또는 3 개의 속성을 나타내거나 전혀 속성을 표시 할 수 없습니다.


객체 확산을 사용하면 구성 객체를 기본값으로 채우기가 매우 간단합니다. 객체 리터럴 내부에서 먼저 기본 객체를 펼친 다음 구성 객체를 펼칩니다.

function multiline(str, config = {}) {
  const defaultConfig = {
    width: 10,
    newLine: '\n',
    indent: ''
  };
  const safeConfig = {
    ...defaultConfig,
    ...config
  };
  let result = '';
  // Implementation of multiline() using
  // safeConfig.width, safeConfig.newLine, safeConfig.indent
  // ...
  return result;
}

safeConfig 객체 리터럴을 살펴 보겠습니다.


Object spread ... defaultConfig는 기본값에서 속성을 추출합니다. 그런 다음 ... config는 이전 기본값을 사용자 정의 특성 값으로 겹쳐 씁니다.


그 결과 safeConfig는 multiline() 메인 코드가 사용할 수 있는 모든 속성 세트를 갖습니다. 일부 속성을 놓칠 수 있는 입력 구성에 관계없이 safeConfig에 필요한 값이 있다고 확신합니다.


오브젝트 스프레드의 기본값 구현은 직관적이며 훌륭합니다.


2.6 “더 깊이 가야한다” 


객체 확산에 대한 멋진 점은 중첩 된 객체에 사용할 수 있다는 것입니다. 큰 객체를 업데이트 할 때 가독성이 뛰어나 Object.assign() 대안보다 권장됩니다.


다음 상자 객체는 항목 상자를 정의합니다.

const box = {
  color: 'red',
  size: {
    width: 200, 
    height: 100 
  },
  items: ['pencil', 'notebook']
};


box.size는 상자의 크기를 설명하고 box.items는 상자에 포함 된 항목을 열거합니다.


box.size.height를 늘려 상자를 높이려면 중첩 된 객체에 속성을 펼치십시오.

const biggerBox = {
  ...box,
  size: {
    ...box.size,    height: 200
  }
};
console.log(biggerBox);
/*
{
  color: 'red',
  size: {
    width: 200, 
    height: 200 <----- Updated value
  },
  items: ['pencil', 'notebook']
}
*/


... box는 biggerBox가 상자 소스에서 속성을 수신하도록 합니다.


중첩 된 객체 box.size의 높이를 업데이트하려면 추가 객체 리터럴 {... box.size, height : 200}이 필요합니다. 이 리터럴은 box.size의 속성을 새 객체로 확장하고 높이를 200으로 업데이트합니다.


나는 하나의 진술을 통해 여러 업데이트를 수행 할 수 있는 가능성을 좋아합니다.


색상을 검은 색으로 변경하고 너비를 400으로 늘리고 새 항목 눈금자를 추가하면 (확산 배열 사용)? 쉽습니다.

const blackBox = {
  ...box,
  color: 'black',
  size: {
    ...box.size,
    width: 400
  },
  items: [
    ...box.items,
    'ruler'
  ]
};
console.log(blackBox);
/*
{
  color: 'black', <----- Updated value
  size: {
    width: 400, <----- Updated value
    height: 100 
  },
  items: ['pencil', 'notebook', 'ruler'] <----- A new item ruler
}
*/

2.7 비정의, 널 및 프리미티브 확산 


속성을 undefined, null 또는 primitive 값으로 펼칠 때 속성이 추출되지 않으며 오류가 발생하지 않습니다. 결과는 일반 빈 개체입니다.

const nothing = undefined;
const missingObject = null;
const two = 2;

console.log({ ...nothing });       // => { }
console.log({ ...missingObject }); // => { }
console.log({ ...two });           // => { }

Object spread는 none, missingObject 및 two에서 속성을 추출하지 않습니다.


물론 프리미티브 값에 객체 확산을 사용할 이유가 없습니다.


3. 객체 받침대 속성 


소멸 할당을 사용하여 객체의 속성을 변수로 추출한 후 나머지 속성을 나머지 객체로 수집 할 수 있습니다.

이것은 객체 받침대 속성이 훌륭하게 수행하는 것입니다.

const style = {
  width: 300,
  marginLeft: 10,
  marginRight: 30
};

const { width, ...margin } = style;

console.log(width);  // => 300
console.log(margin); // => { marginLeft: 10, marginRight: 30 }


소멸 할당은 새 변수 너비를 정의하고 해당 값을 style.width로 설정합니다. 소멸 할당 내에서 객체 휴식 ... 여백은 나머지 속성 marginLeft 및 marginRight를 객체 여백에 수집합니다.


오브젝트 레스트는 자체적이고 열거 가능한 특성 만 수집합니다.


오브젝트 레스트는 구조 지정 지정의 마지막 요소 여야 합니다. 따라서 코드 const {... margin, width} = style이 유효하지 않으며 SyntaxError : 트리거 요소가 마지막 요소 여야 합니다.


4. 결론 


객체 확산에는 다음 사항을 기억해야 합니다.

  • 소스 객체에서 자체적이고 열거 가능한 속성을 추출합니다.
  • 후반 스프레드 특성은 동일한 키로 이전 특성을 겹쳐 씁니다.

동시에 객체 확산은 짧고 표현력이 뛰어나고 중첩 된 객체에서 잘 작동하면서 업데이트의 불변성을 유지합니다. 기본 속성으로 객체 복제, 병합 및 채우기를 쉽게 구현할 수 있습니다.


구조적 할당 후 나머지 속성을 수집하는 것은 객체 휴식 구문에 의해 수행됩니다.


실제로 객체 휴식 및 분산 속성은 JavaScript에 크게 추가되었습니다.



  • 트위터로 보내기
  • 페이스북으로 보내기
  • 구글플러스로 보내기
  • 카카오톡으로 보내기

페이지 정보

조회 19회 ]  작성일19-08-22 21:05

웹학교