[JS_memo] 화살표 함수, 함수의 this

Lina Hongbi Ko·2023년 3월 17일
0

JavaScript_memo

목록 보기
4/7
post-thumbnail

화살표 함수

오늘은 계속 미루고 미뤘던 화살표함수에 대해 정리하려고 한다. 클론코딩을 하면서 화살표함수를 따라서 쓰긴 하는데 정확하게 알고 있지 않고 쓰니까 찜찜한 기분이라서 정리하고 쓰도록 해야겠다는 생각이 들었다:)

그래서,

💡 What is "Arrow Function"?

: es6부터 나온 함수로 function(){} 함수의 형태를 간단하게 표현한 함수이면서 몇 가지의 특징이 있는 함수.

형태는

() => {}

의 모습을 가지고 있고,

// 매개변수 지정 방법
() => {} // 매개변수가 없을 경우
x => {} // 매개변수가 한 개인 경우, 소괄호 생략가능
(x, y) => {} // 매개변수가 여러 개인 경우, 소괄호 생략 불가능

// 함수 몸체 지정 방법
x => {return x * x} // single line block
x => x * x // 함수 몸체가 한줄의 구문이라면 중괄호를 생략할 수 있고 암묵적으로 return이 되어서 return을 쓰지 않아도 된다.

() => {return {a: 1};}
() => ({a: 1}) // 위의 표현과 동일. 객체 반환시 소괄호 사용

() => { // multi line block
	const x = 10;
    return x * x;
};

화살표함수는 익명함수로만 사용할 수 있어서 "함수 표현식"을 사용해야한다.

ex)

const add = (x, y) => x + y;
console.log(add(2, 3)); // 5

그리고 중요한 사실!
❗️❗️콜백함수로 일반적인 함수 표현식보다 간결하게 사용할 수 있다.

const arr = [1, 2, 3];
const pow = pow.map(function (x) {return x * x;});
// es5에서는 이렇게 써야 했지만,

const pow = pow.map(x => x * x);
// es6부터는 이렇게 간단하게 써도 된다.

console.log(pow); // [1, 4, 9];

💡 화살표함수의 특징
1. 익명함수로만 만들 수 있다
2. 생성자함수로 사용이 불가능하다 (무거운 프로토타입을 만들지 않는다)
3. 함수 자체 arguments(인자에 대해 모든 정보를 알고 있는 객체)를 가지고 있지 않다.
4. this에 대한 바인딩이 정적으로 결정된다. (함수에서 제일 근접한 상위 스코프의 this에 정적으로 바인딩된다)
5. call, apply, bind 메소드를 사용하여 this를 변경할 수 없다.

일반함수와 화살표함수에서의 this

일반함수와 화살표함수의 가장 큰 차이점은 this이다.

일반함수 - this

자바스크립트에서 함수를 호출할 때 this는 동적으로 결정된다. 함수를 선언할 때 this에 바인딩할 객체가 정적으로 결정되는 것이 아니라 함수를 호출할 때, 어떻게 호출되었는지에 따라 this에 바인딩할 객체가 동적으로 결정된다.
콜백함수의 this는 전역객체 window이다.

화살표함수 - this

화살표함수는 함수를 선언할때 this에 바인딩할 객체가 정적으로 결정된다. 동적으로 결정되는 일반함수와는 달리 화살표함수의 this는 언제나 상위 스코프 this를 가리킨다. (Lexcial this) 그리고 call, apply, bind메소드를 사용하여 this를 변경할 수 없다.
💡화살표함수에는 this와 argument가 없기 때문에 일반적으로 this가 개입되는 경우라면 일반함수를 사용해야한다.

let obj = { 
    myName: 'Son', 
    logName: function() { 
        console.log(this.myName); 
    }
};

obj.logName();   //"son"
// 콜백함수로 일반함수표현방식을 사용하면 dot notation법칙에 따라 this는 obj객체가 됩니다.
var obj = { 
    myName: 'Son', 
    logName: () => { 
        console.log(this.myName); 
    }
};

