방송대 HTML5웹프로그래밍 10강: 자바스크립트 데이터 흐름과 이벤트
값이 바뀌는데 const를 써도 될까요? 배열을 처리할 때 map()과 filter() 중 무엇을 골라야 하고, 이벤트 함수에는 왜 괄호를 붙이지 않을까요? 자바스크립트 문법은 이름보다 선택 조건이 중요합니다. 이 글은 변수·연산자·함수·객체·이벤트를 일곱 가지 판정 질문으로 묶어, 새 코드를 만났을 때 알맞은 문법과 실행 결과를 스스로 결정하게 합니다.
문법을 고르는 일곱 가지 판정 질문
학습을 위해 워크숍 등록 코드를 가정해 봅시다. 사용자가 이름과 점수를 입력하고 등록 버튼을 누르는 짧은 기능에도 실행 시점, 변수 종류, 값의 타입, 배열 결과, 객체 구조, 전송 형식, 이벤트 처리 위치를 차례로 결정해야 합니다. 문법을 많이 아는 것보다 현재 요구를 다음 질문 중 어디에 놓을지 판단하는 일이 먼저입니다.
| 흐름의 지점 | 주요 도구 | 확인할 질문 |
|---|---|---|
| 코드 준비 | defer, type="module", export, import | HTML 해석과 코드 실행 순서가 안전한가? |
| 값 저장 | const, let, 원시값, 객체 | 값을 다시 대입할지, 같은 객체를 공유하는지 알 수 있는가? |
| 판정·변환 | ===, ??, ?., 배열 메서드, 함수 | 값 없음과 거짓을 구분하고 결과 모양을 예측하는가? |
| 구조화·전송 | 객체, String, Array, Date, Math, JSON | 원본이 바뀌는지, 문자열과 객체 중 무엇이 필요한가? |
| 사용자 반응 | addEventListener(), 이벤트 객체, 버블링, 위임 | 어느 요소에서 생긴 사건을 어디서 처리할 것인가? |
HTML은 구조, CSS는 표현, JavaScript는 상태와 동작을 맡습니다. JavaScript와 Java는 이름 일부가 비슷할 뿐 별개의 언어입니다. 이 차시에서는 JavaScript가 웹 문서의 요소를 선택하고, 데이터를 계산하며, 사건이 생겼을 때 함수를 실행하는 연결 고리라는 점이 중요합니다.
판정 원칙: 코드를 읽을 때는 “언제 실행할까 → 값을 다시 대입할까 → 결과는 배열인가 단일 값인가 → 원본을 바꿔도 될까 → 지금 호출할까 나중에 맡길까 → 객체와 문자열 중 무엇을 넘길까 → 어느 요소에서 사건을 처리할까”를 순서대로 묻습니다.
실행 준비는 HTML 파싱과 모듈 경계를 먼저 지킨다
짧은 학습 코드는 script 요소 안에 직접 쓸 수 있지만, 코드가 커지면 외부 .js 파일로 분리합니다. head에서 외부 파일을 불러올 때 defer를 사용하면 HTML 파싱과 다운로드를 병행하고, 문서 파싱이 끝난 뒤 스크립트를 실행합니다. 아직 만들어지지 않은 요소를 JavaScript가 먼저 찾는 오류를 피하면서도 코드와 문서를 분리할 수 있습니다.
<head>
<script defer src="./registration.js"></script>
</head>
모듈은 기능을 파일 단위로 나누고 필요한 이름만 주고받게 합니다. 다음은 학습용 계산 함수를 별도 파일에 두는 최소 예입니다.
// score.js
export function getAverage(total, count) {
return count === 0 ? 0 : total / count;
}
export const PASS_SCORE = 70;
<script type="module">
import { getAverage, PASS_SCORE } from "./score.js";
console.log(getAverage(246, 3)); // 82
console.log(PASS_SCORE); // 70
</script>
export한 이름만 다른 모듈에서 가져올 수 있으며, 가져오는 쪽은 import에 같은 이름을 씁니다. 모듈 스크립트는 문서 파싱을 막지 않는 방식으로 실행됩니다. 전역 변수를 늘리는 대신 값은 함수의 매개변수와 반환값, 모듈의 명시적 내보내기로 공유하면 데이터가 어디서 왔는지 추적하기 쉽습니다.
출력 방법 구분: 실행 상태 확인은 console.log(), 사용자 화면 출력은 요소의 textContent 같은 DOM 기능을 사용합니다. document.write()는 문서 로딩 후 실행될 때 기존 페이지를 덮어쓸 수 있으므로 서비스 코드의 출력 수단으로 삼지 않습니다.
변수는 값을 붙잡고 식별자는 역할을 말한다
변수는 프로그램이 다룰 값을 가리키는 이름입니다. 식별자는 변수·함수·클래스 같은 프로그램 요소에 붙이는 이름이며, 숫자로 시작할 수 없고 공백과 하이픈을 포함할 수 없습니다. _와 $는 사용할 수 있지만 예약어를 이름으로 쓸 수 없고 대소문자를 구분합니다.
문법을 통과하는 이름과 읽기 좋은 이름은 다릅니다. 변수는 participantName처럼 담긴 값을 설명하고, 불리언은 isRegistered, hasTicket처럼 상태를 드러내며, 함수는 calculateTotal()처럼 동사로 시작하면 역할을 추측하기 쉽습니다. 클래스 이름은 WorkshopMember처럼 대문자로 시작하는 관례를 사용합니다.
"use strict";
const workshopTitle = "웹 제작 입문";
let participantCount = 0;
const PASS_SCORE = 70;
participantCount += 1;
console.log(`${workshopTitle}: 현재 ${participantCount}명`);
기본 선택은 const이고, 변수 자체에 다른 값을 다시 넣어야 할 때만 let을 사용합니다. const는 값 전체를 얼린다는 뜻이 아니라 그 변수의 재할당을 막습니다. 따라서 const member = { score: 80 }에서 member = 다른객체는 허용되지 않지만 member.score = 85처럼 같은 객체의 속성을 바꾸는 것은 가능합니다.
백틱으로 감싼 템플릿 리터럴은 ${...} 안에 변수나 계산식을 넣고 여러 줄 문자열을 만들 수 있습니다. 문자열 연결 연산자를 연속으로 쓰는 것보다 값과 문장의 경계가 분명합니다. "use strict"는 선언하지 않은 이름에 값을 넣는 실수를 오류로 드러내며 반드시 해당 스크립트나 함수의 앞부분에 둡니다.
원시값 복사와 객체 공유는 화살표를 다르게 그린다
원시 타입에는 Number, String, Boolean, null, undefined, Symbol 등이 있습니다. 숫자는 정수와 실수를 별도 타입으로 나누지 않고, 문자열은 작은따옴표·큰따옴표·백틱으로 만듭니다. undefined는 값이 아직 정해지지 않은 상태, null은 개발자가 비어 있음을 명시한 값으로 구분할 수 있습니다.
객체·배열·함수는 참조 타입으로 다룹니다. 복사 결과를 이해할 때는 “객체 전체가 복제된다”가 아니라 “같은 객체를 가리키는 참조 값이 복사된다”라고 생각해야 합니다.
let firstScore = 74;
let copiedScore = firstScore;
copiedScore = 91;
console.log(firstScore); // 74
const firstMember = { name: "민서", score: 74 };
const sharedMember = firstMember;
sharedMember.score = 91;
console.log(firstMember.score); // 91
첫 경우에는 숫자 74가 복사되므로 두 변수가 독립적입니다. 둘째 경우에는 두 변수가 같은 객체를 가리키므로 한쪽을 통해 바꾼 속성이 다른 쪽에서도 보입니다. 정확히는 JavaScript가 인수와 대입에서 값을 전달하고, 객체 변수에 들어 있는 그 값이 객체를 가리키는 참조라는 구조입니다.
오개념 교정: const 객체의 속성이 바뀌는 현상과 참조 공유는 다른 문제입니다. const는 변수의 재할당을 막고, 참조 공유는 두 변수가 같은 객체를 가리키는지 설명합니다. 독립 사본이 필요하면 복사 방법을 별도로 선택해야 합니다.
연산자는 값 없음과 거짓을 구분해야 한다
===와 !==는 값과 타입을 함께 비교합니다. ==와 !=는 비교 전에 타입을 바꿀 수 있어 0 == false, 0 == ""처럼 의도하기 어려운 결과가 나옵니다. 입력값을 판정할 때는 엄격한 비교를 기본으로 삼으면 숨은 형 변환을 줄일 수 있습니다.
데이터가 없을 때는 선택적 체이닝 ?.과 널 병합 ??를 연결할 수 있습니다. 전자는 중간 객체가 null 또는 undefined이면 속성 접근을 멈추고 undefined를 만들고, 후자는 왼쪽 값이 두 값 중 하나일 때만 기본값을 사용합니다.
const member = {
name: "도윤",
preferences: { city: "대전" },
couponCount: 0
};
const city = member?.preferences?.city ?? "지역 미정";
const coupons = member.couponCount ?? 3;
console.log(city); // "대전"
console.log(coupons); // 0
couponCount가 0이어도 유효한 수치이므로 ??는 3으로 바꾸지 않습니다. 반면 member.couponCount || 3은 0을 거짓 같은 값으로 판단해 3을 선택합니다. &&와 ||는 결과가 확정되는 순간 뒤의 식을 실행하지 않는 단축 평가를 하며, 조건부 실행에 쓸 수 있지만 0, 빈 문자열, false도 함께 고려해야 합니다.
연산 우선순위가 헷갈리면 암기에 의존하지 말고 괄호로 의도를 드러냅니다. 간단한 두 갈래 선택은 조건 ? 참일 때 값 : 거짓일 때 값인 조건 연산자를 쓸 수 있지만, 여러 단계의 업무 규칙은 if 또는 switch가 읽기 쉽습니다. switch에서 break를 빼면 다음 case까지 실행되는 fall-through가 생깁니다.
반복문보다 먼저 결과의 모양을 정한다
for, while, do...while은 반복 조건을 직접 제어합니다. 순서 있는 배열의 값을 하나씩 읽는다면 for...of, 객체의 키를 순회한다면 for...in이 의도에 맞습니다. 배열에 for...in을 쓰면 값이 아니라 키인 인덱스가 나오므로 배열 값 순회와 혼동하기 쉽습니다.
배열 메서드는 반복 결과의 모양을 먼저 말해 줍니다. 새 배열이 필요한지, 하나의 값이 필요한지, 첫 항목 하나를 찾는지에 따라 선택합니다.
| 질문 | 메서드 | 결과 | 원본 |
|---|---|---|---|
| 각 요소로 부수 작업만 할까? | forEach() | 의미 있는 새 배열이 아니라 undefined | 콜백 동작에 따라 달라짐 |
| 각 요소를 1:1로 바꿀까? | map() | 원본과 같은 개수의 새 배열 | 메서드 자체는 유지 |
| 조건을 통과한 것만 남길까? | filter() | 0개 이상인 새 배열 | 메서드 자체는 유지 |
| 조건을 만족한 첫 요소만 찾을까? | find() | 요소 하나 또는 undefined | 메서드 자체는 유지 |
| 전체를 하나의 값으로 줄일까? | reduce() | 누적한 단일 결과 | 메서드 자체는 유지 |
직접 만든 참가자 점수 예제로 배열의 흐름을 추적해 봅시다.
const scores = [76, 91, 64, 83];
const passed = scores.filter(score => score >= 70);
const labels = passed.map(score => `${score}점`);
const total = passed.reduce((sum, score) => sum + score, 0);
const average = total / passed.length;
console.log(passed); // [76, 91, 83]
console.log(labels); // ["76점", "91점", "83점"]
console.log(total); // 250
console.log(average); // 83.333...
filter() 뒤에는 3개가 남고, map()은 그 3개를 문자열로 바꿉니다. 합계 계산에는 숫자 배열인 passed를 사용하고 초기 누적값 0에서 76 → 167 → 250 순으로 더합니다. 문자열 배열 labels를 줄이면 숫자 합계가 아니라 문자열 결합이 될 수 있습니다. 결과 모양을 먼저 정해야 메서드 연결 순서가 보입니다.
함수는 지금 실행할 코드와 나중에 넘길 코드를 가른다
함수는 입력인 매개변수를 받아 코드를 실행하고 필요하면 값을 반환합니다. 함수 선언문은 선언 위치보다 앞에서도 호출할 수 있는 호이스팅 특성이 있고, const에 담은 함수 표현식은 변수 초기화 이후에만 사용할 수 있습니다. 무명 함수는 이름 없이 변수에 담거나 다른 함수에 전달할 때 사용합니다.
function formatMember(name, level = "기본") {
return `${name} (${level})`;
}
const square = number => number * number;
const makeMember = name => ({ name, active: true });
console.log(formatMember("하린")); // "하린 (기본)"
console.log(formatMember("하린", undefined)); // "하린 (기본)"
console.log(formatMember("하린", null)); // "하린 (null)"
console.log(square(6)); // 36
console.log(makeMember("하린")); // { name: "하린", active: true }
기본 매개변수는 인수를 생략하거나 undefined를 전달했을 때만 적용됩니다. null, 0, false는 전달된 값이므로 기본값으로 교체되지 않습니다. 화살표 함수에서 객체 리터럴을 즉시 반환하려면 중괄호가 함수 본문으로 해석되지 않도록 ({ ... })처럼 소괄호로 감쌉니다.
콜백은 다른 함수에 “나중에 실행할 코드”로 전달하는 함수입니다. 이벤트 처리기, 타이머, 배열 메서드에 자주 쓰입니다. 다음 두 줄은 실행 시점이 다릅니다.
button.addEventListener("click", handleRegistration); // 함수 전달
button.addEventListener("click", handleRegistration()); // 지금 호출한 반환값 전달
첫 줄은 클릭할 때 실행할 함수 자체를 맡깁니다. 둘째 줄은 등록 과정에서 즉시 함수를 호출하고 그 반환값을 처리기로 넘기므로 의도와 어긋납니다. 괄호 유무는 단순 표기 차이가 아니라 실행 시점을 바꿉니다.
alert()는 알림 뒤 undefined, confirm()은 확인 여부인 true 또는 false, prompt()는 입력 문자열 또는 취소 시 null을 반환합니다. 이 대화상자들은 실행을 막고 표시 모양을 자유롭게 바꾸기 어려우므로 학습 단계의 반환값 확인에 적합하며, 입력 숫자는 문자열이므로 계산 전에 변환해야 합니다.
객체는 데이터와 동작을 키로 묶는다
객체는 연관된 속성과 메서드를 키·값 쌍으로 모읍니다. 일회성 데이터 묶음은 객체 리터럴이 간결하고, 같은 구조의 객체를 여러 개 만들 때는 생성자 함수나 클래스가 재사용에 알맞습니다. 클래스는 생성 규칙과 메서드가 분명하게 보이는 현대적인 문법입니다.
class WorkshopMember {
constructor(name, score) {
this.name = name;
this.score = score;
}
isPassed() {
return this.score >= 70;
}
}
const member = new WorkshopMember("서준", 82);
console.log(member.isPassed()); // true
this는 해당 메서드를 호출한 객체를 가리키며, 생성자에서는 새 객체의 속성을 채웁니다. 객체 하나만 필요하다면 { name: "서준", score: 82 }처럼 리터럴로 만들 수 있고, 과거 코드에서는 new와 생성자 함수를 조합한 형태도 만날 수 있습니다.
고정된 유효 키는 member.score 같은 점 표기법이 읽기 쉽습니다. 키가 변수에 들어 있거나 공백·특수문자를 포함하면 member[keyName], member["display name"]처럼 대괄호 표기법을 씁니다. member.keyName은 변수의 내용이 아니라 문자 그대로 keyName이라는 속성을 찾는다는 점이 결정적 차이입니다.
문자열과 배열은 원본 보존 여부를 확인한다
문자열은 원시값이며 불변입니다. trim(), replace(), toUpperCase(), slice() 같은 메서드는 원본을 고치지 않고 새 문자열을 반환하므로 결과를 변수에 다시 담아야 합니다. includes()는 포함 여부, indexOf()는 위치 또는 없을 때 -1, split()은 구분자로 나눈 배열을 만듭니다.
const rawId = " web#learner ";
const cleanedId = rawId
.trim()
.replace("#", "")
.toUpperCase();
const displayId = cleanedId.slice(0, 3)
.padEnd(cleanedId.length, "*");
console.log(rawId); // " web#learner "
console.log(cleanedId); // "WEBLEARNER"
console.log(displayId); // "WEB*******"
배열은 메서드에 따라 원본 변경 여부가 다릅니다. 이 차이를 놓치면 다른 변수가 같은 배열을 참조할 때 예상하지 못한 변경이 퍼집니다.
| 목적 | 원본을 바꾸는 메서드 | 새 결과를 만드는 방법 | 주의점 |
|---|---|---|---|
| 끝에 추가·삭제 | push(), pop() | 전개 연산자로 새 배열 구성 | 공유 배열이면 변경이 함께 보임 |
| 앞에서 삭제 | shift() | slice(1) | 나머지 요소의 위치가 달라짐 |
| 중간 삭제·삽입 | splice() | slice()와 전개 연산자 조합 | 시작 인덱스와 삭제 개수를 구분 |
| 정렬 | sort() | toSorted() | 숫자는 비교 함수를 제공 |
| 일부 복사·결합 | 해당 없음 | slice(), concat() | 끝 인덱스는 포함하지 않음 |
new Array(5)는 값 5가 든 한 칸 배열이 아니라 길이 5인 빈 배열을 만듭니다. 값 하나를 넣을 때도 의미가 분명한 배열 리터럴 [5]을 우선합니다. 숫자 sort()에는 (a, b) => a - b를 주어야 오름차순이 되며, 비교 함수를 생략하면 문자열처럼 비교되어 10이 2보다 앞설 수 있습니다.
Date·Math·JSON은 생성·계산·전송의 경계를 맡는다
Date는 new로 날짜 객체를 만듭니다. 숫자로 생성할 때 월은 0부터 11까지이므로 3월은 2입니다. getFullYear()는 연도, getMonth()는 0부터 시작하는 월, getDate()는 일자, getDay()는 일요일 0부터 토요일 6까지의 요일 인덱스를 반환합니다. 이름이 비슷한 getDate()와 getDay()를 구분해야 합니다.
getTime()은 1970년 1월 1일 UTC부터의 밀리초를 반환하므로 두 시점의 차이를 계산할 수 있습니다. 예를 들어 시작과 마감의 차이가 216,000,000ms라면 1일의 밀리초인 24 × 60 × 60 × 1000 = 86,400,000으로 나누어 2.5일입니다. 남은 날짜를 하루 단위로 올려 표시한다는 정책이라면 Math.ceil(2.5)을 적용해 3일로 표시합니다.
Math는 new 없이 정적 메서드를 사용합니다. round()는 반올림, ceil()은 위쪽 정수, floor()는 아래쪽 정수, trunc()는 소수 부분 제거입니다. 음수 -1.5에서 floor()는 -2, trunc()는 -1이므로 둘을 같은 ‘버림’으로 외우면 틀립니다. 구간의 정수 난수는 다음 형태로 계산합니다.
function getRandomInteger(min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
console.log(getRandomInteger(7, 11)); // 7~11 중 하나
여기서 가능한 정수 개수는 11 - 7 + 1 = 5개입니다. Math.random()의 0 이상 1 미만 값을 5배 하면 0 이상 5 미만, floor() 뒤에는 0~4, 마지막에 7을 더하면 7~11이 됩니다.
JSON은 JavaScript 객체처럼 보이지만 데이터를 저장·전송하는 문자열 형식입니다. 키와 문자열 값은 큰따옴표를 쓰고, 마지막 항목 뒤의 쉼표와 함수·undefined는 허용하지 않습니다. JSON.stringify()는 객체를 JSON 문자열로 직렬화하고, JSON.parse()는 올바른 JSON 문자열을 다시 사용할 수 있는 값으로 역직렬화합니다.
const record = {
name: "지우",
score: 88,
registered: true
};
const payload = JSON.stringify(record);
const restored = JSON.parse(payload);
console.log(typeof payload); // "string"
console.log(restored.score); // 88
이벤트는 대상·종류·핸들러로 출발한다
이벤트는 클릭, 키 입력, 폼 전송 시도, 문서 로딩 완료처럼 브라우저 안에서 일어난 사건입니다. 이벤트 처리는 ‘감시할 대상 + 사건 종류 + 실행할 핸들러’ 세 요소로 읽습니다. HTML의 onclick 속성은 구조와 동작을 섞고, DOM의 element.onclick은 같은 속성에 새 함수를 대입하면 이전 함수가 교체됩니다. addEventListener()는 여러 핸들러를 등록하고 옵션을 지정할 수 있는 기본 방식입니다.
| 사용자·문서 상태 | 이벤트 | 판단 기준 |
|---|---|---|
| 버튼·요소 선택 | click | 한 번의 활성화 동작을 처리 |
| 값이 입력되는 매 순간 | input | 글자 수·실시간 결과를 갱신 |
| 값 변경을 확정하고 포커스 이동 | change | 선택값 확정 뒤 처리 |
| 키를 누르는 순간 | keydown | e.key로 Enter 같은 키 확인 |
| 폼 전송 시도 | submit | 폼 전체의 제출 흐름과 검증 처리 |
| HTML 파싱 완료 | DOMContentLoaded | 이미지 완료를 기다리지 않고 초기화 |
| 이미지 등 자원까지 완료 | load | 전체 자원 완료가 필요한 후속 처리 |
const form = document.querySelector("#registration-form");
form.addEventListener("submit", handleSubmit);
function handleSubmit(event) {
event.preventDefault();
console.log("새로고침 없이 등록 데이터를 처리합니다.");
}
이벤트 이름에는 onclick이 아니라 click처럼 on을 뺀 문자열을 씁니다. 함수 이름 뒤에 괄호를 붙이지 않고 함수 자체를 전달합니다. { once: true } 옵션은 처리기를 한 번 실행한 뒤 자동으로 제거하고, { capture: true }는 캡처 단계에서 처리하도록 지정합니다.
이벤트 객체와 전파 경로가 위임을 가능하게 한다
브라우저는 사건이 발생하면 상세 정보를 담은 이벤트 객체를 핸들러의 첫 인자로 전달합니다. event.target은 실제 사건이 시작된 요소, event.key는 누른 키, clientX와 clientY는 브라우저 화면 기준 포인터 좌표입니다. preventDefault()는 폼 제출이나 링크 이동 같은 기본 동작을 막고, stopPropagation()은 이벤트가 다음 조상 또는 후속 전파 경로로 이동하는 것을 멈춥니다. 둘은 목적이 다릅니다.
중첩 요소의 이벤트는 기본적으로 대상에서 조상으로 올라가는 버블링 단계를 거칩니다. 캡처링은 반대로 바깥쪽 조상에서 대상 쪽으로 내려오는 단계이며 capture: true로 처리할 수 있습니다. 보통의 클릭 처리와 이벤트 위임에는 버블링이 핵심입니다.
const memberList = document.querySelector("#member-list");
memberList.addEventListener("click", event => {
if (event.target.tagName !== "LI") return;
event.target.classList.toggle("selected");
});
각 li에 리스너를 하나씩 붙이지 않고 부모 목록 하나에 등록했습니다. 자식에서 생긴 클릭이 부모로 올라오면 target을 검사해 실제 클릭된 항목만 처리합니다. 나중에 같은 목록에 새 li가 들어와도 사건은 같은 부모까지 버블링하므로 별도 리스너 없이 동작합니다. 이것이 이벤트 위임입니다.
실패 원인: 위임 코드에서 event.target을 확인하지 않으면 목록의 빈 영역이나 다른 자식 요소를 클릭해도 항목 처리 코드가 실행될 수 있습니다. 반대로 자식에서 무조건 stopPropagation()을 호출하면 부모가 사건을 받지 못해 위임이 끊깁니다.
여섯 판정 규칙으로 등록 코드를 검수한다
이제 흩어진 문법을 한 번의 등록 코드에서 판정해 봅시다. 완성 코드를 암기하는 대신 각 줄이 앞에서 정한 선택 조건을 충족하는지 검수합니다.
const members = [];
const form = document.querySelector("#registration-form");
const nameInput = document.querySelector("#member-name");
const scoreInput = document.querySelector("#member-score");
form.addEventListener("submit", event => {
event.preventDefault();
const name = nameInput.value.trim();
const scoreText = scoreInput.value.trim();
const score = Number(scoreText);
if (name === "" || scoreText === "" || Number.isNaN(score)) {
console.log("이름과 숫자 점수를 확인하세요.");
return;
}
const member = {
id: Date.now(),
name,
score,
passed: score >= 70
};
members.push(member);
const passedNames = members
.filter(item => item.passed)
.map(item => item.name);
const payload = JSON.stringify(members);
console.log(`통과자: ${passedNames.join(", ")}`);
console.log(payload);
});
- 사건: 폼의
submit이 콜백 실행을 시작하고 기본 새로고침은 중단합니다. - 정제·변환: 이름과 점수 문자열은
trim()으로 공백을 정리하고, 점수는Number()로 문자열에서 숫자로 바꿉니다. - 판정: 엄격한 비교와
Number.isNaN()으로 잘못된 입력을 걸러 조기 반환합니다. - 구조화: 관련 값을 객체 하나로 묶고 배열에 추가합니다. 여기서
push()는 원본 배열을 바꿉니다. - 파생 데이터:
filter()가 통과 객체만,map()이 이름만 담은 새 배열을 만듭니다. - 전송 형태:
JSON.stringify()가 객체 배열을 JSON 문자열로 바꿉니다.
이 흐름을 변형해 볼 수 있습니다. 점수 0도 유효한 입력이므로 if (!score)로 검사하면 0을 누락으로 오판합니다. 또한 const payload = members는 JSON 변환이 아니라 같은 배열 객체를 가리키는 새 변수일 뿐입니다. 무엇을 검사하고 어떤 형태로 넘길지 구분해야 코드의 경계가 분명해집니다.
핵심 개념 정리
- 외부 스크립트와 모듈은 실행 시점과 이름의 공개 범위를 명시해 코드의 출발점을 안정시킵니다.
const는 재할당을 막고, 원시값 복사와 객체 참조 공유는 변경이 퍼지는 범위를 결정합니다.===는 타입까지 비교하고,??는null·undefined만 값 없음으로 판단합니다.- 배열 메서드는 반복 횟수보다 새 배열·첫 요소·단일 누적값 같은 결과 형태로 선택합니다.
- 함수 이름을 전달하는 것과 함수를 호출하는 것은 콜백의 실행 시점을 다르게 만듭니다.
- 문자열 메서드는 새 문자열을 만들고, 배열 메서드는 원본 변경 여부를 개별적으로 확인해야 합니다.
- 이벤트 객체와 버블링을 이해하면 기본 동작 제어와 부모 요소의 이벤트 위임을 설명할 수 있습니다.
최종 판정 순서: JavaScript 코드를 만나면 실행 시점, 재할당 여부, 값의 타입과 공유 범위, 함수 결과의 모양, 원본 변경 여부, 콜백의 실행 시점, 이벤트 처리 위치를 차례로 판정합니다. 마지막으로 화면이나 서버에 넘기기 직전 값이 객체인지 JSON 문자열인지 확인합니다. 이 선택 기준은 다음 차시의 DOM·BOM에서 문서와 브라우저 객체를 다룰 때도 그대로 이어집니다.
예상문제 10선
1. head에서 외부 JavaScript 파일을 불러오면서 HTML 파싱을 막지 않고, 파싱 후 실행하려는 선언은?
정답입니다.
오답입니다. 답안을 다시 선택해 보세요.
정답 및 해설 보기
정답: ④
- ① 오답:
async는 다운로드가 끝난 시점에 실행될 수 있어 문서 파싱 완료 뒤라는 조건을 보장하지 않습니다. - ② 오답: 외부 JavaScript는
link가 아니라script의src로 연결합니다. - ③ 오답: 파일 주소는
href가 아니라src이며 모듈은type="module"로 지정합니다. - ④ 정답:
defer는 외부 파일을 병행 다운로드하고 문서 파싱이 끝난 뒤 실행하도록 합니다.
2. 다음 코드 실행 후 first.score가 95가 되는 이유를 가장 정확히 설명한 것은?
const first = { score: 70 };
const second = first;
second.score = 95;
정답입니다.
오답입니다. 답안을 다시 선택해 보세요.
정답 및 해설 보기
정답: ②
- ① 오답:
const는 변수 재할당을 막을 뿐 객체 병합이나 복사를 수행하지 않습니다. - ② 정답: 객체가 복제된 것이 아니라 같은 객체를 가리키는 값이 복사되어 한 객체의 속성을 수정했습니다.
- ③ 오답: 숫자 원시값은 값이 독립적으로 복사되며 객체 속성 공유의 원인이 아닙니다.
- ④ 오답: 점 표기법은 현재 객체의 특정 속성에 접근할 뿐 다른 객체를 검색하지 않습니다.
3. 값 0은 그대로 보존하고 null 또는 undefined일 때만 기본값 10을 쓰는 표현은?
정답입니다.
오답입니다. 답안을 다시 선택해 보세요.
정답 및 해설 보기
정답: ①
- ① 정답: 널 병합은 왼쪽이
null또는undefined일 때만 오른쪽 값을 선택합니다. - ② 오답: 논리합은 0도 거짓 같은 값으로 처리해 유효한 0을 10으로 바꿉니다.
- ③ 오답: 논리곱은 왼쪽이 거짓 같은 값이면 그 값을 반환해 누락 기본값을 채우지 않습니다.
- ④ 오답: 엄격 비교는 두 값의 일치 여부를 불리언으로 만들 뿐 기본값을 선택하지 않습니다.
4. 다음 코드의 total 값은?
const scores = [55, 72, 88, 61];
const total = scores
.filter(score => score >= 70)
.reduce((sum, score) => sum + score, 0);
정답입니다.
오답입니다. 답안을 다시 선택해 보세요.
정답 및 해설 보기
정답: ③
- ① 오답: 조건에 맞지 않는 55와 61까지 포함해 원본 전체를 더한 값입니다.
- ② 오답: 55와 88을 더해 필터 조건을 각 요소에 잘못 적용했습니다.
- ③ 정답: 필터 뒤 [72, 88]이 남고 누적값은 0 → 72 → 160이 됩니다.
- ④ 오답: 통과한 두 점수의 평균이며 이 코드는 개수로 나누지 않고 합계를 구합니다.
5. 버튼을 클릭할 때만 saveMember 함수를 실행하도록 등록한 코드는?
정답입니다.
오답입니다. 답안을 다시 선택해 보세요.
정답 및 해설 보기
정답: ②
- ① 오답:
addEventListener()의 이벤트 이름은on을 붙이지 않은click입니다. - ② 정답: 사건 이름과 함수 자체를 올바른 순서로 넘겨 클릭 시점까지 실행을 미룹니다.
- ③ 오답: 괄호 때문에 등록문을 평가하는 즉시 함수를 호출하고 반환값을 넘깁니다.
- ④ 오답: 첫 인자는 이벤트 이름, 둘째 인자는 실행할 함수여야 하므로 순서가 반대입니다.
6. const makeMember = name => ({ name, active: true });에서 객체를 소괄호로 감싼 이유는?
정답입니다.
오답입니다. 답안을 다시 선택해 보세요.
정답 및 해설 보기
정답: ④
- ① 오답: 매개변수 개수는 화살표 왼쪽에서 정하며 반환식의 소괄호와 관계없습니다.
- ② 오답: 소괄호는 문법 해석을 돕지만 객체를 동결하거나 불변으로 만들지 않습니다.
- ③ 오답: JSON 변환에는
JSON.stringify()가 필요하며 이 함수는 객체를 반환합니다. - ④ 정답: 소괄호가 없으면 중괄호를 함수 본문으로 해석해 객체 즉시 반환의 의도가 사라집니다.
7. 배열 원본을 유지하면서 숫자를 오름차순으로 정렬한 새 배열을 만드는 코드는?
정답입니다.
오답입니다. 답안을 다시 선택해 보세요.
정답 및 해설 보기
정답: ③
- ① 오답:
sort()는 원본을 바꾸고 비교 함수가 없으면 숫자도 문자열 기준으로 정렬합니다. - ② 오답: 숫자 오름차순은 맞지만
sort()가 원본 배열을 직접 수정합니다. - ③ 정답:
toSorted()가 새 배열을 만들고a - b가 숫자 오름차순을 정합니다. - ④ 오답: 원본은 보존하지만
b - a는 큰 값부터 놓는 내림차순입니다.
8. Math.floor(Math.random() * 5) + 7이 만들 수 있는 정수 범위는?
정답입니다.
오답입니다. 답안을 다시 선택해 보세요.
정답 및 해설 보기
정답: ①
- ① 정답: 무작위 값에 5를 곱하고 내리면 0~4이며 7을 더해 7~11이 됩니다.
- ② 오답:
Math.random()은 1을 포함하지 않아 곱한 값 5와 최종값 12는 나오지 않습니다. - ③ 오답: 곱하는 5는 가능한 개수이지 최솟값이 아니며 마지막의 7이 시작값입니다.
- ④ 오답:
floor()결과가 0일 수 있어도 마지막에 7을 더하므로 최종 0은 불가능합니다.
9. 폼 제출 시 페이지 새로고침만 막고 이벤트가 부모로 버블링하는 것은 유지하려면 사용할 메서드는?
정답입니다.
오답입니다. 답안을 다시 선택해 보세요.
정답 및 해설 보기
정답: ④
- ① 오답: 전파를 중단하지만 폼의 기본 제출과 새로고침을 막는 메서드는 아닙니다.
- ② 오답:
target은 실제 사건 요소를 가리키는 속성이며 호출하는 제어 메서드가 아닙니다. - ③ 오답: 이벤트 객체에는
stopDefault()라는 표준 메서드가 없습니다. - ④ 정답: 브라우저 기본 동작만 취소하므로 별도로 전파를 막지 않는 한 버블링은 계속됩니다.
10. 동적으로 추가되는 여러 li의 클릭을 적은 리스너로 처리하면서 실제 클릭 항목만 구분하려는 설계는?
정답입니다.
오답입니다. 답안을 다시 선택해 보세요.
정답 및 해설 보기
정답: ②
- ① 오답: 새 항목마다 등록 작업을 반복해 이벤트 위임이 해결하려는 중복을 유지합니다.
- ② 정답: 부모가 버블링된 사건을 한 번 받고 target으로 시작 요소를 구분하므로 새 자식도 처리됩니다.
- ③ 오답: 자식에서 전파를 멈추면 사건이 부모에 도달하지 않아 위임 처리기가 실행되지 않습니다.
- ④ 오답: 같은
onclick속성에 다시 대입하면 앞선 함수가 교체되어 여러 처리를 누적하지 못합니다.
참고 자료와 작성 기준
이 글은 해당 차시 강의자료 76쪽 전체를 확인한 뒤 표지·목차·단원 구분·정리·다음 차시 예고를 본문과 구분하고, 자바스크립트 문법을 요구사항별 선택 규칙으로 재구성한 비공식 학습자료입니다. 강의자료의 범위 안에서 워크숍 등록 코드 검수, 참조 공유 추적, 배열 처리 단계, 난수 범위 계산, 이벤트 위임 진단과 선택지별 해설을 새로 구성했습니다.
- 작성·편집: 올에이클래스 학습연구팀
- 주요 근거: 한국방송통신대학교 HTML5웹프로그래밍 10강 「자바스크립트 언어」 강의자료
- 보충 자료: 외부 자료를 사용하지 않았으며, 강의자료의 설명 범위 안에서 예제와 데이터 흐름을 재구성했습니다.
- 편집 원칙: 올에이클래스 편집 정책
- 최종 내용 검토: 2026-09-05
댓글
댓글 쓰기