코딩/Javascript

자바스크립트 함수를 선언하는 6가지 방법

드리프트 2021. 8. 24. 15:02
728x170

목차

 

1. 함수 선언 (Function declaration)

 

   1.1 일반적인 함수

 

   1.2 함수 표현식과의 차이점

 

   1.3 조건문에서의 함수 선언

 

2. 함수 표현식(function expression)

 

   2.1 명명된 함수 표현(Named function expression)

 

   2.2 선호된 명명된 함수 표현식(Favor named function expression)

 

 3. 약식 메서드 정의

 

   3.1 계산된 속성 이름 및 메서드(Computed property names and methods)

 

4. 애로우 함수(Arrow function)

 

   4.1 콘텍스트 투명성(Context transparency)

 

   4.2 짧은 콜백

 

5. 제너레이터 함수(Generator function)

 

6. new Function

 

 

 

함수는 한 번 정의하면 나중에 여러 번 호출할 수 있는 코드 블록을 의미하는데요,

 

JavaScript에서 함수는 아래와 같은 많은 구성 요소에 의해 영향을 받습니다.

 

1) 함수 본문을 구성하는 JavaScript 코드

 

2) 매개변수 목록

 

3) 렉시컬 범위(lexical scope)에서 액세스 할 수 있는 변수

 

4) 반환된 값

 

5) 함수가 호출될 때의 this 컨텍스트

 

6) 이름 있는 함수 또는 익명 함수

 

7) 함수 객체를 담는 변수

 

8) arguments 객체(또는 애로우 함수에서의 누락)

 

이점에 유의해서 앞으로 JavaScript 함수를 선언하는 6가지 접근 방식에 대해 알아보겠습니다.

 

 

1. 함수 선언 (Function declaration)

 

 

함수 선언은 function 키워드, 함수 이름(필수), 한 쌍의 괄호 안에 있는 매개변수 목록(para1,..., paramN) 및 본문 코드를 구분하는 한 쌍의 중괄호 {...}로 구성됩니다..

 

함수 선언의 예:

 

// function declaration
function isEven(num) {
  return num % 2 === 0;
}

isEven(24); // => true[text](link)
isEven(11); // => false

 

function isEven(num) {...}은 숫자가 짝수인지 판별하는 이름이 isEven인 함수를 정의하는 함수 선언입니다.

 

이렇게 함수를 선언하면 함수 이름과 동일한 식별자를 사용하여 현재 범위(current scope)에 변수를 만듭니다.

 

이 변수는 함수 개체(function object)를 보유합니다.

 

함수 변수는 Javascript의 Hoisting에 의해 현재 범위의 맨 위로 올라갑니다.

 

그래서 함수 선언 전에 우리는 그 함수를 호출할 수 있습니다.

(자세한 내용은 아래 참조)

https://cpro95.tistory.com/431

 

자바스크립트 호이스팅(Hoisting) 완벽 이해

안녕하세요? 우리가 자바스크립트나 다른 랭귀지로 코드를 짤 때 제일 중요한 것 중 하나가 바로 변수인데요. 변수란 서로 상호 작용하는 작은 데이터 또는 논리 조각이라고 볼 수 있습니다. 이

cpro95.tistory.com

 

생성된 함수에는 이름(name)이 지정됩니다.

 

그래서 함수 개체의 name 속성(property)에 이름이 저장됩니다.

 

다음 예제에서 이러한 속성을 살펴보겠습니다.

 

// Hoisted variable
console.log(hello('Aliens')); // => 'Hello Aliens!'

// Named function
console.log(hello.name) // => 'hello'

// Variable holds the function object
console.log(typeof hello); // => 'function'

function hello(name) {
  return `Hello ${name}!`;
}

 

function hello(name) {...}라고 쓰는 함수 선언은 현재 범위의 맨 위로 호이스트 되는 변수 hello를 만듭니다.

 

그리고 hello 변수에는 함수 개체가 포함되고 hello.name 에는 'hello'라는 함수 이름이 지정됩니다.

 

 

1.1 일반적인 함수

 

함수 선언(function declaration)은 일반적인 함수가 필요한 경우에 가장 적합합니다.

 

