처음 회사에 갔을때 this에 대해 잘 몰라서 로직을 파악하기 너무 힘들었던 기억이 난다.. 그 이후 this 에 대해서 찾아보기도 많이 찾아보고 잘 알고 있다고 생각했지만 설명하라고 해보면 하기가 막막한것 같아서 this에 관해 확실히 정리하고 넘어가면 좋을것 같아서 이 글을 작성한다.
자바스크립트의 this 란?
생각해보면 우리 모두 자기 자신을 "나"라고 부른다. 자바스크립트도 비슷하다. 함수나 객체 안에서 this는 "내가 누구인가?"를 알려주는 역할을 한다.
즉 "내 자신" 또는 "내가 속한 주체"를 가리키는 특별한 키워드이다.
this를 왜 사용할까?
코드를 작성할 때 객체의 내부 함수에서 매번 객체의 이름을 쓰는 대신 this를 사용하면 코드가 더 깔끔해지고, 객체의 이름이 바뀌어도 수정이 편해진다.
이해가 잘 안간다.. 그렇다면 직접 해보는게 가장 좋다. this 없이 객체의 속성에 접근해보자
const person = {
name: "Bob",
greet: function() {
console.log("안녕, 나는 " + person.name + " 이야!");
}
};
person.greet();
// 실행 결과: 안녕, 나는 Bob 이야!
여기서 문제점이 발생한다. 만약 person의 이름이 바뀐다면? 직접 함수 내부의 person.name 의 값을 변경해줘야 한다.
반면 this를 사용한 경우는
const person = {
name: "Bob",
greet: function() {
console.log("안녕, 나는 " + this.name + " 이야!");
}
};
person.greet();
// 실행 결과: 안녕, 나는 Bob 이야!
객체의 이름을 변경해도 내부 코드를 수정할 필요가 없다. 왜냐하면 this는 항상 현재 객체를 가리키기 때문이다.
이해가 되는가? 설명이 좀 부족해보인다. 다른 예시를 들어보자,
this없이 객체 속성에 접근한 예시를 하나 더 살펴보자
const person = {
name: "Bob",
greet: function() {
console.log("안녕, 나는 " + person.name + " 이야!");
}
};
const anotherPerson = {
name: "Alice",
// person 객체의 greet 함수를 재사용
greet: person.greet
};
person.greet();
// 실행 결과: 안녕, 나는 Bob 이야!
anotherPerson.greet();
// 실행 결과: 안녕, 나는 Bob 이야! -> 기대했던 "Alice"가 아니라 "Bob"이 출력됨
이제야 이해가 된다! 위 예시를 보니 this가 정말 편리하다고 느껴진다.
1. 객체 안에서의 this
1-1 메서드 호출과 this
객체 내부의 함수(메서드를 호출할 때, this는 자동으로 그 객체를 가리킨다. 즉, 객체이름.메서드() 형태로 호출하면, 메서드 내부의 this는 객체이름이 된다.
const student = {
school: "초등학교",
study: function() {
console.log("나는 " + this.school + "에서 공부해!");
}
};
student.study();
// 실행 결과: 나는 초등학교에서 공부해!
this.school은 student 객체 안의 school 속성을 가져온다.
1-2 메서드 내부에서의 this
메서드 내부에 this를 사용하면 어떨까?
const user = {
name : "Alice",
greet: function() {
console.log(this);
}
};
user.greet();
// 출력 : {name : '홍길동', greet : [Function: greet] }
위의 코드에서 this 는 greet 메서드를 호출한 객체인 user를 가리킨다. 자바스크립트는 메서드가 호출될 때, 호출한 객체를 자동으로 this에 바인딩한다.
메서드 호출 방식이 this의 동작을 결정짓는 키이다.
1-3 중첩 함수에서의 this 문제
객체의 메서드 안에 또 다른 함수를 만들면, 이 내부 함수에서는 this가 예상과 다르게 동작할 수 있다.
중첩 함수와 this 예제를 보자
const teacher = {
subject: "수학",
explain: function() {
function inner() {
console.log("나는 " + this.subject + "를 가르쳐!");
}
inner();
}
};
teacher.explain();
// 실행 결과: 브라우저에서는 undefined 혹은 전역 객체의 subject (대부분 undefined)
해결방법은
- 외부 변수에 this 저장하기(self, that 사용)
- 화살표 함수 사용하기
가 있다.
self를 사용하기
const chef = {
dish: "파스타",
cook: function() {
const self = this; // this를 self라는 변수에 저장
function innerCook() {
console.log("오늘의 요리는 " + self.dish + "입니다!");
}
innerCook();
}
};
chef.cook();
// 실행 결과: 오늘의 요리는 파스타입니다!
self 변수에 this를 저장하여 내부 함수에서도 외부의 this를 참조할 수 있게 한다.
2. 일반 함수에서의 this
2-1 함수 호출 방식에 따른 this 변화
함수를 그냥 따로 실행할 때도 있는데, 그 경우 this가 가리키는 대상이 달라질 수 있다.
전역 함수에서의 this를 살펴보자
function showThis() {
console.log(this);
}
showThis();
// 실행 결과 (브라우저의 경우): Window 객체(전역 객체)가 출력됨
이렇게 호출하면 this는 브라우저에서는 window 객체를, Node.js에서는 전역 객체를 가리킨다.
3. 화살표 함수와 this
화살표 함수란 무엇인가?
화살표 함수 (Arrow Function)는 짧게 함수를 작성할 수 있게 도와주고, 특별한 점은 자신만의 this를 자지지 않는다는 것이다.
엥? 이건 또 무슨 말인가..
화살표 함수 내부의 this는 바깥 함수의 this를 그대로 사용한다.
예제를 봐보자
const person = {
name: "Alice",
sayName: function() {
const innerFunc = () => {
console.log("내 이름은 " + this.name);
}
innerFunc();
}
};
person.sayName();
// 실행 결과: 내 이름은 Alice
innerFunc 는 화살표 함수이기 때문에 자신의 this를 생성하지 않고, 바로 sayName 함수의 this, 즉 person 객체를 그대로 사용한다.
화살표 함수의 장단점
장점 :
- 코드가 간결해지고, 중첩 함수에서도 외부 this를 그대로 사용할 수 있다.
단점 :
- 자체 this가 없기 때문에, 특정 상황에서는 의도한 대로 동작하지 않을 수 있다.
const animal = {
type: "고양이",
speak: () => {
console.log("나는 " + this.type);
}
};
animal.speak();
// 실행 결과: 나는 undefined (혹은 전역 객체의 type 값)
// 왜냐하면 화살표 함수는 자신의 this가 없으므로 전역 this를 사용하기 때문임
위의 예시를 통해 객체의 메서드를 정의할 때는 일반 함수를 사용하는 것이 좋다는 점을 알 수 있다.
4. 콜백 함수에서의 this
일반 함수와 마찬가지로, 콜백 함수 내부의 this는 함수가 어떻게 호출되는지에 따라 달라진다.
문제는, 콜백 함수로 전달된 함수는 호출될 때 원래의 객체 컨텍스트와는 별개로 호출되는 경우가 많다는 것이다.
const person = {
name: "Bob",
greet: function() {
console.log("안녕, 나는 " + this.name + " 이야!"); // 올바른 출력: "안녕, 나는 Bob 이야!"
// setTimeout에 일반 함수 콜백 전달
setTimeout(function() {
// 이 내부의 this는 전역 객체(window) 또는 undefined(엄격 모드)로 설정됨
console.log("안녕, 나는 " + this.name + " 이야! (setTimeout 내부)");
}, 1000);
}
};
person.greet();
// 실행 결과:
// 즉시 출력: 안녕, 나는 Bob 이야!
// 1초 후 출력 (브라우저 일반 모드): 안녕, 나는 undefined 이야!
위 코드가 어떻게 동작하는지 잠시 생각해보자.
왜 저런 결과가 나오는 것일까?
- person.greet() 에서의 this는 person 객체를 가리킨다
- 하지만, setTimeout에 전달된 일반 함수 콜백은 전역 함수처럼 호출되기 때문에, 그 내부의 this는 전역 객체(브라우저에서는 window)를 가리킨다.
- 만약 전역 객체에 name이라는 속성이 없다면 undefined가 출력되는 것이다
4-1 이벤트 핸들러에서의 this
웹 페이지의 이벤트 핸들러에서도 콜백 함수의 this 동작은 중요하다. 이벤트 핸들러에서의 this는 주로 이벤트를 발생시킨 DOM 요소를 가리킨다.
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>이벤트 핸들러에서의 this 예제</title>
</head>
<body>
<button id="myButton">클릭해봐!</button>
<script>
const button = document.getElementById("myButton");
// 일반 함수 콜백의 경우, this는 버튼 요소를 가리킴
button.addEventListener("click", function() {
console.log("버튼 텍스트 (일반 함수): " + this.innerText);
});
// 화살표 함수 콜백의 경우, this는 상위 스코프의 this를 상속받음 (이 경우 window)
button.addEventListener("click", () => {
console.log("버튼 텍스트 (화살표 함수): " + this.innerText);
});
</script>
</body>
</html>
일반 함수 콜백에서는 버튼의 텍스트가 정상적으로 출력되는걸 볼 수 있다. 하지만 화살표 함수 콜백에서는 this가 전역 객체를 참조하기 때문에, this.innerText가 기대한 값이 나오지 않는다.
즉 일반 함수 콜백으로 사용시 이벤트 핸들러로 등록된 일반 함수(즉, function 키워드로 작성된 함수)는 호출될 때 this가 해당 이벤트를 발생시킨 요소(예: 버튼, 링크 등)를 가리킨다.
<button id="myButton">클릭해봐!</button>
<script>
const button = document.getElementById("myButton");
button.addEventListener("click", function() {
// 이 내부에서의 this는 클릭한 버튼 요소 자체를 가리킴
console.log("버튼의 텍스트는: " + this.innerText);
});
</script>
버튼을 클릭하면 콘솔에 "버튼의 텍스트는: 클릭해봐!" 가 출력된다.
'Front-End > JavaScript' 카테고리의 다른 글
원시 값과 객체의 비교 (1) | 2024.07.24 |
---|---|
함수(2) (0) | 2024.07.21 |
함수(1) (1) | 2024.06.18 |
함수 표현식 (0) | 2024.06.17 |
스코프 (3) | 2024.06.14 |