obj.logName();   //undefined 
/*콜백함수로 화살표함수를 사용하면 이 예제의 경우 this는 상위 스코프인 전역스코프 혹은 
window객체가 됩니다. 
현재 상위 스코프에는 변수 myName이 존재하지 않으므로 undefined를 반환합니다.*/

만일 화살표함수에서도 this를 사용해주려면 어떻게 해야할까?

window.myName = "Son";
var obj = {
    myName: 'Son',
    logName: () => {
        console.log(this.myName);
    }
};

obj.logName();   //"Son"
//여기서 this는 obj객체의 상위스코프의 this인 window객체가 됩니다.

이런식으로 위에 window객체로 선언해주면 사용가능하다.

컴포넌트형 같은경우에는 constructor 안에 this.변수명 으로 선언된 변수가 있다면, 화살표 함수 안에서 this.변수명 으로 사용가능하다.

화살표함수의 this 바인딩 객체 결정방식은 렉시컬 스코프(Lexical Scope)와 유사하다.
렉시컬 스코프(Lexical Scope): 함수를 어디서 선언하였는지에 따라 상위 스코프가 정해지는 방식. 자바스크립트를 비롯한 대부분의 프로그래밍 언어는 렉시컬 스코프를 따른다.
↔ 동적 스코프(Dynamic Scope): 화살표 함수에서 this를 사용할 경우 일반 변수와 동일하게 스코프 체인을 따라 탐색하게 된다.
(this에 대한 설명과 스코프는 따로 작성해서 메모해 둔 것을 참조)

화살표함수를 사용해서는 안되는 경우

  • 메소드
const person = {
	name: 'Lee',
    sayHi: () => console.log(`Hi ${this.name}`);
};
person.sayHi(); // Hi undefined

위의 코드를 보면 메소드로 정의한 화살표 함수 내부의 this는 상위컨택스트인 전역객체 window가 되므로 화살표함수로 메소드를 정의하는 것은 바람직하지 않다.

이와 같은 경우에는 메소드를 위한 단축 표기법 '축약 메소드 표현'을 사용하는 것이 좋다.

const person = {
	name: 'Lee',
    sayHi() {
    	console.log(`Hi ${this.name}`);
    }
};
person.sayHi(); // Hi Lee
  • prototype
const person = {
	name: 'Lee',
};
Object.prototype.sayHi = () =>. console.log(`Hi ${this.name}`);
person.sayHi(); // Hi undefined

화살표함수로 객체의 메소드를 위처럼 정의하면 문제가 발생한다.
프로토타입을 가지지 않으니까!

const person = {
	name: 'Lee',
}
Object.prototype.sayHi = function () {
	console.log(`Hi ${this.name}`);.
};
person.sayHi(); // Hi Lee
  • 생성자 함수
const Foo = () => {};

console.log(Foo.hasOwnProperty('prototype')); // false

const foo = new Foo() // TypeError : Foo is not a constructor

화살표함수는 생성자함수로 사용할 수 없다. 생성자 함수는 prototype 프로퍼티를 가지며 prototype 프로퍼티가 가리키는 프로토타입 객체의 constructor을 사용한다. 하지만 화살표함수는 위에 언급한 것처럼 prototype 프로퍼티를 가지고 있지 않다.

  • addEventListener함수의 콜백함수
const button = document.getElementById('myButton');
button.addEventListener('click', () => {
	console.log(this === window); // true
    this.innerHTML = `Clicked button`;
});
const button = document.getElementById('mybutton');
button.addEventListener('click', function() {
	console.log(this === button); // true
    this.innerHTML = 'Clicked button';.
});

addEventListener 함수의 콜백함수를 화살표 함수로 정의하면 this가 상위 컨택스트인 전역객체 window를 가리킨다. 따라서 addEventListener함수의 콜백함수 내에서 this를 사용하는 경우, function키워드로 정의한 일반함수를 사용해야한다. 일반함수로 정의된 addEventListener함수의 콜백함수 내부의 this는 이벤트 리스너에 바인딩된 요소(currentTarget)를 가리킨다.