보통 함수를 한 번 선언하고 나서, 나중에 전체 코드 여러 위치에서 호출할 수 있다는 것을 의미합니다.

 

일반적인 함수의 기본적인 사용형태는 다음과 같습니다.

 

function sum(a, b) {
  return a + b;
}

sum(5, 6); // => 11

([3, 7]).reduce(sum) // => 10

 

함수 선언은 함수 호출과 함께 현재 범위에서 변수를 생성하기 때문에 재귀 형식이나 또는 이벤트 리스너 대응에 유용합니다.

 

특히, 함수 이름으로 함수 변수와 바인딩을 생성하지 않는 함수 표현식이나 애로우(arrow) 함수와 다릅니다.

 

예를 들어, 다음 코드와 같이 팩토리얼을 재귀적으로 계산하려면 내부 함수에 액세스해야 합니다.

 

function factorial(n) {
  if (n === 0) {
    return 1;
  }
  return n * factorial(n - 1);
}

factorial(4); // => 24

 

factorial() 내부에서 재귀 호출은 factorial(n - 1) 형식처럼 함수를 보유하는 변수를 사용하여 만들어집니다.

 

물론 const factorial = function(n) {...}처럼 함수 표현식을 사용하여도 변수에 함수를 할당하는 것이 가능합니다.

 

그러나 function factorial(n) 형식처럼 함수 선언은 함수 표현식 보다 훨씬 간결합니다(const, = 가 필요 없음).

 

함수 선언의 중요한 속성은 호이 스팅 메커니즘입니다.

 

즉, 동일한 범위에서 함수 선언 전에 그 함수를 사용할 수 있다는 점이죠.

 

어떤 상황에서는 호이 스팅이 매우 유용합니다.

 

예를 들어, 함수 구현을 읽지 않고 스크립트 시작 부분에서 함수가 어떻게 호출되는지 보고 싶을 때입니다.

 

함수 구현은 파일 아래에 있을 수 있으므로 거기까지 스크롤하지 않아도 되거든요.

 

여기에서 자바스크립트 호이 스팅에 대한 자세한 내용을 읽을 수 있습니다.

 

 

1.2 함수 표현식과의 차이점

 

함수 선언과 함수 표현식은 혼동하기 쉬운데요.

 

이 둘은 매우 유사해 보이지만 다른 속성을 가진 함수를 생성합니다.

 

이 둘의 차이를 기억하기 쉬운 팁은 함수 선언은 항상 키워드 function으로 시작하고. 만약 그렇지 않으면 전부 함수 표현식입니다.

 

다음 예제는 function 키워드로 시작하는 함수 선언입니다.

// Function declaration: starts with "function"
function isNil(value) {
  return value == null;
}

 

반면, JavaScript에서는 함수 표현식의 경우 function 키워드로 시작하지 않습니다.

 

// Function expression: starts with "const"
const isTruthy = function(value) {
  return !!value;
};


// Function expression: an argument for .filter()
const numbers = ([1, false, 5]).filter(function(item) {
  return typeof item === 'number';
});


// Function expression (IIFE): starts with "("
(function messageFunction(message) {
  return message + ' World!';
})('Hello');

 

 

1.3 조건문에서의 함수 선언

 

일부 JavaScript 환경에서는 if, for 또는 while 문의 블록 {...} 내에 선언되는 함수를 호출할 때 참조 오류가 발생할 수 있습니다.

 

Javascript의 엄격 모드(strict mode)를 활성화한 상태에서 다음 예제를 통해 함수가 조건부에서 선언될 때 어떤 일이 발생하는지 봅시다.

 

(function() {
  'use strict';
  if (true) {
    function ok() {
      return 'true ok';
    }
  } else {
    function ok() {
      return 'false ok';
    }
  }

console.log(typeof ok === 'undefined'); // => true
console.log(ok()); // Throws "ReferenceError: ok is not defined"
})();

 

ok()를 호출하면 JavaScript에서 ReferenceError: ok is not defined가 발생합니다.

 

함수 선언이 조건부 블록 안에 있기 때문입니다.

 

비 엄격 모드에서는 조건부에서의 함수 선언이 허용되므로 더욱 혼란스럽습니다.

 

