JavaScript를 배우면서 가장 어려운 개념을 꼽으라면 단연 프로토타입, this, 그리고 실행 컨텍스트라고 말할 수 있을 거 같습니다. 우연히 좋은 내용에 글을 보고 이해한 내용을 정리하도록 하겠습니다.
목차
1. 프로토타입과 클래스의 근본적 차이
1.1 클래스 기반의 철학적 배경
1.2 프로토타입 기반의 철학적 배경
1.3 프로토타입의 핵심 특징
1.4 프로토타입의 실제 구현 메커니즘
1.5 가족 유사성의 실제 구현
1.6 컨텍스트(맥락)에 따른 의미 변화
1.7 프로토타입과 인스턴스 수준의 수정
1.8 위임(Delegation)의 실제 동작
1.9 동적 확장의 장점과 단점
2. 자바스크립트의 실행 컨텍스트와 렉시컬 환경
2.1 실행 컨텍스트의 구조
2.2 렉시컬 스코프와 스코프 체인
2.3 실행 컨텍스트와 렉시컬 환경의 차이점
2.4 호이스팅과 실행 컨텍스트의 관계
2.5 실제 브라우저에서의 동작 예시
브라우저의 DOM 이벤트와 this 바인딩 이해하기
1. 브라우저의 이벤트 시스템
2. 이벤트 위임과 this 바인딩
3. 동적으로 생성되는 엘리먼트와 this 바인딩
챕터별 정리
핵심 요약
JavaScript 프로토타입의 철학적 배경과 이해
1. 프로토타입과 클래스의 근본적 차이
1.1 클래스 기반의 철학적 배경
클래스 기반 객체지향 프로그래밍은 플라톤의 이데아 이론에 기반을 두고 있습니다. 이는 "모든 현실 세계의 객체는 이상적인 형태(이데아)가 존재한다"는 철학에서 출발합니다.
- 우리가 보는 모든 의자는 완벽한 "의자의 이데아"를 본떠 만들어진 것
- 현실의 의자들은 모두 불완전하지만, 완벽한 의자의 본질(이데아)은 존재
예를 들어 Java에서는:
class Chair {
private String material;
private int legs;
public Chair(String material, int legs) {
this.material = material;
this.legs = legs;
}
}
// 실제 의자 인스턴스 생성
Chair woodenChair = new Chair("wood", 4);
Chair metalChair = new Chair("metal", 3);
여기서 Chair
클래스는 이데아적 개념의 의자이며, woodenChair
, metalChair
는 이를 구현한 현실 세계의 객체입니다.
이데아적 접근의 한계
// 의자이면서 침대인 것은 어떻게 표현할까?
class SofaBed extends Chair, Bed { // 다중 상속 불가!
// ...
}
- 의자의 이데아와 침대의 이데아는 분리되어 있지만, 현실에는 두 가지 성질을 모두 가진 객체가 존재
- 단일 상속만 허용하는 대부분의 OOP 언어에서는 이러한 현실 세계의 복잡성을 표현하기 어려움
// 의자의 본질적 기능이 아닌 것은 어떻게 할까?
class ArtisticChair extends Chair {
@Override
public void sit() {
throw new UnsupportedOperationException("앉을 수 없는 예술 작품입니다");
// 이게 정말 의자일까?
}
}
- 의자의 본질적 기능(앉을 수 있음)을 만족하지 않는 의자가 존재
- 이는 "완벽한 의자의 이데아"라는 개념과 충돌
- LSP(리스코프 치환 원칙)를 위반하는 상황 발생
1.2 프로토타입 기반의 철학적 배경
반면, 프로토타입 기반 프로그래밍은 비트겐슈타인의 "가족 유사성" 이론에 기반합니다. 이는 "객체들 간의 공통된 속성이 반드시 존재하지 않더라도, 유사성을 통해 분류될 수 있다"는 개념입니다.
// 원형이 되는 의자 객체
const chair = {
sit() {
console.log("앉을 수 있습니다");
}
};
// 기존 의자를 기반으로 새로운 의자 생성
const officeChair = Object.create(chair);
officeChair.hasWheels = true;
officeChair.rotate = function() {
console.log("회전합니다");
};
// 또 다른 종류의 의자
const recliner = Object.create(chair);
recliner.recline = function() {
console.log("등받이를 눕힙니다");
};
1.3 프로토타입의 핵심 특징
1.위임을 통한 상속:
const animal = {
makeSound() {
console.log(this.sound);
}
};
const dog = Object.create(animal); // "상속" - animal을 프로토타입으로 하는 새 객체 생성
dog.sound = "멍멍!";
dog.makeSound(); // "멍멍!" 출력
// "위임" - dog 객체에 makeSound가 없으므로 프로토타입 체인을 통해 animal의 메서드를 호출
2. 동적 확장 가능성
동적 확장 가능성이란:
1. 이미 생성된 객체에 실행 시점에 새로운 속성이나 메서드를 추가할 수 있는 능력
2. 기존 기능을 유지하면서 새로운 기능을 동적으로 확장할 수 있는 특성
3. 필요에 따라 계속해서 새로운 기능을 추가할 수 있음
const baseCalculator = {
add(a, b) { return a + b; }
};
const scientificCalculator = Object.create(baseCalculator);
// 기존 객체에 새로운 기능 추가
scientificCalculator.multiply = function(a, b) {
return a * b;
};
1.4 프로토타입의 실제 구현 메커니즘
프로토타입 체인을 통한 속성 검색 과정을 자세히 살펴보겠습니다:
const bird = {
wings: 2,
canFly: true
};
const sparrow = Object.create(bird);
sparrow.species = "참새";
const cityBird = Object.create(sparrow);
cityBird.habitat = "도시";
console.log(cityBird.wings); // 2
// 검색 과정:
// 1. cityBird 객체에서 wings 속성 검색
// 2. 없으므로 sparrow 프로토타입에서 검색
// 3. 없으므로 bird 프로토타입에서 검색
// 4. 찾았으므로 값 반환
// cityBird -> sparrow -> bird (체이닝)
1.5 가족 유사성의 실제 구현
비트겐슈타인의 가족 유사성 이론이 JavaScript에서 어떻게 구현되는지 살펴보겠습니다:
Javascript
// 게임의 기본 특성
const game = {
hasRules: true,
isInteractive: true
};
// 승패가 있는 게임
const competitiveGame = Object.create(game);
competitiveGame.hasWinner = true;
// 협동 게임
const cooperativeGame = Object.create(game);
cooperativeGame.hasWinner = false;
cooperativeGame.requiresTeamwork = true;
// 자유 놀이
const freePlay = Object.create(game);
freePlay.hasRules = false;
freePlay.hasWinner = false;
// 각 게임은 완전히 동일한 속성을 공유하지 않지만,
// 유사성을 통해 '게임'이라는 범주에 속함
Java
// 기본 게임 클래스
public class Game {
protected boolean hasRules = true;
protected boolean isInteractive = true; }
// 승패가 있는 게임
public class CompetitiveGame extends Game {
private boolean hasWinner = true;
}
// 협동 게임
public class CooperativeGame extends Game {
private boolean hasWinner = false;
private boolean requiresTeamwork = true;
}
// 자유 놀이
public class FreePlay extends Game {
public FreePlay() {
this.hasRules = false; // 부모의 속성을 오버라이드 this.hasWinner = false;
}
}
- 엄격한 상속 구조:
- Game 클래스를 상속받은 모든 하위 클래스는 hasRules와 isInteractive를 반드시 포함해야 함
- 원하지 않는 속성도 강제로 상속받아야 함
- 단일 상속만 가능하여 여러 종류의 게임 특성을 자유롭게 조합하기 어려움
- 속성의 동적 추가/제거 불가:
- 클래스 정의 시점에 모든 속성이 고정됨
- 런타임에서 새로운 속성을 추가하거나 제거할 수 없음
- 각 게임 유형별로 새로운 클래스를 만들어야 함
- 유연성 부족:
- FreePlay처럼 부모의 속성을 변경하려면 생성자에서 명시적으로 오버라이드 해야 함
- 게임의 특성이 바뀔 때마다 새로운 클래스를 정의해야 함
- 실행 중에 게임의 특성을 동적으로 변경하기 어려움
1.6 컨텍스트(맥락)에 따른 의미 변화
프로토타입의 또 다른 중요한 특징은 맥락에 따라 객체의 의미가 달라질 수 있다는 점입니다:
// 기본 프로토타입들 정의
const game = {
isPlayable: true, hasRules: true
};
const teachingTool = {
isEducational: true, hasLearningObjectives: true
};
// 게임으로서의 체스
const chessAsGame = Object.create(game);
chessAsGame.name = "체스";
chessAsGame.hasWinner = true;
chessAsGame.moveTypes = ["캐슬링", "앙파상", "프로모션"];
// 교육도구로서의 체스
const chessAsEducation = Object.create(teachingTool);
chessAsEducation.name = "체스";
chessAsEducation.teachesStrategy = true;
chessAsEducation.teachingPoints = ["전략적 사고", "계획 수립", "결과 예측"];
// 프로토타입 체인 확인
console.log(chessAsGame.isPlayable); // true (game에서 상속)
console.log(chessAsGame.isEducational); // undefined
console.log(chessAsEducation.isEducational); // true (teachingTool에서 상속)
console.log(chessAsEducation.isPlayable); // undefined
- 동일한 "체스"라는 대상이 다른 맥락에서:
- 게임으로서: 승자가 있고, 특정 게임 규칙(이동 방식)을 가짐
- 교육도구로서: 전략적 사고를 가르치는 도구로 사용됨
- 프로토타입 체인을 통해:
chessAsGame
은game
의 속성들을 상속받음chessAsEducation
은teachingTool
의 속성들을 상속받음
같은 체스이지만 어떤 맥락이나 용도로 사용되는지에 따라 다른 프로토타입 체인을 가질 수 있습니다. 이를 통해 객체의 속성과 메서드를 상황에 맞게 유연하게 정의하고 사용할 수 있습니다.
1.7 프로토타입과 인스턴스 수준의 수정
프로토타입 기반 언어의 주요 특징 중 하나는 개별 객체 수준에서 속성과 메서드를 자유롭게 수정할 수 있다는 점입니다.
// 날수 있는 존재의 프로토타입
const flyingCreature = {
wings: 2,
canFly: true,
fly() {
console.log(`${this.genus}가 날아갑니다.`);
}
};
// 프로토타입을 기반으로 객체 생성
const sparrow = Object.create(flyingCreature);
sparrow.genus = "참새";
const penguin = Object.create(flyingCreature);
penguin.genus = "펭귄";
// 개별 객체 수준에서 속성과 메서드 재정의
penguin.canFly = false;
penguin.fly = function() {
console.log(`${this.genus}는 헤엄을 칩니다.`);
};
sparrow.fly(); // "참새가 날아갑니다."
penguin.fly(); // "펭귄는 헤엄을 칩니다."
1.8 위임(Delegation)의 실제 동작
위임은 프로토타입의 핵심 메커니즘입니다. 이는 객체가 자신에게 없는 속성이나 메서드를 프로토타입 체인을 통해 찾아가는 과정입니다:
// 부모 (원형) 객체
const parent = {
greet() { console.log("안녕하세요!");
}
};
// 자식 객체 생성
const child = Object.create(parent);
// 자식 객체에는 greet 메서드가 없지만
// 부모에게 위임해서 사용할 수 있습니다
child.greet(); // "안녕하세요!"
// 자식이 직접 가지고 있는 메서드인지 확인
console.log(child.hasOwnProperty('greet')); // false
// greet 메서드는 부모에게 있음
console.log(parent.hasOwnProperty('greet')); // true
이 예시에서 위임이 작동하는 방식:
child.greet()
를 호출하면- 자바스크립트는 먼저
child
객체에서greet
메서드를 찾습니다. child
에 없으니까 프로토타입인parent
에게 위임합니다.parent
의greet
메서드가 실행됩니다.
1.9 동적 확장의 장점과 단점
프로토타입 기반 시스템의 동적 특성은 장단점을 모두 가지고 있습니다:
(이렇게 사용해 본적은 없지만 예시로 참고)
👍 장점:
- 런타임 중에도 모든 객체에 새로운 기능을 추가할 수 있음
- 이미 생성된 객체들에도 자동으로 새 기능이 적용됨
Object.prototype.toFormattedString = function() {
return JSON.stringify(this, null, 2);
};
const user = { name: "John", age: 30 };
console.log(user.toFormattedString());
// {
// "name": "John",
// "age": 30
// }
👎 단점:
- 모든 배열 객체에 영향을 미치므로 예상치 못한 부작용 발생 가능
- 다른 개발자가 작성한 코드와 충돌할 수 있음
- 코드의 예측 가능성이 떨어짐
Array.prototype.customMethod = function() {
console.log("모든 배열에 영향을 미침");
};
// 이제 모든 배열이 이 메서드를 가지게 됨
[1, 2, 3].customMethod(); // "모든 배열에 영향을 미침"
2. 자바스크립트의 실행 컨텍스트와 렉시컬 환경
2.1 실행 컨텍스트의 구조
실행 컨텍스트는 자바스크립트 코드가 실행되는 환경을 추상화한 개념입니다. 각 실행 컨텍스트는 다음과 같은 구성요소를 가집니다:
- Variable Environment (변수 환경)
- 현재 컨텍스트 내의 식별자들에 대한 정보를 담고 있습니다
- var로 선언된 변수
- 함수 선언문으로 정의된 함수
- arguments 객체
- 최초 스냅샷이 유지되며 변경사항이 반영되지 않습니다
- Lexical Environment (렉시컬 환경)
- Variable Environment를 상속받아 만들어집니다
- let, const로 선언된 변수
- 블록 스코프를 반영합니다
- 변수의 값이 변경되면 이를 실시간으로 반영합니다
- 외부 환경에 대한 참조(Outer Environment Reference)를 통해 스코프 체인을 구현합니다
- This Binding
- 현재 컨텍스트에서 this가 가리키는 대상을 저장합니다
- 함수 호출 방식에 따라 동적으로 결정됩니다
- 일반 함수 호출: 전역 객체(window/global)
- 메서드 호출: 메서드를 호출한 객체
- 생성자 함수: 새로 생성되는 인스턴스
- apply/call/bind: 명시적으로 지정된 객체
EnvironmentRecord(환경 레코드)
그냥 "현재 영역에서 사용할 수 있는 변수들을 담아둔 기록장" 이라고 생각하면 됩니다.
function 주방() {
var 냄비 = "스테인레스 냄비"; // Variable Environment
const 프라이팬 = "티팔 프라이팬"; // Lexical Environment
EnvironmentRecord
// {
// 냄비: "스테인레스 냄비",
// }
EnvironmentRecord
// {
// 프라이팬 : "티팔 프라이팬",
// }
}
OuterReference
OuterReference는 "바깥 영역의 기록장을 볼 수 있는 통로"라고 생각하면 됩니다.
예시 1
const global = "전역";
function outer() {
const x = 1; // Lexical Environment
var y = 2; // Variable Environment
function inner() {
const a = 3;
var b = 4;
console.log(x, y); // outer의 x, y 접근 가능
}
inner();
}
// outer의 실행 컨텍스트
{
// 변수 환경 (Variable Environment)
VariableEnvironment: {
EnvironmentRecord: {
y: 2,
inner: function
},
OuterReference: 전역 환경
},
// 렉시컬 환경 (Lexical Environment)
LexicalEnvironment: {
EnvironmentRecord: {
x: 1
},
OuterReference: 전역 환경
}
}
예시2
let globalVar = "전역 변수";
function outerFunction() {
let outerVar = "외부 함수 변수";
function innerFunction() {
let innerVar = "내부 함수 변수";
console.log(globalVar); // 접근 가능
console.log(outerVar); // 접근 가능
console.log(innerVar); // 접근 가능
}
innerFunction();
}
outerFunction();
이 코드가 실행될 때 생성되는 실행 컨텍스트의 구조는 다음과 같습니다:
// 전역 실행 컨텍스트
GlobalExecutionContext = {
VariableEnvironment: {
EnvironmentRecord: {
outerFunction: <function> // 함수 선언문
},
OuterReference: null
},
LexicalEnvironment: {
EnvironmentRecord: {
globalVar: "전역 변수" // let으로 선언
},
OuterReference: null
},
ThisBinding: window
}
// outerFunction 실행 컨텍스트
OuterFunctionExecutionContext = {
VariableEnvironment: {
EnvironmentRecord: {
innerFunction: <function> // 함수 선언문
},
OuterReference: GlobalExecutionContext
},
LexicalEnvironment: {
EnvironmentRecord: {
outerVar: "외부 함수 변수" // let으로 선언
},
OuterReference: GlobalExecutionContext
},
ThisBinding: window
}
// innerFunction 실행 컨텍스트
InnerFunctionExecutionContext = {
VariableEnvironment: {
EnvironmentRecord: {}, // 함수 선언문 없음
OuterReference: OuterFunctionExecutionContext
},
LexicalEnvironment: {
EnvironmentRecord: {
innerVar: "내부 함수 변수" // let으로 선언
},
OuterReference: OuterFunctionExecutionContext
},
ThisBinding: window
}
2.2 렉시컬 스코프와 스코프 체인
렉시컬 스코프는 함수가 정의된 위치에 따라 상위 스코프가 결정되는 개념입니다:
const value = "글로벌";
function first() {
const value = "first 값";
function second() {
console.log(value); // "first 값" 출력
}
return second;
}
const secondFn = first();
secondFn(); // "first 값" 출력
이 예시에서 second
함수는 자신이 정의된 위치인 first
함수의 렉시컬 환경을 기억합니다.(내부에서 first의 value를 참조하고 있기 때문) 이것이 바로 클로저(Closure)의 기본 원리입니다.
2.3 실행 컨텍스트와 렉시컬 환경의 차이점
실행 컨텍스트와 렉시컬 환경은 밀접하게 연관되어 있지만, 다음과 같은 차이점이 있습니다:
// 예시를 통한 실행 컨텍스트와 렉시컬 환경의 차이 이해
function outer() {
let count = 0; // 렉시컬 환경의 일부
function inner() {
count++; // 클로저를 통한 렉시컬 환경 접근
console.log(count);
}
return inner;
}
const increment = outer(); // outer의 실행 컨텍스트는 종료되지만
increment(); // 1: 렉시컬 환경은 유지됨
increment(); // 2: 계속 접근 가능
- 실행 컨텍스트:
- 코드가 실행되는 동안의 환경
- 함수 호출이 끝나면 스택에서 제거됨
- this 바인딩, 변수 객체 등을 포함
- 렉시컬 환경:
- 변수와 함수 선언이 저장되는 곳
- 클로저를 통해 함수가 종료된 후에도 접근 가능
- 중첩된 스코프의 계층 구조를 형성
// 개념적인 구조 설명
// 실행 컨텍스트
ExecutionContext = {
ThisBinding: <this value>,
LexicalEnvironmentRef: -> LexicalEnvironment, // 참조
VariableEnvironmentRef: -> VariableEnvironment // 참조
}
// 렉시컬 환경
LexicalEnvironment = {
EnvironmentRecord: {/* 변수, 함수 등의 기록 */},
OuterLexicalEnvironmentRef: -> OuterLexicalEnvironment
}
// 실행 컨텍스트가 사라져도 렉시컬 환경은 다른 곳(클로저)에서 참조중이라면 계속 존재
2.4 호이스팅과 실행 컨텍스트의 관계
호이스팅(Hoisting)은 실행 컨텍스트가 생성될 때 일어나는 현상으로, 코드가 실행되기 전에 변수와 함수 선언이 메모리에 저장되는 과정입니다. 실제로 코드가 위로 올라가는 것이 아니라, 자바스크립트 엔진이 코드를 실행하기 전에 먼저 선언부를 읽어 메모리에 저장하는 것입니다.
실행 컨텍스트 생성 시 렉시컬 스코프 내의 선언이 끌어올려 올려지는 것처럼 동작하는 것
console.log(varVariable); // undefined
console.log(letVariable); // ReferenceError / TDZ(Temporal Dead Zone)
console.log(constVariable); // ReferenceError / TDZ(Temporal Dead Zone)
console.log(myFunction); // 함수 전체 출력
console.log(myArrowFunc); // undefined (let || const = ReferenceError)
var varVariable = "var";
let letVariable = "let";
const constVariable = "const";
function myFunction() {
return "일반 함수";
}
var myArrowFunc = () => "화살표 함수";
// 실행 컨텍스트에서의 처리 과정
ExecutionContext = {
LexicalEnvironment: {
EnvironmentRecord: {
varVariable: undefined, // 호이스팅 됨
letVariable: <uninitialized>, // TDZ
constVariable: <uninitialized>, // TDZ
myFunction: <function>, // 전체 호이스팅
myArrowFunc: undefined // 변수로 호이스팅
}
}
}
2.5 실제 브라우저에서의 동작 예시
크롬 개발자 도구의 Sources 패널에서 다음 코드를 실행하며 살펴보겠습니다:
debugger;
let x = 10;
const y = 20;
function example() {
debugger;
let a = 1;
const b = 2;
function inner() {
debugger;
let c = 3;
console.log(x, y, a, b, c);
}
inner();
}
example();
각 debugger 지점에서 Scope 패널을 확인하면 실행 컨텍스트와 렉시컬 환경의 실제 구조를 볼 수 있습니다.
브라우저의 DOM 이벤트와 this 바인딩 이해하기
1. 브라우저의 이벤트 시스템
이벤트 시스템을 이해하기 전에, 브라우저가 어떻게 DOM 이벤트를 처리하는지 살펴볼 필요가 있습니다. 브라우저의 이벤트 처리는 크게 캡처링(Capturing)과 버블링(Bubbling) 두 단계로 이루어집니다.
이 과정에서 this
바인딩이 어떻게 이루어지는지 자세히 살펴보겠습니다:
1. 이벤트 전파 과정 이해하기
집 구조로 비유하면:
할아버지의 방(outer div)
↓
아버지의 방(inner div)
↓
내 방(button)
누군가 "내 방"의 전등을 켰을 때:
1.캡처링 단계 (위에서 아래로)
// 할아버지가 확인하러 내려옴
outer.addEventListener('click', function(e) {
console.log('할아버지: 누가 불 켰나 보러 가야겠다');
}, true);
// 아버지도 확인하러 내려옴
inner.addEventListener('click', function(e) {
console.log('아버지: 누가 불 켰나 보러 가야겠다');
}, true);
2. 타겟 단계 (실제 클릭된 곳)
// 실제로 내가 불을 켬
button.addEventListener('click', function(e) {
console.log('나: 내가 불을 켰어요!');
});
3.버블링 단계 (아래에서 위로)
- 소문이 다시 위로 퍼져나감 (물거품처럼 위로 올라감)
2. this 바인딩
각 이벤트 핸들러에서 this는 "현재 이벤트가 처리되고 있는 요소"를 가리킵니다:
outer.addEventListener('click', function(e) {
console.log(this); // outer div (할아버지 방)
// "나는 지금 할아버지 방에서 확인 중이야"
}, true);
inner.addEventListener('click', function(e) {
console.log(this); // inner div (아버지 방)
// "나는 지금 아버지 방에서 확인 중이야"
}, true);
button.addEventListener('click', function(e) {
console.log(this); // button (내 방)
// "나는 지금 내 방에서 확인 중이야"
});
ㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡ
// this와 화살표 함수의 관계
outer.addEventListener('click', () => {
console.log(this); // window 객체를 가리킴
// 화살표 함수는 자신만의 this를 가지지 않음
}, true);
// 일반 함수의 경우
outer.addEventListener('click', function(e) {
console.log(this); // outer div를 가리킴
// 일반 함수는 자신의 this를 가짐
}, true);
- 누군가 내 방의 전등을 켬 (클릭 이벤트 발생)
- 할아버지가 위에서부터 확인하러 내려옴 (캡처링)
- 아버지도 확인하러 내려옴 (캡처링)
- 내 방에서 실제로 불이 켜짐 (타겟)
- 이 소식이 다시 위로 전달됨 (버블링)
3. event.target vs event.currentTarget
<div class="건물">
<div class="방">
<button class="전등스위치">스위치</button>
</div> <
/div>
document.querySelector('.건물').addEventListener('click', function(e) {
console.log('불이 켜진 곳:', e.target); // 전등스위치
console.log('확인하러 온 사람:', e.currentTarget); // 건물
console.log('지금 확인중인 사람:', this); // 건물
}
);
만약 전등스위치를 클릭하면:
- `e.target`: "전등스위치" (실제로 불이 켜진 곳)
- `e.currentTarget`: "건물" (지금 이벤트를 확인하고 있는 곳)
- `this`: "건물" (현재 확인하고 있는 사람의 위치)
- target: 실제로 어디서 불이 켜졌는지
- currentTarget: 지금 누가 확인하고 있는지
- this: 현재 누가 확인하고 있는지
2. 이벤트 위임과 this 바인딩
이벤트 위임(Event Delegation)은 여러 자식 엘리먼트의 이벤트를 부모 엘리먼트에서 한 번에 처리하는 패턴입니다. 이때의 this 바인딩도 살펴보겠습니다:
// HTML 구조
// <ul id="todoList">
// <li><span>할 일 1</span><button class="delete">삭제</button></li>
// <li><span>할 일 2</span><button class="delete">삭제</button></li>
// </ul>
const todoList = document.getElementById('todoList');
todoList.addEventListener('click', function(e) {
// this는 항상 todoList(ul 엘리먼트)를 가리킴
console.log('이벤트 핸들러의 this:', this); // ul 엘리먼트
// 실제 클릭된 엘리먼트는 e.target으로 확인
if (e.target.matches('.delete')) {
const li = e.target.closest('li');
li.remove();
}
});
3. 동적으로 생성되는 엘리먼트와 this 바인딩
동적으로 엘리먼트를 생성하고 이벤트를 처리하는 상황에서의 this 바인딩을 살펴보겠습니다:
<div id="app">
<button id="addBtn">아이템 추가</button>
<ul id="itemList">
<!-- 동적으로 생성될 아이템들이 들어갈 자리 -->
</ul>
</div>
const addBtn = document.getElementById('addBtn');
const itemList = document.getElementById('itemList');
let itemCount = 0;
// 아이템 추가 버튼 클릭 이벤트
addBtn.addEventListener('click', function() {
itemCount++;
const li = document.createElement('li');
li.innerHTML = `
아이템 ${itemCount}
<button class="deleteBtn">❌</button>
`;
itemList.appendChild(li);
});
// 동적으로 생성되는 삭제 버튼들의 이벤트 처리
itemList.addEventListener('click', function(e) {
// 불이 켜진 곳(실제 클릭된 요소)이 삭제 버튼인 경우
if (e.target.className === 'deleteBtn') {
// target: 클릭된 삭제 버튼
console.log('클릭된 요소:', e.target);
// currentTarget: itemList (이벤트를 처리하는 곳)
console.log('이벤트 처리 주체:', e.currentTarget);
// this: itemList (현재 이벤트를 처리중인 곳)
console.log('현재 처리중:', this);
// 클릭된 삭제 버튼의 부모 li 요소를 삭제
e.target.parentElement.remove();
}
});
아이템 추가
버튼 클릭 시:- 새로운 li 요소가 동적으로 생성됨
- 각 아이템에는 삭제 버튼(❌)이 포함됨
- 삭제 버튼 클릭 시:
- itemList에 달린 이벤트 리스너가 처리 (이벤트 위임)
- e.target으로 실제 클릭된 삭제 버튼을 확인
- this와 currentTarget은 이벤트를 처리하는 itemList를 가리킴
챕터별 정리
📌 1. 프로토타입과 클래스의 근본적 차이
- 클래스 기반
- 플라톤의 이데아 이론 기반
- 완벽한 "이데아적 개념"을 정의하고 이를 구현
- 단일 상속의 한계가 있음
- 프로토타입 기반
- 비트겐슈타인의 "가족 유사성" 이론 기반
- 객체들 간의 유사성을 통해 분류
- 동적으로 속성과 메서드 확장 가능
📌 2. 실행 컨텍스트의 구조
- 주요 구성요소
- Variable Environment: var, 함수 선언문
- Lexical Environment: let, const
- This Binding: 현재 컨텍스트의 this 참조
- Environment Record
- 현재 영역의 사용 가능한 변수들을 담는 기록장
- OuterReference를 통해 외부 환경 참조
📌 3. 브라우저의 DOM 이벤트와 this 바인딩
- 이벤트 전파 과정
- Capturing Phase (위→아래)
- Target Phase (실제 클릭된 곳)
- Bubbling Phase (아래→위)
- 주요 개념
- target: 실제로 이벤트가 발생한 요소
- currentTarget: 현재 이벤트를 처리 중인 요소
- this: currentTarget과 동일 (화살표 함수 제외)
🔍 핵심 요약
- 프로토타입
- 객체 간의 유사성을 기반으로 한 유연한 상속 구조
- 동적 확장이 가능한 장점
- 실행 컨텍스트
- 코드 실행 환경을 추상화한 개념
- 변수와 함수의 스코프 관리
- 이벤트 & this
- 이벤트 위임을 통한 효율적인 이벤트 처리
- this는 이벤트 핸들러가 바인딩된 요소를 참조
레퍼런스
자바스크립트는 왜 프로토타입을 선택했을까
프로토타입으로 검색하면 으레 나오는 서두처럼 저 또한 자바스크립트를 처음 접했을 때 가장 당황스러웠던 게 프로토타입이었습니다.
medium.com
ES5 prototype(프로토타입) 상속기능.
Object.create() ES5 문법 중 하나인 프로토타입 상속 기능으로 간단하게 사용 가능하다. let parents = { name: "Oh", age: 50}; let child = Object.create(parents); console.log(child) let grandChild = Object.create(child); console.log(g
covelope.tistory.com
객체지향 복습 OOP , 객체지향 개념정리
OOP - 객체지향 개념 정리 캡슐화 데이터와 데이터를 활용하는 함수를 캡슐 혹은 컨테이너 안에 두는 것을 의미하는데 이 경우 캡슐은 class를 의미한다. (함수와 데이터가 개념적으로 연관되어 있
covelope.tistory.com
Javascript <자바스크립트> this
this this 는 함수가 호출되는 방식에 따라서 동적으로 결정된다. 1. 일반 함수에서 this 는 전역 객체인 window와 바인딩 된다. console.log(this); //window{...} function a (){ console.log(this) } a(); //window{...} 2. 메
covelope.tistory.com
'JavaScript' 카테고리의 다른 글
[JavaScript] - 호출 스택, 콜백 큐, 이벤트 루프: 실행 프로세스 이해하기 (1) | 2023.10.06 |
---|---|
자바스크립트(javascript) - 엔진,런타임,힙,스택,이벤트루프,프로세스 (0) | 2023.09.25 |
자바스크립트 비동기(Asynchronous) 과정 .feat(AST) (0) | 2023.05.08 |
객체지향 복습 OOP , 객체지향 개념정리 (0) | 2022.05.10 |
JavaScript 자바스크립트 ES6 문법 정리하기. (0) | 2022.05.02 |