...
this 정의
let group = {
title: "1모둠",
students: ["보라", "호진", "지민"],
title2 : this.title,
title3() { console.log(this.title) }
};
console.log(group.title2); //undefined
group.title3(); // 1모둠
this는 함수의 블록 스코프 내에서 선언 되야 작동한다.
브라우저 콘솔(F12)을 켜고, this를 쳐보자
this; // Window {}
이번엔 변수와 함수 안에 넣어서 해보자.
var ga = 'Global variable';
console.log(this.ga); // === window.ga
function a() { console.log(this); };
a(); // Window {}
window 이다.
(함수 일 경우 strict 모드일 경우는 undefined).
여기서 한 가지 사실을 알 수 있다.
this는 기본적으로 window 이다.
일반 함수 내에서 혼자 this를 선언하면, 그 this는 window객체를 가르킨다.
이번엔, 일반 함수가 아닌 객체의 메서드의 경우를 보자.
var obj = {
a: function() { console.log(this); },
};
obj.a(); // obj
객체 메서드 a 안의 this는 객체 obj를 가리킨다.
이것은 객체의 메서드를 호출할 때 this를 내부적으로 바꿔주기 때문에 그렇다.
단 위의 예제에서 다음과 같이 하면 결과가 달라진다.
var a2 = obj.a;
a2(); // window
a2는 obj.a를 꺼내온 것이기 때문에 더 이상 obj의 메서드가 아니고 변수에 담긴 그냥 일반함수 이다.
호출할 때,
호출하는 함수가 객체의 메서드인지 그냥 함수인지가 중요하다.
Java에서의 this는 인스턴스 자신(self)을 가리키는 참조변수이다.
this가 객체 자신에 대한 참조 값을 가지고 있다는 뜻이다.
주로 매개변수와 객체 자신이 가지고 있는 멤버변수명이 같을 경우 이를 구분하기 위해서 사용된다.
아래 Java 코드의 생성자 함수 내의 this.name은 멤버변수를 의미하며 name은 생성자 함수가 전달받은 매개변수를 의미한다.
// JAVA
public Class Person {
private String name;
public Person(String name) {
this.name = name; // this.name과 그냥 name은 완전히 다른 놈이다.
}
}
하지만 자바스크립트의 경우
Java와 같이 this에 바인딩되는 객체는 한가지로 고정되는게 아니라
해당 함수 호출 방식에 따라 this에 바인딩되는 객체가 달라진다.
함수 호출 방식과 this 바인딩
자바스크립트의 경우 함수 호출 방식에 의해 this에 바인딩할 어떤 객체가 동적으로 결정된다.
다시 말해, 함수를 선언할 때 this에 바인딩할 객체가 정적으로 결정되는 것이 아니고, 함수를 호출할 때 함수가 어떻게 호출되었는지에 따라 this에 바인딩할 객체가 동적으로 결정된다.
함수의 호출하는 방식은 아래와 같이 다양하다.
- 함수 호출
- 메소드 호출
- 생성자 함수 호출
- 콜백 호출
- apply/call/bind 호출
1. 함수 호출
기본적으로 this는 전역객체(Global object)에 바인딩된다.
일반 전역함수는 물론이고 내부함수의 경우도 this는 외부함수가 아닌 전역객체에 바인딩된다.
function foo() {
console.log("foo's this: ", this); // window
function bar() {
console.log("bar's this: ", this); // window
}
bar();
}
foo();
객체의 메소드의 내부함수일 경우에도 this는 전역객체에 바인딩된다.
var value = 1;
var obj = {
value: 100,
foo: function() {
console.log("foo's this: ", this); // obj
console.log("foo's this.value: ", this.value); // 100
function bar() { /* 내부함수 */
console.log("bar's this: ", this); // window
console.log("bar's this.value: ", this.value); // 1
}
bar();
}
};
obj.foo();
콜백함수의 경우에도 this는 전역객체에 바인딩된다.
var value = 1;
var obj = {
value: 100,
foo: function() {
setTimeout(function() { /* 콜백함수 */
console.log("callback's this: ", this); // window
console.log("callback's this.value: ", this.value); // 1
}, 100);
}
};
obj.foo();
내부함수는 일반 함수, 메소드, 콜백함수 어디에서 선언되었든 관계없이 this는 전역객체를 바인딩한다.
내부함수의 this가 전역객체를 참조하는 것을 회피방법은 아래와 같다.
- var that = this; 를 해서 객체의 this를 변수에 저장해 사용
- call,bind,apply로 this 설정
- => 화살표 함수 사용
var value = 1;
var obj = {
value: 100,
foo: function() {
var that = this; // Workaround : this === obj
console.log("foo's this: ", this); // obj
console.log("foo's this.value: ", this.value); // 100
function bar() {
//console.log("bar's this: ", this); // window
// console.log("bar's this.value: ", this.value); // 1
console.log("bar's that: ", that); // obj
console.log("bar's that.value: ", that.value); // 100
}
bar();
}
};
obj.foo();
2. 메소드 호출
함수가 객체의 프로퍼티 값이면 메소드로서 호출된다.
이때 메소드 내부의 this는 해당 메소드를 소유한 객체, 즉 해당 메소드를 호출한 객체에 바인딩된다.
var obj1 = {
name: 'Lee',
sayName: function() {
console.log(this.name);
}
}
var obj2 = {
name: 'Kim'
}
obj2.sayName = obj1.sayName;
obj1.sayName(); // Lee
obj2.sayName(); // Kim
3. 프로토타입
프로토타입 객체도 메소드를 가질 수 있다.
프로토타입 객체 메소드 내부에서 사용된 this도 일반 메소드 방식과 마찬가지로 해당 메소드를 호출한 프로토타입 오브젝트 객체에 바인딩된다.
그래서 this의 프로퍼티를 찾을 때 우선, 직접 바인딩 되어잇는 프로토타입 오브젝트에서 찾고, 없으면 체이닝에 의해 new생성자로 생성된 객체에서 찾게 도니다.
function Person(name) {
this.name = name;
}
Person.prototype.getName = function() {
return this.name;
}
var me = new Person('Lee');
console.log(me.getName());
// Lee
// 우선 프로토타입에서 name프로퍼티를 찾는다. 없으니 체이닝에 의해 me 객체에서 찾아서 반환
Person.prototype.name = 'Kim';
console.log(Person.prototype.getName());
// Kim
// 우선 프로토타입에서 name프로퍼티를 찾는다. 찾았으니 반환.
4 생성자 함수 호출
자바스크립트의 생성자 함수는 말 그대로 객체를 생성하는 역할을 한다.
하지만 자바와 같은 객체지향 언어의 생성자 함수와는 다르게 그 형식이 정해져 있는 것이 아니라
기존 함수에 new 연산자를 붙여서 호출하면 해당 함수는 생성자 함수로 동작한다.
이는 반대로 생각하면 생성자 함수가 아닌 일반 함수에 new 연산자를 붙여 호출하면 생성자 함수처럼 동작할 수 있다.
따라서 일반적으로 생성자 함수명은 첫문자를 대문자로 기술하여 혼란을 방지하려는 노력을 한다.
// 생성자 함수
function Person(name) {
this.name = name;
}
var me = new Person('Lee');
console.log(me); // Person {name: "Lee"}
// new 연산자와 함께 생성자 함수를 호출하지 않으면 생성자 함수로 동작하지 않는다.
var you = Person('Kim');
console.log(you); // undefined
5. 콜백 함수 호출
let userData = {
signUp: '2020-10-06 15:00:00',
id: 'minidoo',
name: 'Not Set',
setName: function(firstName, lastName) {
this.name = firstName + ' ' + lastName;
}
}
function getUserName(firstName, lastName, callback) {
callback(firstName, lastName);
}
getUserName('PARK', 'MINIDDO', userData.setName);
console.log('1: ', userData.name); // Not Set
console.log('2: ', window.name); // PARK MINIDDO
우리는 첫 번째 콘솔의 값이 PAKR MINIDDO 이기를 기대했지만, Not Set이 출력된다.
setName() 함수가 실행되기 전의 name 값이 나오는 것인데, 이는 getUserName() 이 전역 함수이기 때문이다.
userData.setName를 아규먼트로 넘겨줄때 CALL BY VALUE로 가는걸 명심해야 한다. (JS는 무조건 call by value)
한마디로 함수가 복사되어 callback 파라미터에 담기게 되니, 당연히 setName()의 this는 전역객체 window를 가리키게 되는 것이다.
해결 방안 : call()과 apply()를 사용하여 this를 보호할 수 있다.
function getUserName(firstName, lastName, callback) {
callback.call(userData, firstName, lastName);
}
getUserName('PARK', 'MINIDDO', userData.setName);
console.log('1: ', userData.name); // PARK MINIDDO
console.log('2: ', window.name); // 빈칸. 왜냐하면 변수가 없으니까
6. apply/call/bind 호출
func.apply(thisArg, [argsArray])
func.call(thisArg, argsArray)
func.bind(thisArg)(argsArray)
// thisArg: 함수 내부의 this에 바인딩할 객체
// argsArray: 함수에 전달할 argument의 배열
기억해야 할 것은 apply() 메소드를 호출하는 주체는 func함수이며 .apply() 메소드는 this를 특정 객체에 바인딩할 뿐 본질적인 기능은 함수 호출이라는 것이다.
var name = "window name";
function Person(name) {
this.name = name;
}
Person.prototype.doSomething = function(callback) {
if(typeof callback == 'function') {
// --------- 1
callback();
}
};
function foo() {
console.log(this.name); // --------- 2
}
var p = new Person('Lee');
p.doSomething(foo); // window name
1의 시점에서 this는 Person 객체이다.
그러나 2의 시점에서 this는 전역 객체 window를 가리킨다.
기본적으로 콜백함수 내부의 this는 window를 가리킨다!
따라서 콜백함수 내부의 this를 콜백함수를, 호출하는 함수 내부의 this와 일치시켜 주어야 한다.
// --------- 1
callback.bind(this)();
실무에서, 이벤트리스너나 제이쿼리같은 것을 썼을 때를 보자..
document.body.onclick = function() {
console.log(this); // <body>
}
이건 그냥 함수인데 this가 window가 아니라 <body>이다.
객체 메서드도 아니고, bind한 것도 아니고, new 붙인 것도 아닌데 말이다. 누가 바꿨을까?
바로 이벤트가 발생할 때, 내부적으로 this가 바뀐 것이다.
내부적으로 바뀐 것이기 때문에 동작을 외울 수밖에 없다.
$('div').on('click', function() {
console.log(this);
});
이런 제이쿼리 코드 많이 본적이 있을 것이다. this는 클릭한 div가 된다.
내부적으로 function을 호출할 때 그렇게 this를 바꿔버렸다. 이런 건 어쩔 수 없이 외워야 한다.
$('div').on('click', function() {
console.log(this); // <div>
function inner() {
console.log('inner', this); // inner Window
}
inner();
});
응용사례 다.
방금 전 클릭 이벤트에서 제이쿼리가 내부적으로 this를 바꿔버린다고 했다.
근데 inner 함수 호출 시에는 this가 window이다. (일반적으로 내부함수의 this는 window !!)
그저 click 이벤트 리스너가 잘못한 것이다(잘못했다기 보다는 내부적으로 this를 바꿨음에도 명시적으로 알리지 않은 것).
위의 문제를 해결하기 위해서는
$('div').on('click', function() {
console.log(this); // <div>
var that = this; // <-------------------------------------------------------------
function inner() {
console.log('inner', that); // inner <div>
}
inner();
});
위처럼 this를 that이라는 변수에 저장하든지
$('div').on('click', function() {
console.log(this); // <div>
const inner = () => {
console.log('inner', this); // inner <div>
}
inner();
});
ES6 화살표 함수를 쓴다.
ES6 화살표 함수는 this로 window 대신 상위 함수의 this를 가져온다(여기서는 <div>)
체이닝
함수에서 자기 자신 this을 리턴하면 자기객체를 가리키기 때문에 연속으로 . 을 사용할수 있다.
let ladder = {
step: 0,
up() {
this.step++;
return this;
},
down() {
this.step--;
return this;
},
showStep() {
alert( this.step );
}
}
ladder.up().up().down().up().down().showStep(); // 1
정리하자면,
this는 기본적으로 window이지만,
객체 메서드, bind call apply, new일 때 this가 바뀐다.
그리고 이벤트리스너나 기타 document라이브러리처럼 this를 내부적으로 바꿀 수도 있으니항상 this를 확인해봐야 한다.
여러분이 선언한 function의 this는 항상 window라는 것 알아두자.
(strict 모드에서는 undefined !!)
this 값은 런타임에 결정된다
함수를 선언할 때 this를 사용할 수 있다.
다만, 함수가 호출되기 전까지 this엔 값이 할당되지 않는다.
함수를 복사해 객체 간 전달할 수 있다.
함수를 객체 프로퍼티에 저장해 object.method()같이 ‘메서드’ 형태로 호출하면 this는 object를 참조한다.
화살표 함수는 자신만의 this를 가지지 않는다는 점에서 독특하다.
화살표 함수 안에서 this를 사용하면, 외부에서 this 값을 가져온다.
Refernece
https://www.zerocho.com/category/JavaScript/post/5b0645cc7e3e36001bf676eb
이 글이 좋으셨다면 구독 & 좋아요
여러분의 구독과 좋아요는
저자에게 큰 힘이 됩니다.