이러한 상황에 대한 일반적인 대응으로 조건에 의해 함수를 생성해야 하는 경우에는 함수 표현식을 사용합니다.

 

그것이 어떻게 가능한지 봅시다:

 

(function() {
  'use strict';
  let ok;
  if (true) {
    ok = function() {
      return 'true ok';
    };
  } else {
    ok = function() {
      return 'false ok';
    };
  }

console.log(typeof ok === 'function'); // => true
console.log(ok()); // => 'true ok'
})();

 

함수는 일반 객체이므로 조건에 따라 변수에 할당합니다.

 

이제 ok()를 호출하면 오류 없이 잘 작동합니다.

 

 

2. 함수 표현식(function expression)

 

함수 표현식은 함수 키워드, 함수 이름, 한 쌍의 괄호(para1,..., paramN) 안에 있는 매개변수들, 그리고 본문을 구분하는 한 쌍의 중괄호 {... }에 의해 결정됩니다.

 

 

함수 표현식 예제:

 

const count = function(array) { // Function expression
  return array.length;
}


const methods = {
  numbers: [1, 5, 8],
  sum: function() { // Function expression
    return this.numbers.reduce(function(acc, num) { // func. expression
      return acc + num;
    });
  }
}

count([5, 7, 8]); // => 3
methods.sum(); // => 14

 

함수 표현식은 다양한 상황에서 사용할 수 있게끔 함수 객체를 생성합니다.

 

1) 객체로써 변수에 할당 count = function(...) {...}

2) 객체 안에서의 메서드 생성 sum : function() {...}

3) 함수를 콜백으로 사용. reduce(function(...) {...})

 

함수 표현식은 JavaScript에서 아주 활발히 사용되고 있습니다.

 

일반적으로 애로우(Arrow) 함수와 함께 이러한 유형의 함수 선언을 처리합니다(짧은 구문과 어휘 콘텍스트를 선호하는 경우).

 

 

2.1 명명된 함수 표현(Named function expression)

 

이름이 없는 함수는 익명 함수라고 불립니다(name 속성은 당연히 빈 문자열' ' 이 됩니다).

(

function(variable) {return typeof variable; }

).name; // => ''

 

위 예제는 이름이 빈 문자열인 익명 함수입니다.

 

때때로 함수 이름은 유추될 수도 있습니다.

 

예를 들어, 익명 함수가 변수에 할당된 경우 말입니다.

 

const myFunctionVar = function(variable) {
  return typeof variable;
};

myFunctionVar.name; // => 'myFunctionVar'

 

myFunctionVar 변수 이름이 함수 이름을 유추하는 데 사용되기 때문에 익명 함수 이름은 'myFunctionVar'입니다.

 

표현식에 지정된 이름이 있는 경우 명명된 함수 표현식이라고 합니다.

 

명명된 함수 표현식에는 몇 가지 추가 속성이 있는데요,

 

1) 이름이 있는 함수가 생성됩니다. 즉, name 속성이 함수 이름을 가지게 됩니다.

2) 함수 본문 내에서 같은 이름을 가진 변수는 함수 객체를 가리킵니다.

 

예를 사용하여 이해해 보겠습니다.

 

const getType = function funName(variable) {
  console.log(typeof funName === 'function'); // => true
  return typeof variable;
}


console.log(getType(3)); // => 'number'
console.log(getType.name); // => 'funName'
console.log(typeof funName); // => 'undefined'

 

function funName(variable) {...}은 명명된 함수 표현식입니다.

 

funName 변수는 함수 범위 내에서 액세스 할 수 있지만 외부에서는 액세스할 수 없습니다.

 

어느 쪽이든 함수 객체의 name 속성에는 funName이라는 이름이 있습니다.

 

 

2.2 선호된 명명된 함수 표현식(Favor named function expression)

 

함수 표현식 const fun = function() {}처럼 함수가 변수에 할당되면 어떤 자바스크립트 엔진은 이 변수에서 함수 이름을 유추합니다.

 

그러나 콜백은 변수에 저장하지 않고 익명 함수 표현식으로 전달되므로 자바스크립트 엔진이 이 이름을 유추할 수 없습니다.

 

