function outer() {
let count = 0; // 외부 함수의 지역 변수
return function inner() {
count++;
console.log(`현재 count는 ${count}입니다.`);
};
}
const counter = outer(); // outer는 실행되고, inner가 반환됨
counter(); // 현재 count는 1입니다.
counter(); // 현재 count는 2입니다.
겉으로만 봤을 때는 counter를 실행하면 count가 각각 1, 1로 출력될 것 같지만 실제로는 아니다.
outer() 함수는 실행되고 사라졌지만,그 안에서 만든 inner() 함수는 count를 기억하고 있다.
왜냐하면 count가 클로저에 포함되어서 메모리에 살아있기 때문이다.
클로저의 핵심 구성
구성 요소
설명
외부 함수 (outer)
지역 변수를 선언하는 함수
내부 함수 (inner)
외부 함수의 지역 변수에 접근하는 함수
반환
내부 함수가 외부로 반환되어 사용됨
기억
외부 함수의 지역 변수를 내부 함수가 기억 (→ 클로저)
그렇다면 클로저를 왜 쓰는걸까?
1. 데이터 보호 / 은닉 (캡슐화): 외부에서 직접 접근 못하게 하고, 함수로만 조작하도록 할 수 있음.
[객체와 Map의 차이점 💡] 1) 객체는 주로 문자열을 key로 사용하고, 순서가 보장되지 않음. (비록 ES6 이후 객체는 키의 순서를 어느 정도 보장하지만 Map보다는 덜 신뢰할 수 있음) 2) Map은 임의의 자료형을 key로 사용 가능하고, 항상 순서를 보장하며, 성능이 더 뛰어날 때가 많음.
var, let, const는 크게 변수의 범위(스코프) / 중복 선언 / 호이스팅으로 구분하여 비교할 수 있다.
변수의 범위로 보는 차이점
1. var는 함수 스코프(function scope)를 가지는 변수이다. 즉, 변수를 선언한 함수 내에서만 유효하고, 함수 밖에서는 접근할 수 없다.
함수 바깥에서 선언하면 전역 변수(global variable)로 취급된다.
* 함수 스코프에서만 가지므로 if문이나 for문 등에서는 적용이 되지 않는다.
function sayHi () {
var say = 'Hi';
console.log(say); // Hi 출력
}
console.log(say); // Error 발생
if(true) {
var title = 'JavaScript';
console.log(title); // JavaScript 출력
}
console.log(title); // JavaScript 출력
for(var i = 1; i < 4; i++;) {
console.log(i); // 1부터 3까지 출력
}
console.log(i); // 4 출력
2. let, const은 블록 스코프(block scope)를 가지므로, 변수를 선언한 {} 안에서만 유효하고, 그 밖에서는 접근할 수 없다.
function sayHi () {
let say = 'Hi';
console.log(say); // Hi 출력
}
console.log(say); // Error 발생
if(true) {
const title = 'JavaScript';
console.log(title); // JavaScript 출력
}
console.log(title); // Error 발생
for(let i = 1; i < 4; i++;) {
console.log(i); // 1부터 3까지 출력
}
console.log(i); // Error 발생
중복 선언
1. var는 같은 이름으로 선언된 변수를 다시 선언할 수 있다.
중복 선언 시에는 나중에 선언된 값으로 덮어씌워진다.
var title = 'JavaScript';
console.log(title); // JavaScript 출력
var title = 'HTML';
console.log(title); // HTML 출력
title = 'CSS';
console.log(title); // CSS 출력
2. let는 같은 스코프 내에서 같은 이름의 변수는 다시 선언할 수 없다.
let title = 'JavaScript';
console.log(title); // JavaScript 출력
let title = 'HTML';
console.log(title); // Syntax Error
title = 'CSS';
console.log(title); // CSS 출력
3. const도 같은 스코프 내에서 같은 이름의 변수는 다시 선언할 수 없다.
또한, 선언 후 재할당이 불가능하고, 한번 값이 할당되면 그 값을 변경할 수 없다.
하지만 객체나 배열 같은 참조형 데이터의 경우, 객체의 속성이나 배열의 요소를 변경할 수 있다.
const title = 'JavaScript';
console.log(title); // JavaScript 출력
const title = 'CSS' // Syntax Error
title = 'HTML'; // Type Error
const obj = { name: 'Alice' };
obj.name = 'Bob'; // 가능
호이스팅(Hoisting)
[호이스팅이란? 🔍]
호이스팅은 자바스크립트의 변수 선언과 함수 선언이 코드 실행 전에 끌어올려지는 현상이다.
자바스크립트는 코드를 실행하기 전에 선언을 먼저 처리하고, 그 다음에 실제 코드 실행을 처리한다는 특징이 있다.
하지만 호이스팅이 어떻게 작동하는지에 따라 다르게 동작할 수 있다.
1. var로 선언된 변수는 선언만 호이스팅되고 초기화는 되지 않는다.
즉, 변수가 함수의 제일 위로 끌어올려지지만 값은 할당되지 않은 상태로 올라간다.
console.log(title); // undefined 출력
var title = 'This is title';
2. let, const으로 선언된 변수도 호이스팅이 일어나지만, 변수 선언 전에 접근하면 ReferenceError가 발생한다.
초기화 전까지는 Temporal Dead Zone (TDZ)에 있기 때문에 값을 참조할 수 없다.
console.log(title); // Reference Error
console.log(name); // Reference Error
let title = 'This is title';
const name = 'My name is name';
.js는 자바스크립트의 파일 확장자이며 일반적으로 사용하는 순수 자바스크립트 코드가 들어간다.
브라우저가 이해할 수 있는 코드이고, 리액트를 쓰지 않아도 문제 없는 파일 형식이다.
[예시 코드]
function sayHello(name) {
console.log("Hello, " + name);
}
sayHello("John");
.jsx 란?
.jsx는 JavaScript XML의 약자이며 자바스크립트 코드 안에 HTML과 비슷한 문법을 쓸 수 있게 해주는 확장자이다.
주로 React에서 사용되며, JSX 문법을 사용하면 마치 HTML처럼 UI를 구성할 수 있다.
하지만 브라우저는 JSX를 이해하지 못하므로 컴파일 변환이 필요하다.
* .js에서도 JSX 문법을 사용할 수는 있음!
[개념 알고가기 💡] 1. XML(eXtensible Markup Language)이란? 확장 가능한 마크업 언어로, HTML처럼 태그로 데이터를 표현하지만 HTML보다 훨씬 더 유연하고 데이터 중심적이다. 원래는 데이터 저장/전송용 포맷으로 만들어졌으며, 특징은 다음과 같다. 1) 사용자가 직접 태그를 정의 가능 2) 구조적 데이터 표현에 적합 3) 사람도 읽기 쉽고, 기계도 파싱하기 쉬움 * 단순히 데이터를 표현하는 방법이며, 실제 동작은 하지않고 데이터를 설명하는 "형식"이다.
* 따라서 JSX는 JavaScript XML이란 이름이지만, 실제로는 XML이 아니고XML처럼 생긴 문법을자바스크립트에 끼워 넣은 것이다.
[예시 코드]
export default function Hello() {
return <h1>Hello, world!</h1>;
}
.js 확장자에서도 JSX 문법을 사용할 수 있는데, 왜 굳이 .jsx로 사용하면서 컴파일 변환을 해줘야하는거지?
.js에서도 JSX를 사용할 수 있는 이유
그 이유는 Babel이나 Webpack 같은 빌드 도구가 .js 파일 안에서도 JSX 문법을 인식하고 변환해줄 수 있게 설정되어 있기 때문이다.
그렇기 때문에 .js 파일에서 JSX 문법을 사용한다해도, 브라우저는 애초에 JSX를 이해하지 못하기 때문에 컴파일 변환은 일어난다.
그렇다면 .jsx로 확장자를 지정해주는 이유는 뭘까?
1) 가독성: JSX가 들어있다는 걸 파일 이름만 보고 바로 알 수 있다.
2) 협업: 다른 개발자에게 "이 파일은 리액트 컴포넌트입니다"라는 신호를 준다.
3) 에디터 지원: .jsx 파일은 대부분의 에디터가 JSX 문법 강조를 더 잘 지원한다.
=> 즉, 기술적으로 꼭 .jsx를 써야 하진 않지만, 명확한 의도를 전달하는 좋은 습관이 될 수 있다!
그렇다면 컴파일 변환은 어떻게 일어나는 걸까?
JSX의 컴파일 흐름
1) JSX 코드 작성
const element = <h1>Hello</h1>;
2) Babel이 JSX 코드를 자바스크립트로 변환
const element = React.createElement("h1", null, "Hello");
라면으로 예시를 들어보자면, 라면을 먹기 위해 물을 끓이기 시작하고, 물이 다 끓으면 면을 넣고 끓인 뒤 라면을 먹는다.
코드로 표현하자면 다음과 같다.
function boilWater() {
console.log('1. 물 끓이기 시작');
for (let i = 0; i < 3; i++) {
console.log('2. 물 끓이는 중 ...');
}
console.log('3. 물을 다 끓였어요!');
}
function makeRamen() {
boilWater();
console.log('4. 면 넣고 끓이기');
console.log('5. 라면 완성!');
}
makeRamen();
코드의 흐름을 보면, makeRamen이 호출되면서 먼저 boilWater 함수가 실행된다.
boilWater 함수에서 1번이 출력된 후, 2번이 3번 출력되고 나서 3번이 출력된다.
이 과정이 끝날 때까지 다음 코드는 실행되지 않으며, 작업이 끝나면 함수를 빠져나와 그 이후의 작업인 4번과 5번이 순서대로 출력된다.
// 출력
1. 물 끓이기 시작
2. 물 끓이는 중 ...
2. 물 끓이는 중 ...
2. 물 끓이는 중 ...
3. 물을 다 끓였어요!
4. 면 넣고 끓이기
5. 라면 완성!
그렇다면 비동기에서의 라면은 어떻게 끓일까?
비동기란(Asynchronous)? : 일이 끝나길 기다리지 않고 다른 일을 먼저 처리하는 방식
일단 라면을 먹기 위해 물을 끓일 것이다.
하지만 이전과 다른 점은, 라면을 끓일동안 게임을 하다 올 것이다.
게임을 하다가 물이 끓으면 타이머가 울리고, 그때 면을 넣고 끓인 후 라면을 먹으면 된다.
코드로 표현하자면 다음과 같다.
function boilWaterAsync() {
console.log("1. 물 끓이기 시작");
setTimeout(() => {
console.log("3. 물 끓었음");
console.log("4. 면 넣고 끓이기");
console.log("5. 라면 완성!");
}, 3000);
}
function doSomethingElse() {
console.log("2. 게임하기 🎮");
}
boilWaterAsync();
doSomethingElse();
먼저 boilWaterAsync가 호출되고 1번이 출력된다.
그 후 setTimeout으로 3초 뒤 실행하게끔 3, 4, 5번이 출력되게끔하고,
그 사이에 doSomethingElse가 호출되면서 2번이 출력된다.
그리고 3초 후 3, 4, 5번이 순서대로 출력된다.
1. 물 끓이기 시작
2. 게임하기
(3초 후 ...)
3. 물 끓었음
4. 면 넣고 끓이기
5. 라면 완성!
그럼 이제 비동기에서 중요한 개념들인 콜백, Promise, async/await은 뭘까?
콜백 함수란? : 콜백은 “나중에 실행할 함수”를 인자로 전달해서, 어떤 일이 끝난 뒤 실행되게 하는 방식