JavaScript Language Model
"JavaScript 코드의 변수, 값, 함수, this, 객체, 모듈은 어떤 규칙으로 동작하고, ES6+ 문법은 각각 어떤 문제를 풀려고 생겼는가?" 이 page는 그 질문에 답한다. ECMAScript는 JavaScript 언어의 표준 사양이고, JavaScript는 브라우저나 Node.js 같은 실행 환경에서 동작하는 실제 언어다.[1] Frontend Wiki에서 이 page는 맨 아래 층에 놓인다고 볼 수 있다. SPA and CSR이 "browser JavaScript runtime"이라고, Vue.js가 "JavaScript state"라고만 부르던 층의 규칙을 여기서 설명한다. 오래 걸리는 작업의 흐름은 JavaScript Asynchronous Execution이, DOM과 script 실행 시점은 Browser DOM and Script Loading이 맡는다.
언어와 실행 환경의 경계
- ECMAScript는 JavaScript의 핵심 문법과 동작 방식을 정의하며, 표준은 ECMA가 관리한다.[1:1][2]
- 브라우저의 DOM API,
window,document,fetch는 실행 환경이 제공하는 API이므로 ECMAScript 핵심 문법과 구분해서 읽어야 한다.[3] - 이 경계 때문에 이 page는 언어 규칙만 다루고, DOM 조작은 Browser DOM and Script Loading으로 넘긴다. 이 분할은 원문의 경계 문장을 page 구성에 적용한 이 Wiki의 해석이다.
- JavaScript는 웹 클라이언트뿐 아니라 Node.js를 이용한 backend 개발에도 쓰인다.[4]
언어 특성
- JavaScript는 동적 타이핑을 쓰고 객체 지향과 함수형 프로그래밍을 함께 활용하는 스크립트 언어로 정리된다.[5]
- 객체지향 특성은 class 기반 상속이 아니라 prototype으로 상속과 다형성을, closure로 캡슐화를 구현하는 방식으로 설명된다.[6] 이 설명은 아래 "객체 모델" 절의 prototype chain과 이어진다.
- 스크립트 언어로서 호스트 실행 환경에 종속되며, 일반적으로 독립 실행 파일을 만들지 않는다.[7]
- JavaScript 엔진이 코드를 어떻게 실행하는지(인터프리터, JIT)는 이 page에서 다루지 않는다.[8]
ES6가 풀려던 문제
- ES6는 JavaScript 문법에 큰 변화를 가져온 버전이며, 그 이전 JavaScript에는 다섯 가지 불편이 있었다.[9]
- ES6 이후
let,const, arrow function, class, module, destructuring, spread, Promise가 추가되어 JavaScript를 더 안전하고 구조적으로 작성할 수 있게 되었다.[10] - ES6는 2015년 버전으로 정리된다.[11]
- 아래 표는 원문의 ES6 이전 불편 목록과 ES6 이후 추가 문법 목록을 짝지은 것이다. 짝짓기는 이 Wiki의 해석이고, 각 문법의 동작은 오른쪽 열의 절에서 원문 근거와 함께 설명한다.
| ES6 이전의 불편[9:1] | ES6+에서 쓰는 문법[10:1] | 이 Wiki에서 다루는 곳 |
|---|---|---|
var만 있어 스코프 관리가 어렵다. |
let, const |
스코프와 변수 선언 |
| 표준 모듈 시스템이 없어 파일 간 의존성을 관리하기 어렵다. | module (import/export) |
모듈 |
| 객체 지향 문법이 prototype 기반이라 처음 배우기 어렵다. | class |
객체 모델: prototype과 class |
| 비동기 코드를 작성하면 콜백 중첩이 심해진다. | Promise | [[Wiki/frontend/concepts/javascript-asynchronous-execution |
| 객체와 배열을 다루는 문법이 장황하다. | destructuring, spread, arrow function | 객체·배열 문법, 함수 호출과 this |
스코프와 변수 선언
- 스코프는 변수가 유효한 범위다. 현재 스코프에 변수가 없으면 바깥 스코프로 올라가며 찾는데, 이 구조를 스코프 체인이라 한다.[12]
- 스코프를 이해하지 못하면 변수의 수명을 예측하기 어렵고, 같은 이름의 변수끼리 영향을 주며, 전역 변수가 의도치 않게 바뀌고, 클로저와 모듈을 이해하기 어려워진다.[13]
var는 블록 스코프가 아니라 함수 스코프를 따르므로if·for의 중괄호가 범위를 제한하지 못한다. 그래서 아래 코드에서 루프 안의var count는 바깥count와 같은 변수로 취급되어, 루프가 끝나면 값이 5로 바뀐다.[14]
var count = 99;
for (var count = 0; count < 5; count++) {
console.log(count);
}
console.log(count); // 5
let은 블록 스코프를 따르므로 블록 밖에서 접근하면 ReferenceError가 나고, 반복문의 임시 변수가 바깥 스코프로 새지 않는다.[15]const는 재할당만 막는다. 객체나 배열을const로 선언해도 참조가 가리키는 객체의 내부 값은 바꿀 수 있으므로, 객체 자체를 불변으로 만들려면 별도의 불변 처리 방식이 필요하다.[16]
const user = {
name: "hong",
};
user.name = "kim"; // 가능
user = {
name: "lee",
}; // TypeError
- 호이스팅은 선언이 코드 실행 전에 먼저 처리되는 동작이다.
var는 선언과 초기화가 함께 처리되므로 선언 전에 접근해도 에러가 아니라undefined가 나온다.[17] let과const도 호이스팅되지만 선언문에 닿기 전 구간, 즉 TDZ(일시적 사각지대)에서는 접근할 수 없어 ReferenceError가 난다.[18]var는 선언 전 접근을undefined로 넘겨 버그를 숨길 수 있지만,let과const는 초기화 전 접근을 에러로 드러내 실수를 일찍 발견하게 한다.[18:1]- 아래 표는 위 bullet의 원문 내용을 선언 방식별로 모은 것이다. 원문이 따로 적지 않은 칸은 그렇게 표시했다.
| 선언 | 스코프 | 선언 전 접근 | 재할당 |
|---|---|---|---|
var |
함수 스코프, 블록이 범위를 제한하지 못한다.[14:1] | undefined가 나온다.[17:1] |
가능하다(위 루프 예제에서 count가 바뀐다).[14:2] |
let |
블록 스코프다.[15:1] | TDZ에서 ReferenceError가 난다.[18:2] | 가능하다.[19] |
const |
원문은 따로 적지 않는다. | TDZ에서 ReferenceError가 난다.[18:3] | 불가능하다. 다만 객체 내부 프로퍼티 변경은 막지 않는다.[16:1] |
값과 참조
- 원시 타입은 값 자체를 저장한다. 객체와 배열 같은 참조 타입은 같은 공간의 값을 참조하므로, 한쪽에서 바꾸면 다른 쪽에서도 바뀐 값이 보인다.[20]
- Spread 복사는 얕은 복사다. 가장 바깥 레벨만 새로 만들고 중첩 객체는 원본과 같은 참조를 공유하므로, 복사본의
address.city를 바꾸면 원본도 바뀐다.[21]
const original = {
name: "hong",
address: {
city: "seoul",
},
};
const copied = {
...original,
};
copied.address.city = "busan";
console.log(original.address.city); // busan
- 중첩 객체까지 독립적으로 복사하려면
structuredClone()으로 깊은 복사를 할 수 있다. 다만 함수나 DOM 노드처럼 복사할 수 없는 값이 있다.[22] const객체의 내부 변경과 spread 복사의 참조 공유는 모두 변수에 담긴 것이 객체 자체가 아니라 참조라는 같은 규칙에서 나온다고 볼 수 있다. 이 연결은 이 Wiki의 해석이다.
값 평가: truthy와 falsy, ||, ??, ?.
false,0,-0,"",null,undefined,NaN은 falsy로 평가되고, 그 외 대부분의 값은 truthy로 평가된다. 그런데0,"",false는 실제로 의미 있는 값일 수 있다.[23]||는true나false를 돌려주는 연산자가 아니다. 왼쪽 값이 truthy면 왼쪽 값을, falsy면 오른쪽 값을 반환하므로, 나이로는 유효한0도 기본값으로 덮인다.[24]??는 nullish 값인null과undefined만 기본값으로 대체하고0,false,""는 유효한 값으로 보존한다. 이 값들이 의미 있는 입력인 경우가 많아서 기본값 처리에는??가 더 안전한 경우가 많다.[25]
const age = 0;
console.log(age || "값누락"); // "값누락"
console.log(age ?? "값누락"); // 0
- 아래 표는 원문의 두 규칙을 입력 값별로 펼친 것이다.
0의 결과는 원문 예제에 있고, 나머지 행은 같은 규칙을 적용한 이 Wiki의 정리다.
a의 값 |
조건 평가 | a || "기본값" |
a ?? "기본값" |
|---|---|---|---|
0 |
falsy | "기본값" |
0 |
"" |
falsy | "기본값" |
"" |
false |
falsy | "기본값" |
false |
null |
falsy | "기본값" |
"기본값" |
undefined |
falsy | "기본값" |
"기본값" |
- Optional chaining(
?.)은 중간 값이null이나undefined일 때 TypeError 대신undefined를 반환한다. API 응답처럼 구조가 항상 보장되지 않는 데이터에 유용하지만, 에러를 숨기는 도구가 아니라 값이 없을 수 있는 구조를 명시적으로 다루는 도구로 이해해야 한다.[26] - Template literal은 문자열 안에 표현식을 직접 넣게 하고, 여러 줄 문자열을 자연스럽게 쓰게 한다.[27]
- 각 문법이 몇 번째 ECMAScript 버전에 들어왔는지는 원문에 없어 적지 않는다.[28]
함수는 값이다
- JavaScript에서 함수는 값이다. 변수에 담고, 다른 함수의 인자로 넘기고, 반환값으로 쓸 수 있으며, 이 특성 때문에 콜백, 고차 함수, 비동기 처리 같은 패턴이 가능하다.[29][30] 콜백이 비동기 흐름에서 어떻게 쓰이는지는 JavaScript Asynchronous Execution에서 이어진다.
- 기본 매개변수는 인자가 전달되지 않았을 때의 기본값을 정해 함수 안의 방어 코드를 줄인다.[31]
- Rest parameter는 개수가 정해지지 않은 인자를 배열로 모으며, 항상 마지막 매개변수여야 한다.[32]
- 배열 고차 함수는 목적이 서로 다르다.
map은 각 요소를 변환해 새 배열을 만들고,filter는 조건을 만족하는 요소만 모으고,reduce는 배열을 하나의 값으로 누적하고,forEach는 작업만 수행하고 반환값을 모으지 않는다.[33][34]
const numbers = [1, 2, 3, 4, 5];
const doubled = numbers.map((number) => number * 2);
const evens = numbers.filter((number) => number % 2 === 0);
const sum = numbers.reduce((total, number) => total + number, 0);
reduce는map과filter를 모두 구현할 수 있어 가장 범용적인 고차 함수로 정리된다.[35]- Spread와 배열 메서드는 원본을 직접 바꾸지 않고 새 배열이나 객체를 만드는 코드를 쓸 때 유용하다.[33:1][36]
함수형 프로그래밍과 클로저
- 함수형 프로그래밍은 함수를 일급 시민으로 취급하고, 부수 효과를 피하며, 불변 데이터를 다루는 프로그래밍 패러다임으로 정리된다.[37] 일급 시민은 위의 "함수는 값이다" 규칙과 같고, 아래 순수 함수는 부수 효과를 피하는 부분에, 불변성은 불변 데이터를 다루는 부분에 대응한다고 볼 수 있다. 이 대응은 이 Wiki의 해석이다.
- 순수 함수는 같은 입력에 항상 같은 출력을 내고, 외부 상태를 바꾸지 않으며, 참조 투명성을 보장한다.[38]
- 불변성은 값을 한 번 할당한 뒤 바꾸지 않는 성질이며, 예측 가능한 코드를 쓰는 데 도움이 된다.[39] 위 "값과 참조" 절에서 본 것처럼
const만으로는 객체 내부 불변이 보장되지 않으므로, 불변성은 선언 키워드가 아니라 코드 작성 방식의 문제로 볼 수 있다. 이 연결은 이 Wiki의 해석이다. - 클로저는 함수가 선언된 범위의 변수를 캡처해 저장하는 것이고, JavaScript는 closure로 캡슐화를 구현한다.[40][6:1]
- 아래 FizzBuzz 예제는 3과 5의 배수 판단을
fizz와buzz함수로 분리한다.[41] 순회 콜백은 바깥 스코프에 선언된 두 함수를 참조해 결과를 조합하므로, 콜백이 바깥 변수를 캡처하는 클로저의 예로 읽을 수 있다. 이 설명은 이 Wiki의 해석이다.
function fizzclosure() {
const fizz = (index) => (index % 3 === 0) ? "fizz" : "";
const buzz = (index) => (index % 5 === 0) ? "buzz" : "";
Array(100).fill().map((x, i) => i + 1).forEach(index => {
let result = fizz(index) + buzz(index);
console.log(result.length == 0 ? `${index}` : result);
});
}
함수 호출과 this
- 일반 함수의
this는 함수가 선언된 위치가 아니라 호출 방식으로 정해진다.[42] Java와 달리 함수가 호출되는 시점에 동적으로 결정된다.[43] user.sayHello()처럼 객체의 메서드로 호출하면this는 그 객체다. 하지만 메서드를 변수에 따로 담아 호출하면this연결이 끊긴다.[42:1]- 콜백으로 넘긴 일반 함수는 원래 객체의
this를 잃을 수 있다. 이때this는 브라우저 일반 script에서는window를, Node.js에서는Timeout객체를 가리킬 수 있고, strict mode나 module에서는 다르게 동작할 수 있다. 핵심은 콜백 안 일반 함수의this가 바깥 객체와 자동으로 연결되지 않는다는 점이다.[44] Non-strict 환경에서 호출 객체를 생략하면window가 된다는 기본 바인딩 설명도 같은 방향이다.[45] - 화살표 함수는 자기
this를 만들지 않고, 선언된 위치의 상위 스코프this를 그대로 쓴다(lexical this).[46][47] 그래서setTimeout콜백을 화살표 함수로 쓰면 바깥 메서드의this가 유지된다.
const person = {
name: "Alice",
sayHello: function () {
setTimeout(() => {
console.log(this.name);
}, 100);
},
};
person.sayHello(); // Alice
- 화살표 함수를 객체 메서드로 쓰면 호출한 객체를
this로 받지 못한다. ES Module이나 strict mode에서는this가undefined라 에러가 날 수 있으므로, 객체 메서드에는 일반 함수 문법이 안전하다.[48]
| 구분 | 일반 함수 | 화살표 함수 |
|---|---|---|
this를 정하는 기준 |
호출 방식[42:2] | 선언된 위치의 상위 스코프 this[46:1] |
setTimeout 같은 콜백 안에서 |
바깥 객체의 this를 잃을 수 있다.[44:1] |
바깥 함수의 this를 그대로 쓴다.[46:2] |
| 객체 메서드로 쓸 때 | 호출한 객체를 this로 받는다.[42:3] |
호출한 객체를 받지 못한다.[48:1] |
- 묵시적 바인딩, 명시적 바인딩,
new바인딩과call·apply·bind는 원문에 제목만 있어 이 page에서 다루지 않는다.[49]
객체·배열 문법
- 객체 단축 문법은 변수명과 프로퍼티명이 같을 때 반복을 줄이고 메서드도 짧게 쓰게 해, 객체 구조를 더 명확하게 보여준다.[50]
- 계산된 프로퍼티 이름은
[]안 표현식의 평가 결과를 key로 쓰며, API 응답 가공, 동적 폼 데이터, 설정 객체를 만들 때 유용하다.[51] - Destructuring은 배열이나 객체에서 필요한 값을 꺼내 변수로 선언하는 문법이다. 별칭, rest, 중첩 패턴을 지원하지만, 중첩이 너무 깊으면 가독성이 떨어지므로 적절히 나누어 쓰는 편이 좋다.[52]
- Vue Reactivity는 reactive object의 property를 destructuring하면 추적이 끊긴다고 설명한다. Destructuring이 값을 꺼내 새 변수로 선언하는 문법이라는 점과 함께 읽으면, 꺼낸 변수가 원래 객체를 거치지 않는 이유를 이해하기 쉽다고 볼 수 있다. 이 연결은 이 Wiki의 해석이다.
Set은 중복 없는 값의 집합이다.Map은 key-value를 저장하며, 일반 객체와 달리 문자열뿐 아니라 객체나 함수도 key로 쓸 수 있다.[53]
객체 모델: prototype과 class
- JavaScript는 prototype 기반 객체 지향 언어다. 객체는 자기 프로퍼티를 먼저 찾고, 없으면 prototype에서, 거기에도 없으면 그 위 prototype으로 계속 올라가며 찾는다. 이 연결 구조가 prototype chain이다.[54]
const parent = {
sayHello() {
console.log("hello");
},
};
const child = Object.create(parent);
child.sayHello(); // hello
- 생성자 함수 방식에서 메서드를 생성자 안에서 만들면 인스턴스마다 함수가 새로 생기고, prototype에 등록하면 모든 인스턴스가 같은 메서드를 공유해 메모리 효율적이다.[55]
- ES6
class는 새 객체 모델이 아니다. 기존 prototype 구조를 더 명확한 문법으로 표현한 문법적 설탕이다.[56] class 문법으로 쓴 메서드도 내부적으로는 prototype에 등록된다.[57] 그래서 JavaScript 객체 모델을 제대로 이해하려면 class만 외우기보다 prototype chain을 함께 이해해야 한다.[57:1] constructor는 인스턴스를 초기화한다.static메서드는 인스턴스가 아니라 클래스 자체에 속하고,#private field는 클래스 안에서만 접근할 수 있으며,extends로 상속한 뒤 같은 이름의 메서드를 다시 정의하면 오버라이딩이 된다.[58]- 객체지향 특성과의 연결은 이 정도로 정리된다. 캡슐화는 private field와, 상속은
extends·super·오버라이딩과 관련된다. SOLID는 설계 원칙이라 별도의 객체지향 설계 문서가 맡을 범위다.[59] - prototype 기반 상속 패턴을 더 자세히 다룬 노트는 이 batch에서 확인하지 못했다.[60]
모듈
- 모듈이 없으면 여러 파일이 같은 전역 스코프를 공유해 변수 이름 충돌, 로딩 순서에 따른 깨짐, 의존성 파악 곤란이 생긴다. 모듈 시스템은 파일마다 독립 스코프를 주고
export와import로 의존성을 명시한다.[61] - Named export는 여러 값을 이름을 붙여 내보내고, 같은 이름이나 alias로 가져온다. Default export는 대표 값 하나를 내보내고, 가져오는 쪽에서 원하는 이름으로 받는다.[62]
// 내보내기
export const PI = 3.14;
export default function greet() {}
// 가져오기
import greet, { PI } from './module.js';
- ESM의
import는 정적이라 코드 실행 전에 모듈 의존성을 분석할 수 있고, 그래서 tree shaking에 유리하다.import선언은 모듈 평가 전에 처리되므로 개념적으로는 호이스팅된다고 설명할 수 있다.[63] - CommonJS는 Node.js에서 많이 쓰이던 모듈 시스템으로 런타임에
require()로 모듈을 불러온다. ESM은import/export구조를 실행 전에 정적으로 분석하며, 브라우저 표준 모듈 시스템으로도 쓰인다.[64] - 브라우저에서 ESM을 쓰려면 script에
type="module"을 지정한다.[65] 이 script의 실행 시점과 스코프 차이는 Browser DOM and Script Loading에서 일반 script와 비교한다.
Proxy와 Reflect
- Proxy는 객체의 기본 동작을 가로채는 객체다. 속성 조회, 할당,
in검사, 삭제 같은 동작 사이에 trap(get,set,has,deleteProperty)으로 중간 로직을 넣는다.[66] - Reflect는 객체의 기본 동작을 함수 형태로 제공하는 내장 객체다. Proxy trap 안에서 원래 동작을 그대로 위임할 때 쓴다. Proxy는 가로채는 역할, Reflect는 기본 동작을 안전하게 위임하는 역할로 나눠 이해할 수 있다.[67]
- Proxy로 값 검증과 접근 제어처럼 객체 사용 규칙을 객체 바깥에서 감싸 정의할 수 있다.[68]
- Proxy는 값이 바뀌는 순간을 감지할 수 있어 데이터가 바뀔 때 화면을 다시 그리는 반응형 시스템의 기초가 되며, Vue 3 반응형 시스템도 이 아이디어와 연결된다.[69]
const state = new Proxy(
{
count: 0,
},
{
set(target, prop, value, receiver) {
const result = Reflect.set(target, prop, value, receiver);
console.log(`${prop} 값이 ${value}로 변경되었습니다.`);
return result;
},
}
);
state.count = 1;
- Vue Reactivity는 Vue 3의
reactive()가 Proxy로 object access를 가로채 dependency tracking과 trigger를 수행한다고 설명한다. 이 절은 그 설명이 기대는 Proxy trap과 Reflect 위임을 언어 차원에서 보여준다고 읽을 수 있다. Vue 내부 구현의 세부는 이 page의 원문 범위 밖이다.
흔한 실패 모드
- 아래 표는 위 절의 원문 근거를 실수 중심으로 다시 모은 것이다.
| 실수 | 결과 | 원인 |
|---|---|---|
||로 기본값을 준다. |
유효한 0, "", false가 기본값으로 덮인다.[24:1] |
||는 falsy 전체를 대체한다. |
| 선언 전에 변수를 쓴다. | var는 undefined로 버그를 숨기고, let·const는 ReferenceError를 낸다.[17:2][18:4] |
호이스팅 뒤 초기화 방식이 다르다. |
const 객체를 불변으로 여긴다. |
내부 프로퍼티가 바뀐다.[16:2] | const는 재할당만 막는다. |
| 메서드를 떼어 호출하거나 일반 함수를 콜백으로 넘긴다. | this가 원래 객체를 가리키지 않을 수 있다.[42:4][44:2] |
일반 함수의 this는 호출 방식으로 정해진다. |
| 화살표 함수를 객체 메서드로 쓴다. | 호출한 객체를 this로 받지 못한다.[48:2] |
화살표 함수는 자기 this를 만들지 않는다. |
| Spread 복사본의 중첩 객체를 바꾼다. | 원본의 중첩 객체도 바뀐다.[21:1] | Spread는 얕은 복사다. |
class를 prototype과 별개인 모델로 여긴다. |
prototype chain으로 동작하는 코드를 이해하지 못한다.[56:1][57:2] | class는 prototype 위의 문법적 설탕이다. |
관련
- JavaScript Asynchronous Execution: 함수가 값이라 콜백이 가능하고, ES6가 풀려던 "콜백 중첩" 문제가 Promise와 async/await로 이어진다.
- Browser DOM and Script Loading: ECMAScript 문법과 호스트 DOM API의 경계, module script의 실행 시점을 이어서 설명한다.
- Vue Reactivity: Proxy와 Reflect 절이
reactive()의 Proxy 추적을, destructuring 절이 추적이 끊기는 지점을 언어 차원에서 받친다. - Vue.js: Vue가 "JavaScript state" 위에서 동작한다고 할 때 그 state의 값, 참조, 함수 규칙이 이 page의 범위라고 볼 수 있다.
테스트 질문
age = 0일 때age || "값누락"과age ?? "값누락"의 결과가 다른 이유는 무엇인가?let도 호이스팅되는데 왜 선언 전에 접근하면 에러가 나는가?setTimeout콜백 안의 일반 함수는 왜 바깥 객체의this를 잃고, 화살표 함수는 왜 잃지 않는가?- ES6
class는 prototype과 다른 객체 모델인가?
출처
ECMAScript.md (Study) 36-40줄 — "ECMAScript는 JavaScript 언어의 표준 사양이다.", "JavaScript는 브라우저나 Node.js 같은 실행 환경에서 동작하는 실제 언어이고, ECMAScript는 그 언어가 어떤 문법과 동작 규칙을 가져야 하는지 정의하는 표준이다.", "즉, ECMAScript는 JavaScript의 핵심 문법과 동작 방식을 정의한다." ↩︎ ↩︎
ECMAScript.md (Computing) 14줄 — "ECMA에서 표준 관리" ↩︎
ECMAScript.md (Study) 42줄 — "브라우저의 DOM API,
window,document,fetch같은 기능은 JavaScript 실행 환경이 제공하는 API이며, ECMAScript 자체의 핵심 문법과는 구분된다." ↩︎JavaScript.md 22줄 — "웹 클라이언트 뿐 아니라 node.js를 이용해 backend 부분도 제작" ↩︎
JavaScript.md 9줄 — "동적 타이핑, 객체 지향, 함수 프로그래밍 등 활용도 높은 스크립트 언어" ↩︎
JavaScript.md 13-15줄 — "abstraction: 객체나 함수를 통한 추상화 제공", "inheritance & polymorphism: class기반 상속이 아닌 prototype 개념 활용", "encapsulation: closure 활용" ↩︎ ↩︎
JavaScript.md 19-20줄 — "호스트 실행 환경에 종속적", "일반적으로 독립적인 실행파일을 만들지 않음" ↩︎
출처 공백 — 실행 방식을 다룬 줄은 JavaScript.md 17-18줄과 ECMAScript.md (Computing) 15줄뿐이고, 두 노트의 설명은 서로 맞지 않는다. 같은 노트 묶음의 hub가 링크한 V8 노트는 Raw에 없다. 엔진 실행 방식은 공식 자료가 들어올 때까지 보류한다. ↩︎
ECMAScript.md (Study) 46-54줄 — "ES6는 JavaScript 문법에 큰 변화를 가져온 버전이다.", "
var만 존재해서 스코프 관리가 어렵다.", "모듈 시스템이 표준으로 존재하지 않아 파일 간 의존성을 관리하기 어렵다.", "객체 지향 문법이 prototype 기반이라 처음 배우기 어렵다.", "비동기 코드를 작성하면 콜백 중첩이 심해진다.", "객체와 배열을 다루는 문법이 장황하다." ↩︎ ↩︎ECMAScript.md (Study) 56줄 — "ES6 이후에는
let,const, arrow function, class, module, destructuring, spread, Promise 같은 문법이 추가되어 JavaScript를 더 안전하고 구조적으로 작성할 수 있게 되었다." ↩︎ ↩︎ECMAScript.md (Computing) 16줄 — "2015년 ES6부터 객체지향 프로그래밍 언어로 진화". 이 page는 연도만 옮겼다. "객체지향 언어로 진화"라는 부분은 같은 노트 10줄과 함께, class 기반이 아닌 prototype 상속(JavaScript.md 14줄)과 class가 prototype 위의 문법적 설탕이라는 설명(ECMAScript.md (Study) 796-798줄)에 맞지 않아 옮기지 않았다. ↩︎
ECMAScript.md (Study) 64-66줄 — "스코프는 변수가 유효한 범위이다.", "JavaScript에서 변수를 찾을 때는 현재 스코프에서 먼저 찾고, 없으면 바깥 스코프로 올라가며 찾는다. 이 구조를 스코프 체인이라고 한다." ↩︎
ECMAScript.md (Study) 68-73줄 — "변수가 어디까지 살아 있는지 예측하기 어렵다.", "같은 이름의 변수가 서로 영향을 줄 수 있다.", "전역 변수가 의도치 않게 변경될 수 있다.", "클로저와 모듈을 이해하기 어렵다." ↩︎
ECMAScript.md (Study) 77-91줄 — "
var는 블록 스코프를 따르지 않고 함수 스코프를 따른다.", "즉,if,for의 중괄호{}는var의 스코프를 제한하지 못한다.", "기존count와 같은 스코프의 변수로 취급되기 때문에 루프가 끝난 후count값이 5로 바뀐다." 코드 81-89줄을 그대로 옮겼다. ↩︎ ↩︎ ↩︎ECMAScript.md (Study) 97-120줄 — "
let은 블록 스코프를 따른다.", 코드 105줄 "console.log(message); // ReferenceError", "반복문에서 사용하는 임시 변수가 외부 스코프로 새어 나가지 않으므로 안전하다." ↩︎ ↩︎ECMAScript.md (Study) 131-151줄 — "참조가 가리키는 객체의 내부 값은 변경할 수 있다.", "
const는 재할당을 막는다.", "객체 내부의 프로퍼티 변경까지 막지는 않는다.", "객체 자체를 불변으로 만들고 싶다면 별도의 불변 처리 방식이 필요하다." 코드 135-145줄을 그대로 옮겼다. ↩︎ ↩︎ ↩︎ECMAScript.md (Study) 155-174줄 — "호이스팅은 선언이 코드 실행 전에 먼저 처리되는 JavaScript의 동작 방식이다.", "
var는 선언과 초기화가 함께 처리된다.", "그래서 선언 전에 접근해도 에러가 아니라undefined가 나온다." ↩︎ ↩︎ ↩︎ECMAScript.md (Study) 178-189줄 — "
let과const도 호이스팅된다. 하지만 선언 전에 접근할 수 없다.", "변수가 선언되기 전까지의 구간을 TDZ, 즉 일시적 사각지대라고 한다.", "TDZ는 실수를 조기에 발견하게 해준다.", "var는 선언 전에 접근해도undefined를 반환하기 때문에 버그를 숨길 수 있다. 반면let과const는 초기화 전에 접근하면 에러를 발생시켜 잘못된 사용을 막는다." ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ECMAScript.md (Computing) 28-31줄 — 코드 주석 "재할당 가능"(
let), "재할당 불가"(const) ↩︎ECMAScript.md (Computing) 20-23줄 — 원시타입 "값 자체를 저장", 참조타입(객체, 배열) "같은 공간에서 값을 참조 → 변경 시 같은 값으로 출력". 같은 표의 원시 타입 이름 목록(
string, integer, boolean)은 JavaScript 타입 이름과 맞는지 원문으로 확인할 수 없어 옮기지 않았다. ↩︎ECMAScript.md (Study) 589-610줄 — "Spread는 얕은 복사를 수행한다.", "얕은 복사는 가장 바깥 레벨만 새로 만들고, 내부의 중첩 객체는 같은 참조를 공유한다.", "
copied는 새 객체지만,address는 원본과 같은 객체를 가리킨다." 코드 593-608줄을 그대로 옮겼다. ↩︎ ↩︎ECMAScript.md (Study) 612-618줄 — "중첩 객체까지 독립적으로 복사하려면 깊은 복사가 필요하다.", "단,
structuredClone()도 함수나 DOM 노드처럼 복사할 수 없는 값이 있다." ↩︎ECMAScript.md (Study) 199-213줄 — falsy 값 목록(
false,0,-0,"",null,undefined,NaN), "그 외 대부분의 값은 truthy로 평가된다.", "중요한 점은0,"",false도 실제로는 의미 있는 값일 수 있다는 것이다." ↩︎ECMAScript.md (Study) 217-236줄 — "
||는 단순히true또는false를 반환하는 연산자가 아니다.", "왼쪽 값이 truthy면 왼쪽 값을 그대로 반환하고, 왼쪽 값이 falsy면 오른쪽 값을 반환한다.", "0은 나이 값으로는 유효하지만, JavaScript의 조건 평가에서는 falsy이므로 오른쪽 값이 선택된다." ↩︎ ↩︎ECMAScript.md (Study) 240-263줄 — "
??는 nullish 값만 기본값으로 대체한다.", "??는0,false,""를 유효한 값으로 보존한다.", "실무에서는0,false,""가 의미 있는 입력값인 경우가 많으므로 기본값 처리에는??가 더 안전한 경우가 많다." 코드 249-254줄을 그대로 옮겼다. ↩︎ECMAScript.md (Study) 279-302줄 — "Optional Chaining을 사용하면 중간 값이
null또는undefined일 때 에러를 발생시키지 않고undefined를 반환한다.", "이 문법은 API 응답처럼 구조가 항상 보장되지 않는 데이터를 다룰 때 유용하다.", "Optional Chaining은 에러를 숨기는 도구가 아니라, 값이 없을 수 있는 구조를 명시적으로 다루는 도구로 이해하는 것이 좋다." ↩︎ECMAScript.md (Study) 317-323줄 — "템플릿 리터럴을 사용하면 문자열 안에 표현식을 직접 넣을 수 있다.", "또한 여러 줄 문자열을 자연스럽게 작성할 수 있다." ECMAScript.md (Computing) 38-42줄에 같은 문법의 예시가 있다. ↩︎
출처 공백 — ECMAScript.md (Study)는
??,?.,structuredClone()등의 도입 버전을 적지 않는다. TypeScript와 JavaScript의 관계도 이 batch의 page 근거에 없다. ↩︎ECMAScript.md (Study) 338-354줄 — "JavaScript에서 함수는 값이다.", "함수를 변수에 담을 수 있고, 다른 함수의 인자로 전달할 수 있으며, 함수의 반환값으로 사용할 수도 있다.", "이 특성 때문에 콜백 함수, 고차 함수, 비동기 처리 같은 패턴이 가능하다." ↩︎
함수형 프로그래밍.md 17-22줄 — "일급 시민 (First-class Citizen)", "변수에 할당", "함수의 인자로 전달", "함수의 반환값으로 사용" ↩︎
ECMAScript.md (Study) 358-377줄 — "기본 매개변수는 인자가 전달되지 않았을 때 사용할 기본값을 정한다.", "기본 매개변수를 사용하면 함수 내부에서 다음과 같은 방어 코드를 줄일 수 있다." ECMAScript.md (Computing) 56-61줄에 같은 문법의 예시가 있다. ↩︎
ECMAScript.md (Study) 381-393줄 — "Rest Parameter는 여러 인자를 배열로 모은다.", "Rest Parameter는 인자의 개수가 정해지지 않은 함수를 만들 때 사용한다.", "주의할 점은 Rest Parameter는 항상 마지막 매개변수여야 한다." ↩︎
ECMAScript.md (Study) 693-700줄 — "
map: 각 요소를 변환해 새 배열을 만든다.", "filter: 조건을 만족하는 요소만 모아 새 배열을 만든다.", "reduce: 배열을 하나의 값으로 누적한다.", "forEach: 각 요소에 대해 작업을 수행하지만 반환값을 모으지는 않는다.", "배열 메서드는 원본 데이터를 직접 변경하지 않는 방식의 코드를 작성할 때 유용하다." 코드 685-691줄을 그대로 옮겼다. ↩︎ ↩︎함수형 프로그래밍.md 54, 64, 74줄 — "각 요소를 변환하여 새로운 배열 생성. 원본 불변.", "조건을 만족하는 요소들만 모아 새로운 배열 생성.", "배열 요소를 순회하며 하나의 결과값 생성." ↩︎
함수형 프로그래밍.md 88줄 — "
reduce는map과filter모두 구현 가능 — 가장 범용적인 고차함수." 바로 위 81-85줄의 "map을 reduce로 구현" 예제는 숫자 배열(77줄)에value.length를 써서 의도대로 동작하지 않으므로 옮기지 않았다. ↩︎ECMAScript.md (Study) 585줄 — "Spread는 원본을 직접 수정하지 않고 새 배열이나 객체를 만들 때 자주 사용된다." ↩︎
함수형 프로그래밍.md 11줄 — "함수를 일급 시민으로 취급하고, 부수 효과를 피하며, 불변 데이터를 다루는 프로그래밍 패러다임." ↩︎
함수형 프로그래밍.md 26-28줄 — "동일한 입력 → 항상 동일한 출력", "외부 상태를 변경하지 않음 (Side Effect 없음)", "참조 투명성 보장" ↩︎
함수형 프로그래밍.md 32, 34줄 — "값이 한 번 할당되면 변경되지 않음", "예측 가능한 코드 작성 가능". 사이의 33줄 "람다 계산법의 근간"은 근거가 없는 단정이라 옮기지 않았다. ↩︎
함수형 프로그래밍.md 40줄 — "선언된 범위(스코프)의 변수를 캡처해서 저장". 38-39줄의 "람다 계산식의 구현체", "이름 없는 함수로 리터럴하게 작성 가능"은 익명 함수와 클로저를 섞은 설명이라 옮기지 않았다. ↩︎
함수형 프로그래밍.md 104-112줄 — "2. 클로저 활용 — 각 판단 로직을 함수로 분리" 코드를 그대로 옮겼다. ↩︎
ECMAScript.md (Study) 403-425줄 — "JavaScript의 일반 함수에서
this는 함수가 선언된 위치가 아니라 호출 방식에 따라 결정된다.", "위 코드에서sayHello는user.sayHello()형태로 호출되었기 때문에this는user가 된다.", "하지만 메서드를 변수에 따로 담아 호출하면this연결이 끊길 수 있다." ↩︎ ↩︎ ↩︎ ↩︎ ↩︎DOM.md (JavaScript) 13-14줄 — "현재 실행 문맥(context) - 현재 함수를 호출한 객체", "자바와 달리 함수가 선언될 때가 아니라 함수가 호출될 때 동적으로 결정" ↩︎
ECMAScript.md (Study) 429-449줄 — "콜백 함수로 일반 함수를 전달하면, 원래 객체의
this를 잃어버릴 수 있다.", "브라우저 일반 script에서는window를 가리킬 수 있고, Node.js에서는Timeout객체를 가리킬 수 있다. strict mode나 module 환경에서는 다르게 동작할 수 있다.", "핵심은 콜백 내부 일반 함수의this가 바깥 객체와 자동으로 연결되지 않는다는 것이다." ↩︎ ↩︎ ↩︎DOM.md (JavaScript) 17-19줄 — "기본 바인딩", "일반적인 방식으로 그 함수를 호출한 객체", "non-strict에서 호출 객체 생략 시 window" ↩︎
ECMAScript.md (Study) 453-471줄 — "화살표 함수는 자기만의
this를 만들지 않는다.", "대신 자신이 선언된 위치의 상위 스코프에 있는this를 그대로 사용한다. 이를 lexical this라고 한다.", "화살표 함수는 자신의this를 만들지 않으므로, 바깥 함수인sayHello의this를 사용한다." 코드 457-468줄을 그대로 옮겼다. ↩︎ ↩︎ ↩︎DOM.md (JavaScript) 32-33줄 — "arrow함수는 자신만의 this를 가지지 않음", "자신이 선언된 상위 scope의 this를 그대로 물려받음(lexical this)" ↩︎
ECMAScript.md (Study) 475-493줄 — "화살표 함수는 콜백에서는 유용하지만, 객체 메서드 자체로 사용할 때는 주의해야 한다.", "화살표 함수는 자기만의
this를 만들지 않기 때문에person을this로 받지 않는다.", "ES Module이나 strict mode 환경에서는this가undefined라 에러가 날 수 있다.", "따라서 객체 메서드에는 일반 함수 문법을 사용하는 것이 안전하다." ↩︎ ↩︎ ↩︎출처 공백 — DOM.md (JavaScript) 21-25줄에 "묵시적 바인딩", "명시적 바인딩", "new 바인딩" 제목만 있고 본문이 없다.
call,apply,bind는 이 batch의 어느 Raw에도 없다. 명시적 바인딩을 다룬 source가 들어오면 이 절을 보강한다. ↩︎ECMAScript.md (Study) 501-534줄 — "객체를 만들 때 변수명과 프로퍼티명이 같으면 단축해서 쓸 수 있다.", "메서드도 짧게 작성할 수 있다.", "객체 단축 문법은 코드의 반복을 줄이고 객체 구조를 더 명확하게 보여준다." ↩︎
ECMAScript.md (Study) 538-561줄 — "객체의 key를 변수나 표현식으로 만들 수 있다.", "
[]안의 표현식이 평가된 결과가 프로퍼티 이름이 된다.", "API 응답 가공, 동적 폼 데이터, 설정 객체를 만들 때 유용하다." ↩︎ECMAScript.md (Study) 622-677줄 — "구조 분해 할당은 배열이나 객체에서 필요한 값을 꺼내 변수로 선언하는 문법이다.", "변수 이름을 바꾸고 싶으면 별칭을 사용할 수 있다.", "Rest 문법과 함께 사용할 수도 있다.", "중첩 객체에서도 사용할 수 있다.", "다만 중첩이 너무 깊으면 가독성이 떨어질 수 있으므로 적절히 나누어 쓰는 것이 좋다." ECMAScript.md (Computing) 44-48줄에 배열·객체 destructuring 예시가 있다. ↩︎
ECMAScript.md (Study) 704-725줄 — "
Set은 중복 없는 값의 집합이다.", "Map은 key-value 데이터를 저장한다.", "일반 객체와 달리Map은 key로 문자열뿐 아니라 객체, 함수 등 다양한 값을 사용할 수 있다." ↩︎ECMAScript.md (Study) 733-751줄 — "JavaScript는 prototype 기반 객체 지향 언어다.", "객체는 자신이 직접 가진 프로퍼티를 먼저 찾고, 없으면 자신의 prototype에서 찾는다. prototype에도 없으면 그 위의 prototype으로 계속 올라간다.", "이 연결 구조를 prototype chain이라고 한다." 코드 739-749줄을 그대로 옮겼다. ↩︎
ECMAScript.md (Study) 773-775줄 — "
sayHello를 생성자 내부에 만들면 인스턴스마다 함수가 새로 생성된다. 반면 prototype에 메서드를 등록하면 모든 인스턴스가 같은 메서드를 공유한다.", "이 방식은 메모리 효율적이다." ↩︎ECMAScript.md (Study) 779, 796-798줄 — "ES6의
class는 prototype 기반 객체 모델을 더 익숙하고 읽기 쉬운 문법으로 감싼 것이다.", "class는 새로운 객체 모델을 만든 것이 아니라, 기존 prototype 구조를 더 명확한 문법으로 표현한 것이다.", "즉, class는 prototype의 대체가 아니라 prototype을 사용하는 문법적 설탕이라고 볼 수 있다." ↩︎ ↩︎ECMAScript.md (Study) 1421-1423줄 — "class 문법으로 작성한 메서드도 내부적으로는 prototype에 등록된다.", "따라서 JavaScript의 객체 모델을 제대로 이해하려면 class만 외우는 것보다 prototype chain을 함께 이해해야 한다." 이 문장은 노트의 질문 절(1333줄
[!ai] AI 답변callout 아래)에 있다. ↩︎ ↩︎ ↩︎ECMAScript.md (Study) 802-884줄 — "
constructor는 인스턴스가 생성될 때 실행되는 초기화 메서드다.", "static메서드는 인스턴스가 아니라 클래스 자체에 속한 메서드다.", "#을 사용하면 클래스 내부에서만 접근 가능한 private field를 만들 수 있다.", "extends를 사용하면 기존 클래스를 상속할 수 있다.", "자식 클래스에서 부모 클래스의 메서드를 같은 이름으로 다시 정의하면 오버라이딩이 된다." ECMAScript.md (Computing) 63-73줄에 class 예시가 있다. ↩︎ECMAScript.md (Study) 894-899줄 — "캡슐화: private field와 관련", "상속:
extends,super, method overriding과 관련", "SOLID는 설계 원칙이므로 별도 OOP 또는 객체지향 설계 문서로 분리하는 것이 좋다." ↩︎출처 공백 — 이 노트 묶음의 JavaScript hub 노트(
Raw/legacy/knowledge/04 - Knowledge/Computing/JavaScript/_JavaScript.md20줄)가 링크한 "프로토타입 패턴" 노트는 Raw에 없다. 부스트캠프 학습정리 원본은 이번 batch에서 읽지 않았다. ↩︎ECMAScript.md (Study) 909-916줄 — "모듈이 없다면 여러 파일이 같은 전역 스코프를 공유하게 된다.", "서로 다른 파일의 변수명이 충돌할 수 있다.", "파일 로딩 순서에 따라 코드가 깨질 수 있다.", "어떤 파일이 어떤 기능에 의존하는지 파악하기 어렵다.", "모듈 시스템은 각 파일에 독립적인 스코프를 제공하고,
export와import를 통해 명시적으로 의존성을 연결한다." ↩︎ECMAScript.md (Study) 920-960줄 — "Named Export는 여러 값을 이름을 붙여 내보내는 방식이다.", "이름을 바꾸고 싶으면 alias를 사용할 수 있다.", "Default Export는 한 파일에서 대표 값을 하나 내보내는 방식이다.", "가져올 때는 원하는 이름으로 받을 수 있다." 코드는 ECMAScript.md (Computing) 91-98줄을 그대로 옮겼다. ↩︎
ECMAScript.md (Study) 964-974줄 — "ESM의
import는 정적 import다.", "정적 import는 코드 실행 전에 모듈 의존성을 먼저 분석할 수 있다는 뜻이다.", "사용하지 않는 코드를 제거하는 tree shaking 최적화에 유리하다.", "import선언은 모듈 평가 전에 처리된다. 그래서 개념적으로는 호이스팅된다고 설명할 수 있다." ↩︎ECMAScript.md (Study) 998-1019줄 — "CommonJS는 Node.js에서 많이 사용되던 모듈 시스템이다.", "CommonJS는 런타임에
require()로 모듈을 불러온다.", "ESM은 코드 실행 전에import/export구조를 정적으로 분석한다.", "ESM은 브라우저 표준 모듈 시스템으로도 사용된다." ↩︎ECMAScript.md (Study) 980줄 — "브라우저에서 ESM을 사용하려면 script 태그에
type="module"을 지정한다." ↩︎ECMAScript.md (Study) 1183-1214줄 — "Proxy는 객체의 기본 동작을 가로채는 객체다.", "객체의 속성을 읽거나, 값을 변경하거나, 속성을 삭제하는 동작 사이에 중간 로직을 넣을 수 있다.", "Proxy에서 특정 동작을 가로채는 메서드를 trap이라고 한다.", "
get: 속성 조회를 가로챈다.", "set: 속성 할당을 가로챈다.", "has:in연산자를 가로챈다.", "deleteProperty: 속성 삭제를 가로챈다." ↩︎ECMAScript.md (Study) 1229-1249줄 — "Reflect는 객체의 기본 동작을 함수 형태로 제공하는 내장 객체다.", "Proxy trap 안에서 원래 동작을 그대로 수행하고 싶을 때 사용한다.", "Proxy는 가로채는 역할이고, Reflect는 기본 동작을 안전하게 위임하는 역할로 이해할 수 있다." ↩︎
ECMAScript.md (Study) 1253, 1279, 1301줄 — "Proxy를 사용하면 객체에 값을 넣을 때 검증할 수 있다.", "Proxy를 사용하면 특정 속성 접근을 제한할 수도 있다.", "이처럼 Proxy는 객체의 사용 규칙을 객체 바깥에서 감싸는 방식으로 정의할 수 있다." ↩︎
ECMAScript.md (Study) 1305-1327줄 — "Proxy는 값이 변경되는 순간을 감지할 수 있다.", "이 원리를 이용하면 데이터가 바뀔 때 화면을 다시 그리는 반응형 시스템의 기초를 만들 수 있다.", "Vue 3의 반응형 시스템도 Proxy 기반 아이디어와 연결된다." 코드 1307-1323줄을 그대로 옮겼다. ↩︎