다음과 같은 이점을 얻으려면 명명된 함수 선언 방식을 선호하고 익명 함수를 피하는 것이 합리적일 겁니다.

 

1) 함수 이름을 사용할 때 오류 메시지, 호출 스택은 더 자세한 정보를 표시합니다.

2) 익명 스택 이름의 숫자를 줄여 보다 편안한 디버깅이 가능합니다.

3) 단순히 함수 이름은 함수가 하는 일을 나타낼 수 있습니다.

4) 재귀 호출 또는 이벤트 리스너 분리를 위해 해당 범위 내의 함수에 액세스 할 수 있습니다.

 

 

3. 약식 메서드 정의

 

약식 메서드 정의는 객체 리터럴 및 ES2015 클래스의 메서드 선언에서 사용할 수 있습니다.

 

함수 이름을 사용하여 정의할 수 있으며, 그 뒤에 한 쌍의 괄호(para1,..., paramN)로 묶인 매개변수들과 본문을 구분하는 한 쌍의 중괄호 {... }를 사용할 수 있습니다.

 

다음 예제에서는 객체 리터럴에서 약식 메서드 정의를 보여주고 있습니다.

 

const collection = {
  items: [],
  add(...items) {
    this.items.push(...items);
  },
  get(index) {
    return this.items[index];
  }
};

collection.add('C', 'Java', 'PHP');
collection.get(1) // => 'Java'

 

컬렉션 객체의 add() 및 get() 메서드는 약식 메서드 정의를 사용하여 정의되고 있습니다.

 

이러한 메서드는 collection.add(...) 및 collection.get(...)처럼 평소대로 호출됩니다.

 

메서드 정의의 약식 접근 방식은 예전에 쓰였던 add: function(...) {...} 같은 기존 방식에 비해 몇 가지 이점이 있습니다.

 

1) 구문이 짧을수록 이해하기 쉽습니다.

2) 약식 메서드 정의는 함수 표현식과 반대로 명명된 함수를 생성합니다. 디버깅에 유용합니다.

 

 

클래스에서 함수를 선언할 때는 약식 형식의 메서드 선언을 써야 합니다.

 

class Star {
  constructor(name) {
    this.name = name;
  }

  getMessage(message) {
    return this.name + message;
  }
}

const sun = new Star('Sun');
sun.getMessage(' is shining') // => 'Sun is shining'

 

 

3.1 계산된 속성 이름 및 메서드(Computed property names and methods)

 

ECMAScript 2015에는 객체 리터럴 및 클래스의 계산된 속성 이름(Computed property names)이라는 멋진 기능이 추가되었습니다.

 

계산된 속성(Computed property)은 약간 다른 구문 [methodName]() {...}을 사용하므로 메서드 정의는 다음과 같습니다.

const addMethod = 'add',
getMethod = 'get';
const collection = {
  items: [],
  [addMethod](...items) {
    this.items.push(...items);
  },
  [getMethod](index) {
    return this.items[index];
  }
};

collection[addMethod]('C', 'Java', 'PHP');
collection[getMethod](1) // => 'Java'

 

[addMethod](...) {...} 및 [getMethod](...) {...}는 계산된 속성 이름이 있는 약식 메서드 선언입니다.

 

 

4. 애로우 함수(Arrow function)

 

화살표 함수는 매개변수들(param1, param2,..., paramN)을 포함하는 한 쌍의 괄호를 사용하여 정의되며, 그 뒤에 굵은 화살표 =>와 본문을 구분하는 한 쌍의 중괄호 {...}가 옵니다.

 

애로우 함수에 매개변수가 하나만 있는 경우 괄호를 생략할 수 있는데요,

 

또, 함수 본문에 단일 문장이 오는 경우 중괄호도 생략할 수 있습니다.

 

애로우 함수의 기본 사용법을 살펴보겠습니다.

const absValue = (number) => {
  if (number < 0) {
    return -number;
  }

  return number;
}

absValue(-10); // => 10
absValue(5); // => 5

 

absValue는 숫자의 절댓값을 계산하는 애로우 함수입니다.

 

굵은 화살표를 사용하여 선언된 애로우 함수는 다음과 같은 속성을 가집니다.

 