📍어떤 사람이 문의했던 글을 이곳에 다시 기록하면서 까먹지 말기

🧑🏻 :
Why doesn't my arrow function work in my addEventListener?
I created this arrow function in app.js but ran into an error:

"Uncaught TypeError: Cannot set property 'display' of undefined"

button.addEventListener('click', () => {
    game.startGame()
    this.style.display = 'none';
    document.getElementById('play-area').style.opacity = '1';
});

I double checked my code and the block was the same as the solution just not as an arrow function. Tried it as below and it worked:

button.addEventListener('click', function(){
    game.startGame()
    this.style.display = 'none';
    document.getElementById('play-area').style.opacity = '1';
});

Did I declare my arrow function wrong? Or is it not possible to use arrow functions in instances like this?

Thanks!

👱 :
Hi Sean,

Arrow functions do not have their own this. It doesn't bind this like the function keyword does, so yes, that it why this is undefined in the first code example.

In this case you'll want to use the function keyword, or you write your callback to accept the event parameter, like so:

button.addEventListener('click', (evt) => {
    game.startGame()
    evt.target.style.display = 'none';
    document.getElementById('play-area').style.opacity = '1';
});

In this case, evt is the click event, and the target is the button that receives the event. As such you can change the style for the button that way.

Hope that helps. Great question! :)

✏️ 화살표함수를 쓰면서 헷갈렸던것 정리

클론코딩을 하면서 addEventListener함수에 화살표함수를 쓰는 것을 보고, 위처럼 정리한 내용을 보며 왜 여기서는 화살표함수를 썼지? 하면서 의아해하며 이 글을 정리했는데, 다시 한번 내가 헷갈렸던 내용들을 여기에 작성하려고 한다. 오직 내가 그냥 헷갈렸던 것이므로.. 그냥 말그대로 메모!

<script>
const scriptURL = 'https://script.google.com/macros/s/AKfycbzslEnJtPfNT6z0ohrXP9cZYGhWvKVsFjQV7eLcriT3tok5D5ty/exec'
const form = document.forms['submit-to-google-sheet']
form.addEventListener('submit', e => {
  e.preventDefault()
  fetch(scriptURL, { method: 'POST', body: new FormData(form)})
  .then($("#form").trigger("reset"))
  .catch(error => console.error('Error!', error.message))
})
</script>

화살표함수 쓰면 안된다고 했는데 왜 여기선 썼느냐?!!?

stackoverflow 홈페이지의 답변에 따르면

e => {
    e.preventDefault();
}

is equivalent to

function (e) {
    e.preventDefault();
}

... In this specific example

form.addEventListener('submit', e => { e.preventDefault(); ... });

e is the eventObject, which the event was triggered by.

form.addEventListener('submit', eventObj => { eventObj.preventDefault(); ... });

라고 한다 :)
function() {} 을 쓴것과 같은 것이었다.

그리고 자꾸 addEventListener함수의 콜백함수랑 일반 함수 호출에서의 화살표함수를 헷갈리지 말자.

finishBanner.setClickListener(() => {
    game.start();
    game.showTxt();
  });

setClickListener함수의 콜백함수를 여러개 부르기 위해 function(){} 함수를 화살표함수로 간략하게 적은 것이다.
그리고 중요한것!!!
this 바인딩을 위해서 addEventListener함수에서 콜백함수로 화살표함수를 쓴 것도 항상 유의해서 생각할 것.


출처
https://poiemaweb.com/es6-arrow-function
https://stackoverflow.com/questions/55327118/what-does-addeventlistener-e-mean
https://teamtreehouse.com/community/why-doesnt-my-arrow-function-work-in-my-addeventlistener

profile
프론트엔드개발자가 되고 싶어서 열심히 땅굴 파는 자

0개의 댓글