JS Deep Dive - 함수

이승윤·2022년 11월 6일
3

함수란?

프로그래밍 일련의 과정을 문으로 구현하고 코드 불록으로 감싸 하나으 ㅣ실행 단위로 정의한 것.
함수 내부로 입력을 전달받는 변수를 매개변수(파라미터), 입력을 인수(아규먼트), 출력을 반환값(리턴 데이터)라고 한다. 또한 함수는 값이며, 여러 개 존재할 수 있으므로 특정 함수를 구별하기 위해 식별자인 함수 이름을 사용할 수 있다.

// 함수 정의
function add(x, y) {
  return x + y;
}
// 함수 호출
var result = add(2, 5);

console.log(result); // 7

함수는 재사용성이 가능하다는 점에서 높은 점수를 받는다. 그렇기 때문에 유지보수의 편의성을 높이고 실수를 줄여 코드의 신뢰성을 높이는 효과가 있다. 뿐만 아니라 코드의 가독성을 향상시킨다.

함수 정의


함수 선언문

// 함수 선언문은 함수 이름을 생략할 수 없다.(함수 호출이 이루어져야 한다.)
function add(x, y) {
  return x + y;
}

// 함수 참조
// console.dir은 함수 객체의 프로퍼티까지 출력한다.(브라우저 상에서만 작동)
console.dir(add); // f add(x,y)

// 함수 호출
console.log(add(2, 5));

함수 표현식

var add = function (x, y) {
  return x + y;
};

console.log(add(2, 5)); // 7

// 기명 함수 표현식
var add = function foo (x, y) {
  return x + y;
};

// 함수 객체를 가리키는 식별자로 호출
console.log(add(2, 5)); // 7

// 함수 이름은 함수 몸체 내부에서만 유효한 식별자이다.
console.log(foo(2, 5)); // 레퍼런스 에러

함수 생성 시점과 함수 호이스팅

// 함수 참조
console.dir(add); // f add(x, y)
console.dir(sub); // undefined

// 함수 호출
console.log(add(2, 5)); // 7
console.log(sub(2, 5)); // 타입 에러

// 함수 선언문
function add(x, y) {
  return x + y;
};

// 함수 표현식
var sub = function (x, y) {
  return x - y;
};

위처럼 함수 선언문이 코드의 선두로 끌어 올려진 것 처럼 동작하는 자바스크립트 고유의 특징을 함수 호이스팅이라 한다.
함수 표현식과 같이 변수로 선언된 값은 undefined로 호이스팅된다.

함수 호이스팅은 함수를 호출하기 전에 반드시 함수를 선언해야 한다는 당연하다고 볼수 있는 규칙을 무시하기 때문에, 함수 선언문 대신 함수 표현식을 사용하는 것이 적절하다.

Function 생성자 함수

var add = new Function('x', 'y', 'return x + y');

console.log(add(2, 5)); // 7

생성자 함수의 경우, 클로저를 생성하지 않는 등, 함수 선언문이나 함수 표현식으로 생성한 함수와 다르게 동작하기 때문에 가급적 사용을 하지 않는다.

화살표 함수

// 중괄호 없이 한줄로 적을 경우, return 키워드를 생략할 수 있다.
const add = (x, y) => x + y;
console.log(add(2, 5)); // 7

화살표 함수, 일반 함수에서의 this 호출 위치

this는 '일반 함수'와 '화살표 함수'에 따라 다르게 정의된다.
일반함수는 호출 위치에서 정의되고,
화살표 함수는 this가 자신이 선언된 렉시컬(함수) 범위에서 정의된다.

let newC
let newD

// 객체 리터럴 선언!
const literal = {
  a: 1,
  b: 2,
  // 일반 함수는 호출 위치에서 따라 this 정의!
  c: function () {
    console.log(this.a)
  },
  // 화살표 함수는 this가 자신이 선언된 렉시컬(함수) 범위에서 this 정의!
  d: () => {
    console.log(this.a)
  }
}
literal.c() // 1
literal.d() // undefined
newC = literal.c
newD = literal.d
newC() // undefined
newD() // undefined
newC.call(literal) // 1
newD.call(literal) // undefined

