🔸 자료형: 값(value)의 종류. 각각의 자료형은 고유한 속성과 메서드를 가짐. 크게 두 가지로 구분하며 원시 자료형과 참조 자료형이 존재.
🔸 number
, string
, boolean
, undefined
, null
, symbol
.
🔸 원시자료형은 고정된 저장 공간을 차지.
🔸 원시 자료형이 아닌 모든 자료형. 여러 데이터를 한 번에 다룰 수 있는 array
, object
, function
등
🔸 원시 자료형을 변수에 할당하면 값 자체가 할당됨.
let num = 20;
- 변수
num
을 선언하면 컴퓨터는num
이라는 이름의 공간을 확보,20
이라는 원시 값 자체를 그 공간에 저장함.
🔸 원시 자료형의 데이터가 저장되는 공간 스택(stack)
🔸 JavaScript에서는 특별한 저장 공간에 참조 자료형을 저장한 후, 그 저장공간을 참조할 수 있는 주소값을 변수에 저장.
let arr = [0, 1, 2, 3]
- 변수 arr을 선언하면 arr에 해당하는 저장공간에는 주소값이 저장됨.
- 이때 참조 자료형은 힙(heap)이라는 특별한 저장 공간에 저장됨.
- 따라서 변수 arr에 해당하는 저장공간에는 주소값이 저장되어 있고, 그 주소값을 통해 참조 자료형에 접근, 이를 참조한다(refer)라고 함.
let num = 20;
let copiedNum = num;
🔸 원시 자료형은 값 자체가 복사되므로, 변수 num과 변수 copiedNum은 동일하게 20이라는 값을 가짐.
🔸 원본에 다른 값을 재할당해도 복사본에는 영향을 미치지 않음.
let arr = [0, 1, 2, 3];
let copiedArr = arr;
🔸 참조 자료형은 주소값을 복사하므로, 할당된 변수를 다른 변수에 할당하면, 두 변수는 같은 주소를 가리킴.
🔸 같은 주소를 참조하고 있기 때문에 원본을 변경하면 복사본도 영향을 받음.
🔸 한 번 생성된 원시 값은 변경할 수 없음. 변수에 다른 값을 재할당해도 원시 값 자체가 변경된 것이 아니라 새로운 원시 값을 생성하고, 변수가 다른 메모리 공간을 참조함.
let num = 20; num = 30;
num
이라는 변수를 선언하고 숫자20
을 할당한 뒤,30
을 변수에 재할당하면, 메모리 내부에서는 30이라는 원시 값을 저장하기 위한 새로운 공간을 확보.- 이후 그 공간에
num
이라는 이름을 붙이고 30을 저장함. 남아 있는 값20
은 JavaScript 엔진이 사용하지 않는 값을 자동으로 메모리에서 삭제함.
🔸 이렇게 사용하지 않는 값을 자동으로 메모리에서 삭제하는 기능을 가비지 콜렉터(garbage collector) 라고 함 (하지만 이러한 동작이 어느 시점에 진행되는지 예측할 수 없음).
🔸 따라서 원시 자료형은 어떤 상황에서도 불변하는 읽기 전용 데이터로 높은 신뢰성을 가질 수 있음.
🔸 원시 자료형의 경우 값의 크기가 거의 일정하기 때문에 새로운 공간을 확보하여 값을 복사하는 방법이 유용. 하지만, 크기가 일정하지 않는 참조 자료형의 경우 매번 값을 복사하면, 그만큼 효율성이 떨어질 수 밖에 없음.
🔸 이러한 이유로 참조 자료형은 변경이 가능하도록 설계됨.
🔸 원시 자료형을 할당한 변수를 다른 변수에 할당하면, 값 자체의 복사가 일어나기 때문에, 둘 중 하나의 값을 변경해도 다른 하나에는 영향을 미치지 않음.
🔸 참조 자료형이 할당된 변수를 다른 변수에 할당하면 주소가 복사되어 원본과 복사본이 같은 주소를 참조하고, 주소값을 복사한 변수에 요소를 추가하면 같은 주소를 참조하고 있는 원본에도 영향을 미침.
참조 자료형을 복사하여, 똑같은 요소와 프로퍼티를 가지지만 원본과 복사본이 서로 영향을 미치지 않도록 할 수는 없을까?
🔸 slice()
: 배열 내장 메서드로 원본 배열을 복사할 수 있음.
let arr = [0, 1, 2, 3];
let copiedArr = arr.slice();
console.log(copiedArr); // [0, 1, 2, 3]
console.log(arr === copiedArr); // false
copiedArr.push(4);
console.log(copiedArr); // [0, 1, 2, 3, 4]
console.log(arr); // [0, 1, 2, 3]
🔸 spread문법 : ES6에서 새롭게 추가된 문법으로, 배열을 펼칠 수 있음. 펼치는 방법은 배열이 할당된 변수명 앞에 ...
을 붙이며, 배열을 펼치면 배열의 각 요소를 확인할 수 있음.
let arr = [0, 1, 2, 3];
let copiedArr = [...arr];
console.log(copiedArr); // [0, 1, 2, 3]
console.log(arr === copiedArr); // false
copiedArr.push(4);
console.log(copiedArr); // [0, 1, 2, 3, 4]
console.log(arr); // [0, 1, 2, 3]
slice()
메서드를 사용한 것과 동일하게 동작.🔸 Object.assign(target, 복사할 값)
let obj = { firstName: "coding", lastName: "kim" };
let copiedObj = Object.assign({}, obj);
console.log(copiedObj) // { firstName: "coding", lastName: "kim" }
console.log(obj === copiedObj) // false; 다른 주소를 가지기 때문
const target = { a: 1, b: 2 };
const source = { b: 4, c: 5 };
const returnedTarget = Object.assign(target, source);
console.log(target);
// Expected output: Object { a: 1, b: 4, c: 5 }
console.log(returnedTarget === target);
// Expected output: true
🔸 spread syntax
let obj = { firstName: "coding", lastName: "kim" };
let copiedObj = {...obj};
console.log(copiedObj) // { firstName: "coding", lastName: "kim" }
console.log(obj === copiedObj) // false
🔸 참조 자료형 내부에 참조 자료형이 중첩되어 있는 경우, slice()
, Object.assign()
, spread syntax
를 사용해도 참조 자료형 내부에 참조 자료형이 중첩된 구조는 복사할 수 없음.
🔸 참조 자료형이 몇 단계로 중첩되어 있던지, 위에서 설명한 방법으로는 한 단계까지만 복사할 수 있음.
let copiedUsers = users.slice();
console.log(users === copiedUsers);
// false; 각각 다른 주소를 참조하고 있기 때문
console.log(users[0] === copiedUsers[0]);
// true; users[0]과 copiedUsers[0]는 여전히 같은 주소값을 참조하고 있기 때문
🔸 이처럼 slice()
, Object.assign()
, spread syntax
등의 방법으로 참조 자료형을 복사하면, 중첩된 구조 중 한 단계까지만 복사하는 것을 얕은 복사(shallow copy) 라고 함.
🔸 참조 자료형 내부에 중첩되어 있는 모든 참조 자료형을 복사하는 것.
🔸 그러나 JavaScript 내부적으로는 깊은 복사를 수행할 수 있는 방법이 없지만, JavaScript의 다른 문법을 응용하면 깊은 복사와 같은 결과물을 만들어 낼 수 있음.
🔸 JSON.stringify()
: 참조 자료형을 문자열 형태로 변환하여 반환.
🔸 JSON.parse()
: 문자열의 형태를 객체로 변환하여 반환.
🔸 먼저 중첩된 참조 자료형을 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
로 바뀌게 됨. 따라서 이 방법 또한 완전한 깊은 복사 방법이라고 보기 어려움.const arr = [1, 2, [3, function(){ console.log('hello world')}]];
const copiedArr = JSON.parse(JSON.stringify(arr));
console.log(arr); // [1, 2, [3, function(){ console.log('hello world')}]]
console.log(copiedArr); // [1, 2, [3, null]]
console.log(arr === copiedArr) // false
console.log(arr[2] === copiedArr[2]) // false
🔸 완전한 깊은 복사를 반드시 해야 하는 경우, node.js 환경에서 외부 라이브러리인 lodash, 또는 ramda를 설치하면 됨.
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
🔸 스코프 (Scope) : 변수가 정의될 때 해당 변수가 접근 가능한 범위.
🔸 바깥쪽 스코프에서 선언한 변수는 안쪽 스코프에서 사용 가능하지만, 안쪽 스코프에 선언한 변수는 바깥쪽 스코프에서 사용 불가능 (이 경우 RefereneceError
가 나타남).
🔸 중첩이 가능함.
🔸 가장 바깥의 스코프는 전역 스코프(Global scope)라고 부름. 반대는 지역 스코프(Local scope).
🔸지역 변수는 전역 변수보다 더 높은 우선순위를 가짐.
🔸 동일한 변수 이름으로 인해 바깥쪽 변수가 안쪽 변수에 의해 가려지는(shadow) 현상을 쉐도잉(variable shadowing)이라고 부름.
🔸 중괄호({}
)로 둘러싼 범위 (if, for, switch 등). 블록 스코프 안에서 정의된 변수는 블록 범위를 벗어나는 즉시 접근할 수 없음.
🔸 그러나 var
키워드는 블록 스코프를 무시하기 때문에, 블럭 범위를 벗어나도 사용 가능.
🔸 화살표 함수는 블록 스코프로 취급됨.
🔸 함수 function
으로 둘러싼 범위 (함수 표현식, 함수 선언식).
🔸 var
키워드는 함수 스코프를 따르고 (화살표 함수, 함수 표현식, 함수 선언식), 함수 스코프의 최상단에 선언됨.
🔸 함수 스코프는 함수의 실행부터 종료까지이고, 함수 내에서 선언 키워드 없는 선언은 최고 스코프에 선언되며, 함수의 실행 전까지 선언되지 않은 것으로 취급.
🔸 블록 단위로 스코프를 구분했을 때, 훨씬 더 예측 가능한 코드를 작성할 수 있으므로 var
보단 let
키워드 사용이 권장됨.
🔸 var
를 사용하지 않는다 해도, 함수 스코프는 let
으로 선언된 변수의 접근 범위를 제한함.
🔸 var
키워드는 변수를 재선언해도 아무런 에러도 내지 않지만, let
키워드는 재선언하는 경우, 대부분 버그를 일으킴.
🔸 블록 스코프를 따름
🔸 값의 변경을 최소화하여 보다 안전한 프로그램을 만들 수 있음. 값을 새롭게 할당할 일이 없다면, const
키워드의 사용이 권장됨.
🔸 값의 재할당이 불가능. 값을 재할당할 경우, TypeError를 내므로, 의도하지 않은 값의 변경을 막을 수 있음.
var
로 선언된 전역 변수와 전역 함수가 window 객체에 속함. 함수 선언식으로 함수를 선언하거나, var
로 전역 변수를 만들면, window 객체에서도 동일한 값을 찾을 수가 있습니다. var myName = '김코딩';
console.log(window.myName); // 김코딩
function foo() {
console.log('bar');
}
console.log(foo === window.foo); // true;
var
는 블록 스코프를 무시하며, 재선언을 해도 에러를 내지 않음 (같은 스코프에서 동일한 이름의 변수를 재선언하는 것은 버그를 유발).var
로 선언하는 경우 문제가 될 수 있음 (var
로 선언한 전역 변수가 window 기능을 덮어씌워서 내장 기능을 사용할 수 없게 만들 수 있음).var
, let
, const
)없이 변수를 할당할 시, 해당 변수는 전역 변수처럼 작동함.'use strict'
라고 입력하면 됨 (문법적으로 실수할 수 있는 부분들을 에러로 판단함).🔸 함수와 함수가 선언된 어휘적(lexical) 환경의 조합. 이 환경은 클로저가 생성된 시점의 유효 범위 내에 있는 모든 지역 변수로 구성됨. (함수와 그 함수가 접근할 수 있는 변수의 조합)
🔸 자바스크립트는 함수가 호출되는 환경과 별개로 기존에 선언되어 있던 환경, 즉 어휘적 환경을 기준으로 변수를 조회하려고 함.
🔸 이와 같은 이유로 "외부 함수의 변수에 접근할 수 있는 내부 함수"를 클로저 함수라고 함.
🔸 함수가 실행되는 동안 함수 내부에서 정의된 변수들을 계속 유지하고, 함수 외부에서도 접근 가능하게 만들어 줌.
클로저
1. 함수outerFn
과outerFn
에서 접근할 수 있는globalVar
2. 함수innerFn
과innerFn
에서 접근할 수 있는globalVar
,outerFnVar
🔸 클로저가 중요한 이유 : 클로저의 함수는 어디에서 호출되느냐와 무관하게 선언된 함수 주변 환경에 따라 접근할 수 있는 변수가 정해지기 때문
const globalVar = '전역 변수'; function outerFn() { const outerFnVar = 'outer 함수 내의 변수'; const innerFn = function () { return ( 'innerFn은 ' + outerFnVar + '와 ' + globalVar + '에 접근할 수 있습니다.' ); }; return innerFn; } const innerFnOnGlobal = outerFn(); const message = innerFnOnGlobal(); console.log(message); // ?
innerFnOnGlobal
은outerFn
내부의innerFn
의 주소값을 가짐.- 이때,
innerFnOnGlobal
은innerFn
밖에 있기 때문에outerFnVar
에는 접근하지 못한다고 생각할 수 있지만,innerFnOnGlobal
은innerFn
의 주소값을 가지고 있고,innerFn
은 클로저로서outerFnVar
에 접근할 수 있기 때문에innerFnOnGlobal
은outerFnVar
에 접근할 수 있음.- 이 “환경”을 어휘적 환경(Lexical Environment)라고 함.
- 만약 클로저가 JS에 없는 개념이라면,
outerFnVar
에 접근할 수 없어 에러가 남.
🔸 일반적으로 함수 내부에 선언한 변수와 매개변수에 접근할 수 없음.
function getFoodRecipe (foodName) {
let ingredient1, ingredient2;
return `${ingredient1} + ${ingredient2} = ${foodName}!`;
}
console.log(ingredient1); // ReferenceError: ingredient1 is not defined (함수 내부에 선언한 변수에 접근 불가)
console.log(foodName); // ReferenceError: foodName is not defined (매개변수에 접근 불가)
🔸 클로저를 응용하면, 함수 내부에 선언한 변수와 매개변수에 접근가능하여, 클로저의 함수 내에 데이터를 보존해 두고 사용할 수 있음.
🔸 기존 함수 내부에서 새로운 함수를 리턴하면 클로저로서 활용할 수 있음. 즉, 리턴한 새로운 함수의 클로저에 데이터가 보존됨.
function createFoodRecipe (foodName) {
const getFoodRecipe = function (ingredient1, ingredient2) {
return `${ingredient1} + ${ingredient2} = ${foodName}!`;
}
return getFoodRecipe;
}
const highballRecipe = createFoodRecipe('하이볼');
highballRecipe('콜라', '위스키'); // '콜라 + 위스키 = 하이볼!'
highballRecipe('탄산수', '위스키'); // '탄산수 + 위스키 = 하이볼!'
highballRecipe('토닉워터', '연태고량주'); // '토닉워터 + 연태고량주 = 하이볼!'
highballRecipe
함수는 문자열 ‘하이볼’
을 보존하고 있어서 전달인자를 추가로 전달할 필요가 없고, 다양한 하이볼 레시피를 하나의 함수로 제작할 수 있음.🔸 여러 전달인자를 가진 함수를 연속적으로 리턴하는 함수로 변경하는 행위.
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
sum
과 currySum
이 같은 값을 리턴하기 위해서는 currySum
함수에서 리턴한 함수에 두 번째 전달인자 20을 전달하여 호출. currySum
과 같은 함수를 커링 함수라고 부름.🔸 전체 프로세스의 일정 부분까지만 실행하는 경우 유용.
🔸 함수의 일부만 호출하거나, 일부 프로세스가 완료된 상태를 저장하기에 용이.
function makePancakeAtOnce (powder, sugar, pan) {
return `팬케이크 완성! 재료: ${powder}, ${sugar} 조리도구: ${pan}`;
}
const morningPancake = makePancakeAtOnce('팬케이크가루', '백설탕', '후라이팬')
// 잠깐 낮잠 자고 일어나서 만든 팬케이크를 표현할 방법이 없다.
----
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('후라이팬');
makePancakeAtOnce
함수는 일부 조리 과정이 생략된 모습을 표현할 수 없음.makePancake
함수는 팬케이크 제작 과정을 커링 함수로 만들어 일부 조리 과정을 표현할 수 있음.🔸 모듈: 하나의 기능을 온전히 수행하기 위한 모든 코드를 가지고 있는 코드 모음
🔸 JS에 class
키워드가 없었을 때, 모듈 패턴을 구현하기 위해 클로저를 사용했음.
🔸 특정 데이터를 다른 코드의 실행으로부터 보호해야 할 때 용이.
function makeCalculator() {
let displayValue = 0;
return {
add: function(num) {
displayValue = displayValue + num;
},
subtract: function(num) {
displayValue = displayValue - num;
},
multiply: function(num) {
displayValue = displayValue * num;
},
divide: function(num) {
displayValue = displayValue / num;
},
reset: function() {
displayValue = 0;
},
display: function() {
return displayValue
}
}
}
const cal = makeCalculator();
cal.display(); // 0
cal.add(1);
cal.display(); // 1
console.log(displayValue) // ReferenceError: displayValue is not defined
displayValue
는 makeCalculator
의 코드 블록 외에 다른 곳에서는 접근이 불가능하지만, cal
의 메서드는 모두 클로저의 함수로서 displayValue
에 접근할 수 있음. 🔸 클로저를 이용하면 특정 함수가 데이터를 보존할 수 있음.
🔸 커링을 이용하면 함수의 일부만 호출하거나, 일부 프로세스가 완료된 상태를 저장할 수 있음.
🔸 모듈 패턴을 이용하면 특정 데이터를 다른 코드의 실행으로부터 보호할 수 있음.
현재까지 가장 최신 버전은 2019년에 출시된 ES2019이지만, 2015년에 출시된 ES6(ECMA Script6)에서 가독성과 유지보수성을 획기적으로 향상할 수 있는 문법이 많이 추가됨.
🔸 주로 배열을 풀어서 인자로 전달하거나, 배열을 풀어서 각각의 요소로 넣을 때에 사용. 객체 혹은 배열을 펼칠 수 있음.
🔸 기존 배열을 변경하지 않으므로(immutable) 새롭게 할당해야 함.
function sum(x, y, z) {
return x + y + z;
}
const numbers = [1, 2, 3];
sum(...numbers) // 6;
🔸 파라미터를 배열의 형태로 받아서 사용할 수 있음. 파라미터 개수가 가변적일 때 유용.
function sum(...theArgs) {
return theArgs.reduce((previous, current) => {
return previous + current;
});
}
sum(1,2,3) // 6;
sum(1,2,3,4) // 10;
Array.reduce(초기값, 현재 처리할 요소)
: 배열의 모든 요소에 대해 주어진 함수를 순차적으로 실행하면서 하나의 결과값을 생성.🔸 배열 합치기
let parts = ['shoulders', 'knees'];
let lyrics = ['head', ...parts, 'and', 'toes'];
console.log(lyrics); // (5) ['head', 'shoulders', 'knees', 'and', 'toes']
let arr1 = [0, 1, 2];
let arr2 = [3, 4, 5];
arr1 = [...arr1, ...arr2]; // (6) [0, 1, 2, 3, 4, 5]
🔸 배열 복사
let arr = [1, 2, 3];
let arr2 = [...arr]; // arr.slice() 와 유사
arr2.push(4);
// arr1 = [1, 2, 3]
// arr2 = [1, 2, 3, 4]
let obj1 = { foo: 'bar', x: 42 };
let obj2 = { foo: 'baz', y: 13 };
let clonedObj = { ...obj1 };
let mergedObj = { ...obj1, ...obj2 };
// clondedObj = { foo: 'bar', x: 42 };
// mergeObj = { foo: 'baz', x: 42, y: 13 };
function myFun(a, b, ...manyMoreArgs) {
console.log("a", a); // a one
console.log("b", b); // b two
console.log("manyMoreArgs", manyMoreArgs); //manyMoreArgs [ 'three', 'four', 'five', 'six' ]
}
myFun("one", "two", "three", "four", "five", "six");
// a one
// b two
// manyMoreArgs (4) ['three', 'four', 'five', 'six']
🔸 배열이나 객체의 속성을 해체하여 그 값을 개별 변수에 담을 수 있게 하는 JavaScript 표현식.
🔸 spread 문법을 이용하여 값을 해체한 후, 개별 값을 변수에 새로 할당하는 과정.
🔸 배열
const [a, b, ...rest] = [10, 20, 30, 40, 50];
// a= 10;
// b= 20;
// rest = [30, 40, 50];
🔸 객체
const {a, b, ...rest} = {a: 10, b: 20, c: 30, d: 40}
// a= 10;
// b= 20,
// rest = {c: 30, d: 40};
const
, let
, var
)과 함께 사용하지 않으면 에러가 발생할 수 있음.선언 없이 할당할 경우, 해당 문서 참고
var a, b; ({ a, b } = { a: 1, b: 2 });
{a, b} = {a:1, b:2}
는 유효한 독립 구문이 아님. 좌변의{a, b}
이 객체 리터럴이 아닌 블록으로 간주되기 때문.- 하지만
()
내에 작성할 경우,var {a, b} = {a:1, b:2}
와 같음.
function whois({displayName: displayName, fullName: {firstName: name}}){
console.log(displayName + " is " + name);
}
let user = {
id: 42,
displayName: "jdoe",
fullName: {
firstName: "John",
lastName: "Doe"
}
};
whois(user) // jdoe is John Doe
// 함수선언문
function sum (x, y) {
return x + y;
}
// 함수표현식
const subtract = function (x, y) {
return x - y;
}
// 화살표 함수
const multiply = (x, y) => {
return x * y;
}
🔸 매개변수가 한 개일 때, 소괄호(()
) 생략 가능. 매개변수가 없는 경우 소괄호 생략 불가능.
// 매개변수가 한 개일 때, 소괄호를 생략할 수 있습니다.
const square = x => { return x * x }
// 위 코드와 동일하게 동작합니다.
const square = ( x ) => { return x * x }
// 단, 매개변수가 없는 경우엔 소괄호를 생략할 수 없습니다.
const greeting = () => { return 'hello world' }
🔸 함수 코드 블록 내부가 하나의 문으로 구성되어 있다면 중괄호({}
)를 생략 가능. 이때 코드 블록 내부의 문이 값으로 평가될 수 있으면 return
키워드를 생략 가능.
const squre = x => x * x
// 위 코드와 동일하게 동작합니다.
const square = x => { return x * x }
// 위 코드와 동일하게 동작합니다.
const square = function (x) {
return x * x
}
it("테스트 케이스 설명", function () {
// 테스트 코드
// 예상 결과와 실제 결과를 비교하여 테스트 수행
}
)
🔸 expect(테스트하는값).matcher
: 테스트하는 값과 기대값을 비교.
matcher
: 기대하는 조건.equal
: 두 값이 타입까지 엄격하게 같은지 검사.const
사용 이유?🔸 불변셩(immutability) 유지 : const
는 변수에 재할당을 허용하지 않기 떄문에, 선언된 이후 값이 변경되지 않음. 이로인해, 코드의 예측 가능성이 높아지고, 코드를 이해하고 유지보수하기 쉬워짐.
🔸 실수 방지 : 실수로 값이 변경되는 경우를 방지해 코드의 안정성을 높임.
🔸 코드 최적화 : 일부 JS 엔진은 const
를 사용한 변수를 상수로 취급하고 더 효율적으로 최적화할 수 있음. 이는 런타임 최적화와 관련이 있으며, 코드 실행 속도를 높일 수 있음.
🔸 버그 예방 : 의도치 않은 값 변경으로 인한 버그를 방지할 수 있음.
🔸 의미 전달 : 해당 변수는 변경되지 않을 것이라는 의미를 코드에서 명확하게 전달하여 변수의 용도와 특성을 명확히 전달하는데 도움을 줌.
🔸 자바스크립트는 함수가 호출되는 환경와 별개로, 기존에 선언되어 있던 환경(어휘적 환경)을 기준으로 변수를 조회하려고 함.
🔸 이는 외부함수의 변수에 접근할 수 있는 내부함수
를 클로져 함수로 부르는 이유가 됨.
🔸 함수 내부에서 객체를 동적으로 생성하고 반환하는 함수로, 처음 함수를 호출할 때만 인수를 전달하고 이후 함수 내부에 있는 객체에 접근할 때 인수전달이 필요하지 않고 필요시 프로퍼티에 접근해서 값을 수정할 수 있음.
🔸 객체 생성에 필요한 로직을 모듈화하고, 재사용성을 높일 수 있음.
function createCounter() {
let count = 0; // 클로저에 의해 기억되는 변수
return function() {
count++;
console.log(count);
};
}
const counterA = createCounter();
counterA(); // 1
counterA(); // 2
const counterB = createCounter();
counterB(); // 1 (새로운 클로저 생성)
🔸 네임스페이스(namespace) : 구분이 가능하도록 정해놓은 범위나 영역. 이름 공간을 선언하여 다른 공간과 구분하도록 하는 것.
🔸 네임스페이싱(namespacing) : 객체나 변수가 겹치지 않는 안전한 소스코드를 만드는 것.
const myNamespace = (function () {
// 프라이빗 변수
let privateVar = "I am private!";
// 프라이빗 함수
function privateFunction() {
console.log("This is a private function!");
}
// 네임스페이싱된 오브젝트를 반환
return {
publicVar: "I am public!",
publicFunction: function() {
console.log("This is a public function!");
// 프라이빗 변수 및 함수에 접근 가능
console.log(privateVar);
privateFunction();
}
};
})();
// 외부에서는 privateVar와 privateFunction에 접근할 수 없음
console.log(myNamespace.publicVar); // "I am public!"
myNamespace.publicFunction(); // "This is a public function!" 및 private 변수 및 함수 출력
🔸 가장 기본적인 방식으로, 하나의 전역 객체를 만든 후 모든 함수, 객체, 변수를 여기에 추가하여 구현.
// 하나의 전역 객체
var MYAPP = {};
MYAPP.Parent = function() { console.log('Parent'); };
MYAPP.Child = function() { console.log('Child'); };
// 객체 컨테이너
MYAPP.modules.module1 = {};
MYAPP.modules.module1.data = {a: 1, b: 2};
MYAPP.Parent(); // Parent 출력
console.log(MYAPP.modules.module1.data.a); // 1 출력
MYAPP.Child(); // Child 출력
🔸 단점
💡 namespace로 사용되고 있는 객체를
this
를 사용하여 참조할 수 없음.
🔸 함수 영역 안에 있는this
키워드는 부모의 자식으로 불렸을 때만 그 부모객체를 가리키고, 직접 호출하였을 때는 더 이상 부모객체가 아닌 전역 객체(브라우저 환경에서는window
)를 가리켜undefined
가 반환됨.var MYAPP = {}; MYAPP.message = "Hi"; MYAPP.sayHello = function() { // this를 사용하여 리턴 return this.message; }; console.log(MYAPP.sayHello()); // Hi 출력 var direct = MYAPP.sayHello; console.log(direct()); // undefined 출력
✔️ 이러한 단점을 해결하기 위해 샌드반스 패턴(Sandbox Pattern)을 사용함
🔸 생성자를 유일한 전역으로 사용하며, 유일한 전역인 생성자에게 콜백 함수(Callback function)를 전달해 모든 기능을 샌드박스 내부 환경으로 격리 시키는 방법을 사용.
🔸 프로그램이 복잡해짐에 따라, 코드의 각 부분들이 별개의 파일로 분리되어 선택적으로 문서에 포함되는 경우가 많음.
🔸 네임스페이스로 사용할 객체를 선언할 때나, 그 내부의 프로퍼티를 정의 할 때, 이미 있는 것을 재정의하는 일도 생길 수 있음.
🔸 따라서 미리 선언되었는지를 확인하고 정의해야 함.
if(typeof MYAPP === "undefined") {
var MYAPP = {};
}
// 혹은
var MYAPP = MYAPP || {};
const adder = x => {
return y => {
return x + y
}
}
adder(50)(10) // 60
const subtractor = x => y => {
return x - y
}
subtractor(50)(10) // 40
const htmlMaker = tag => textContent => `<${tag}>${textContent}</${tag}>`
const liMaker = htmlMaker('li')
liMaker('1st item') // `<li>1st item</li>`
const overTwenty = ['hongsik', 'minchul', 'hoyong'];
let allowedToDrink = overTwenty;
overTwenty.push('san');
expect(allowedToDrink).to.deep.equal(['hongsik', 'minchul', 'hoyong', 'san']);
overTwenty[1] = 'chanyoung';
expect(allowedToDrink[1]).to.deep.equal('chanyoung');
.equal
아닌.deep.equal
을 사용하는 이유?
.equal
는 간단한 값(원시 자료형)의 비교에 사용되며, 값이 정확하게 일치해야 함..deep.equal
: 객체나 배열과 같은 복합 자료형에 사용되며, 요소나 속성 값들이 일치해야 함.- 따라서, 배열에
.equal
을 사용하게 되면 두 배열이 정확히 동일한 객체여야 하기 때문에 그 배열의 요소가 같은지 비교하려면.deep.equal
을 사용해야 함.
🔸 JS에서 객체를 비교할 때, ===
연산자는 참조(주소)를 비교함. 따라서 객체의 내용이 같더라도 서로 다른 객체일 경우 ===
비교연산자는 항상 false
를 반환함.
🔸 Array를 함수의 전달인자로 전달할 경우, reference가 전달됨.
🔸 함수 호출 시 인자가 어떻게 전달되는지를 나타내는 개념으로
🔸 call(pass) by value (값에 의한 호출)
🔸 Call by Reference (참조에 의한 호출) or call by sharing
🔸 method는 '어떤 객체의 속성으로 정의된 함수' (method는 항상 '어떤 객체'의 method)
🔸 전역 변수에 선언한 함수도 브라우저 환경에서는 window 객체의 속성으로 정의된 함수라고 할 수 있음. window.
접두사 없이도 참조가 가능하기 때문에(window.foo()
라고 사용해도 됨), 생략하고 쓰는 것뿐
🔸 따라서 호출될 때마다 어떠한 객체의 method일 텐데, 그 '어떠한 객체'를 묻는 것이 this
.
🔸 반면에, 화살표 함수는 자신의 this
가 없음. 화살표 함수는 함수가 정의된 시점에서 외부 스코프(lexical context)의 this
값을 가져옴. 이것을 Lexical scoping이라고 함.
🔸 전역 범위에서 정의된 화살표 함수의 this
는 전역 객체. (보통 브라우저 환경에서는 window
, Node.js 환경에서는 global
.)
🔸 이러한 특성 때문에, 화살표 함수는 주로 콜백 함수나 간단한 익명 함수 등에서 사용되며, 자체적인 this
가 필요하지 않은 상황에서 효과적으로 사용됨.
🔸 this 참고 자료
// rest parameter
function getAllParamsByRestParameter(...args) {
return args;
}
// arguments (spread syntax 도입 이전에 함수의 전달인자들을 다룰 때 사용. 모든 함수의 실행 시 자동으로 생성되는 '객체')
function getAllParamsByArgumentsObj() {
return arguments;
}
const restParams = getAllParamsByRestParameter('first', 'second', 'third');
const argumentsObj = getAllParamsByArgumentsObj('first', 'second', 'third');
expect(Array.isArray(restParams)).to.deep.equal(true);
expect(Array.isArray(argumentsObj)).to.deep.equal(false);
...args
는 rest parameter로 함수에 전달된 모든 인자들을 항상 배열로 수집함. 따라서 함수가 호출될 때 넘겨진 모든 값들이 배열 형태로 args
변수에 저장됨. 반환값은 이 배열.arguments
는 함수 내에서 항상 사용 가능한 특별한 객체로, 함수에 전달된 모든 인자들을 수집함. 배열이 아닌 유사 배열 객체로 배열 메서드를 직접 사용할 수 없음. 반환값은 arguments
객체 자체.argument
를 배열로 변환하고자 한다면, Array.from(arguments)
[...arguments]
🔸 할당하기 전 왼쪽에는, rest 문법 이후에 쉼표가 올 수 없음.
const [first, ...middle, last] = array // X
🔸 객체의 단축(shorthand) 문법
const name = 'John';
const age = 30;
const person = {
name: name,
age: age,
};
// 단축 문법
const person = {
name,
age,
};
🔸 변수나 함수를 선언했을 때 코드 범위(scope) 내의 최상단으로 끌어올려지는 현상.
🔸 호이스팅은 스코프 단위로 일어남
var
의 생성 과정선언
+ 초기화
할당
🔸 선언
과 초기화
가 동시에 일어나 할당
전에 호출하면 에러를 내지 않고 undefined
가 발생
let
의 생성 과정선언
초기화
할당
🔸 선언
다음에 초기화
가 일어나 호이스팅되면서 선언
이 일어나지만, 초기화
단계는 실제 코드에 도달했을 때 일어나기 때문에 에러를 발생시킴
🔸 변수가 호이스팅 되기는 하지만, 그 값이 할당되지 않을 경우에는 사용불가
- let은 TDZ의 영향을 받지 않음
let num = 10 function showNum() { console.log(num) // 10 }
- 정상적으로 10을 출력
let num = 10 function showNum() { console.log(num) // Error let num = 20 }
- 함수 내부에 있는
let
이 호이스팅이 일어나 최상단에 끌어올려지지만, 변수 선언(let num
)만 끌어올려지고 값의 초기화는 이루어지지 않았기 때문에 에러를 발생시킴.- 만약 호이스팅이 없었으면 함수 바깥에 있는 10을 출력.
const
의 생성 과정선언
+ 초기화
+ 할당
🔸 선언과 할당이 동시에 됨. 따라서 재할당시 에러를 일으킴
선언
+ 초기화
🔸 함수 표현식 : 호이스팅 ❌. (변수 선언부만 호이스팅함)
🔸 함수 선언문 : 호이스팅 ⭕. (함수 전체를 호이스팅함)
🔸 일시적인 사각지대로, 변수를 사용하는 것을 비허용하는 개념상의 공간.
🔸 TDZ에 있는 변수는 선언은 되었지만 아직 초기화가 되지 않아서 변수에 담길 값을 위한 공간이 메모리에 할당되지 않은 상태.
🔸 var
는 TDZ에 영향을 받지 않으나, let
과 const
는 영향을 받아 TDZ에 있는 값에 접근하면, ReferenceError
발생