관리 메뉴

bright jazz music

함수 관련 본문

Language/Javascript

함수 관련

bright jazz music 2023. 12. 31. 13:15

1. 함수를 정의하는 방법

 

JS에서 함수를 정의하는 방법은 크게 네 가지이다.

 

- 함수 선언문

- 함수 표현식

- function 생성자

- 화살표 함수

 

1.1. 함수 선언문

JS에서 함수를 선언할 때 가장 일반적으로 사용하는 방식

function add(a, b) { //이름이 있다
	return a + b
}

 

함수 선언문은 표현식이 아닌 일반 문(statement)로 분류된다. 표현식이란 무언가 값을 산출하는 구문을 의미한다. 즉, 함수 선언으로는 어떠한 값도 표현되지 않았으므로 표현식이 아닌 문으로 분류된다.

 

그러나 JS엔진이 코드의 문맥에 따라 동일한 함수를 문이 아닌 표현식으로 해석하는 경우가 있다. 

const sum = function sum(a, b) { //함수에 이름이 있다
	return a + b
}

sum(10, 24) // 34

함수 선언문은 어떠한 값도 표현하지 않으므로 표현식과는 다르게 다른 변수에 할당할 수 없는 것이 자연스러워 보이지만, 위에서는 엔진이 선언문을 표현식으로 해석하였기 때문에 계산이 되었다.  따라서 위와 같이 이름을 가진 함수 리터럴은 코드 문맥에 따라 선언문으로도, 표현식으로 사용될 수 있음을 숙지해야 한다.

 

1.2. 함수 표현식

JS의 함수는 일급 객체다. 일급 객체는 변수에 할당이 가능하며, 함수의 매개변수가 될 수 있고, 반환값이 될 수도 있다. 

const sum = function (a, b) {
	return a + b
}

sum(10, 24) // 34

 

함수 표현식에서는 할당하려는 함수의 이름을 생략하는 것이 일반적이다. 코드의 혼란을 방지하기 위해서이다.

 

const sum = function add(a, b) {
	// 함수 몸통에서 현재 실행 중인 함수를 참조하는 데 사용할 수 있다.
    
	console.log(argument.callee.name)
    return a + b
}

sum(10, 24)
// add
add(10, 24) // Uncaught TypeError: add is not defined

 

 

위 함수 표현식에서 실제로 함수를 호출하기 위해서 사용된 것은 sum임을 알 수 있다. 그리고 add는 실제 함수 내부에서만 유효한 식별자일 뿐, 함수를 외부에서 호출하는 데에는 사용할 수 없는 식별자이다. 따라서 함수 표현식에서 함수에 이름을 주는 것은 함수 호출에 도움이 전혀 안 되는 요소이다.

 

함수 표현식과 선언 식의 차이

 

- 호이스팅: 함수 선언문이 마치 코드 맨 앞단에 작성된 것처럼 작동하는 자바스크립트의 특징. 호이스팅은 함수에 대한 선언을 실행 전에 미리 메모리에 등록하는 작업을 의미한다. 따라서 코드의 순서에 상관 없이 정상적으로 함수를 호출할 수 있다. 

hello() //hello

function hello() {
	console.log('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()

 

위 코드에서는 hello가 undefined로 남아 있다. 함수와 다르게 변수는 undefined로 초기화되고, 할당문이 실행되는 시점, 즉 런타임 시점에 함수가 할당되어 작동한다는 것을 알 수 있다.

 

- 함수를 자유로이 선언하고 호출하고 싶다면 함수 선언문이 더 좋을 수 있다. 그러나 호출이 먼저 발생한 경우 선언부를 찾기가 어려울 수 있다.

 

 

1.3. Function 생성자

const add = new Function('a', 'b', 'return a + b')
add(10, 24) //34

eval만큼이나 사용되지 않으며 불편하다.

 

 

1.4. 화살표 함수

function이라는 키워드 대신 =>라는 화살표를 활용해서 함수를 만든다.

const add = (a, b) => {
	return a + b
}

//또는 {}와 return문을 생략하고 아래와 같이 생성할 수 있다.
const add = (a, b) => a + b

 

 

화살표 함수의 특징

 

1.4.1. constructor 사용 불가

- 화살표 함수에서는 constructor를 사용할 수 없다. 생성자 함수로 화살표 함수를 사용하는 것은 불가능하다.

const Car = (name) => {
	this.name = name
}

//uncaught TypeError: Car is not a constructor
const myCar = new Car('하이')

 

 

1.4.2. arguments 미존재

function hello() {
	console.log(arguments)
}

// Argument(3) [1, 2, 3, callee: f, Symbol(Symbol.iterator):f ]
hello(1, 2, 3)

cons hi = () => {
	console.log(arguments)
}

// Uncaught ReferenceError: arguments is not defined
hi(1, 2, 3)

 

arguments는 JS의 함수 내부에서 사용할 수 있는 특별한 객체이다. 이 객체에는 함수에 전달된 모든 인수(argument)가 포함되어 있다. arguments 객체를 사용하면 함수 내에서 명시적으로 매개변수를 정의하지 않고도 모든 인수에 접근할 수 있다. 그러나 화살표 함수에서는 사용할 수 없다. 대신 rest parameter(나머지 매개변수)를 사용하여 인수를 처리할 수 있다.

const hi = (...args) => {
  console.log(args);
}

hi(1, 2, 3); // [1, 2, 3]

 

 

cf. arguments 객체를 사용하여 전달된 모든 인수에 접근하는 예시

function printArguments() {
  console.log(arguments); // arguments 객체 출력
}

printArguments(1, "Hello", [3, 4]);

// 출력 결과:
// Arguments(3) [1, "Hello", Array(2), callee: ƒ, Symbol(Symbol.iterator): ƒ]

 

arguments 객체는 배열과 유사한 동작을 하지만 진짜 배열은 아니다. 따라서 배열 메서드를 사용할 수는 없다. 하지만 인덱스를 사용하여 개별 인수에 접근하고 반복문을 통해 모든 인수를 처리할 수 있다.

 

 

1.4.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) // 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>
        )
    }
}

 

