
인트로
화살표함수, 생성자함수 그리고 렉시컬 관련 이야기가 나올 때마다 항상 등장하던 this...
this에 대해 모르는 상태에서 다른 걸 이해하려고하니 아무것도 못하겠더라구요. 아무것도 못한달까 대충 추론을 해보는데 이게 맞는지 증명이 안돼요.. (더 심각)
그래서 오늘 this를 끝내보려고 합니다.
this 왜 만듦?
메서드는 자신이 속한 객체의 프로퍼티를 참조하려면 자신이 속한 객체를 가리키는 식별자가 필요합니다.
아래 코드의 경우에는 문제가 없습니다.
객체 리터럴은 객체가 생성되기 전에 평가되고 변수에 할당되기 때문에
getDiameter 메서드는 circle 식별자를 참조할 수 있습니다.
const circle = {
radius : 5,
getDiameter() {
return 2 * circle.radius;
}
}
console.log(circle.getDiameter()); //10
문제는 함수 방식으로 인스턴스를 생성할 때입니다.
생성한 인스턴스를 가리켜야 하는데 아무것도 몰라요 (당연함 생성 안 했음)
function Circle(radius) {
인스턴스.radius = radius;
}
Circle.prototype.getDiameter = function () {
return 2 * 인스턴스.radius;
}
const circle = new Circle(5);
생성자 함수로 인스턴스 생성하는 서순을 따져봅시다.
1. 생성자 함수를 정의한다.
2. 인스턴스 생성을 하지 않았기 때문에 생성할 인스턴스를 가리킬 수 없다. -끝-
은 아니고 그래서 this가 나왔습니다.
this의 정의
this는 자신이 속한 객체 또는 자신이 생성할 인스턴스를 가리키는 참조 변수 -모자딥다-
우리는 이제 this를 통해 자신이 속한 객체나 자신이 생성할 인스턴스의 프로퍼티, 메서드를 참조할 수 있습니다.
참고로 this는 JS 엔진이 알아서 만들어줍니다. (낼름)
함수 내부에서 this를 지역변수처럼 사용할 수 있습니다.
이때 this가 가리키는 값은 함수 호출 방식에 의해 동적으로 결정됩니다.
즉 함수를 어떻게 호출하냐에 따라 this의 값은 마구 바뀝니다.
this 바인딩 들어봤나..?
공부하다 보면 this 바인딩이라는 말이 나오는데요.
먼저 바인딩이란 식별자와 값을 연결하는 걸 말합니다.
this 바인딩은 this와 this가 가리킬 객체를 연결하는 걸 말합니다.
this 바인딩은 함수 호출 시점에 결정됩니다.
앞에서도 말했지만 JS의 this는 something special 하기 때문에 함수가 호출되는 방식에 따라 this에 바인딩될 값이 바뀌는데요... 그건 나중에 이야기하고 먼저 다양한 환경에서 this를 참조한 예제를 보겠습니다.
(브라우저에서 실행)
전역
console.log(this) // Window
일반 함수 내부
function foo() {
console.log(this); // Window
}
foo();
사실상 this는 객체의 프로퍼티나 메서드를 참조하기 위해 만들었기 때문에
객체의 메서드 내부나 생성자 함수 내부에서 써야 의미가 있습니다.
전역과 일반 함수 내부에서 사용하면 모두 window를 가리킨다는 것에 주의하세요.
this 바인딩 동적 결정
이제 드디어 함수 호출하는 방식에 따라 바뀌는 this바인딩 내용을 살펴보겠습니다.
먼저 JS에서 함수 호출하는 방식에 무엇이 있는지 알아봐야겠죠.
- 일반 함수 호출
- 메서드 호출
- 생성자 함수 호출
- Function.prototype.apply/call/bind 메서드에 의한 간접 호출
예제에서도 살펴본 친구들입니다. 간단하게 표로 정리하면 다음과 같습니다.
함수 호출 방식 | this 바인딩 |
일반 함수 호출 | 전역 객체 |
메서드 호출 | 메서드를 호출한 객체 |
생성자 함수 호출 | 생성자 함수가 생성한 인스턴스 |
Function.prototype.apply/call/bind 메서드에 의한 간접 호출 |
첫번째 인수로 전달한 객체 |
일반 함수 호출
this에는 전역 객체가 바인딩됩니다.
사실 객체를 생성하지 않는 일반함수에서 this는 의미가 없습니다.
strict mode가 적용된 일반 함수 내부의 this에는 undefeind가 바인딩된다고는 하는데
strict mode 안 쓸 거니까 예제는 던지겠습니다.
일반 함수로 호출된 모든 함수 내부의 this에는 전역 객체가 바인딩됩니다.
브라우저 console 환경에서 진행했습니다.
(예제를 보고 싶으면 더보기를 클릭하세요)
//전역 객체 프로퍼티
var value = 1;
const obj = {
value : 100,
foo() {
console.log("foo "+this); //[object Object]
console.log("foo this value " + this.value); //100
function bar() { //메서드 내에서 정의한 중첩 함수
console.log("bar "+this); //[object Window]
console.log("bar this value " + this.value); // 1
};
bar();
}
};
obj.foo();
콜백도 마찬가지입니다.
//전역 객체 프로퍼티
var value = 1;
const obj = {
value : 100,
foo() {
console.log("foo "+this); // foo [object Object]
setTimeout(function () {
console.log("callback this "+this); //[object Window]
console.log("callback this value "+ this.value); // 1
})
}
};
obj.foo();
아래와 같이 메서드의 this 바인딩과 일치시키는 방법(1)이 있긴 합니다.
this를 명시적으로 바인딩할 수 있는 메서드도 있습니다.(Function.prototype.apply, ...등)
var value = 1;
const obj = {
value : 100,
foo() {
const that = this; // (1)
setTimeout(function () {
console.log(that.value); //100
})
}
};
obj.foo();
하지만 그냥 화살표 함수를 쓰면 this 바인딩이 일치됩니다.
화살표 함수의 this
화살표 함수는 바로 바깥 범위 this를 가져옵니다.
(화살표 함수는 일반 변수 조회 규칙(normal variable lookup rules)을 따릅니다)
자신의 this가 없기 때문에 대신 화살표 함수를 둘러싸는 렉시컬 범위의 this가 사용됩니다.
setTimeout에 전달된 함수 내부의 this는 foo 메서드의 this와 동일합니다.
var value = 1;
const obj = {
value: 100,
foo() {
console.log(this); //{ value: 100, foo: [Function: foo] }
setTimeout(() => {
console.log(this); //{ value: 100, foo: [Function: foo] }
}, 100); //100
},
};
obj.foo();
메서드 호출
메서드 내부 this에는 메서드 이름 앞의 마침표(.) 연산자 앞에 객체가 바인딩됩니다.
이때 메서드 내부의 this는 메서드를 소유한 객체가 아니라 메서드를 호출한 객체에 바인딩됩니다.
const person = {
name: 'Ryu',
getName() {
return this.name;
}
};
console.log(person.getName()); //Ryu
예제에서 메서드 getName을 호출한 객체는 person입니다.
이때 메서드는 프로퍼티에 바인딩된 함수로 person 객체에 포함된 것이 아닙니다.
독립적으로 존재하지만 getName 프로퍼티가 독립적인 함수 객체를 가리키고 있을 뿐인데요.
따라서 getName 메서드를 다른 객체 프로퍼티에 할당할 수 있으며 일반 변수 할당해 일반 함수로 호출할 수도 있습니다.
const person = {
name: 'Ryu',
getName() {
return this.name;
}
};
const anotherPerson = {
name: 'Kim'
};
anotherPerson.getName = person.getName; //다른 객체의 메서드로 할당
console.log(person.getName()); //Ryu
console.log(anotherPerson.getName()); //Kim
const getName = person.getName; //변수에 할당
console.log(getName()); // '' <- window.name
일반 함수로 호출하여 this가 window를 가리키고 window.name의 값 '' 이 나오는 걸 확인할 수 있습니다.
결국 우리가 기억해야 하는 건 딱 이겁니다.
메서드 내부의 this는 메서드를 호출한 객체에 바인딩된다. this에 바인딩될 객체는 호출 시점에 결정된다.
function Person(name) {
this.name = name;
}
Person.prototype.getName = function () {
return this.name;
}
const me = new Person('Lee');
console.log(me.getName()); // Lee
Person.prototype.name = 'Kim';
console.log(Person.prototype.getName()); //Kim
생성자 함수 호출
생성자 함수 내부의 this에는 생성자 함수가 생성할 인스턴스가 바인딩됩니다.
function Circle(radius) {
this.radius = radius;
this.getRadius = function () {
return this.radius
}
}
const circle1 = new Circle(5);
const circle2 = new Circle(10);
console.log(circle1.getRadius()); //5
console.log(circle2.getRadius()); //10
이때 new 연산자와 함께 생성자 함수를 호출해야 합니다! new 연산자가 없으면 일반 함수로 동작합니다.
Function.prototype.apply/call/bind 메서드에 의한 간접 호출
apply, call, bind 메서드는 Function.prototype의 메서드입니다.
이 메서드들은 모든 함수가 상속받아 사용할 수 있습니다.
위 메서드들은 인수에 this로 사용할 (1) 객체와 (2) 인수 리스트를 전달받아 함수를 호출합니다.
아래 예제는 객체만 전달하고 있습니다.
function getThisBinding() {
return this;
}
const thisArg = { a: 1};
console.log(getThisBinding()); //Window
console.log(getThisBinding.apply(thisArg)); // {a:1}
console.log(getThisBinding.call(thisArg)); //{a:1}
apply와 call 메서드는 함수를 호출할 때 첫 번째 인수로 전달한 객체를 호출한 함수의 this에 바인딩합니다.
apply와 call은 대표적으로 유사 배열 객체에 배열 메서드를 사용하기 위해 사용합니다.
bind는 apply, call과 달리 함수를 호출하지 않고 this 바인딩이 교체된 함수를 새롭게 생성해서 반환합니다.
일반 함수로 호출된 콜백 함수 내부의 this -> Window
const person = {
name: "Lee",
foo(callback) {
setTimeout(callback, 100);
}
}
person.foo(function() {
console.log(this.name); // '' <- window.name
})
bind로 함수 내부 this와 외부 함수 내부의 this 일치시키기
const person = {
name: "Lee",
foo(callback) {
setTimeout(callback.bind(this), 100);
}
}
person.foo(function() {
console.log(this.name); // 'Lee'
})
function setName() {
console.log(this);
}
let someone = {
name: "Halee"
};
//일반함수 호출
setName(); //Window
//bind
setName.bind(someone)(); //{name: Halee}
이제 this를 두려워하지 말자
MDN & 모던 자바스크립트 Deep Dive 내용을 참고하였습니다.
'내가 해냄 > JS' 카테고리의 다른 글
프로토타입 내가 해냄 (0) | 2023.03.15 |
---|---|
배열 고차 함수 내가 해냄 (0) | 2023.03.14 |
ES6 함수 내가 해냄 (0) | 2023.03.14 |
클래스 내가 해냄 (0) | 2023.03.08 |
DOM 내가 해냄 (1) | 2023.03.07 |
포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!