-
[Javascript]원시 값과 객체의 비교사부작사부작/Javascript 2022. 2. 8. 20:36
#모던자바스크립트_딥다이브 스터디를 진행하면서 정리한 내용입니다.
원시 타입의 값
원시 값은 변경 불가능 한 값이다. 한번 생성된 원시 값은 읽기전용으로 변경할 수 없다. 원시 값 자체가 변경 불가능하다는 것이지, 변수에 할당된 값을 변경할 수 없다는 이야기가 아니다. 따라서 원시타입의 값을 가지고 있는 변수의 값을 바꿀 때 변수에 할당되 있는 값 자체를 다른 것으로 변경하는 것이 아니라 변수에 새로운 값을 할당하는 방식으로 변경한다.
원시 타입의 값 변경 과정  즉 원시 타입의 값은 불변성을 가지며 재할당 외에 변수 값을 변경할 수 있는 방법이 없다.
1) 문자열과 불변성
문자열은 유사 배열 객체이다. 즉, 배열 처럼 인덱스로 문자열의 일부 값에 접근할 수 있고 객체와 유사하게 length 프로퍼티도 갖는다. 심지어 유사 배열이라 반복문으로 순회할 수도 있다.
var str = 'string'; // 문자열은 유사 배열이므로 배열과 유사하게 인덱스를 사용해 각 문자에 접근할 수 있다. console.log(str[0]); // s // 원시 값인 문자열이 객체처럼 동작한다. console.log(str.length); // 6 console.log(str.toUpperCase()); // STRING
하지만 객체 처럼 이미 생성된 문자열의 일부 문자를 변경해도 반영되지 않는다. 이것이 원시 타입의 값인 문자열이 변결 불가능한 값이란 뜻이며, 문자열을 바꾸는 것은 기존 문자열 값을 변경하는 것이 아니라 기존 변수에 새로운 문자열을 재할당 한다는 것이다.
var str = 'string'; str[0] = 'S'; console.log(str); // string
2) 값에 의한 전달(pass by value)
값을 할당받은 변수가 새로운 변수에 할당되어 새로운 변수에 값을 전달할 경우, 원시타입의은 값에 의한 전달 방식을 취한다.
var score = 80; var copy = score; console.log(score); // 80 console.log(copy); // 80 score = 100; console.log(score); // 100 console.log(copy); // ?
값에 의한 전달이라는 것은 두 가지 방식으로 해석되는데, 첫번째로 기존 변수의 값이 다른 변수에 할당될 때, 기존 변수가 가지고 있는 값 자체를 전달(복사)해서 전달 받는 변수에 할당하는 것이지, 전달 받은 변수가 기존 변수의 값을 참조하고 있는 것은 아니며, 기존 변수와 전달 받은 변수는 서로 전혀 다른 메모리를 보고 있다는 뜻이다.
var score = 80; // copy 변수에는 score 변수의 값 80이 복사되어 할당된다. var copy = score; console.log(score, copy); // 80 80 console.log(score === copy); // true // score 변수와 copy 변수의 값은 다른 메모리 공간에 저장된 별개의 값이다. // 따라서 score 변수의 값을 변경해도 copy 변수의 값에는 어떠한 영향도 주지 않는다. score = 100; console.log(score, copy); // 100 80 console.log(score === copy); // false
두번째는, 기존 변수가 다른 변수에 할당 될 때는 기존 변수의 값을 참조하고 있었으나, 둘 중에 어느 하나의 변수를 재할당 하는 순간에 새로운 메모리에 할당하는 방식일 수도 있다.
결국 두가지 방식 모두, 기존 변수와 전달받은 변수의 사이엔 어떠한 커넥션도 없으며, 서로에게 어떠한 영향도 끼치지 못한다.
하지만, 값에 의한 전달 이라는 용어는 자바스크립트에 명시되 있는 용어가 아니고, 쉬운 이해를 위해 사용하고 있으며, 실제로 자바스크립트는 변수에 값 자체를 참조하고 있는 것이 아니라, 변수의 메모리 공간의 주소를 전달하는 것이다.
물론, 메모리주소를 전달 받은 뒤 그 메모리 주소를 가서 값을 복사해서 새로운 메모리에 할당하거나(방식1), 새로운 값으로 재할당 하기 전까진 두 변수가 같은 메모리 주소를 가지고 있는 것 이다(방식2)
객체 타입의 값
객체는 프로퍼티 개수가 정해져 있지 않으며, 동적으로 추가되고 삭제할 수 있다. 따라서 객체는 원시 타입 값처럼 확보해야 할 메모리 공간의 크기를 미리 정해 둘 수 없다.
1) 변경 가능 한 값
객체를 할당하는 경우, 객체를 할당받는 변수의 메모리 주소에 객체가 있는 메모리 주소를 할당한다. 객체가 있는 메모리 주소를 참조 값이라고 한다. 즉, 객체를 할당한 변수를 참조하면 변수 메모리에 저장되어 있는 참조 값을 통해 실제 객체에 접근하는 것이다.
따라서 객체의 값을 변경할 때, 변경된 객체를 변수에 재할당 하는 것이 아니라, 변수의 메모리 주소가 참조하고 있는 객체의 메모리 주소로 가서 객체를 변경하는 방식으로 객체의 값을 변경할 수 있다.
var person = { name: 'Lee' }; // 프로퍼티 값 갱신 person.name = 'Kim'; // 프로퍼티 동적 생성 person.address = 'Seoul'; console.log(person); // {name: "Kim", address: "Seoul"}
2) 참조에 의한 전달(pass by reference)
객체의 크기가 매우 클 경우, 복사해서 다른 변수에 할당하는 것은 매우 비효율적인 일이다. 그래서 객체를 가진 변수를 다른 변수에 할당하는 경우, 메모리를 효율적으로 사용하기 위해 객체가 있는 메모리 주소(참조 값)을 다른 변수에 할당하는 방식으로 복사한다. 즉, 하나의 참조 값을 여러개의 식별자가 공유할 수 있다. 이것을 참조에 의한 전달이라고 한다.
var person = { name: 'Lee' }; var copy = person;
여러개의 식별자가 하나의 객체를 공유한다는 것은 어느 한 식별자를 통해 객체를 변경할 경우 다른 식별자들도 같은 객체를 참조하고 있으므로 서로 영향을 받게 된다.
var person = { name: 'Lee' }; // 참조값을 복사. copy와 person은 동일한 참조값을 갖는다. var copy = person; // copy와 person은 동일한 객체를 참조한다. console.log(copy === person); // true // copy를 통해 객체를 변경한다. copy.name = 'Kim'; // person을 통해 객체를 변경한다. person.address = 'Seoul'; // copy와 person은 동일한 객체를 가리킨다. // 따라서 어느 한쪽에서 객체를 변경하면 서로 영향을 주고 받는다. console.log(person); // {name: "Kim", address: "Seoul"} console.log(copy); // {name: "Kim", address: "Seoul"}
원시타입의 값에 의한 전달과 객체의 참조에 의한 전달 모두 변수가 기억하는 메모리 공간에 있는 값을 복사한다는 면에서는 동일하다. 다만 전달한 값이 원시 값 그 자체인지, 객체의 참조값인지의 차이가 있다. 결국 값을 복사한다는 면에서 자바스크립트는 값에 의한 전달만 있다고도 볼 수 있다. 또, 자바스크립트의 동작 방식이 공식적으로 정의되지 않았으므로, 공유에 의한 전달이라고 표현하는 경우도 있다.
3) 객체의 비교
객체를 할당한 변수를 비교하면 참조 값을 비교하고, 원시 값을 할당한 객체의 프로퍼티를 비교하면 값 자체를 비교한다.
var person1 = { name: 'Lee' }; var person2 = { name: 'Lee' }; console.log(person1 === person2); // false console.log(person1.name === person2.name); // true
4) 얉은 복사와 깊은 복사
객체를 프로퍼티 값으로 갖는 객체의 경우 얕은 복사는 한 단계까지만 복사하는 것을 말하고, 깊은 복사는 객체 안의 중첩된 객체까지 모두 복사하는 것을 말한다. 객체의 일반적인 복사는 얕은 복사인 것이다.
const o = { x: { y: 1 } }; // 얕은 복사 const c1 = { ...o }; // 35장 "스프레드 문법" 참고 console.log(c1 === o); // false console.log(c1.x === o.x); // true // lodash의 cloneDeep을 사용한 깊은 복사 const _ = require('lodash'); // 깊은 복사 const c2 = _.cloneDeep(o); console.log(c2 === o); // false console.log(c2.x === o.x); // false
추가 자료: 출처 [코어 자바스크립트]
원시 타입의 데이터
var a = 'abc';
변수 영역 주소 ... 1002 1003 1004 1005 ... 데이터 이름:a
값: @5004데이터(값)
영역주소 ... 5002 5003 5004 5005 ... 데이터 'abc' 원시타입의 데이터 재할당
var a = 'abc'; a = 'abcde';
변수 영역 주소 ... 1002 1003 1004 1005 ... 데이터 이름:a
값: @5005데이터(값)
영역주소 ... 5002 5003 5004 5005 ... 데이터 'abc' 'abcde' 참조 타입의 데이터
var obj1 = { a:1, b:'bbb', }
변수 영역 주소 ... 1002 1003 1004 1005 ... 데이터 이름:obj1
값: @5004데이터(값)
영역주소 ... 5002 5003 5004 5005 ... 데이터 1 'bbb' @7101~2 주소 7101 7102 7103 7104 7105 ... 데이터 이름:a
값:@5002이름:b,
값: @5003참조 타입의 데이터 변경
var obj1 = { a:1, b:'bbb', } obj1.a = 2;
변수 영역 주소 1001 1002 1003 1004 1005 ... 데이터 이름:obj1
값: @5004데이터(값)
영역주소 5001 5002 5003 5004 5005 5006 데이터 1 'bbb' @7101~2 2 주소 7101 7102 7103 7104 7105 ... 데이터 이름:a
값:@5005이름:b,
값: @5003깊은 복사와 얕은 복사
var obj1 = { a:1, b: { b1:'bbb', b2:123 } } var obj2 = obj1;
변수 영역 주소 ... 1002 1003 1004 1005 ... 데이터 이름:obj1
값: @5004이름:obj2
값: @5005데이터(값)
영역주소 5001 5002 5003 5004 5005 5006 데이터 'bbb; 1 @7104~5 @7101~2 @7101~2 123 주소 7101 7102 7103 7104 7105 ... 데이터 이름:a
값:@5002이름:b,
값: @5003이름:b1,
값: @5001이름:b2,
값: @5006'사부작사부작 > Javascript' 카테고리의 다른 글
[Javascript]함수(2)호출,전달,형태 (0) 2022.02.15 [Javascript] 얕은 복사에 대한 고찰 (0) 2022.02.10 [Javascript] 함수(1) 정의와 표현 (0) 2022.02.08 [Javascript]객체 리터럴 (0) 2022.02.01 [Javascript] 타입 변환과 단축 평가 (0) 2022.02.01