number
,string
,boolean
,null
,undefined
처럼 고정된 저장 공간을 차지하는 자료형을 의미한다.
대량의 데이터를 다루기에 적합한 array
,object
,function
의 자료형을 의미한다.
위의 그림처럼 참조 자료형은 하나의 공간에 값을 넣는게 아닌 주소를 부여하고 heap
이라는 공간에 그 주소에 맞는 데이터를 묶어둔다. 그리고 값에 변동을 준다면 주소를 보고 heap
에서 해당 데이터 묶음을 찾은 뒤에 변경을 준다.
이와 같은 이유 때문에 원시 자료형은 데이터를 복사한 뒤 복사본에 변화를 주어도 원본에는 영향이 없지만, 참조 자료형의 데이터는 주소를 복사하기 때문에 복사본에 변화를 주게되면 원본에도 영향을 주게된다.
원시 자료형과 참조 자료형의 차이점이 한가지 더 있다. 원시 자료형은 병경이 불가능하다는 것이고 참조 자료형은 변경이 가능하다는 것이다.
물론 원시 자료형의 경우 let num = 20
을 num = 30
이라는 재할당을 이용하여 변경이 가능하다. 그렇다면 변경이 불가능하다는 말은 무엇인가?
원시 자료형의 경우 기존의 20
이 들어있던 공간에 30
을 넣는 것이 아니고 새로운 공간에 let num = 30
을 넣는 것이다.
그리고 이렇게 변경 후에 남겨진 자료는 JS엔진이 메모리에서 자동을 삭제하고 이런 기능을 가비지 콜렉터라고 한다.
• 배열의 복사
let new = arr.slice()
let new = [...arr]
console.log(copiedArr); // [0, 1, 2, 3]
console.log(arr === copiedArr); // false
• 객체 복사
let new = Object.assign({},obj)
let new = {...obj}
console.log(copiedObj) // { firstName: "coding", lastName: "kim" }
console.log(obj === copiedObj) // false
❗️이렇게 복사를 하더라도 참조 자료형의 경우 주소가 다르기 때문에console.log(original === copied)
를 했을 때 각각의 값은 같을지라도 false
가 나오게 된다.
❗️참조 자료형 내부에 또 참조 자료형이 중첩되어 있을 경우에는 위의 방법을사용해도 복사할 수 없다.
참조 자료형 내부에 중첩되어 있는 모든 참조 자료형을 복사하는 것을 의미한다. JS 내부적으로는 깊은 복사를 수행할 수 없기 때문에 다른 문법을 응용한다.
JSON.stringify()
: 참조 자료형을 문자열 형태로 변환하여 반환
JSON.parse()
: 문자열을 참조 자료형으로 반환한다.
const arr = [1, 2, [3, 4]];
const copiedArr = JSON.parse(JSON.stringify(arr));
console.log(arr); // [1, 2, [3, 4]]
console.log(copiedArr); // [1, 2, [3, 4]]
console.log(arr === copiedArr) // false
console.log(arr[2] === copiedArr[2]) // false
❗️깊은 복사라고 할지라도 함수의 경우에 위의 방법을 사용하면 null
을 반환하게 된다.
또 다른 깊은 복사의 방법으로는 외부 라이브러리를 사용하는 방법이 있고 이 방법이 완전한 깊은 복사 방법이다.
node.js환경에서 외부 라이브러리인 lodash
또는 ramda
를 설치하면 된다.
<lodash의 cloneDeep을 이용>
const lodash = require('lodash');
const arr = [1, 2, [3, 4]];
const copiedArr = lodash.cloneDeep(arr);
console.log(arr); // [1, 2, [3, 4]]
console.log(copiedArr); // [1, 2, [3, 4]]
console.log(arr === copiedArr) // false
console.log(arr[2] === copiedArr[2]) // false
안쪽 스코프에서 바깥쪽 스코프로는 접근할 수 있지만 반대는 불가능하다.
스코프는 중첩이 가능하다.
가장 바깥쪽의 스코프는 전역 스코프(Global Scope), 그 이외에는 지역 스코프(local scope)라고 부른다.
지역 변수가 글로벌 변수보다 더 높은 우선순위를 가지고 있다.
if(true) {
console.log('good!')
}
const getAge = () => {
return user.name;
}// 화살표 함수는 함수지만 블록 스코프에 포함된다.
function getName (user) {
return user.name;
}
클로저는 함수와 그 함수 주변의 상태의 주소 조합이다. 조금 더 이해하기 쉽게 풀어서 설명한다면 함수와 그 함수가 접근할 수 있는 변수의 조합이다
const globalVar = '전역 변수';
function outerFn() {
const outerFnVar = 'outer 함수 내의 변수';
const innerFn = function() {
return 'innerFn은 ' + outerFnVar + '와 ' + globalVar + '에 접근할 수 있습니다.';
//함수 innerFn의 반환값
}
return innerFn; //함수 outerFn의 반환값은 innerFn이라는 함수.
}
const innerFnOnGlobal = outerFn(); // innerFnOnGlobal은 outerFn의 반환값인 함수 innerFn이 된다.
console.log(innerFnOnGlobal); // [Function: innerFn]
console.log(innerFnOnGlobal()); // innerFn은 outer 함수 내의 변수와 전역 변수에 접근할 수 있습니다.
const message = innerFnOnGlobal(); // innerFnOnGlobal()의 반환값이 message가 된다.
console.log(message); // innerFn은 outer 함수 내의 변수와 전역 변수에 접근할 수 있습니다.
위의 함수innerFnOnGlobal
를 호출해도 변수outerFnVar
는 스코프 밖에 있기 때문에 값을 받을 수 없어야 한다. 하지만 innerFnOnGlobal
은 innerFn
의 주솟값을 가지고 있고, innerFn
은 클로저로서 outerFnVar
에 접근 할 수 있다.
이러한 클로저의 특성을 이용하면 다음과 같은 코드를 구현할 수 있다.
function createFoodRecipe (foodName) {
const getFoodRecipe = function (ingredient1, ingredient2) {
return `${ingredient1} + ${ingredient2} = ${foodName}!`;
}
return getFoodRecipe;
}
const highballRecipe = createFoodRecipe('하이볼');
highballRecipe('콜라', '위스키'); // '콜라 + 위스키 = 하이볼!'
highballRecipe('탄산수', '위스키'); // '탄산수 + 위스키 = 하이볼!'
highballRecipe('토닉워터', '연태고량주'); // '토닉워터 + 연태고량주 = 하이볼!'
여러 전달인자를 가진 함수를 함수를 연속적으로 리턴하는 함수로 변경하는 행위이다. 다음 예시에서 currySum
과 같은 함수가 커링 함수이다.
function sum(a, b) {
return a + b;
}
function currySum(a) {
return function(b) {
return a + b;
};
}
console.log(sum(10, 20) === currySum(10)(20)) // true
이러한 커링 함수는 전체 프로세스의 일정 부분까지만 실행하는 경우 유용하다.
function makePancake(powder) {
return function (sugar) {
return function (pan) {
return `팬케이크 완성! 재료: ${powder}, ${sugar} 조리도구: ${pan}`;
}
}
}
const addSugar = makePancake('팬케이크가루');
const cookPancake = addSugar('백설탕');
const morningPancake = cookPancake('후라이팬');
// 잠깐 낮잠 자고 일어나서 ...
const lunchPancake = cookPancake('후라이팬');
위의 예시처럼 필요에 따라서 함수 전체를 돌리는게 아닌 필요한 과정만을 실행할 수 있도록 해준다.