1) 애로우 함수는 실행 콘텍스트를 생성하지 않고 렉시컬(lexically)하게 사용합니다(호출에 따라 이를 생성하는 함수 표현식 또는 함수 선언과 반대 의미입니다).

2) 애로우 함수는 익명입니다. 그러나 Javascript 엔진은 함수를 보유하는 변수에서 이름을 유추할 수 있습니다.

3) arguments 객체는 애로우 함수에서 사용할 수 없습니다(arguments 객체를 제공하는 다른 선언 유형과 달리). 하지만 나머지 매개변수(... params)를 자유롭게 사용할 수 있습니다.

 

 

4.1 콘텍스트 투명성(Context transparency)

 

이 키워드는 JavaScript의 혼란스러운 측면입니다.

 

함수는 자체 실행 콘텍스트를 생성하기 때문에 종종 this 값을 감지하기 어렵습니다.

 

ECMAScript 2015는 애로우 함수 기능을 도입하여 this 사용법을 개선했습니다.

 

함수가 둘러싸는 콘텍스트를 필요로 할 때 .bind(this)를 사용하거나 컨텍스트 var self = this를 저장할 필요가 없기 때문에 매우 좋습니다.

 

this가 어떻게 외부 함수에서 상속되는지 봅시다.

 

class Numbers {
  constructor(array) {
    this.array = array;
  }

  addNumber(number) {
    if (number !== undefined) {
    this.array.push(number);
    }

    return (number) => {
      console.log(this === numbersObject); // => true
      this.array.push(number);
    };
  }
}

const numbersObject = new Numbers([]);
const addMethod = numbersObject.addNumber();

addMethod(1);
addMethod(5);
console.log(numbersObject.array); // => [1, 5]

 

Numbers 클래스는 숫자 배열을 보유하고 새 숫자를 삽입하기 위해 addNumber() 메서드를 제공합니다.

 

addNumber()가 인수 없이 호출되면 숫자 삽입을 허용하는 클로저가 반환됩니다.

 

이 클로저는 addNumbers() 메서드에서 사전적으로 콘텍스트를 가져오기 때문에 this 를 numbersObject 인스턴스로 갖는 애로우 함수입니다.

 

애로우 함수 기능이 없으면 컨텍스트를 수동으로 수정해야 합니다.

 

즉,. bind() 메서드와 같은 해결 방법을 사용해야 하는 것을 의미합니다.

 

//...

return function(number) {
  console.log(this === numbersObject); // => true
  this.array.push(number);
}.bind(this);

//...

 

또는 콘텍스트를 분리된 변수 var self = this에 저장합니다.

//...

const self = this;
return function(number) {
  console.log(self === numbersObject); // => true
  self.array.push(number);
};

//...

 

컨텍스트 투명도는 둘러싸는 콘텍스트에서 가져온 그대로 this를 유지하려는 경우 사용할 수 있습니다.

 

 

4.2 짧은 콜백

 

애로우 함수를 생성할 때 만약 단일 매개변수 또는 단일 본문 코드일 경우에는 괄호 쌍과 중괄호를 선택적으로 사용할 수 있습니다.

 

이 방식으로 애로우 함수는 매우 짧은 콜백 함수를 만드는 데 도움이 됩니다.

 

배열에 0이 포함되어 있는지 찾는 함수를 만들어 보겠습니다.

 

const numbers = [1, 5, 10, 0];

numbers.some(item => item === 0); // => true

 

item => item === 0은 직관적으로 보이는 애로우 함수입니다.

 

중첩된 짧은 애로우 함수는 읽기 어렵습니다.

 

가장 짧은 애로우 함수 형식을 사용하는 편리한 방법은 단일 콜백(중첩 없음)입니다.

 

필요한 경우 중첩 애로우 함수를 작성할 때 확장된 애로우 함수 구문을 사용하시면 읽기가 더 쉽습니다.

 

 

5. 제너레이터 함수(Generator function)

 

JavaScript의 Generator 함수는 Generator 객체를 반환합니다.

 

제너레이터 함수 문법은 별표 문자 *가 필요하다는 점만 제외하면 함수 표현식, 함수 선언 또는 메서드 선언과 비슷합니다.

 

