JavaScript의 this 키워드와 바인딩: 초보자도 쉽게 이해하는 가이드 🚀
안녕하세요, 여러분! 오늘은 JavaScript의 세계에서 가장 헷갈리는 주제 중 하나인 'this 키워드와 바인딩'에 대해 알아볼 거예요. 이 주제는 많은 개발자들이 처음에 어려워하는 부분이지만, 걱정 마세요! 우리가 함께 차근차근 파헤쳐 볼 테니까요. 😉
이 글을 통해 여러분은 마치 JavaScript와 친구가 된 것처럼 편하게 'this'를 다룰 수 있게 될 거예요. 그리고 누가 알아요? 이런 지식을 바탕으로 여러분이 재능넷에서 JavaScript 튜터로 활동하게 될지도 모르잖아요? 🤓
자, 그럼 시작해볼까요? 준비되셨나요? 고고씽~! 🏃♂️💨
1. this가 뭐길래 이렇게 난리야? 🤔
여러분, 'this'라는 단어를 들으면 뭐가 떠오르나요? "이것"? "저것"? 아니면 "어, 뭐지?"ㅋㅋㅋ JavaScript에서 'this'는 그냥 "이것"이 아니라, 아주 특별한 키워드예요. 이 녀석, 정말 여러 얼굴을 가지고 있어서 개발자들을 헷갈리게 하죠.
'this'는 현재 실행 중인 코드의 문맥(context)을 가리키는 특별한 키워드예요. 쉽게 말해서, 'this'는 "지금 이 코드를 실행하고 있는 주체가 누구냐?"를 알려주는 역할을 한다고 볼 수 있죠.
근데 이게 왜 중요할까요? 🧐
왜 'this'가 중요한가요?
- 객체 지향 프로그래밍에서 메서드가 자신이 속한 객체의 프로퍼티에 접근할 수 있게 해줘요.
- 같은 함수라도 다른 객체에서 재사용할 수 있게 해줘요.
- 이벤트 핸들러에서 이벤트가 발생한 요소를 참조할 수 있게 해줘요.
하지만 여기서 문제가 생겨요. 'this'는 고정된 값이 아니라, 함수가 어떻게 호출되었는지에 따라 다른 값을 가질 수 있어요. 이게 바로 많은 개발자들이 'this'를 어려워하는 이유죠. 😅
예를 들어볼까요? 🎭
const person = {
name: "김코딩",
sayHello: function() {
console.log(`안녕하세요, 제 이름은 ${this.name}입니다.`);
}
};
person.sayHello(); // 출력: 안녕하세요, 제 이름은 김코딩입니다.
const sayHelloFunc = person.sayHello;
sayHelloFunc(); // 출력: 안녕하세요, 제 이름은 undefined입니다.
어라? 같은 함수인데 왜 결과가 다르죠? 이게 바로 'this'의 마법이에요! 🎩✨
첫 번째 경우에는 'this'가 'person' 객체를 가리키지만, 두 번째 경우에는 'this'가 전역 객체(브라우저에서는 'window')를 가리키게 돼요. 그래서 'name'이 'undefined'가 되는 거죠.
이런 상황을 보면, "아 진짜 헷갈려... 🤯" 하고 생각하실 수 있어요. 맞아요, 처음엔 다들 그래요. 하지만 걱정 마세요! 우리가 함께 이 미스터리를 풀어나갈 거예요. 😎
다음 섹션에서는 'this'가 어떻게 결정되는지, 그리고 우리가 어떻게 'this'를 제어할 수 있는지 자세히 알아볼 거예요. 준비되셨나요? Let's dive deeper! 🏊♂️
2. this의 결정 과정: 누가 나를 불렀니? 📞
자, 이제 'this'가 어떻게 결정되는지 알아볼 차례예요. 'this'는 마치 "누가 나를 불렀어?"라고 묻는 것과 같아요. 그리고 그 대답에 따라 'this'의 값이 결정되죠. 🕵️♀️
JavaScript에서 'this'의 값은 크게 네 가지 경우로 결정돼요. 하나씩 살펴볼까요?
2.1. 기본 바인딩 (Default Binding) 🏠
기본 바인딩은 함수가 그냥 호출될 때 적용돼요. 이때 'this'는 전역 객체를 가리키게 돼요. 브라우저에서는 'window' 객체, Node.js에서는 'global' 객체가 되는 거죠.
function showThis() {
console.log(this);
}
showThis(); // 브라우저에서는 window, Node.js에서는 global
근데 여기서 주의할 점! 엄격 모드('use strict')에서는 기본 바인딩이 적용되지 않아요. 대신 'this'는 'undefined'가 돼요.
'use strict';
function showThis() {
console.log(this);
}
showThis(); // undefined
이런 차이 때문에 "어? 왜 갑자기 'this'가 'undefined'야?" 하고 당황하는 경우가 많죠. 그래서 항상 현재 코드가 어떤 모드에서 실행되고 있는지 확인하는 게 중요해요! 😉
2.2. 암시적 바인딩 (Implicit Binding) 🤫
암시적 바인딩은 객체의 메서드로 함수가 호출될 때 적용돼요. 이때 'this'는 그 메서드를 소유하고 있는 객체를 가리키게 되죠.
const cat = {
name: '나비',
meow: function() {
console.log(`${this.name}가 야옹하고 울어요.`);
}
};
cat.meow(); // 출력: 나비가 야옹하고 울어요.
여기서 'this'는 'cat' 객체를 가리키고 있어요. 그래서 'this.name'이 '나비'가 되는 거죠. 귀여운 나비 😺
하지만 여기서도 함정이 있어요! 만약 객체의 메서드를 다른 변수에 할당하고 그 변수를 통해 호출하면 어떻게 될까요?
const meowFunc = cat.meow;
meowFunc(); // 출력: undefined가 야옹하고 울어요.
어라? 갑자기 'undefined'가 되어버렸어요! 이유가 뭘까요? 🤔
이는 'meowFunc'가 그냥 일반 함수로 호출되었기 때문이에요. 이때는 암시적 바인딩이 아니라 기본 바인딩이 적용되어 'this'가 전역 객체를 가리키게 되는 거죠. 전역 객체에는 'name' 프로퍼티가 없으니까 'undefined'가 되는 거예요.
이런 상황을 "this의 바인딩이 풀렸다"고 표현하기도 해요. 마치 고무줄이 툭 하고 끊어진 것처럼요! 🧵💥
2.3. 명시적 바인딩 (Explicit Binding) 🗣️
명시적 바인딩은 우리가 직접 'this'의 값을 지정해주는 방법이에요. JavaScript에서는 'call()', 'apply()', 'bind()' 메서드를 사용해서 이를 할 수 있죠.
function introduce(hobby1, hobby2) {
console.log(`안녕하세요, 제 이름은 ${this.name}이고, ${hobby1}와 ${hobby2}를 좋아해요.`);
}
const person = {
name: '김자바'
};
introduce.call(person, '코딩', '독서');
// 출력: 안녕하세요, 제 이름은 김자바이고, 코딩와 독서를 좋아해요.
introduce.apply(person, ['운동', '여행']);
// 출력: 안녕하세요, 제 이름은 김자바이고, 운동와 여행를 좋아해요.
const boundIntroduce = introduce.bind(person);
boundIntroduce('게임', '영화감상');
// 출력: 안녕하세요, 제 이름은 김자바이고, 게임와 영화감상를 좋아해요.
'call()'과 'apply()'는 함수를 즉시 실행하면서 'this'를 바인딩해요. 차이점은 'call()'은 인자를 쉼표로 구분해서 전달하고, 'apply()'는 인자를 배열로 전달한다는 거예요.
'bind()'는 조금 달라요. 이 메서드는 새로운 함수를 반환하는데, 이 함수는 항상 지정된 'this' 값을 사용해요. 나중에 실행할 수 있는 함수를 만들 때 유용하죠.
이런 방법들을 사용하면 우리가 원하는 대로 'this'를 제어할 수 있어요. 마치 마법사가 지팡이를 휘두르듯이 말이죠! 🧙♂️✨
2.4. new 바인딩 (new Binding) 🆕
new 바인딩은 함수를 생성자로 사용할 때 적용돼요. 'new' 키워드와 함께 함수를 호출하면, 그 함수 내부의 'this'는 새로 생성된 객체를 가리키게 돼요.
function Person(name) {
this.name = name;
this.introduce = function() {
console.log(`안녕하세요, 제 이름은 ${this.name}입니다.`);
};
}
const kim = new Person('김철수');
kim.introduce(); // 출력: 안녕하세요, 제 이름은 김철수입니다.
const lee = new Person('이영희');
lee.introduce(); // 출력: 안녕하세요, 제 이름은 이영희입니다.
여기서 'new Person()'을 호출할 때마다 새로운 객체가 생성되고, 그 객체가 'this'가 되는 거예요. 그래서 각 객체마다 다른 'name'을 가질 수 있는 거죠.
이렇게 'new' 키워드를 사용하면, 마치 공장에서 제품을 찍어내듯이 비슷한 구조의 객체를 여러 개 만들 수 있어요. 개발자들의 효율적인 코딩을 위한 필수 도구죠! 🏭
자, 이제 'this'가 어떻게 결정되는지 알았어요. 하지만 여기서 끝이 아니에요! 'this'에는 더 많은 비밀이 숨어있답니다. 다음 섹션에서 계속해서 파헤쳐볼까요? 💪
3. this의 함정: 어라? 이게 왜 이러지? 😵
여러분, 지금까지 'this'의 기본적인 동작에 대해 알아봤어요. 하지만 실제로 코딩을 하다 보면 "어? 이게 왜 이러지?" 하는 순간이 올 거예요. 그래서 이번에는 'this'와 관련된 몇 가지 함정들을 살펴볼 거예요. 준비되셨나요? 고고! 🚀
3.1. 콜백 함수에서의 this 🎭
콜백 함수에서 'this'는 예상치 못한 동작을 할 수 있어요. 특히 이벤트 리스너나 setTimeout 같은 비동기 함수에서 자주 발생하는 문제죠.
const button = {
content: '클릭해주세요',
click() {
console.log(this.content);
}
};
setTimeout(button.click, 1000);
// 1초 후 출력: undefined
어라? 'undefined'가 출력됐어요. 왜 그럴까요? 🤔
이는 'setTimeout'이 'button.click'을 콜백 함수로 받아서 실행할 때, 그 함수의 'this'가 전역 객체(window)로 바인딩되기 때문이에요. 전역 객체에는 'content' 프로퍼티가 없으니까 'undefined'가 출력되는 거죠.
이런 문제를 해결하려면 어떻게 해야 할까요? 몇 가지 방법이 있어요:
- 화살표 함수 사용하기
- bind() 메서드 사용하기
- 래퍼 함수 사용하기
하나씩 살펴볼까요?
3.1.1. 화살표 함수 사용하기 🏹
화살표 함수는 자신만의 'this'를 가지지 않고, 외부 스코프의 'this'를 그대로 사용해요. 이를 이용하면 콜백 함수에서도 원하는 'this'를 유지할 수 있죠.
const button = {
content: '클릭해주세요',
click: () => {
console.log(this.content);
}
};
setTimeout(button.click, 1000);
// 1초 후 출력: 클릭해주세요
짜잔! 이제 원하는 결과가 나왔어요. 화살표 함수의 마법이죠! ✨
3.1.2. bind() 메서드 사용하기 🔗
'bind()' 메서드를 사용하면 함수의 'this'를 명시적으로 바인딩할 수 있어요.
const button = {
content: '클릭해주세요',
click() {
console.log(this.content);
}
};
setTimeout(button.click.bind(button), 1000);
// 1초 후 출력: 클릭해주세요
'bind(button)'을 사용해서 'click' 함수의 'this'를 'button' 객체로 고정시켰어요. 이제 어디서 호출하든 'this'는 항상 'button'을 가리키게 되죠.
3.1.3. 래퍼 함수 사용하기 🎁
콜백 함수를 다른 함수로 감싸서 'this'를 보존할 수도 있어요.
const button = {
content: '클릭해주세요',
click() {
console.log(this.content);
}
};
setTimeout(function() {
button.click();
}, 1000);
// 1초 후 출력: 클릭해주세요
이 방법은 간단하지만, 코드가 좀 더 복잡해질 수 있다는 단점이 있어요.
3.2. 객체 메서드로서의 화살표 함수 🎯
앞서 화살표 함수가 'this' 문제를 해결할 수 있다고 했죠? 하지만 주의할 점이 있어요. 객체의 메서드로 화살표 함수를 사용하면 예상치 못한 결과가 나올 수 있어요.
const person = {
name: '김코딩',
sayHello: () => {
console.log(`안녕하세요, 제 이름은 ${this.name}입니다.`);
}
};
person.sayHello();
// 출력: 안녕하세요, 제 이름은 undefined입니다.
어라? 'undefined'가 나왔어요! 왜 그럴까요? 🤔
화살표 함수는 자신만의 'this'를 가지지 않고, 외부 스코프의 'this'를 그대로 사용한다고 했죠? 여기서 외부 스코프는 전역 스코프예요. 그래서 'this'는 전역 객체(window)를 가리키게 되고, 전역 객체에는 'name' 프로퍼티가 없으니 'undefined'가 출력되는 거예요.
이런 경우에는 일반 함수를 사용하는 게 좋아요:
const person = {
name: '김코딩',
sayHello() {
console.log(`안녕하세요, 제 이름은 ${this.name}입니다.`);
}
};
person.sayHello();
// 출력: 안녕하세요, 제 이름은 김코딩입니다.
이제 원하는 결과가 나왔어요! 👏
3.3. 클래스에서의 this 🏫
ES6에서 도입된 클래스에서도 'this'는 중요한 역할을 해요. 하지만 여기서도 주의할 점이 있죠.
class Counter {
constructor() {
this.count = 0;
}
increment() {
this.count++;
}
printCount() {
console.log(this.count);
}
}
const counter = new Counter();
counter.increment();
counter.printCount(); // 출력: 1
const incrementFunc = counter.increment;
incrementFunc();
counter.printCount(); // 출력: 1 (예상은 2)
어라? 두 번째 'printCount()'에서 2가 아니라 1이 출력됐어요. 왜 그럴까요? 🤔
이는 'incrementFunc'을 호출할 때 'this'가 전역 객체로 바인딩되기 때문이에요. 그래서 'counter' 객체의 'count'가 증가하지 않은 거죠.
이 문제를 해결하려면 클래스의 메서드를 화살표 함수로 정의하거나, 생성자에서 메서드를 바인딩해줄 수 있어요:
class Counter {
constructor() {
this.count = 0;
this.increment = this.increment.bind(this);
}
increment() {
this.count++;
}
printCount() {
console.log(this.count);
}
}
const counter = new Counter();
counter.increment();
counter.printCount(); // 출력: 1
const incrementFunc = counter.increment;
incrementFunc();
counter.printCount(); // 출력: 2
이제 원하는 대로 동작하네요! 👍
자, 여러분! 이렇게 'this'와 관련된 몇 가지 함정들을 살펴봤어요. 이런 상황들을 알고 있으면 나중에 "어? 이게 왜 이러지?" 하는 순간을 많이 줄일 수 있을 거예요. 😉
하지만 아직 끝이 아니에요! 'this'의 세계는 더 깊고 넓답니다. 다음 섹션에서는 'this'를 더 효과적으로 다루는 방법에 대해 알아볼 거예요. 준비되셨나요? Let's go! 🚀
4. this 마스터하기: 프로처럼 다루기 🏆
여러분, 지금까지 'this'의 기본 개념과 몇 가지 함정들에 대해 알아봤어요. 이제 'this'를 더 프로페셔널하게 다루는 방법에 대해 알아볼 차례예요. 준비되셨나요? 고고씽~! 🚀
4.1. 렉시컬 this와 화살표 함수 🏹
화살표 함수는 'this'를 렉시컬하게 바인딩해요. 이게 무슨 말이냐고요? 쉽게 말해서, 화살표 함수는 자신이 선언된 곳의 'this'를 그대로 사용한다는 거예요.
const obj = {
name: '김자바',
sayHello: function() {
setTimeout(() => {
console.log(`안녕하세요, ${this.name}입니다!`);
}, 1000);
}
};
obj.sayHello(); // 1초 후 출력: 안녕하세요, 김자바입니다!
여기서 화살표 함수는 'sayHello' 메서드 안에서 선언됐어요. 그래서 화살표 함수의 'this'는 'sayHello'의 'this'와 같아요. 즉, 'obj'를 가리키는 거죠.
이런 특성 때문에 화살표 함수는 콜백 함수로 자주 사용돼요. 특히 React 같은 프레임워크에서 컴포넌트 메서드를 정의할 때 많이 쓰이죠.
class MyComponent extends React.Component {
handleClick = () => {
console.log('버튼이 클릭되었습니다!');
console.log(this); // MyComponent의 인스턴스
}
render() {
return <button onclick="{this.handleClick}">클릭하세요</button>;
}
}
이렇게 하면 'handleClick' 메서드 안에서 'this'가 항상 컴포넌트 인스턴스를 가리키게 돼요. 편리하죠? 😉
4.2. Function.prototype.bind() 깊게 파헤치기 🕵️ 4.2. Function.prototype.bind() 깊게 파헤치기 🕵️♀️
'bind()' 메서드는 'this'를 고정시키는 강력한 도구예요. 이 메서드는 새로운 함수를 반환하는데, 이 함수는 항상 지정된 'this' 값을 사용해요. 더 자세히 알아볼까요?
const person = {
name: '김바인딩',
greet(greeting, punctuation) {
console.log(`${greeting}, 제 이름은 ${this.name}입니다${punctuation}`);
}
};
const greetFunction = person.greet.bind(person);
greetFunction('안녕하세요', '!'); // 출력: 안녕하세요, 제 이름은 김바인딩입니다!
const anotherPerson = { name: '이자바스크립트' };
const greetAnotherPerson = person.greet.bind(anotherPerson);
greetAnotherPerson('반갑습니다', '.'); // 출력: 반갑습니다, 제 이름은 이자바스크립트입니다.
'bind()'의 첫 번째 인자로 'this'로 사용할 객체를 전달해요. 그 뒤의 인자들은 원본 함수에 전달될 인자들이에요.
또한, 'bind()'는 부분 적용(partial application)을 구현하는 데도 사용할 수 있어요:
const greetInKorean = person.greet.bind(person, '안녕하세요');
greetInKorean('~'); // 출력: 안녕하세요, 제 이름은 김바인딩입니다~
이렇게 하면 'greetInKorean' 함수는 항상 '안녕하세요'라는 인사말을 사용하게 돼요. 꽤 유용하죠? 😊
4.3. call()과 apply() 활용하기 📞
'call()'과 'apply()'는 함수를 즉시 호출하면서 'this'를 지정할 수 있어요. 이 두 메서드의 차이점은 인자를 전달하는 방식뿐이에요.
function introduce(hobby1, hobby2) {
console.log(`안녕하세요, 제 이름은 ${this.name}이고, ${hobby1}와 ${hobby2}를 좋아합니다.`);
}
const person1 = { name: '김콜' };
const person2 = { name: '이어플라이' };
introduce.call(person1, '코딩', '독서');
// 출력: 안녕하세요, 제 이름은 김콜이고, 코딩와 독서를 좋아합니다.
introduce.apply(person2, ['운동', '여행']);
// 출력: 안녕하세요, 제 이름은 이어플라이고, 운동와 여행를 좋아합니다.
'call()'은 인자를 쉼표로 구분해서 전달하고, 'apply()'는 인자를 배열로 전달해요. 상황에 따라 더 편리한 방법을 선택하면 돼요.
이 메서드들은 특히 메서드 빌려쓰기(method borrowing)에 유용해요:
const numbers = [1, 2, 3, 4, 5];
const max = Math.max.apply(null, numbers);
console.log(max); // 출력: 5
여기서는 'Math.max()'가 배열을 직접 받지 못하기 때문에 'apply()'를 사용해서 배열의 요소들을 개별 인자로 전달했어요. 똑똑하죠? 😎
4.4. this와 클로저 🔒
클로저와 'this'를 함께 사용할 때는 주의가 필요해요. 클로저는 자신이 생성될 때의 환경을 기억하지만, 'this'는 함수가 호출될 때 결정되기 때문이에요.
function Counter() {
this.count = 0;
this.increment = function() {
this.count++;
}
setInterval(function() {
this.increment();
console.log(this.count);
}, 1000);
}
const counter = new Counter();
// 출력: NaN (매 초마다)
이 코드는 의도한 대로 동작하지 않아요. 'setInterval' 콜백 함수 내부의 'this'가 'Counter' 인스턴스가 아니라 전역 객체(또는 undefined)를 가리키기 때문이에요.
이를 해결하는 방법은 여러 가지가 있어요:
- 화살표 함수 사용하기
- that = this 사용하기
- bind() 메서드 사용하기
화살표 함수를 사용한 예시를 볼까요?
function Counter() {
this.count = 0;
this.increment = function() {
this.count++;
}
setInterval(() => {
this.increment();
console.log(this.count);
}, 1000);
}
const counter = new Counter();
// 출력: 1, 2, 3, ... (매 초마다)
이제 의도한 대로 동작하네요! 👍
4.5. this와 이벤트 핸들러 🖱️
브라우저 환경에서 이벤트 핸들러 내부의 'this'는 보통 이벤트가 발생한 요소를 가리켜요. 하지만 여기서도 주의할 점이 있어요.
const button = document.querySelector('button');
button.addEventListener('click', function() {
console.log(this); // 버튼 요소
setTimeout(function() {
console.log(this); // window 객체 (또는 undefined in strict mode)
}, 100);
});
첫 번째 'console.log'에서는 'this'가 버튼 요소를 가리키지만, 'setTimeout' 콜백 함수 내부의 'this'는 전역 객체를 가리켜요.
이런 경우에도 화살표 함수나 'bind()'를 사용해서 해결할 수 있어요:
button.addEventListener('click', function() {
console.log(this); // 버튼 요소
setTimeout(() => {
console.log(this); // 여전히 버튼 요소
}, 100);
});
이렇게 하면 'setTimeout' 콜백 함수 내부의 'this'도 버튼 요소를 가리키게 돼요.
자, 여러분! 이제 'this'에 대해 꽤 많이 알게 되셨죠? 'this'는 처음에는 어렵게 느껴질 수 있지만, 이렇게 하나씩 파헤치다 보면 결국 마스터할 수 있어요. 여러분도 'this' 마스터가 되실 수 있을 거예요! 💪😄
다음 섹션에서는 'this'와 관련된 몇 가지 고급 테크닉과 실제 사용 사례들을 살펴볼 거예요. 준비되셨나요? Let's dive deeper! 🏊♂️
5. this의 고급 테크닉과 실제 사용 사례 🚀
여러분, 이제 'this'의 기본부터 심화 개념까지 알아봤어요. 이번에는 'this'를 실제로 어떻게 활용하는지, 그리고 몇 가지 고급 테크닉에 대해 알아볼 거예요. 준비되셨나요? Let's go! 🏃♂️💨
5.1. 메서드 체이닝과 this 🔗
메서드 체이닝은 객체 지향 프로그래밍에서 자주 사용되는 패턴이에요. 이 패턴을 구현할 때 'this'가 중요한 역할을 해요.
class Calculator {
constructor() {
this.value = 0;
}
add(n) {
this.value += n;
return this;
}
subtract(n) {
this.value -= n;
return this;
}
multiply(n) {
this.value *= n;
return this;
}
getValue() {
return this.value;
}
}
const calc = new Calculator();
console.log(calc.add(5).subtract(2).multiply(3).getValue()); // 출력: 9
각 메서드에서 'this'를 반환함으로써, 메서드를 계속 연결해서 호출할 수 있어요. 이런 방식으로 코드를 더 간결하고 읽기 쉽게 만들 수 있죠. 👍
5.2. 커링(Currying)과 this 🍛
커링은 여러 개의 인자를 가진 함수를 한 번에 하나의 인자만 받는 함수들의 체인으로 바꾸는 기법이에요. 'this'와 'bind()'를 활용하면 커링을 구현할 수 있어요.
function multiply(x, y) {
return x * y;
}
const multiplyByTwo = multiply.bind(null, 2);
console.log(multiplyByTwo(4)); // 출력: 8
const multiplyByThree = multiply.bind(null, 3);
console.log(multiplyByThree(4)); // 출력: 12
여기서 'bind()'의 첫 번째 인자로 'null'을 전달했어요. 이는 'this'의 값이 중요하지 않을 때 사용하는 방법이에요.
5.3. 프라이빗 변수와 클로저를 이용한 this 🔒
JavaScript에서 진정한 의미의 프라이빗 변수를 구현하는 것은 쉽지 않아요. 하지만 클로저와 'this'를 활용하면 비슷한 효과를 낼 수 있어요.
function Counter() {
let count = 0; // 프라이빗 변수
this.increment = function() {
count++;
console.log(count);
};
this.decrement = function() {
count--;
console.log(count);
};
}
const counter = new Counter();
counter.increment(); // 출력: 1
counter.increment(); // 출력: 2
counter.decrement(); // 출력: 1
console.log(counter.count); // 출력: undefined
이 예제에서 'count' 변수는 'Counter' 함수의 클로저 내부에 있어서 외부에서 직접 접근할 수 없어요. 하지만 'increment'와 'decrement' 메서드를 통해 간접적으로 조작할 수 있죠. 이렇게 하면 데이터를 캡슐화할 수 있어요. 👀
5.4. 이벤트 위임과 this 🎭
이벤트 위임은 여러 요소에 대해 일일이 이벤트 리스너를 등록하는 대신, 부모 요소에 하나의 이벤트 리스너를 등록하는 기법이에요. 이때 'this'를 활용하면 실제 이벤트가 발생한 요소를 쉽게 찾을 수 있어요.
<ul id="todo-list">
<li>할 일 1</li>
<li>할 일 2</li>
<li>할 일 3</li>
</ul>
<script>
document.getElementById('todo-list').addEventListener('click', function(e) {
if(e.target && e.target.nodeName == "LI") {
console.log('클릭된 항목:', this); // ul 요소
console.log('실제 클릭된 항목:', e.target); // 클릭된 li 요소
e.target.style.textDecoration = 'line-through';
}
});
</script>
이 예제에서 'this'는 이벤트 리스너가 등록된 'ul' 요소를 가리키고, 'e.target'은 실제로 클릭된 'li' 요소를 가리켜요. 이렇게 하면 동적으로 추가되는 리스트 항목에 대해서도 이벤트 처리를 할 수 있어요. 효율적이죠? 😎
5.5. 함수형 프로그래밍과 this 🧮
함수형 프로그래밍에서는 'this'의 사용을 최소화하는 경향이 있어요. 대신 순수 함수를 사용하죠. 하지만 때로는 'this'를 활용해 함수형 프로그래밍의 개념을 구현할 수 있어요.
const obj = {
x: 5,
y: 10
};
const add = function(a, b) {
return this.x + this.y + a + b;
}.bind(obj);
console.log(add(1, 2)); // 출력: 18
const partial = function(fn, ...args) {
return fn.bind(this, ...args);
};
const add5 = partial(add, 5);
console.log(add5(3)); // 출력: 23
여기서 'partial' 함수는 부분 적용을 구현하는데 'this'를 활용했어요. 이렇게 하면 함수형 프로그래밍의 개념을 객체 지향적인 컨텍스트에서도 사용할 수 있어요.
5.6. 프록시와 this 🕵️♀️
ES6에서 도입된 Proxy 객체를 사용하면 객체의 기본적인 동작을 재정의할 수 있어요. 이때 'this'를 활용하면 더욱 강력한 기능을 구현할 수 있죠.
const person = {
name: '김프록시',
age: 30
};
const handler = {
get: function(target, prop, receiver) {
console.log(`${prop} 프로퍼티에 접근했습니다.`);
return Reflect.get(target, prop, receiver);
},
set: function(target, prop, value, receiver) {
console.log(`${prop} 프로퍼티를 ${value}로 설정했습니다.`);
return Reflect.set(target, prop, value, receiver);
}
};
const proxyPerson = new Proxy(person, handler);
console.log(proxyPerson.name);
// 출력:
// name 프로퍼티에 접근했습니다.
// 김프록시
proxyPerson.age = 31;
// 출력: age 프로퍼티를 31로 설정했습니다.
이 예제에서 'handler' 객체의 메서드들 내부에서 'this'는 'handler' 객체 자체를 가리켜요. 이를 활용하면 더 복잡한 로직을 구현할 수 있죠.
자, 여러분! 이렇게 'this'의 고급 테크닉과 실제 사용 사례들을 살펴봤어요. 'this'는 정말 다재다능하죠? 😄 이런 기법들을 잘 활용하면 더욱 강력하고 유연한 코드를 작성할 수 있을 거예요.
이제 'this'에 대해 거의 모든 것을 알게 되셨어요! 여러분은 이제 'this' 마스터에 한 걸음 더 가까워졌습니다. 축하드려요! 🎉
마지막으로, 'this'를 사용할 때 주의할 점과 베스트 프랙티스에 대해 간단히 정리해볼까요?
6. this 사용 시 주의사항 및 베스트 프랙티스 ⚠️
여러분, 'this'에 대해 정말 많은 것을 배웠어요. 이제 마지막으로 'this'를 사용할 때 주의해야 할 점과 베스트 프랙티스에 대해 알아볼까요? 이 내용을 잘 기억하면 'this' 관련 버그를 예방하고 더 깔끔한 코드를 작성할 수 있을 거예요. 😉
6.1. 주의사항 ⚠️
- 전역 스코프에서의 this: 브라우저에서 전역 스코프의 'this'는 'window' 객체를 가리켜요. 하지만 Node.js에서는 다르게 동작할 수 있으니 주의하세요.
- strict mode에서의 this: strict mode에서는 함수 내부의 'this'가 'undefined'가 될 수 있어요. 이는 의도치 않은 전역 객체 사용을 방지하기 위함이에요.
- 화살표 함수와 this: 화살표 함수는 자신만의 'this'를 가지지 않아요. 대신 외부 스코프의 'this'를 그대로 사용해요. 이 점을 잘 기억해두세요!
- 이벤트 핸들러에서의 this: 일반 함수로 정의된 이벤트 핸들러에서 'this'는 이벤트가 발생한 요소를 가리켜요. 하지만 화살표 함수로 정의하면 다르게 동작할 수 있어요.
- 메서드 단축 구문과 this: ES6의 메서드 단축 구문을 사용할 때는 'this' 바인딩이 일반 함수와 동일해요. 하지만 화살표 함수로 정의하면 다르게 동작하니 주의하세요.
6.2. 베스트 프랙티스 🏆
- 화살표 함수 활용하기: 콜백 함수에서 외부 스코프의 'this'를 사용하고 싶다면 화살표 함수를 사용하세요.
- bind() 메서드 사용하기: 특정 객체에 'this'를 고정하고 싶다면 'bind()' 메서드를 사용하세요.
- 클래스 사용하기: 'this' 관련 문제를 피하고 싶다면 ES6 클래스를 사용하는 것도 좋은 방법이에요.
- 명시적으로 this 전달하기: 콜백 함수에 'this'를 명시적으로 전달하면 혼란을 줄일 수 있어요.
- this 사용 최소화하기: 가능하다면 'this' 사용을 최소화하고, 명시적인 인자 전달을 활용하세요.
const obj = {
name: '김화살표',
greet() {
setTimeout(() => {
console.log(`안녕하세요, ${this.name}입니다!`);
}, 1000);
}
};
obj.greet(); // 1초 후 출력: 안녕하세요, 김화살표입니다!
const person = {
name: '김바인딩',
greet() {
console.log(`안녕하세요, ${this.name}입니다!`);
}
};
const greet = person.greet.bind(person);
greet(); // 출력: 안녕하세요, 김바인딩입니다!
class Person {
constructor(name) {
this.name = name;
}
greet() {
console.log(`안녕하세요, ${this.name}입니다!`);
}
}
const person = new Person('김클래스');
person.greet(); // 출력: 안녕하세요, 김클래스입니다!
const obj = {
name: '김명시적',
greet(callback) {
callback.call(this);
}
};
obj.greet(function() {
console.log(`안녕하세요, ${this.name}입니다!`);
}); // 출력: 안녕하세요, 김명시적입니다!
function greet(person) {
console.log(`안녕하세요, ${person.name}입니다!`);
}
const person = { name: '김최소화' };
greet(person); // 출력: 안녕하세요, 김최소화입니다!
자, 여러분! 이렇게 'this'에 대한 모든 여정이 끝났어요. 'this'는 처음에는 복잡하고 어렵게 느껴질 수 있지만, 이렇게 하나씩 파헤치다 보면 결국 마스터할 수 있어요. 여러분도 이제 'this' 마스터가 되셨을 거예요! 👨🎓👩🎓
앞으로 코딩을 하면서 'this'를 만날 때마다, 오늘 배운 내용을 떠올려보세요. 그리고 자신있게 'this'를 다뤄보세요. 여러분은 할 수 있어요! 💪😄
JavaScript와 'this'의 세계에서 즐거운 코딩 되세요! Happy Coding! 🎉🚀