// 인스턴스 생성!
const instance = new function () {
  this.a = 3
  this.b = 4
  // 일반 함수는 호출 위치에서 따라 this 정의!
  this.c = function () {
    console.log(this.a)
  }
  // 화살표 함수는 this가 자신이 선언된 렉시컬(함수) 범위에서 this 정의!
  this.d = () => {
    console.log(this.a)
  }
}
instance.c() // 3
instance.d() // 3
newC = instance.c
newD = instance.d
newC() // undefined
newD() // 3
newC.call(instance) // 3
newD.call(instance) // 3

함수 호출


함수는 함수를 가리키는 식별자와 한 쌍의 소괄호인 함수 호출 연산자로 호출한다.

매개변수와 인수

function add(x, y) {
  // 모든 함수의 매개변수는 arguments에서 보관된다.
  console.log(arguments); // 2, 5, 10
  console.log(x, y); // 2 5
  return x + y;
}

add(2, 5);
 // 인수가 매개변수보다 부족할 경우, undefined를 매개변수로 가리킨다.
console.log(add(2)); // NaN
// 매개변수보다 인수가 더 많은 경우, 초과된 인수는 무시된다.
console.log(add(2, 5, 10));

// add 함수의 매개변수 x, y는 함수 몸체 내부에서만 참조할 수 있다.
console.log(x, y); // 레퍼런스 에러

인수 확인

// 확인 방법 1
function add(x, y) {
  if (typeof x !== 'number' || typeof y !== 'number') {
    //매개변수를 통해 전달된 인수의 타입이 부적절한 경우 에러를 발생시킨다.
    throw new TypeError('인수는 모두 숫자 값이어야 한다.');
  }
  
  return x + y;
}

console.log(add(2)); // TypeError: 인수는 모두 숫자 값이어야 한다.
// 확인 방법 2: 단축 평가
function add(a, b, c) {
  // 인수를 전달받지 못할 경우, undefined를 반환
  a = a || 0;
  b = b || 0;
  c = c || 0;
  return a + b + c;
}

console.log(add(1, 2, 3)); // 6
console.log(add(1, 2)); // 3
console.log(add(1)); // 1
// 문자열과 같은 다른 타입일 경우, 에러를 발생시키지 않고 그대로 출력
console.log(add(1, '2')); // '120'
console.log(add()); // 0
// 매개변수 기본값(인수를 전달하지 않을 경우와 undefined를 전달한 경우에만 유효)
function add(a = 0, b = 0, c = 0) {
  return a + b + c;
}

console.log(add(1, 2, 3)); // 6
console.log(add(1)); // 1
console.log(add(1,'2',)); // '120'

반환문

function multiply(x, y) {
  return x * y; // 반환문
  // 반환문 이후에 다른 문이 존재하면 그 문은 실행되지 않고 무시된다.
  console.log('실행되지 않는다.');
}

console.log(multiply(3, 5)); // 15

// 반환문은 생략 가능하며, 생략시 암묵적으로 undefined를 반환한다.
function foo() {
  let i = 0;
}
console.log(foo()); // undefined

참조에 의한 전달과 외부 상태의 변경


원시 값과 객체에서 살펴본 값에 의한 전달, 참조에 의한 전달과 마찬가지로 함수 또한 그 방식을 그대로 따른다.

function changeVal(primitive, obj) {
  primitive += 100;
  obj.name = 'Kim';
  return primitive;
}

// 외부 상태
var num = 100;
var person = { name: 'Lee' };

// 원시 값은 값 자체가 복사되어 전달되고, 객체는 참조 값이 복사되어 전달된다.
console.log(changeVal(num, person)); // 200

// 원시 값은 원본이 훼손되지 않는다.
console.log(num); // 100

// 객체는 원본이 훼손된다.
console.log(person); // {name: 'Kim'}

이러한 함수 상의 참조에 의한 전달을 막는 방법으로는 1. 옵저버 패턴 등을 통해 객체의 참조를 공유하는 모든 이들에게 변경사실을 통지하고 이에 대처하는 추가 대응을 하는 방법과 2. 객체를 불변 객체로 만들어 사용하는 방법이 있다.

