Notice
Recent Posts
Recent Comments
Link
«   2024/12   »
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30 31
Archives
Today
Total
관리 메뉴

과거의 내가 미래의 나에게

딥다이브 :: 객체 리터럴 & 원시 값과 객체의 비교 본문

js 이론

딥다이브 :: 객체 리터럴 & 원시 값과 객체의 비교

양바삭 2023. 1. 13. 15:08
「딥다이브 자바스크립트」 정리 - 객체 리터럴 & 원시 값과 객체의 비교

 

※ 아래 내용은 책을 통해 학습한 것을 개인적으로 정리한 것으로 내용이 다소 부정확 할 수 있습니다.

 


1. 객체 리터럴

객체란

1. 객체 특징

  • 원시 값을 제외한 나머지를 모두 객체
  • 객체는 원시타입과 달리 변경 가능한 값
  • 객체는 0개 이상의 속성(프로퍼티)로 구성된 집합
  • 객체의 속성은 키와 값으로 구성
  • 객체는 속성(객체의 상태를 나타내는 값)과 메서드(속성을 참조하고 조작할 수 있는 동작)로 구성된 집합체

 

2. 객체 생성 방법

클래스 기반 객체지향 언어(ex:자바)는 객체를 생성하려면 클래스를 정의하고 new 연산자와 함께 생성자를 호출하여 인스턴스를 생성한다.

하지만 js는 프로토타입 기반 객체지향 언어로, 위와 달리 다양한 객체 생성 방법을 지원한다.

  1. 객체 리터럴
  2. Object 생성자 함수
  3. 생성자 함수
  4. Object.create 메서드
  5. 클래스

객체 리터럴

객체 리터럴은 객체를 생성하기 위한 표기법이다. 리터럴은 사람이 이해할 수 있는 문자,기호를 사용해 값을 생성하는 표기법으로 객체 리터럴은 문자열을 만들 듯 객체를 아주 간단하게 만들 수 있고 추가 수정도 용이하다. 그렇기에 이는 js의 유연함과 강력함을 대표하는 방식이라 불린다. 참고로 객체 리터럴 이외의 객체 생성 방식은 모두 함수를 사용해 객체를 생성한다.

 

객체 안에 들어가는 것들을 속성이라 부른다. 속성에는 속성 키와 속성 값으로 나뉜다.

1. 속성 키

빈 문자열 포함한 모든 문자열(숫자를 넣어도 문자열로 암묵 변환된다) 또는 심벌 값.

식별자 네이밍 규칙을 준수하는 이름인 경우에는 따옴표를 생략할 수 있다.

또한 속성 키를 대괄호를 통해 동적으로 생성할 수도 있다.

 

2. 속성 값

JS에서 사용할 수 있는 모든 값은 속성 값으로 사용 가능하다.

함수도 값으로 취급하기에 속성 값으로 사용할 수 있다. 함수를 속성 값으로 사용하면 일반 함수와 구분하기 위해 이를 메서드라고 부른다.

 

3. 속성 접근법

마침표 표기법과 대괄호 표기법이 있다. 속성 키가 식별자 네이밍 규칙을 준수하지 않은 이름을 사용했다면 반드시 대괄호 표기법으로 접근해야한다.

대괄호로 접근하는 경우 속성 키는 반드시 따옴표로 감싼 문자열이어야 한다. 만약 따옴표를 쓰지 않으면 JS는 이를 식별자로 평가하기 때문에 ReferenceError가 발생한다.

객체에 존재하지 않은 속성에 접근하면 에러가 아닌 undefined를 반환다.

let persone = {
	'last-name': 'Kim',
    firstName: 'Yo'
}
person['last-name'] // Kim   // 대괄호 접근법
person.firstName // Yo  // 마침표 접근법

4. 속성 삭제

delete person.age 하면 age의 속성이 삭제된다. 존재하지 않은 속성을 삭제해도 에러는 나지 않는다.

 

5. ES6 추가된 확장 기능

  • 속성 축약 표현

속성 값을 변수로 사용하는 경우, 변수 이름과 속성 키가 동일한 이름이라면 속성키를 생략 할 수 있다. 

let x = 1
const obj = {x} 
console.log(obj) //{x:1}
  • 객체 내부에서 computed 속성 키 생성 가능

ES6 이전에는 객체를 생성한 후에 외부에서 대괄호 표기법으로 computed 값을 넣어줬지만 ES6 이후로는 내부에 넣어줘도 가능하다

let obj = {}
obj[foo + ++i] = i

//ES6
let obj = {
	[`${foo}++i`] = i
}
  • 메서드 축약표현
let obj = {
	test: function() {
		console.log(3)
	}
}

let obj = {
	test() {
		console.log(3)
	}
}

 


 