일반 함수에서의 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를 사용할 수밖에 없는 클래스형 컴포넌트의 내부에서 각별한 주의가 필요하다.

 

2. 함수 종류

 

리액트에서 자주 사용되는 함수 종류 

 

2.1. 즉시 실행 함수(IIEF, Immediately Invoked Function Expression)

함수를 정의하고 그 순간 즉시 실행되는 함수. 단 한 번만 호출되고 다시 호출될 수 없는 함수.

//일반 함수
(function (a, b) {
	return a + b
})(10, 24); //34

// 화살표 함수
((a, b) => {
	return a + b
},)(10, 24) //34

 

즉시 실행 함수는 한 번 선언하고 호출된 이후부터는 더 이상 재호출이 불가능하다. 따라서 일반적으로 이름을 붙이지 않는다.

 

이러한 즉시 실행 함수의 특성을 활욯하면 글로벌 스코프를 오염시키지 않는 독립적인 함수 스코프를 운용할 수 있다. 함수의 선언과 실행이 바로 그자리에서 끝나기 때문에 즉시 실행 함수 내부에 있는 값은 그 함수 내부가 아니고서는 접근이 불가능하기 때문이다.

 

또한 코드 맥락적으로 재호출되지 않는다는 점을 각인시킬 수 있어 리팩터링에 도움이 된다. 다른 곳에서 쓰이지 않는 함수라는 것을 각인시킬 수 있기 때문이다.

 

2.2. 고차 함수(High Order Function)

함수를 인수로 받거나 반환하는 역할을 하는 함수를 고차함수라고 한다.

// 함수를 매개변수로 받는 대표적 고차 함수인 Array.prototype.map
const doubleArray = [1, 2, 3].map((item) => item * 2)
doubleArray // [2,4,6]

// 함수를 반환하는 고차 함수의 예
const add = function(a) {
	// a가 존재하는 클로저를 생성
    return function (b) {
    	//b를 인수로 받아 두 합을 반환하는 또 다른 함수를 생성
        return a + b
    }
}

add(1)(3) //4

 

이러한 특징을 활용해 함수형 컴포넌트를 인수로 받아 새로운 함수형 컴포넌트를 반환하는 고차 함수를 만들 수도 있다. 이를 고차 컴포넌트(Hight Order Component)라고 부르는데, 고차 함수형 컴포넌트를 만들면 컴포넌트 내부에서 공통으로 관리되는 로직을 분리해 관리할 수 있어 효율적으로 리팩터링할 수 있다. 

 

3. 함수 생성 시의 주의사항

 

3.1. 함수의 부수 효과(side-effect)를 주의할 것

부수효과란 함수 내의 작동으로 인해 함수가 아닌 함수 외부에 영향을 끼치는 것을 의미한다. 이러한 부수 효과가 없는 함수를 순수 함수라고 하고, 부수 효과가 존재하는 함수를 비순수 함수라고 한다. 즉, 순수함수는 부수 효과가 없고, 언제 어디서나 어떠한 상황에서든 동일한 인수를 받으면 동일한 결과를 반환해야 한다.  그리고 이러한 작동 중에 외부에 어떤 영향도 미쳐서는 안 된다.

 

function PureComponent(props) {
	const {a, b} =  props
    return <div>{a + b}</div>
}

 

위 컴포넌트는 순수한 함수형 컴포넌트이다. props 값을 기준으로 a와 b의 합을 계산하고 HTMLDivElement로 렌더링한다. 이 동작 중 외부에 어떤 영향도 미치지 않았고 언제 어디서든 동일하나 인수를 받아서 동일한 결과를 반환한다. 이와 같이 순수함수 결과가 동일하기 때문에 예측 가능하며 안정적이라는 장점이 있다.

 

그러나 항상 순수함수만 작성할 수는 없다. 부수효과는 피할 수 없다. 부수효과는 애플리케이션 제작 시 피할 수 없는 요소이지만 최대한 억제할 수 있는 방향으로 함수를 설계해야 한다. 리액트의 관점에서 보자면 부수 효과를 처리하는 훅인 useEffect의 작동을 최소화 하는 것이 그 일환이라고 할 수 있다. 

 

3.2. 함수를 가능한 한 작게 만들라

함수 하나에 코드가 너무 길거나 기능이 많아지면 추적하기 어려워진다.

 

3.3. 함수에 이해 가능한 이름을 줄 것

함수명은 간결하고 이해 가능할수록 좋다. 리액트에서 사용하는 useEffect나 useCallback 등의 훅에 넘겨주는 콜백 함수에 네이밍을 붙여준다면 가독성에 도움이 된다.

useEffect(function apiRequest() {
	//...
}, [])


//그러나 화살표 함수를 사용하는 경우 이름을 붙일 수 없다.

 

 

 

 

 

 

 

'Language > Javascript' 카테고리의 다른 글

자바스크립트 기본 문법  (0) 2024.02.26
이벤트 루프와 비동기 통신  (0) 2024.01.03
클로저(closure)  (0) 2024.01.03
자바스크립트 클래스  (0) 2024.01.01
자바스크립트의 타입과 동등비교  (1) 2023.12.29
Comments