다양한 함수의 형태


즉시 실행 함수

함수 정의와 동시에 즉시 호출되는 함수를 즉시 실행 함수라고 한다. 즉시 실행 함수는 단 한번만 호출되면 다시 호출할 수 없다.

// 기명 즉시 실행 함수(익명 또한 가능)
(function foo() {
  var a = 3;
  var b = 5;
  return a * b;
}());

// 즉시 실행 함수도 일반 함수처럼 값을 반환할 수 있고 인수를 전달할 수도 있다.
var res = (function () {
  return a * b;
}(3, 5));

console.log(res); // 15

재귀 함수

함수가 자기 자신을 호출하는 것을 재귀 호출이라 한다. 재귀 함수는 자기 자신을 호출하는 행위 즉 재귀 호출을 수행하는 함수를 말한다.

function factorial(n) {
  if (n <= 1) return 1;
  // 재귀 호출
  return n * factorial(n -1);
}

console.log(factorial(5)); // 5! = 5 * 4 * 3 * 2 * 1 = 120

중첩 함수

함수 내부에 정의된 함수를 중첩 함수 또는 내부 함수라 한다. 그리고 중첩 함수를 포함하는 함수는 외부 함수라 부른다.

function outer() {
  var x = 1;
  
  // 중첩 함수
  function inner() {
    var y = 2;
    // 외부 함수의 변수를 참조할 수 있다.
    console.log(x + y); // 3
  }
  
  inner();
}

outer();

콜백 함수

함수의 매개변수를 통해 다른 함수의 내부로 전달되는 함수를 콜백 함수라고 하며, 매개변수를 통해 함수의 외부에서 콜백 함수를 전달받은 함수를 고차 함수라고 한다.

const a = callback => {
  console.log('A')
  callback()
}

const b = () => {
  console.log('B') 
}

a(b) // b가 콜백이다.

// 예제
function sum(a, b, callback) {
  setTimeout(() => {
    callback(a + b)
  }, 1000)
}
sum(3, 7, function(value) {
  console.log(value)
  console.log('B') // 비동기 처리
}) // 10 B
// 예제2 - 이미지 로딩 후, 생성한 태그 append
const loadImage = (url, cb) => {
  const imgEl = document.createElement('img')
  imgEl.src = url
  imgEl.addEventListener('load', () => {
    setTimeout(() => {
      cb(imgEl)
    }, 1000)
  })
}

const containerEl = document.querySelector('.container')
loadImage('https://www.gstatic.com/webp/gallery/4.jpg', imgEl => {
  containerEl.innerHTML = ''
  containerEl.append(imgEl)
})

순수 함수와 비순수 함수

함수형 프로그래밍에서는 어떤 외부 상태에 의존하지도 않고 변경하지도 않는, 즉 부수 효과가 없는 함수를 순수 함수라 하고, 외부 상태에 의존하거나 외부 상태를 변경하는, 즉 부수 효과가 있는 함수를 비순수 함수라고 한다.

순수 함수는 어떤 외부 상태에도 의존하지 않고 오직 매개변수를 통해 함수 내부로 전달된 인수에게만 의존해 값을 생성하여 반환한다. 순수 함수의 또 하나의 특징은 함수의 외부 상태를 변경하지 않는다는 것이다. 즉, 순수 함수는 어떤 외부 상태에도 의존하지 않으며 외부 상태를 변경하지도 않는 함수이다.

var count = 0;
// 순수 함수 increase는 동일한 인수가 전달되면 언제나 동일한 값을 반환한다.
function increase(n) {
  return ++n;
}

// 순수 함수가 반환된 결과값을 변수에 재할당해서 상태를 변경
count = increase(count);
console.log(count); // 1
var count = 0;

// 비순수 함수
function increase() {
  return ++count; // 외부 상태에 의존하며 외부 상태를 반환한다.
}

// 비순수 함수는 외부 상태(count)를 변경하므로 상태 변화를 추적하기 어려워진다.
increase();
console.log(count); // 1
profile
항상 꿈꾸는 개발자

0개의 댓글