리액트를 함수형으로 구현하면서 this를 접할 일은 거의 없었던 것 같다. 그런데 컴포넌트를 인자로 받고, 그 인자의 prototype에 접근하는 함수가 있었는데 계속 undefined
라는 에러 메시지를 보게 되었다.
영문을 알지 못한채 ?를 붙여가며 예외사항처리를 진행하고 있다가 추후에 그 이유를 알게되었다. 내가 인자로 넘겨준 컴포넌트는 화살표함수로 선언되었었기 때문이다.
//rsc
const Component = () =>{
return <></>
}
//rsf
function Conponent(){
return <></>
}
그저 함수형이냐 화살표형이냐 선언하는 스타일의 차이라고 생각했는데 꽤 큰 문제가 연관되어있었다. 아래 내용을 공부하고 한마디로 정리하자면, 화살표 함수로 선언한 컴포넌트는 익명함수로서 prototype을 가질 수 없고 this를 인지하지 못하는 문제가 있어 undefined에러를 던지고 있던 이유였다. 그냥 간단해보인다는 이유로 화살표함수를 쓰고있었는데 이런 부분까지 생각하며 코드 스타일을 확립해야함을 알게되었다.
아래는 일반함수와 화살표함수의 차이에 대해 정리해보았다.
화살표 함수에서의 this는 전역객체 window를 바라본다. 익명함수로서 전역환경에서 선언된 것과 같고 선언 시점
의 this로 설정되기 때문에 전역객체를 바라보게 되는 것이다.
일반함수에서의 this는 함수가 실행되는 시점
에 따라 달라진다. 그렇기 때문에 일반함수에서는 call, apply, bind로 this를 지정해준다.
# case1
var obj = {
bar: function() {
var x = () => this;
return x;
}
};
var fn = obj.bar();
console.log(fn() === obj); //true
일반함수로 선언된 bar메소드를 실행한 fn함수.
이 경우 일반함수가 실행된 환경인 obj를 this로 설정하게 된다.
# case2
var obj = {
bar: function() {
var x = () => this;
return x;
}
};
var fn2 = obj.bar;
console.log(fn2()() == window); //true
메소드를 실행하지 않은 obj.bar 자체로 선언된 함수 fn2는 전역환경에서 실행되고 (fn2()),
반환된 화살표함수 x를 재실행하면 (fn2()()) 전역함수 환경에서 선언되었던 x의 this는 윈도우 객체를 바라보게 된다.
# case3
var obj = {
bar: () => {
console.log(this)
var x = () => this;
return x;
}
};
var fn3 = obj.bar();
console.log(fn3() == window);
화살표 함수로 선언된 obj.bar메소드에서의 this는 전역객체이고, fn3() 역시 전역객체에 선언되어 window객체로 설정된다.
const myPet = {
name: 'ratPan',
gender: 'male',
introduce: () =>
console.log(`hi ${this.name}`)
}
myPet.introduce.introduce() // `hi undefined`
const myPet = {
name: 'ratPan',
gender: 'male',
introduce() {
console.log(`hi ${this.name}`)
}
}
myPet.introduce.introduce() // `hi ratPan`
화살표 함수 => 선언된 환경 this : window
const playList = {
music : 'dynamite'
}
Object.prototype.play = () => console.log(`this song is ${this.music}`)
playList.play()
//this song is undefined
일반 함수 => 실행된 환경 this : playList
Object.prototype.playAgain = function(){
console.log(`this song is ${this.music}`)
}
playList.playAgain()
//this song is dynamite
화살표 함수는 prototype 프로퍼티가 없다. 생성자 함수로 사용할 수 없다.
const Foo = () => {};
console.log(Foo.hasOwnProperty('prototype'));
// false
const foo = new Foo();
// TypeError: Foo is not a constructor
const Foo = function(){
//var this = {}
//return this
};
console.log(Foo.hasOwnProperty('prototype'));
// true
const foo = new Foo();
+) 생성자 함수는 내부적으로 이미 this가 지정되어있고, return문으로 값을 반환하지 않아도 this를 반환하고 있다. 때문에 생성자 함수에서 return문으로 값을 반환하면 내부 this를 바라보지 않고 반환된 return문을 보게 된다.
- 일반적 생성자함수
function Singer(){
this.name = 'newJeans'
}
const singer = new Singer()
singer.name //'newJeans'
- _return 문이 있는 생성자 함수
function Singer(){
this.name = 'newJeans'
return {name: 'cookies'}
}
const singer = new Singer()
singer.name //'cookies'
이벤트 처리시의 콜백함수로 일반함수를 사용할 시, this는 이벤트가 발생되는 Dom객체로 설정된다. (함수 실행시점의 this)
반면, 화살표 함수로 콜백함수를 전달하는 경우, 함수가 선언되었던 시점의 this인 전역객체 window로 설정된다.
const button = document.getElementByClassname('change_btn')
function changeText(e){
console.log(this === button) //true
this.innerText = 'changed!'
}
const arrowChangeText = (e) =>{
console.log(this === window) //true
this.innerText = 'changed!'
}
button.addEventListener('click', changeText)
button.addEventListener('click', arrowChangeText)
일반함수와 화살표 함수간의 큰 차이는 prototype을 가지고 있느냐 없느냐로 생각하면 이해하기 쉬운 것 같다. 일반함수는 prototype이 있기 때문에 지칭하는 this를 찾아갈 수 있고, 화살표 함수는 프로토타입이 없기 때문에 선언시점의 this가 정적으로 고정되는 수밖에 없는 것이다.