변수에 값(value)
자체가 담긴다.보관함의 주소(reference)
가 담긴다.number
: 3.141592string
: ‘zerovodka’boolean
: true & falseundefined
: 변수가 정의되지 않았거나 값이 없다.null
: 의도적으로 비어있음을 표현하기 위해 null 이라는 것이 들어있다.symbol
배열
– Array : [0,1,2,3,4]객체
– Object {name : “zerovodka”, age : 28}정규표현식(RegExp)
Map
Set
WeakMap
WeakSet
즉, 변수에는 하나의 값 혹은 주소만 저장할 수 있다.
두 타입의 가장 대표적인 차이로는 기본형에는 바로 값을 그대로 할당한다는 것이고 참조형에는 값이 저장된 주소값을 할당(참조)한다는 것이다.( ̄︶ ̄)↗
아니 자바스크립트에서 데이터의 불변성이라는게 있다는데 그게 뭘까???
불변성을 이해하려면 자바스크립트에서 데이터를 저장하는 방식을 알아야 한다고 한다
그럼 알아볼까요?!☝
let a = 'abc'
a
라는 변수를 선언하고 abc
라는 데이터 값을 할당했다.
그럼 자바스크립트 엔진은 메모리에 a
라는 식별자를 가진 빈 공간을 만들고 주소를 할당한다.
그리고 할당한 주소에 'abc'
라는 값을 저장한다
즉, a
라는 식별자를 가진 메모리 공간에 'abc'
라는 데이터 값이 아닌 데이터 값의 메모리 주소값을 할당한다는 것이다 ( ̄︶ ̄)↗
let a = 'abc'
a = 'bbb'
선언해 둔 a
라는 변수에 'bbb'
를 할당했다.
그럼 메모리에서 'bbb'
라는 데이터를 찾고 없으면, 공간을 하나 더 만들어 'bbb'
를 저장한다
그리고 그 주소를 a
에 저장한다.
즉, a
의 값을 변경했더라도 그 값을 변경한게 아니라 데이터 주소 값이 변경된 것이다 ( ̄︶ ̄)↗
이처럼 한 번 만든 값을 변경할 수 없는 것이 불변성이다~!!🙂
하지만 가변성이 있는 데이터가 있다!!
위에 설명했던 두 타입 중에
let obj ={
a = 1,
b = 'abc'
}
메모리에 obj
라는 식별자를 가진 빈 공간을 마련하고, 주소를 할당한다.
그리고 그 주소에 여러개의 프로퍼티로 이루어진 데이터 그룹을 저장하기 위해 별도의 공간을 마련하고 그 영역의 주소를 저장한다
let obj ={
a = 1,
b = 'abc'
}
obj.a = 2
obj
의 a
프로퍼티에 2를 할당하면 obj
가 바라보고 있는 주소 b3030이 변경되는게 아닌 기존의 내부값만 바뀐다 ( ̄︶ ̄)↗
바로 이 부분 때문에 참조형 데이터는 가변값이라고 하는 것이다.
💥참고로, 참조형 데이터 자체를 변경할 경우는 불변값이고 그 내부의 프로퍼티를 변경할 때만 가변성이 성립된다.
자바스크립트에서 불변 객체를 만드는 방법은 2가지가 있다 ( ̄︶ ̄)↗
- const
- Object.freeze()
ES6 버전부터 let
과 const
를 지원하게 되었다
const
키워드는 변수를 상수로 선언할 수 있다, 일반적으로 상수로 선언된 변수는 값을 바꾸지 못하는 것으로 알려져 있다.
const test = {};
test.name = "seowoo";
console.log(test); // {"seowoo"}
ES6에서의 const
는 할당된 값이 상수가 되는 것이 아닌
바인딩된 값이 상수가 되는, 즉 test변수가 상수가 되기 때문에
const 키워드로 선언된 test변수에는 객체 재할당은 불가능하지만 객체의 속성은 변경 가능하다.
==> 때문에 비록 재할당은 불가능하지만 객체의 속성을 변경함으로 인해 변수에 바인딩된 객체의 내용까지 변경이 되기 때문에 불변객체라고 하기는 힘들다.
자바스크립트에서 기본적으로 제공하는 메소드인 Object.freeze() 메소드이다. 공식 문서에서는 "객체를 동결하기 위한 메소드" 라고 적혀있다.
let test = {
name : 'Choi'
}
Object.freeze(test);
test 변수에 key value를 가진 객체를 바인딩 후 Object.freeze(test)를 사용해 바인딩된 변수를 동결 객체로 만들었다. 때문에 test 객체는 객체의 속성을 변경하는 시도는 불가능하다.
test.name = 'Kim';
console.log(test) // {name: 'Choi'}
그러나 Object.freeze()는 동결된 객체를 반환하지만 객체의 재할당은 가능하다.
test = {
age : 28
};
console.log(test); // {age: 28}
==> 때문에 Object.freeze()도 불변 객체라고 할 수는 없을 것 같다.
const
와 Object.freeze()
를 조합하여 만들 수 있다.
(const의 재할당불가 + Object.freeze()의 객체속성 변경불가)
const test = {
'name' : 'jung'
};
Object.freeze(test);
먼저 const
키워드로 바인딩 된 변수를 상수화 시킨 다음,
Object.freeze()
로 해당 변수를 동결 객체를 만들면
객체의 재할당과 객체의 속성 둘 다 변경불가능한 불변 객체가 된다.
얕은 복사는 객체의 참조값(주소 값)을 복사하고,
깊은 복사는 객체의 실제 값을 복사합니다.
🔅먼저 이 설명을 듣기 전에 위의 자바스크립트의 두가지 타입을 다시 보고 오는 것을 추천드립니다🔅
이게 무슨 말인지 아래 코드들을 보면서 이해해나가시면 됩니다.
const a = 'a';
let b = 'b';
b = 'c';
console.log(a); // 'a';
console.log(b); // 'c';
// 기존 값에 영향을 끼치지 않는다.
const a = {
one: 1,
two: 2,
};
let b = a;
b.one = 3;
console.log(a); // { one: 3, two: 2 } 출력
console.log(b); // { one: 3, two: 2 } 출력
// 기존 값에 영향을 끼친다.
깊은 복사가 뭔지는 알겠는데 얕은 복사를 해버리면 원본 객체가 수정이 되니 당황스러울 수 있을 것 같은데, 보통 복사를 하는 이유는 원본을 수정하는 것 보다는 사본을 떠서 작업을 하고 싶어 한다고 생각한다.
그럼 각각의 복사들을 하는 방법을 알고 있으면 좋을 것 같았다.
각각의 복사를 할 수 있는 방법들에 대해 알아보자( ̄︶ ̄)↗
- 얕은 복사란 객체를 복사할 때 기존 값과 복사된 값이 같은 참조를 가리키고 있는 것을 말합니다.
- 객체 안에 객체가 있을 경우 한 개의 객체라도 기존 변수의 객체를 참조하고 있다면 이를 얕은 복사라고 합니다.
const original = ['a',2,true,4,"hi"];
const copy = original.slice();
console.log(JSON.stringify(original) === JSON.stringify(copy)); // true
copy.push(10);
console.log(JSON.stringify(original) === JSON.stringify(copy)); // false
console.log(original); // [ 'a', 2, true, 4, 'hi' ]
console.log(copy); // [ 'a', 2, true, 4, 'hi', 10 ]
==> 기존 배열에는 영향을 끼치지 않아서 깊은 복사로 보일 수 있지만, 원시값을 저장한 1차원 배열일 뿐입니다.
const original = [
[1, 1, 1, 1],
[0, 0, 0, 0],
[2, 2, 2, 2],
[3, 3, 3, 3],
];
const copy = original.slice();
console.log(JSON.stringify(original) === JSON.stringify(copy)); // true
// 복사된 배열에만 변경과 추가.
copy[0][0] = 99;
copy[2].push(98);
console.log(JSON.stringify(original) === JSON.stringify(copy)); // true
console.log(original);
// [ [ 99, 1, 1, 1 ], [ 0, 0, 0, 0 ], [ 2, 2, 2, 2, 98 ], [ 3, 3, 3, 3 ] ]출력
console.log(copy);
// [ [ 99, 1, 1, 1 ], [ 0, 0, 0, 0 ], [ 2, 2, 2, 2, 98 ], [ 3, 3, 3, 3 ] ]출력
만약 1차원 배열이 아닌 중첩 구조를 갖는 2차원 배열이면 얕은 복사를 수행하게 됩니다.
const original = [
{
a: 1,
b: 2,
},
true,
];
const copy = original.slice();
console.log(JSON.stringify(original) === JSON.stringify(copy)); // true
// 복사된 배열에만 변경.
copy[0].a = 99;
copy[1] = false;
console.log(JSON.stringify(original) === JSON.stringify(copy)); // false
console.log(original);
// [ { a: 99, b: 2 }, true ]
console.log(copy);
// [ { a: 99, b: 2 }, false ]
배열 안에 객체를 수정하고자 할 경우 얕은 복사를 수행하는 것을 볼 수 있습니다.
하지만 원시값은 기본적으로 깊은 복사라 기존 변수에 있는 값과는 다른 값을 도출하는 것을 볼 수 있습니다.
Object.assign(생성할 객체, 복사할 객체)
메소드의 첫 번째 인자로 빈 객체를 넣어주고 두 번째 인자로 복사할 객체를 넣어주면 됩니다. ( ̄︶ ̄)↗
const object = {
a: "a",
number: {
one: 1,
two: 2,
},
};
const copy = Object.assign({}, object);
copy.number.one = 3;
console.log(object === copy); // false
console.log(object.number.one === copy.number.one); // true
복사된 객체 copy
자체는 기존 object
와 다른 객체지만 그 안에 들어가 있는 값은 기존 object
안의 값과 같은 참조 값을 가리키고 있습니다.
const object = {
a: "a",
number: {
one: 1,
two: 2,
},
};
const copy = {...object}
copy.number.one = 3;
console.log(object === copy); // false
console.log(object.number.one === copy.number.one); // true
깊은 복사된 객체는 객체 안에 객체가 있을 경우에도 원본과의 참조가 완전히 끊어진 객체를 말합니다.
JSON.stringify()
는 객체를 json 문자열로 변환하는데 이 과정에서 원본 객체와의 참조가 모두 끊어집니다.
객체를 json 문자열로 변환 후, JSON.parse()
를 이용해 다시 원래 객체(자바스크립트 객체)로 만들어줍니다.
이 방법이 가장 간단하고 쉽지만 다른 방법에 비해 느리다는 것과 객체가 function일 경우, undefined로 처리한다는 것이 단점입니다.
const object = {
a: "a",
number: {
one: 1,
two: 2,
},
arr: [1, 2, [3, 4]],
};
const copy = JSON.parse(JSON.stringify(object));
copy.number.one = 3;
copy.arr[2].push(5);
console.log(object === copy); // false
console.log(object.number.one === copy.number.one); // false
console.log(object.arr === copy.arr); // false
console.log(object); // { a: 'a', number: { one: 1, two: 2 }, arr: [ 1, 2, [ 3, 4 ] ] }
console.log(copy); // { a: 'a', number: { one: 3, two: 2 }, arr: [ 1, 2, [ 3, 4, 5 ] ] }
const object = {
a: "a",
number: {
one: 1,
two: 2,
},
arr: [1, 2, [3, 4]],
};
function deepCopy(object) {
if (object === null || typeof object !== "object") {
return object;
}
// 객체인지 배열인지 판단
const copy = Array.isArray(object) ? [] : {};
for (let key of Object.keys(object)) {
copy[key] = deepCopy(object[key]);
}
return copy;
}
const copy = deepCopy(object);
copy.number.one = 3;
copy.arr[2].push(5);
console.log(object === copy); // false
console.log(object.number.one === copy.number.one); // false
console.log(object.arr === copy.arr); // false
console.log(object); // { a: 'a', number: { one: 1, two: 2 }, arr: [ 1, 2, [ 3, 4 ] ] }
console.log(copy); // { a: 'a', number: { one: 3, two: 2 }, arr: [ 1, 2, [ 3, 4, 5 ] ] }
라이브러리를 사용하면 더 쉽고 안전하게 깊은 복사를 할 수 있습니다.
설치를 해야 한다는 점과 일반적인 개발에는 효율적이겠지만, 코딩 테스트에는 사용할 수 없다는 것이 단점입니다.
const deepCopy = require("lodash.clonedeep")
const object = {
a: "a",
number: {
one: 1,
two: 2,
},
arr: [1, 2, [3, 4]],
};
const copy = deepCopy(object);
copy.number.one = 3;
copy.arr[2].push(5);
console.log(object === copy); // false
console.log(object.number.one === copy.number.one); // false
console.log(object.arr === copy.arr); // false
console.log(object); // { a: 'a', number: { one: 1, two: 2 }, arr: [ 1, 2, [ 3, 4 ] ] }
console.log(copy); // { a: 'a', number: { one: 3, two: 2 }, arr: [ 1, 2, [ 3, 4, 5 ] ] }
단순히 코딩 공부를 위해 Javascript 문법 공부만 했지, 이렇게 이론적으로 공부를 해보니 코딩 해석 부분에서 도움이 많이 될 것 같다.
꾸준히 공부해나가야겠다.😁