학부시절에 C++로 처음 프로그래밍 언어를 접한 이후로, Java, PHP, Python, Javascript 를 사용해왔고 일단 하나의 언어를 주력언어로 삼아 깊게 이해하는 것이 필요하다고 생각되어 Javascript 를 공부해보고자 마음먹게 되었다.
이 포스트에서는 Javascript(이하 JS)를 공부하며 내가 잘 몰랐던, 혹은 헷갈렸던 개념 위주로 정리해보고자 한다.
JS 를 공부하며 가장 혼란스러웠던 개념 중 하나이다.
솔직히 프로그래밍을 할 때에는 그냥 '객체 내부 변수를 접근하는 도구' 같은 개념으로만 대충 이해하고 넘어갔던 것이 사실인데... 이런 저런 글들을 찾아보니 복잡하지만 알고보면 그리 어렵지만도 않은?(뭐라는거야)
하여튼 내가 이해한걸 최대한 쉽게 정리해보자.
일반적으로 객채 내 메서드에서 this 를 호출하면 해당 객체를 반환한다.
let foo = {
bar() {
console.log(this);
}
}
foo.bar(); // { bar: [Function: bar] }
일반 함수내에서 this를 호출하면 어떻게 될까.
const bar = function () {
console.log(this);
}
bar(); // undefined
처음에 내가 예상했던 결과값은 global 객체가 출력되는 것이었는데... 위처럼 undefined 가 출력되었다.
알아보니 strict mode(엄격모드) 에서는 함수에서 this를 호출하면 undefined가 리턴된다고 한다.
엄격모드를 풀고나니 예상대로 global 객체가 출력되었다.
(참고로 'use strict' 를 시작부분에 선언하지 않아도, Javascript 모듈은 모두 엄격 모드가 적용된다고 한다, 처음에 undefined가 출력되었던건, package.json 에서 type을 module로 설정해놨기 때문이었다...)
때문에 다음과 같이 사용할 때도 undefined 또는 global 객체가 출력된다.
let foo = {
outer: function() {
function inner() {
console.log(this);
}
inner();
}
}
foo.outer(); // undefined
function sayHi() {
console.log(`Hi! I'm ${this.name}`);
}
let foo = {name: 'John'};
let bar = {name: 'Kang'};
foo.f = sayHi;
bar.f = sayHi;
foo.f(); // Hi! I'm John
bar.f(); // Hi! I'm Kang
위에서처럼, this는 실행될 시점에서 결정된다. 동일한 함수라도 다른 객체에서 호출했다면 참조하는 값이 달라진다.
정말 헷갈렸던 내용이다.
"화살표 함수 내에서 this를 참조하면 결과값이 잘 나오는데 왜 this가 없다고하지?" 라고 생각할 수 있는데(나는 그랬다)
처음에 언급했던 메서드 및 함수에서의 this와 비교해보면 어떤 의미인지 알 수 있다.
let foo = {
outer: function() {
const inner = () => {
console.log(this);
}
inner();
}
}
foo.outer(); // { outer: [Function: outer] }
화살표함수는 자체적인 this를 가지지 않고, 함수 바깥의 가장 가까운 this를 참조한다.
여기서는 outer의 this인 foo 객체를 가리키고 있는 것을 확인할 수 있다.
위에서 화살표 함수를 통해 this가 내부 객체를 바라보게 만들었던 것을 일반 함수로도 구현이 가능하다.
let foo = {
name: 'foo',
outer: function() {
function inner(a) {
console.log(this.name, a);
}
inner.call(this, 1); // === inner.apply(this, [1]);
}
}
foo.outer(); // foo 1
call 함수는 해당 함수의 this에 원하는 객체를 바인딩하여 실행시킨다. 해당 컨텍스트에서의 this는 foo 객체이기에, 원하는대로 출력이 되는 것을 확인할 수 있다.
apply 는 call과 동작은 동일하면서, 함수의 입력값을 배열로 받는다는 차이점이 있다.
let foo = {
name: 'foo',
outer: function() {
function inner(a) {
console.log(this.name, a);
}
inner.bind(this, 1)();
}
}
foo.outer(); // foo 1
bind는 이처럼 원하는 객체와 입력값을 바인딩만 시켜서 함수를 리턴해준다.