JavaScript를 공부하다보면 꼭 알아야하는 개념으로 this를 언급하곤 한다. 하지만 JavaScript의 this는 다른 언어와 조금 다르게 동작하고, JavaScript의 함수가 호출되는 방식에 따라 this가 다른 객체를 바인딩 하기 때문에 처음 보면 어렵다고 생각 할 수 있다. 그래도 필수 개념이니까 꼭 이해해보자 🏃♂️
(본 포스트는 인사이드 자바스크립트 라는 책을 읽고 공부하며 정리했다)
this의 상황별 바인딩에 대해서 다뤄보기 전에 this 란 무엇인가 부터 알아보자!
this는 생성자 혹은 메서드에서 객체를 가리킬 때 사용하는 키워드이며 주로
메서드란 객체의 프로퍼티가 함수일 경우, 이 함수를 메서드라고 부른다.
이 메서드를 호출할 때, 메서드 내부에서 사용된 this는 해당 메서드를 호출한 객체로 바인딩 된다.
var firstObject = {
name: 'first',
sayName: function () {
console.log(this.name);
}
};
var secondObject = {
name: 'second'
};
secondObject.sayName = firstObject.sayName;
// sayName() 메서드 호출
firstObject.sayName(); // first
secondObject.sayName(); // second
firstObject
와 secondObject
객체 모두 name
프로퍼티와 sayName()
메서드를 가지고 있다.
위에서 말했던 대로 해당 메서드를 호출한 객체로 바인딩되므로
firstObject.sayName();
에선 firstObject
객체가 sayName()
메서드를 호출하므로 여기선 this가 firstObject 객체를 가리킨다.
secondObject.sayName();
에선 secondObject
객체가 sayName()
메서드를 호출하므로 여기선 this가 secondObject 객체를 가리킨다.
함수를 호출하면, 해당 함수 내부 코드에서 사용된 this는 전역 객체에 바인딩 된다.
(브라우저에서 JavaScript를 실행하는 경우 전역 객체는 window 객체가 된다)
⚡전역 객체는 전역 범위에 항상 존재하는 객체를 의미합니다. MDN
var test = "This is test"; // 전역변수 선언
console.log(window.test); // This is test
var sayFoo = function () {
console.log(this.test); // 결국 window.test를 의미한다
};
sayFoo(); // This is test
test
라는 전역 변수는 window
전역 객체의 프로퍼티로 접근이 가능하다sayFoo()
함수 내에서 사용된 this는 전역객체에 바인딩 된다고 했기 때문에 sayFoo()
가 호출된 시점에서 this는 전역 객체인 window
에 바인딩 된다.JavaScript에서 객체를 생성하는 방법은 1. 객체 리터럴 방식
, 2. 생성자 함수 이용
두가지 방법이 있다.
생성자 함수를 호출할 때 생성자 함수 코드 내부에서 this는 생성자 함수가 생성하는 객체로 바인딩 된다. 이걸 이해하려면 우선 생성자 함수가 호출됐을 때 동작하는 방식에 대해서 알아야한다.
⚡ 생성자 함수가 동작하는 방식
new
연산자로 JavaScript 함수를 생성자로 호출하면 다음과 같은 순서로 동작하게 된다.
1. 빈 객체 생성 및 this 바인딩
생성자 함수 코드가 실행되기 전 빈 객체를 생성하며 이 객체는 this로 바인딩 된다. 따라서 이후 생성자 함수의 코드 내부에서 사용된 this는 이 빈 객체를 가리키게 된다.
(엄밀히 따지면 빈객체는 아니고 이 객체는 자신을 생성한 생성자 함수의 prototype 프로퍼티가 가리키는 객체를 자신의 프로토타입 객체로 설정한다)
2. this를 통한 프로퍼티 생성
함수 코드 내부에서 this를 사용하여 앞에서 생성된 빈 객체에 동적으로 프로퍼티나 메서드등을 생성할 수 있다.
3. 생성된 객체 리턴
따로 return문이 없는 경우에는 this로 바인딩된 새로 생성된 객체가 return 된다. 만약 따로 return문이 있고 다른 객체를 return 하는 경우에는 this가 아닌 해당 객체가 return 된다.
// 객체 리터럴 방식으로 객체 생성
var me = {
name: 'minkyoung',
age: 26
};
// 생성자 함수로 객체 생성
function Person(name, age) {
this.name = name;
this.age = age;
}
// 같은 형태의 객체를 재생성할 수 있다
var test = new Person('test', 20);
var foo = new Person('foo',22);
console.log(test.name); // test
console.log(foo.name); // foo
만약 생성자 함수를 new
없이 호출하게 되면 일반 함수가 호출되는 것 이므로 이 경우에는 this는 window 전역 객체에 바인딩 된다.
앞서 다뤘던 this의 바인딩은 함수 호출이 발생할 때 각각의 상황에 따라서 자동적으로 바인딩 되는 것을 알 수 있었는데, 이번에는 특정 객체에 명시적으로 바인딩 시키는 방법에 대해서 다룰 것인데 이를 가능하게 하는 것이 바로 apply(), call() 메서드다.
(call()메서드는 apply() 메서드와 기능이 같고, 인자의 형식만 다르다)
function.apply(thisArg, argArray)
와 같은 형식으로 apply() 메서드를 호출하는 것이 가능하다.
호출 방식을 보면 apply() 메서드를 호출하는 주체가 함수고, apply() 메서드도 this를 특정 객체에 바인딩 할 뿐 본질적인 기능은 함수 호출이다.
첫번째 인자인 thisArg
는 함수 내부에서 사용한 this에 바인딩 할 객체를 가리킨다.
두번째 인자인 argArray
는 함수를 호출할 때 넘길 인자들의 배열을 가리킨다.
한마디로 정리하자면 apply() 메서드는 argArray
배열을 자신을 호출한 함수의 인자로 사용하되, 이 함수 내부에서 사용된 this는 thisArg
객체로 바인딩해서 함수를 호출하는 기능을 한다.
// 생성자 함수
function Person(name, age) {
this.name = name;
this.age = age;
}
// me 빈 객체 생성
var me = {};
Person.apply(me, ['minkyoung',26]);
위의 me
객체는 리터럴방식으로 생성한 빈 객체이며, apply() 메서드를 사용해서 Person('minkyoung', 26)
함수를 호출하면서 this를 me
객체에 명시적으로 바인딩하고 있다.
call() 메서드는 apply() 메서드와 기능은 같지만 apply()에서 두번째 인자로 넘긴 배열 형태를 각각 하나의 인자로 넘기게 된다. 만약 위의 예제를 call() 메서드로 바꾸면 다음과 같다.
Person.call(me, 'minkyong', 26);
apply(), call() 메서드는 주로 유사배열 객체에게 배열 메서드를 쓰고 싶을때 사용한다.
❗ 또한 ES6부터 새로 추가된 Array.from()이라는 메서드를 통해서도 유사 배열 객체나 반복 가능한 객체를 얕게 복사해 새로운 객체를 만든다. MDN
이벤트 핸들러에서 this는 이벤트를 받는 HTML 요소로 바인딩 된다
var btn = document.querySelector("#btn");
btn.addEventListener('click', function () {
console.log(this); // #btn
};
위에서 언급했던 일반 함수 호출 시 this의 바인딩과 화살표 함수 호출 시 this의 바인딩은 큰 차이가 있다.
전역 객체에 바인딩 되던 일반 함수의 this와는 다르게 화살표 함수는 가장 가까운 스코프에 존재하는 this 바인딩을 따라한다.
const minkyoung = {
name: 'minkyoung',
happy: true,
printHobby: function() {
const game = "game";
let hobby = game;
function checkHappy() {
if(this.happy) hobby += " is funny";
return hobby;
}
return `minkyoung's hobby : ${checkHappy()}`;
}
}
minkyoung.printHobby(); // minkyoung's hobby : game
이 코드에서 this
는 window를 가리키므로 happy: true
를 이해하지 못하여 원하는대로 출력이 안된다.
같은 코드를 화살표 함수로 작성하게 되면 다음과 같다.
const minkyoung = {
name: 'minkyoung',
happy: true,
printHobby: function() {
const game = "game";
let hobby = game;
let checkHappy = () => {
if(this.happy) hobby += " is funny";
return hobby;
}
return `minkyoung's hobby : ${checkHappy()}`;
}
}
minkyoung.printHobby(); // minkyoung's hobby : game is funny
이 코드에서는 화살표 함수를 이용했기 때문에 printHobby
의 this를 따라하기 때문에 minkyoung
객체를 바인딩한다.
const person = {
name: "minkyoung",
print: () => console.log(this.name)
};
person.pirnt(); // 에러 발생
this는 메서드를 호출한 객체가 아닌 상위 컨텍스트인 전역 객체를 바인딩하게 되므로 에러가 발생한다.
이런 특성 때문에 객체의 메서드를 화살표 함수로 사용하는 것은 적합하지 않다는 점 꼭 알고 있어야 한다!!