작업을 수행하거나 값을 계산하는 등의 과정을 표현하고, 이를 하나의 블록으로 감싸서 실행 단위로 만들어 놓은 것
//함수의 기본적인 형태
function sum(a,b){
return a+b
}
sum(10,24)//34
먼저 function으로 시작해 }로 끝나는 부분까지 함수를 정의한 부분이다.
function뒤에 오는 것이 함수명, 그리고 함수의 입력값으로 받는 a,b를 각각 매개변수라 하며, return으로 작성된 것이 반환값이다.
함수의 이름을 사용해 함수를 호출하는데, sum 뒤에 넘겨준 두 개의 값 10과 24를 인수라고 한다.
function Component(props){
return <div>{props.hello}</div>
}
Component라고 하는 함수를 선언한 뒤 매개변수로는 일반적으로 props라고 부르는 단일 객체를 받으며 return문으로 JSX를 반환한다.
일반적인 함수와의 차이점이라고 한다면, 자바스크립트에서는 Component(props) 형태로 호출하지만, 리액트에서 함수 컴포넌트는 <Component hello = {props.hello} .../>같은 형태로 모든 props를 전개 연산자로 받는다는 차이가 있다.
이러한 JSX형태 외에도 일반적인 자바스크립트 무넙으로 함수 컴포넌트를 호출하는 것도 가능하다.
자바스크립트에서 함수를 선언할 때 가장 일반적으로 사용하는 방식
function add(a,b){
return a+b
}
함수 선언문은 표현식이 아닌 일반 문(statement)으로 분류된다.
표현식이란 무언가 값을 산출하는 구문을 의미
즉 함수 언언으로는 어떤한 값도 표현되지 않았으므로 표현식이 아닌걸로 분류된다.
const sum = function sum(a,b){
return a+b
}
sum(10,24)//32
위 예제는 마치 sum이라는 변수에 함수 sum을 할당하는, 표현식과 같은 작동을 보인다.
그 이유는 자바스크립트 엔진이 코드의 문맥에 따라 동일한 함수를 문이 아닌 표현식으로 해석하는 경우가 있기 때문이다.
따라서 위와 같이 이름을 가진 형태의 함수 리터럴은 코드 문맥에 따라 전자오 같은 선언문으로, 후자와 같은 표현식으로도 사용될 수 있음을 알고 있어야 한다.
'일급 객체'라는 개념을 먼저 알아야 한다.
프로그래밍 세계에서 일급 객체란 다른 객체들에 일반적으로 적용 가능한 연산을 모두 지원하는 객체를 의미한다.
함수는 다른 함수의 매개변수가 될 수 있고, 반환값이 될 수도 있으며, 위에서 본 것처럼 할당도 가능하므로 일급 객체가 되기 위한 조건을 모두 갖췄다.
const sum = function (a,b){
return a+b
}
sum(10,24)//34
함수는 일급 객체이니, 함수를 변수에 할당하는 건 당연히 가능하다.
함수 표현식에서는 할당하려는 함수의 이름을 생략하는 것이 일반적이다.
그 이유는 코드를 봤을 때 혼란을 방지하기 위해서이다.
const sum = function add(a,b){
//함수 콤통에서 현재 실행 중인 함수를 참조하는데 사용할 수 있다.
//이는 단순한 코드에 대한 이해를 돕기 위한 예제 코드로
//실제로 절대로 사용해서는 안된다.
console.log(agruments.callee.name)
return a+b
}
sum(10,24)
//add
add(10,24) // Uncaught ReferenceError: add is not defined
위 함수 표현식 예제를 살펴보면 실제로 함수를 호출하기 위해서 사용된 것은 sum임을 알 수 있다.
add는 실제 함수 내부에서만 유효한 식별자일 뿐 함수 외부에서 호출하는 데에는 사용할 수 없는 식별자이다.
따라서 함수 표현식에서 함수에 이름을 주는 것은 함수 호출에 도움이 전혀 안되는 코드를 읽는데 방해가 될 수 있는 요소이다.
가장 큰 차이는 '호이스팅'이다.
함수 선언문이 마치 코드 맨 앞단에 작성된 것처럼 작동하는 자바스크립트의 특징
function hello(){
console.log('hello')
}
hello() //hello
함수를 선언한 hello는 코드 중간에 있지만, 맨 앞에서 호출한 hello()는 어떠한 에러 없이 마치 함수가 미리 만들어지기라도 한 것 처럼 정상적인 hello함수의 작동을 수행하는 것을 알 수 있다.
함수의 호이스팅은 함수에 대한 선언을 실행 전에 미리 메모리에 등록하는 작업을 의미한다.
호이스팅이라는 특징 덕분에 함수 선언문이 미리 메모리에 등록됐고, 코드의 순서에 상관없이 정상적으로 함수를 호출 할 수 있게 되는 것이다.
반면 함수 표현식은 함수를 변수에 할당했다.
변수도 마찬가지로 호이스팅이 발생한다.
그러나 함수의 호이스팅과는 다르게, 호이스팅되는 시점에서 var의 경우에는 undefined로 초기화한다는 차이가 있다.
console.log(typeof hello === 'undefined')//true
hello() // Uncaught TypeError: hello is not a function
var hello = function(){
console.log('hello')
}
hello()
위 코드는 함수 선언문과 달리 정상적으로 호출되지 않고 undefined로 남아있는 것을 알 수 있다.
함수와 다르게 변수는, 런타임 이전에 undefined로 초기화되고, 할당문이 실행되는 시점, 즉 런타임 시점에 함수가 할당되어 작동한다는 것을 알 수 있다.
그렇다면 둘 중 뭐가 더 좋을까?
함수를 자유롭게 선언하고 호출하고 싶거나, 변수 선언과 다르게 명시적으로 함수를 구별하고 싶을 때는 함수 선언문이 더 좋을 수 있다.
함수 선언문은 함수가 선언된 위치에 상관없이 함수 호이스팅의 특징을 살리면 어디서든 호출할 수 있고, 변수 선언과 뚜렷하게 구별되는 차이점이 있다.
그러나 함수가 선언되기 전에 함수가 호출되는 것이 이상하게 느껴질 수도 있다.
함수 호출은 제일 먼저 보이고, 그 다음에 실제 함수를 어디서 어떻게 선언했는지는 해당 스코프를 끝까지 확인하지 않으면 개발자가 찾기 어렵다.
이는 관리해야할 스코프가 길어질 수록 더 나쁘게 작용한다.
결론적으로 둘 중 어떤 것이 더 낫거나 기능적으로 좋다라고 구별지을 만한 점은 없다.
Function생성자를 활용하는 방식
앞서 만든 add함수를 생성자 함수로 만들어보자.
const add = new Function('a','b', 'return a+b')
add(10,24)//34
Function생성자 함수를 사용해 만든 모습은 썩 좋아보이지는 않는다.
매개변수, 함수의 몸통을 모두 문자열로 작성해야 한다.
이는 메모장에서 코드를 작성한는 것만큼이나 어려운 방법이며, 코드의 양이 길어진다면 더욱 혼란스러워질 것이다.
또한 생성자 방식으로 함수를 만들게 되면 함수의 클로저 또한 생성되지 않는다.
여러가지로 보아 생성자 함수 방식으로 함수를 만드는 건 권장되지 않는다.
ES6에서 새롭게 추가된 함수 생성 방식
function 키워드 대신 =>화살표를 활용해서 함수를 만들기 때문에, 타이핑할 글자 수가 줄어든다는 측면에서 많이 사용된다.
const add = (a,b) =>{
return a+b
}
const add = (a,b) => a+b
화살표 함수는 겉보기와 다르게 앞서 언급한 함수 생성 방식과 몇 가지 큰 차이점이 있다.
생성자 함수로 화살표 함수를 사용하는 것은 불가능하다
const Car = (name) => {
this.name = name
}
//Uncaught TypeError : Car is not a constructor
const myCar = new Car('하이')
function hello(){
console.log(arguments)
}
//Argument(3) [1,2,3, callee: f, Symbol(Symbol.iterator) :fl
hello(1,2,3)
const hi = ()=>{
console.log(agruments)
}
//Uncaught ReferenceError: arguments is not defined
hi(1,2,3)
자신이 속한 객체나 자신이 생성할 인스턴스를 가리키는 값
화살표 함수 이전까지는 함수를 정의할 때 결정되는 것이 아니라, 함수가 어떻게 호출되느냐에 따라 동적으로 결전된다.
만약 함수가 함수로서 호출된다면, 그 내부의 this는 전역 객체를 가리키게 된다.
그러나 화살표 함수는 함수 자체의 바인딩을 갖지 않는다.
화살표 함수 내부에서 this를 참조하면 상위 스코프의 this를 그대로 따르게 된다.
class Component extends React.Component {
constructor(props){
super(props)
this.state = {
counter:1,
}
}
functionCountUp() {
console.log(this)// undefined
this.setState((prev) => ({counter: prev.counter+1}))
}
ArrowFunctionCountUp = () => {
console.log(this)// class Component
this.setState((prev) => ({counter: prev.counter+1}))
}
render(){
return(
<div>
{/* Cannot read properties of undefined (reading 'setState') */}
<button onClick={this.functionCountup}> 일반 함수 </button>
{/* 정상적으로 작동한다*/}
<button onClick={this.ArrowFunctionCountup}> 화살표 함수 </button>
</div>
)
}
}
두 메서드 functionCountUp과 ArrowFunctionCountUp 모두 state를 하나씩 올리는 작업을 동일하게 하고 있다.
그러나 일반 함수에서의 this는 undefined를, 화살표 함수에서의 this는 우리가 원하는 대로 클래의 인스턴스인 this를 가리키는 것을 볼 수 있다.
즉 별도의 작업을 추가로 하지 않고 this에 접근할 수 있는 방법이 바로 화살표 함수인 것이다.
말 그대로 함수를 정의하고 그 순간 즉시 실행되는 함수
단 한 번만 호출되고, 다시금 호출할 수 없는 함수이다.
(function (a,b) {
return a+b
}) (10,24); //34
((a,b) => {
return a+b
},
)(10,24) //34
한 번 선언하고 호출된 이후부터는 더 이상 재호출이 불가능하다.
그래서 일반적으로 즉시 실행 함수에 이럼을 붙이지 않는다.
즉시 실행 함수의 특성을 활용하면 글로벌 스코르를 오염시키지 않는 독립적인 함수 스코프를 운용할 수 있다는 장점을 얻을 수 있다.
함수의 선언가 실행이 바로 그 자리에서 끝나기 때문에 즉시 실행 함수 내부에 있는 값은 그 함수 내부가 아니고서는 접근이 불가능하기 때문이다.
코드를 읽는 이로 하여금 함수는 어디서든 다시금 호출되지 않는다는 점을 각인시킬 수 있어 리팩터링에도 매우 도움이 된다.
일단 선언돼 있으면 어디ㅓㅅ 쓸지 모르는 일반 함수와 다르게, 즉시 실행 함수는 그 선언만으로 실해잉 거기서 끝난다는 것을 각인시킬 수 있기 때문이다.
자바스크립트의 함수가 일급 객체라는 특징을 활용하면 함수를 인수로 받거나 결과로 새로운 함수를 반환시킬 수 있다.
이런 역할을 하는 함수가 고차함수 Higher Order Function 이다.
//함수를 매개변수로 받는 대표적인 고차함수, Array.prototype.map
const doubledArray = [1,2,3].map((item) => item*2)
doubledArray // [2,4,6]
//함수를 반환하는 고차 함수의 예
const add = function (a){
//a가 존재하는 클로저를 생성
return function(b){
//b를 인수로 받아 두 합을 반환하는 또 다른 함수를 생성
return a+b
}
}
add(1)(3)//4
이러한 특징을 활용해 추후에 다룰 함수 컴포넌트를 인수로 받아 새로운 함수 컴포넌트를 반환하는 고차 함수를 만들 수도 있다.
함수 내의 작동으로 인해 함수가 아닌 함수 외부에 영향을 끼치는 것을 의미한다.
부수 효과가 업는 함수를 순수 함수라 하고, 부수 효과가 존재하는 함수를 비순수 함수라고 한다.
즉 순수 함수는 부수 효과가 없고, 언제 어디서나 어떠한 상황에서든 동일한 인수를 받으면 동일한 결과를 반환해야 한다.
function PureComponent(props){
const {a,b} = props
return <div>{a+b} </div>
}
컴포넌트는 앞선 기준에 따라 순수한 함수 컴포넌트로 분류할 수 있다.
props의 값을 기준으로 a,b를 더 하고, 그 결과를 HTMLDIVElement로 렌더링 하고 있다.
외부에 어떤 영향도 미치지 않았고, 언제 어디서든 동일한 함수를 받아서 동일한 결과를 반환하기 때문에 순수 컴포넌트라 볼 수 있다.
항상 결과가 동일하기 때문에 예측 가능하며, 안정적이라는 장점이 있다.
부수효과를 만드는 것은 피할 수 없는 요소이지만, 부수효과를 최대한 억제할 수 잇는 방향으로 함수를 설계해야 한다.
리액트의 관점으로 본다면 부수 효과를 처리하는 훅인 useEffect의 작동을 최소화하는 것이 버그를 줄이며, 컴포넌트의 안정성을 높일 수 있다.
따라서 자바스크립트 함수에서는 가능한 부수 효과를 최소화하고, 함수의 실해오가 결과를 최대한 예측 가능하도록 설계해야 한다.
ESLint에는 max-lines-per-function이라는 규칙이 있다.
함수당 코드의 길이가 길어질수록 코드 냄새(문제를 일으킬 여지가 있는 코드)가 날 확률이 커지고, 내부에서 무슨 일이 일어나는지 추적하기 어려워진다.
이 규칙의 요점은 하나의 함수에서 너무나 많은 일을 하지 않게 하는 것이다.
함수는 하나의 일만 잘하면 된다.
그것이 함수의 원래 목적인 재사용성을 높일 수 있는 방법이다.
useEffect(function apiRequest(){
// ...do something
}, [])
위와 같이 useEffect의 콜백 함수에 이름을 붙여준다고 한들 apiRequest()와 같은 형태로 호출하거나 접근할 수 있는 것은 아니다.
그러나 useEffect 같은 부수 효과를 일으키는 함수가 많아질수록 굳이 useEffect 코드를 유심히 살펴보지 않더라도 어떤 일을 하는지, 또 어떻게 작동하는 지르 ㄹ단번에 알아채는 데 도움이 된다.