자바스크립트의 타입은 원시타입과 참조타입으로 나뉩니다.
원시타입은 Number
, String
, Boolean
, Null
, Undefined
, Symbol
, bigint
타입을 말합니다.
원시타입의 큰 특징은 불변(immutable)하다는 것입니다.
let a = 100;
a = 50;
위 처럼 a
에 숫자를 할당하면 변수 a
는 스택 메모리에 할당된 값을 직접적으로 가리키게 됩니다.
그리고 원시타입은 불변하다고 했으므로 a
에 다른값을 할당한다면 불변성을 지키기 위해서 새로운 a
가 메모리에 할당되어 값을 가리키게 되고 100
의 값을 가진 메모리는 가비지컬렉터가 처리하게 됩니다.
let a = 10;
let b = a;
a = a + 1;
console.log(b); // 10
따라서 위 처럼 b
에 a
를 할당하면 b
는 10
의 값을 가리키게 되고 새로 할당된 a
는 새로운 11
의 값을 가리키게 됩니다.
b
가 가리키는 값을 직접 변경해주지 않았으므로 b
는 계속해서 10
의 값을 가리키게 됩니다.
그 결과 콘솔에는 10
이 출력됩니다.
원시타입을 제외하면 모두 참조타입이 됩니다. ( 객체, 배열, 함수 .. )
참조타입은 불변하다는 특징이 없어 값을 변경할 수 있습니다. ( 동적 )
또한 참조타입은 원시타입과 다르게 동적으로 크기가 변할 수 있어 스택 메모리에 할당할 수 없습니다.
그래서 참조타입은 직접 값을 가리키지않고 힙 메모리를 가리키는 주소의 값을 가지게 됩니다.
let array = []
예를 들어 위 배열에 값을 추가한다면 메모리의 크기는 계속 늘어나므로 동적으로 힙 메모리를 할당해야 합니다.
let newArray = array
만약 위처럼 참조타입을 복사하게 된다면 newArray
도 같은 주소를 가리키게 됩니다.
하지만 자바스크립트에서 참조타입을 복사할 때는 주의를 해야합니다.
복사한 변수를 변경시 의도치 않게 원본을 수정할 수 있기 때문입니다.
그러므로 얕은 복사와 깊은 복사에 대해서 알아봅시다.
참조 타입의 복사 방법은 얕은 복사(shallow coyp)와 깊은 복사(deep copy)로 나뉩니다.
얕은 복사는 데이터 주소를 공유하고 깊은 복사는 데이터를 새로운 메모리에 저장 후 다른 주소를 가집니다.
데이터 주소를 복사하므로 복사한 변수를 수정하면 원본이 수정됩니다.
let origin = { name: 'Jinny' }
let copy = origin;
copy.name = 'Mr.Lee';
console.log(origin.name); // 'Mr.Lee'
console.log(origin === copy); // true
let obj1 = { a: 1, b: { c: 2 } };
let obj2 = { ...obj1 };
obj2.b.c = 3;
console.log(obj1.b.c); // 출력: 3
새로운 주소를 가지므로 원본에 영향을 미치지 않습니다.
JSON
메소드 이용let obj1 = { a: 1, b: { c: 2 } };
let obj2 = JSON.parse(JSON.stringify(obj1));
obj2.b.c = 3;
console.log(obj1.b.c); // 2
lodash
const _ = require('lodash');
const newObj = _.cloneDeep(oldObj);
fast-copy
const copy = require('fast-copy');
const copiedObject = copy(object);
( 호이스팅 = 끌어올림 )
변수의 위치에 따라서 에러가 나왔다가 undefined
가 되는데 왜 그런지 이유를 알아보겠습니다.
function foo() {
a = 2;
var a;
console.log(a);
}
foo(); // 2
뭔가 이상하지만 2가 출력됩니다.
function foo() {
console.log(a);
var a = 2;
}
foo(); // undefined
이번에는 undefined
가 출력됩니다.
왜 이런 결과가 나오는걸까요 ?
먼저 자바스크립트 엔진은 코드를 인터프리팅 하기 전에 소스 코드를 파싱해 AST구조로 만듭니다.
이후 AST를 바이트 코드로 컴파일한 후 인터프리터로 실행합니다.
이 과정에서 var a = 2;
를 하나의 구문으로 생각할 수도 있지만, 자바스크립트는 다음 두 개의 구문으로 분리하여 봅니다.
var a;
a = 2;
변수 선언(생성) 단계와 초기화 단계로 나눈 뒤
선언 단계에서는 그 선언이 소스코드의 어디에 위치하든 해당 스코프의 시작 부분에서 선언이 일어난 것처럼 호이스팅(끌어올림) 후 컴파일되어 바이트 코드가 만들어집니다
그러므로 위 코드는 컴파일 되었을때 바이트코드가 마치 아래처럼 작성한것처럼 변하게 됩니다.
function foo() {
var a;
console.log(a);
a = 2;
}
foo(); // undefined
또한 함수 선언식도 호이스팅이 됩니다.
hoisted(); // Output: "This function has been hoisted."
function hoisted() {
console.log('This function has been hoisted.');
};
반면에 함수 표현식은 호이스팅이 안됩니다.
expression(); //Output: "TypeError: expression is not a function
var expression = function() {
console.log('Will this work?');
};
참고로 let
, const
타입도 호이스팅이 됩니다.
하지만 선언하기 전에 참조를 하면 var
타입과는 다르게 undefined
이 아닌 ReferenceError
가 나오게 됩니다.
function foo() {
console.log(a);
let a = 2;
}
foo(); // ReferenceError
왜 이런 차이가 발생하는 걸까요 ?
이유는 호이스팅시에 타입에 따라 TDZ(Temporal Dead Zone)
가 발생되기 때문입니다.
var
타입의 호이스팅은 여러 혼란과 버그를 야기하므로 이러한 문제를 해결하기 위해서 ES6에서 let
과 const
의 TDZ가 도입되었습니다.
TDZ(Temporal Dead Zone)는 ES6에서 도입된 개념으로
let
과 const
로 선언된 변수가 그 선언 전에 접근될 수 없는 코드 영역을 의미합니다.
즉, 현재 스코프에 진입할 때부터 실제 변수 선언 위치까지의 영역이 TDZ입니다.
따라서 let
, const
타입의 변수가 TDZ에 있을 때는 참조하거나 값을 할당하거나 읽는 것이 불가능합니다.
let, const는 선언 후 초기화 될때까지 TDZ가 존재하게 됩니다.
이 구간에서 변수에 접근하게 되면 ReferenceError
가 발생합니다.
if (true) {
// 호이스팅되었지만 초기화 x -> TDZ
console.log(number); // ReferenceError
let number; // 여기서 undefined로 초기화 됨
console.log(number); // undefined
number = 5;
console.log(number); // 5
}
let
이 호이스팅되어 바이트코드에서는 스코프 첫 부분에 선언된것처럼 되더라도 실제 초기화하는 코드 사이 TDZ구간에서 변수에 접근하고 있으므로 ReferenceError
에러를 반환합니다.
따라서 의도하지 않은 버그를 막기 위해 에러를 알려주는 let
, const
타입 사용을 권장합니다.
var
타입은 선언시 자동으로 undefined
로 할당되기 때문에 호이스팅으로 선언이 끌어올려지더라도 참조하는 타이밍에는 항상 undefined
로 값이 초기화 되어 있어 에러가 발생하지 않습니다.
function multiplyByTen(number) {
// 호이스팅 + undefined로 초기화
console.log(ten); // => undefined
var ten;
ten = 10;
console.log(ten); // => 10
return number * ten;
}
console.log(multiplyByTen(4)); // => 40
함수를 선언식으로 만들게 되면 선언, 초기화, 할당이 동시에 이루어집니다.
console.log(sumArray([5, 10, 8])); // => 23
function sumArray(array) {
return array.reduce(sum);
function sum(a, b) {
return a + b;
}
// return array.reduce((a, b) => a + b); // 한줄로 만들면
}
호이스팅에 의해 끌어올려질 때 할당까지 동시에 되므로 선언전에 호출이 가능합니다.
console.log(foo()); // TypeError: foo is not a function
var foo = function() {
return "Hello";
}
함수 표현식은 선언, 초기화와 실제 할당이 분리되어 있습니다.
foo
는 호이스팅되어 undefined
로 초기화 됩니다.
하지만 표현식의 할당은 실제 할당하는 부분에서 이루어는데 함수를 호출했으므로 TypeError
가 발생하게 됩니다.