2. 원시 값과 객체의 비교

원시 값

원시값은 변경 불가능한 값이기에 확보된 메모리에 한 번 저장되면 이는 읽기만 가능하고 변경은 할 수 없다. 그렇기에 만약 변수에 재할당을 한다면 기존 메모리가 변경되는 것이 아닌 다른 메모리 공간에 새로운 값이 저장되고 변수는 그 새로운 메모리 공간을 가리키는 것으로 변경되는 것이다.

let str = 'string'
str[0] = 'X'
console.log(str) // 'string'
// 문자열은 변경 불가능한 값이므로 일부 문자를 변경해도 반영되지 않는다.
let score = 80
let copy = score
console.log(score) // 80
console.log(copy) // 80 
// score 의 값이 복사되어 copy로 갔음
// 변수에 변수를 할당했지만 둘은 결국 다 다른 메모리공간에 저장되어있다.

score = 100
console.log(score) // 100
console.log(copy) // 80 
// score에 100을 재할당함으로 scroe라는 변수는 100이라는 새로운 메모리 공간을 가리키게 되었고
// score의 그 이전의 80은 어딘가 잘 살고있다가 js엔진에 의해 처리될 것이다

// copy는 score와 전혀 다른곳에서 저장되어있었으니 변경에 영향 받지 않는다. 그냥 딴 집 살고있음

그러나 ECMA스크립트 사양에 변수를 통해 메모리를 어떻게 관리하는지 명확히 정의되어 있지 않기 때문에 실제 내부 동작 방식은 다를수도 있다.

위에서는 변수에 변수를 할당하면 그 값이 복사되어 새로운 메모리에 할당하는 것처럼 표현했지만 파이선 같은경우는 변수에 변수를 할당 시 하나의 메모리 공간을 두 변수가 같이 참조하고있다가 하나의 변수가 재할당되면 다른 메모리 공간으로 넘어가는 식으로 동작한다.

그러나 뭐가 어찌되었든 중요한 것은 두 변수 중 어느 하나의 변수 값에 재할당 하는 시점이면 서로 다른 메모리 공간에 저장된 별개의 값을 가리키게 되어 전혀 연관없는 사이가 되어버리는 것이다.

 

객체 값

객체 리터럴을 통해 객체를 생성하면 객체가 메모리에 생성된다. 그리고 변수를 선언하여 생성된 객체를 할당시키면, 변수는 그 객체 값 자체를 가리키는 것이 아닌, 객체가 저장된 메모리 공간의 주소를 가지고 있는 메모리를 가리키게 된다. 실제 객체가 저장된 공간이 아닌 실제 값의 주소를 저장해놓은 공간을 가리키는 것이다.

만약 객체를 할당한 변수를 다른 변수에 그대로 할당하면 다른 변수는 객체의 주소값을 저장한게 되는 것이다.

 

객체는 변경 가능한 값으로 메모리에 저장된 객체를 직접 수정할 수 있다. 객체가 변경 가능한 값인 이유는 객체의 크기는 원시값처럼 일정치 않기 때문이다. 그렇기에 원시값처럼 객체 자체를 복사해 새로운 메모리에 저장하는 것보다는 객체 자체를 변경 가능하게 설계하여 메모리 사용의 성능을 높인 것이다.

원시 값과 달리 변수의 재할당 없이 객체를 변경 할 수 있기에 변수가 가리키고 있는 메모리는 여전히 동일하다. 이러한 설계로 인해 여러 개의 식별자가 하나의 객체를 공유할 수 있게된다. 이렇게 되면 객체가 변경될 시 그 객체 주소를 가리키고 있는 모든 변수가 동일하게 영향을 받게 된다.

let person = {
	name: 'lee'
}
let copy = person
//person은 객체가 있는 메모리 주소값을 저장하고있고
//copy는 객체가 있는 메모리 주소값을 받았기에 동일한 객체를 바라보고있게 된다.

 

얕은 복사, 깊은 복사

얕은 복사로 하든 깊은 복사로 하든 객체를 새로 만들게 되면 원본과는 다른 객체이다.

그러나 얕은 복사의 경우 중첩되어 있는 객체는 참조 값을 복사하고, 깊은 복사의 경우 중첩되어 있는 객체는 객체값 자체를 복사해서 모두 원시값처럼만든다.

let test = {
	outer : {
		inner: '5'
	}
}
let copy = { ...test } // 객체 리터럴을 선언함으로써 새로운 객체를 만든 거임. 
// 하지만 이 경우, outer의 값이 아닌 참조값을 복사하게 됨으로, 
// test.outer === copy.outer이 된다. 
// 그러므로 속알까지 다르게 하고 싶다면 깊복으로 해야한다.

 

Comments