제너레이터 함수는 다음과 같은 형식으로 선언할 수 있습니다.

 

 

a. 함수 선언 형식 function* <name>():

function* indexGenerator(){
  var index = 0;
  while(true) {
    yield index++;
  }
}

const g = indexGenerator();
console.log(g.next().value); // => 0
console.log(g.next().value); // => 1

 

 

b. 함수 표현 형태 function* ():

const indexGenerator = function* () {
  let index = 0;
  while(true) {
    yield index++;
  }
};

const g = indexGenerator();
console.log(g.next().value); // => 0
console.log(g.next().value); // => 1

 

 

c. 약식 메서드 정의 형식 *<name>():

const obj = {

  *indexGenerator() {
    var index = 0;
    while(true) {
      yield index++;
    }
  }
}

const g = obj.indexGenerator();
console.log(g.next().value); // => 0
console.log(g.next().value); // => 1

 

3가지 경우 모두 제너레이터 함수는 제너레이터 객체 g를 반환합니다.

 

 

6. new Function

 

JavaScript에서 함수는 일급 객체입니다.

 

위에서 설명한 선언 방식은 동일한 함수 객체를 생성합니다. 예를 들어 보겠습니다.

 

function sum1(a, b) {
  return a + b;
}

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

const sum3 = (a, b) => a + b;
console.log(typeof sum1 === 'function'); // => true
console.log(typeof sum2 === 'function'); // => true
console.log(typeof sum3 === 'function'); // => true

 

함수 객체 타입에는 생성자가 있습니다: Function.

 

Function이 생성자 new Function(arg1, arg2,..., argN, bodyString)으로 호출되면 새 함수가 생성됩니다.

 

생성자에 전달된 인수 arg1, args2,..., argN은 새 함수의 매개변수 이름이 되고 마지막 인수 bodyString은 함수 본문 코드로 사용됩니다.

 

두 숫자를 합하는 함수를 만들어 보겠습니다.

 

const numberA = 'numberA', numberB = 'numberB';
const sumFunction = new Function(numberA, numberB,
  'return numberA + numberB'
);

sumFunction(10, 15) // => 25

 

Function 생성자 호출로 생성된 sumFunction은 매개변수 numberA와 numberB를 가지며 본문은 numberA + numberB를 반환합니다.

 

이렇게 생성된 함수는 현재 범위(current scope)에 접근할 수 없으므로 클로저를 생성할 수 없습니다.

 

항상 전역 범위에서 생성됩니다.

 

new Function의 가능한 한 가지 응용 방법은 브라우저 또는 NodeJS 스크립트에서 전역 개체에 액세스 하는데 쓰입니다.

 

(function() {
  'use strict';
  const global = new Function('return this')();
  console.log(global === window); // => true
  console.log(this === window); // => false
})();

 

일반적으로 함수는 new Function()을 사용하여 선언되어서는 안 된다는 것을 기억하십시오.

 

함수 본문은 런타임 시 평가되기 때문에 이 접근 방식은 많은 eval() 사용 문제를 야기합니다.

 

보안 위험, 더 어려운 디버깅, 엔진 최적화를 적용할 방법 없기 때문에 되도록 사용하지 않는 것이 좋습니다.

 

 

7. 어떤 방법이 좋을까요?

 

승자도 패자도 없습니다.

 

선택할 선언 유형은 상황에 따라 다릅니다.

 

그러나 일반적인 상황에서 따를 수 있는 몇 가지 규칙이 있습니다.

 

함수가 내부 함수에서 this를 사용하는 경우 애로우 함수가 좋은 솔루션입니다.

 

콜백 함수에 하나의 짧은 문이 있는 경우 애로우 함수도 짧고 가벼운 코드를 생성하기 때문에 좋은 옵션입니다.

 

객체 리터럴에서 메서드를 선언할 때 구문이 더 짧은 경우에는 약식 메서드 선언이 더 좋습니다.

 

일반적으로 함수를 선언하는 new Function 방식은 사용하지 않아야 합니다.

 

주로 잠재적인 보안 위험이 발생하기 때문에 편집기에서 코드 자동 완성을 허용하지 않으며 엔진 최적화도 못하게 됩니다.

그리드형