● this 키워드
만약 메서드가 있다면, 메서드가 속한 객체의 프로퍼티를 가져오고 싶을 때, 내부에서 참조할 방도가 업다.. 따라서 생기게 됐다. 약간의 손가락같은 포인터 개념이라고 하면 좋을 것 같다.
자바스크립트 내에서 this는 '누가 나를 불렀느냐'를 뜻한다고 한다. this는 즉, 선언이 아닌 호출에 따라 달라진다는게 가장 중요하다.
그럼 각 상황별로 this가 어디에 바인딩되는지 알아보자.
함수 호출 방식 | this 바인딩 |
일반 함수 호출 | 전역객체 |
메서드 호출 | 메서드를 호출한 객체 |
생성자 함수 호출 | 생성자 함수가 생성할 인스턴스 |
Function.prototype.apply/call/bind에 의한 간접 호출 | 메서드의 첫번째 인수로 전달한 객체 |
전역에서 this를 사용한다면, 전역을 가르키게 된다.
console.log(this); // window
this는 주로 함수에서 쓰이게 되는데, 함수가 어떻게 호출되었는지에 따라 동적으로 결정된다.
⚬ 일반 함수 호출
일반 함수 호출에서는 기본적으로 this가 바인딩된다.
const foo = function () {
console.dir(this);
};
foo() // window
위의 예제 처럼, 일반 함수에서 this를 호출하게 되면 전역객체가 출력된다. 중첩된 함수도 전역객체를 출력함.
function foo() {
console.log(this); // window
function bar() {
console.log(this); // window
}
}
하지만 this의 의의는 객체의 프로퍼티나 메서드를 참조하기 위한 참조 변수이기 때문에, 일반함수에 쓰이는건 그다지 의미없음.
const obj = {
value : 200;
foo() {
console.log(this); // {value : 200, foo : f}
function bar() {
console.log(this); // window
}
},
};
객체 안에서 메서드는 this는 객체를 가르키지만, 메서드 안에 있는 함수의 this는 전역객체를 가르킨다.
콜백함수가 일반 함수로 호출되면 콜백 함수 내부의 this도 전역 객체가 바인딩 된다. 어떠한 함수라도 일반 함수로 호출된다면 this가 전역으로 바인딩 되는 것이다.
const obj = {
foo() {
console.log(this); // obj 객체결과나옴
setTimeout(function () {
console.log(this); // window
}, 1000);
},
};
하지만 이렇게 중첩함수, 콜백함수들이 전역객체를 가르키는건 아무런 의미가 없는 행동들23.. 굳이 싶은 것도 있고 중첩함수나 콜백함수는 사실 외부함수를 돕는 역할의 함수인데 이렇게 외부 함수의 this와 내부 함수의 this가 일치하지 않는다는건 동작의 어려움을 준다.
const obj = {
foo() {
const that = this;
setTimeout(function () {
console.log(that.value);
}, 1000);
},
};
따라서 위와같이 아예 객체 내부에서 this를 정의하고 (this는 obj을 가르키게 되니까) 콜백함수에서 그걸 참조하는 식으로 가는게 맏다.
⚬ 메서드 호출
메서드를 호출할 때, 메서드 이름 앞의 마침표(.) 연산자 앞에 기술한 객체가 바인딩된다.
내부의 this는 메서드를 소유한 객체가 아니라, 메서드를 호출한 객체에 바인딩되는 것이다.
const person = {
name: "Lee",
getName() {
return this.name;
},
};
console.log(person.getName()); // Lee
보다시피 getName메서드는 person이 호출했기 때문에, 여기서 this는 person을 가르키게 되는 것이다.
const person = {
name: "Lee",
getName() {
return this.name;
},
};
console.log(person.getName()); // Lee
const daeun = {
name: "daeun",
};
let getNameCopy = person.getName;
daeun.getNameCopy = getNameCopy;
console.log(daeun.getNameCopy()); // daeun
위의 예제에서 getName이라는 함수를 뜯어다가 daeun객체에 프로퍼티로 추가해주었더니 daeun이 출력되고 있다. 따라서 메서드 내부의 this는 프로퍼티로 메서드를 가지고 있는 객체와 관계가 없고 호출한 객체에 바인딩 된다.. 이런 배은망덕한!!
⚬ 생성자 함수 호출
생성자 함수 내부의 this에는 생성자 함수가 미래에 생성할 인스턴스가 바인딩된다.
// 생성자 함수
function Circle(radius) {
// 생성자 함수 내부의 this는 생성자 함수가 생성할 인스턴스를 가리킨다.
this.radius = radius;
this.getDiameter = function () {
return 2 * this.radius;
};
}
// 반지름이 5인 Circle 객체를 생성
const circle1 = new Circle(5);
// 반지름이 10인 Circle 객체를 생성
const circle2 = new Circle(10);
console.log(circle1.getDiameter()); // 10
console.log(circle2.getDiameter()); // 20
⚬ Function.prototype.apply/call/bind 메서드에 의한 간접 호출
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 메서드의 본질적인 기능은 함수를 호출하는 것이다. apply, call 메서드는 함수를 호출하면서 첫 번째 인수로 전달한 특정 객체를 호출한 함수의 this에 바인딩된다. 둘이 인수를 전달하는 방식만 다르고, 동작하는건 똑같다.
function getThisBinding() {
console.log(arguments);
return this;
}
const thisArg = { a: 1 };
console.log(getThisBinding());
console.log(getThisBinding.apply(thisArg, [1,2,3]));
// Arguments(3) [1, 2, 3, callee: ƒ, Symbol(Symbol.iterator): ƒ]
// { a : 1};
console.log(getThisBinding.call(thisArg, 1,2,3));
// Arguments(3) [1, 2, 3, callee: ƒ, Symbol(Symbol.iterator): ƒ]
// { a : 1};
apply는 함수의 전달인자로 배열로 넘기되, call은 그냥 숫자의 나열로 적어도 된다.
Function.prototype.bind는 apply와 call 메서드와 달리 함수를 호출하지 않고, 인수로 주어진 값으로 this가 바인딩된 함수를 반환한다.
function getThisBinding() {
return this;
}
const thisArg = { a: 1 };
console.log(getThisBinding.bind(thisArg)); // getThisBinding
console.log(getThisBinding.bind(thisArg)()); // { a: 1 }
const person = {
name: 'Lee',
foo(callback) {
// ①
setTimeout(callback, 100);
}
};
person.foo(function () {
console.log(`Hi! my name is ${this.name}.`); // ② Hi! my name is .
// 일반 함수로 호출된 콜백 함수 내부의 this.name은 브라우저 환경에서 window.name과 같다.
// 브라우저 환경에서 window.name은 브라우저 창의 이름을 나타내는 빌트인 프로퍼티이며 기본값은 ''이다.
// Node.js 환경에서 this.name은 undefined다.
});
위의 콜백함수로 함수가 들어가니까, 함수안의 this는 무조건 전역객체를 가르키게 되어있다. window.name은 ''이다.
const person = {
name: 'Lee',
foo(callback) {
// bind 메서드로 callback 함수 내부의 this 바인딩을 전달
setTimeout(callback.bind(this), 100);
}
};
person.foo(function () {
console.log(`Hi! my name is ${this.name}.`); // Hi! my name is Lee.
});
따라서 위와 같이 callback함수에 this를 바인딩 해주어야 원하는 대로 출력된다.
● 번외
// 수퍼클래스
class Base {
constructor(name) {
this.name = name;
}
sayHi() {
return `Hi! ${this.name}`;
}
}
class Derived extends Base {
sayHi() {
// __super는 Base.prototype을 가리킨다.
const __super = Object.getPrototypeOf(Derived.prototype);
return `${__super.sayHi.call(this)} how are you doing?`;
}
}
위의 예시에서 __super은 Base.prototype이므로, this는 앞에 바인딩된 객체에서 값을 가져오게된다. 따라서 Base.prototype의 에서의 name프로퍼티는 없기 때문에, __super.sayHi(this) 를 하게 되면 undefined how are you doing? 이 출력됨.
따라서 Base.prototype.sayHi.call(this)로 써주는게 맏다.
// 수퍼클래스
class Base {
constructor(name) {
this.name = name;
}
sayHi() {
return `Hi! ${this.name}`;
}
}
class Derived extends Base {
sayHi() {
// __super는 Base.prototype을 가리킨다.
const __super = Object.getPrototypeOf(Derived.prototype);
return `${super.sayHi()} how are you doing?`;
}
}
super은 자신의 부모 클래스 prototype이 내부슬롯에 저장된다. 즉, super은 Base.prototype을 가르키게 되는 것이다.
그럼 super.sayHi.call(this) 로 호출해야하는 것이 아닌가?
super.sayHi()를 하면 this바인딩을 하지 않아도, 저절로 Derived가 만든 인스턴스로 this 바인딩이 된다•••ㅜ ㅜ 개같은