1.2.2 함수를 정의하는 4가지 방법
함수 선언문
- 자바스크립트에서 함수 선언할 때 가장 일반적으로 사용하는 방식
-
function add(a, b) { return a + b }
- 함수 선언문은 표현식이 아닌 일반 문(statement)으로 분류됨(\표현식: 무언가 값을 산출하는 구문. 즉, 함수 선언으로는 어떠한 값도 표현되지 않았으므로 표현식이 아닌 일반 문)
-
const sum = function sum(a, b) { return a + b } sum(10, 24) // 34
- 함수 선언문은 말 그대로 '선언'이고 어떠한 값도 표현하지 않으므로 표현식과는 다르게 변수에 할당할 수 없는 것이 자연스러워 보임 BUT!! 가능함
- 이유) 자바스크립트 엔진이 코드의 문맥에 따라 동일한 함수를 문이 아닌 표현식으로 해석하는 경우가 있음
- 따라서) 위와 같이 이름을 가진 형태의 함수 리터럴은 코드 문맥에 따라 선언문으로도, 표현식으로도 사용될 수 있음
함수 표현식
- 자바스크립트에서 함수는 일급 객체임
- 일급 객체) 다른 객체들에 일반적으로 적용 가능한 연산을 모두 지원하는 객체
- 함수는 다른 함수의 매개변수가 될 수도 있고, 반환값이 될 수도 있으며, 할당도 가능하므로 일급 객체가 되기 위한 조건을 모두 갖추고 있음
-
const sum = function (a, b) { return a + b } sum(10, 24) // 34
- 함수 표현식에서는 위처럼 할당하려는 함수의 이름을 생략하는 것이 일반적임
- 이유) 혼란 방지
-
const sum = function add(a, b) { // 함수 몸통에서 현재 실행 중인 함수를 참조하는 데 사용할 수 있음 // 실제 프로덕션 코드에서 절대로 사용해선 안 됨! console.log(arguments.callee.name) return a + b } sum(10, 24) // add add(10, 24) // Uncaught TypeError: add is not defined
- 위 함수 표현식 예제 살펴보면 실제로 함수를 호출하기 위해 사용된 것은 sum임
- add는 실제 함수 내부에서만 유효한 식별자일 뿐, 함수를 외부에서 호출하는 데에는 사용할 수 없는 식별자임
- 따라서) 함수 표현식에서 함수에 이름을 주는 것은 함수 호출에 도움이 전혀 안되는, 코드를 읽는 데 방해되는 요소임
함수 표현식과 선언 식의 차이
- 가장 큰 차이점 : 호이스팅(hoisting) 여부
- 함수의 호이스팅
- 함수 선언문이 마치 코드 맨 앞단에 작성된 것처럼 작동하는 자바스크립트의 특징
- 함수에 대한 선언을 실행 전에 미리 메모리에 등록하는 작업
- 함수의 호이스팅
-
함수를 선언한 hello는 코드 중간에 있음에도 불구하고, 맨 앞에서 호출한 hello()는 어떠한 에러도 없이, 정상적인 hello 함수의 작동을 수행하는 것을 알 수 있음. 함수의 호이스팅 특징 덕분에 함수 선언문이 미리 메모리에 등록됐고, 코드의 순서에 상관없이 정상적으로 함수 호출 가능hello() // hello function hello() { console.log('hello') } hello() // hello
-
함수 표현식은 함수를 변수에 할당함. 변수도 마찬가지로 호이스팅이 발생하지만 함수의 호이스팅과 다르게, 호이스팅되는 시점에서 var의 경우에는 undefined로 초기화한다는 차이가 있음. 함수와 다르게 변수는, 런타임 이전에 undefined로 초기화되고, 할당문이 실행되는 시점, 즉 런타임 시점에 함수가 할당되어 작동함console.log(typeof hello === 'undefined') // true hello() // Uncaught TypeError: hello is not a function var hello = function () { console.log('hello') } hello()
- 그럼 뭐가 더 좋지?
- 함수 선언문
- 장점) 함수가 선언된 위치에 상관 없이 함수 호이스팅의 특징을 살리면 어디서든 호출 가능, 변수 선언과 뚜렷하게 구별됨
- 단점) 함수가 선언되기 전에 호출되는 것이 이상하게 느껴짐. 실제 함수를 어디서 어떻게 선언했는지 해당 스코프를 끝까지 확인하지 않으면 개발자기 찾기 어려움. 관리해야 하는 스코프가 길어질 경우 나쁘게 작용함.
- 둘 중 어떠한 것이 기능적으로 우위에 있다고 구별지을 만한 점 없음. 현재 자바스크립트 코드를 작성하는 환경을 살펴보고, 상황에 맞는 작성법을 일관되게 사용하면 됨
- 함수 선언문
Function 생성자
- 자바스크립트 오래 사용했다 하더라도 거의 사용해 본 적 없을 만한 함수 선언 방식
-
const add = new Function('a', 'b', 'return a + b') add(10, 24) // 34
- 코드 작성 관점에서만 보더라도 매개변수, 함수의 몸통을 모두 문자열로 작성해야 함. 코드 양 길어지면 더욱 혼란스러워짐.
- 이렇게 생성자 방식으로 함수를 만들게 되면 함수의 클로저 또한 생성되지 않음
- 자바스크립트의 eval만큼이나 실제 코딩에서 사용되지 않는 방법
화살표 함수
- ES6에서 새롭게 추가된 함수 생성 방식. 최근 자바스크립트 개발자들 사이에서 각광받는 함수 정의 방식.
- function이라는 키워드 대신 =>라는 화살표 활용해 함수를 만드는데, 타이핑할 글자 수가 줄어든다(..!)는 측면에서 많이 사용되는 듯
-
const add = (a, b) => { return a + b } const add = (a, b) => a + b
- 그러나 화살표 함수는 겉보기와 다르게 앞서 언급한 함수 생성 방식과 몇 가지 큰 차이점이 있음
- 화살표 함수에서는 constructor를 사용할 수 없음. 즉, 생성자 함수로 화살표 함수를 사용하는 것 불가능
const Car = (name) { this.name = name } // Uncaught TypeError: Car is not a constructor const myCar = new Car('하이')
- 화살표 함수에서는 arguments가 존재하지 않음
function hello() { console.log(arguments) } // Arguments(3) [1, 2, 3, callee: f, Symbol.iterator): f] hello(1, 2, 3) const hi = () => { console.log(arguments) } // Uncaught TypeError: arguments is not defined hi(1, 2, 3)
- 화살표 함수와 일반 함수의 가장 큰 차이점은 this 바인딩. 이 차이로 인해 뒤에 설명할 클래스형 컴포넌트에서 이벤트에 바인딩할 메서드 선언 시 화살표 함수로 했을 때와 일반 함수로 했을 때 서로 다르게 작동함
- this) 자신이 속한 객체나 자신이 생성할 인스턴스를 가리키는 값
- 이 this는 화살표 함수 이전까지는 함수를 정의할 때 결정되는 것이 아니라, 함수가 어떻게 호출되느냐에 따라 동적으로 결정됨. 만약 함수가 일반 함수로서 호출된다면, 그 내부의 this는 전역 객체를 가리킴
- 그러나 이와 달리 화살표 함수는 함수 자체의 바인딩을 갖지 않음. 화살표 함수 내부에서 this를 참조하면 상위 스코프의 this를 그대로 따르게 됨. 이에 대한 예제를 아래에서 리액트의 클래스형 컴포넌트 기반으로 살펴봄.
-
// 클래스형 컴포넌트에서 일반 함수와 화살표 함수로 state를 갱신하는 예제 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를 접근할 수 있는 방법이 바로 화살표 함수인 것. 이러한 차이점은 바벨에서도 확인 가능
-
// 바벨 트랜스파일링으로 확인해 볼 수 있는 화살표 함수와 일반 함수의 차이 // before: const hello = () => { console.log(this) } function hi() { console.log(this) } // after: 바벨에서는 이렇게 변환함 var _this = void 0 var hello = function hello() { // 바벨에서 화살표 함수 내부의 _this 자체를 undefined로 바꿔버림 console.log(_this) } function hi() { console.log(this) }
- 화살표 함수는 this가 선언되는 시점에 이미 상위 스코프로 결정돼 있어 미리 _this를 받아 사용하는 모습과 다르게, 일반 함수는 호출하는 런타임 시점에 결정되는 this를 그대로 따르는 모습
- 이처럼 화살표 함수의 this는 선언 시점에 결정된다는 일반 함수와 대비되는 큰 차이점이 있기 때문에 단순히 일반 함수의 축약형으로 보기엔 무리가 있음
- 따라서) 화살표 함수와 일반 함수를 사용할 때, 특히 this를 사용할 수 밖에 없는 클래스형 컴포넌트 내부에서 각별한 주의 필요요
'Modern React Deep Dive > 01장 리액트 개발을 위해 꼭 알아야 할 자바스크립트' 카테고리의 다른 글
1.2.4 함수를 만들 때 주의해야 할 사항 (0) | 2024.04.02 |
---|---|
1.2.3 다양한 함수 살펴보기 (0) | 2024.04.02 |
1.2.1 함수란 무엇인가? (0) | 2024.04.02 |
1.1.4 리액트에서의 동등 비교 (0) | 2024.03.29 |
1.1.3 자바스크립트의 또 다른 비교 공식, Object.is (0) | 2024.03.29 |