함수 다루기

carrot·2022년 6월 24일
0
post-thumbnail

함수, 메서드, 생성자

자바스크립트에서 함수는 1급 객체이다. 변수나 데이터에 담길 수 있고 매개변수로 전달이 가능하며(콜백함수), 함수가 함수를 반환하는 고차 함수를 구현할 수 있다.

그중 기본적으로 알아야 할 3가지가 함수, 메서드, 생성자다.

// 함수. 1급 객체.
function func() {
  // 함수의 this는 전역 객체를 가리킨다.
  return this;
}

// 메서드. 객체에 의존성이 있는 함수.
const obj = {
  func() {
    // 메서드의 this는 메서드가 속한 객체를 가리킨다.
    return this;
  },
}

// 생성자 함수. 인스턴스를 생성하는 역할. class로 대체
function Func() {
  // 생성자 함수의 this는 생성될 인스턴스 객체를 가리킨다.
  return this;
}

parameter & argument

parameter는 함수를 생성할 때 정의되는 매개변수 이고, argument는 함수를 호출할 때 전달하는 인자 이다.

// add 함수를 선언할 때 정의되는 매개변수 a, b가 parameter
function add (a, b) {
  return a + b;
}

// add 함수를 사용(호출)할 때 전달되는 인자 1, 2가 argument
add(1, 2);

복잡한 인자 관리하기

예전에 클린코드라는 책에서 주워 듣기로는 인자는 적을수록 좋다고 했던것 같다. 많을수록 오해가 발생할 여지가 높고 이는 곧 의도하지 않은 에러가 발생할 확률이 높아진다는 의미이기 때문이다.

나도 이 부분에 동의하는 바이고, 다만 이번 클린코드 자바스크립트 강의에서는 적절한 함수 네이밍과 함께 사용되는 인자의 수는 많아도 괜찮다고 하는데 이 부분에 대해서도 적절히 동의한다.

클린코드에서 가장 기억에 남는 것은 변수, 함수 등에 대한 적절한 네이밍이고 이것이 중요한 이유는 코드를 혼자서만 작성하지 않기 때문이다. 읽기 좋은 코드가 좋은 코드라는 보편적 진리를 바탕으로 생각해 보자면, 인자가 많아져도 읽기에만 좋다면 크게 문제될 것은 없다고 생각한다. 다만 개인적으로는, 좋은 이름을 짓는 것이 인자의 수를 줄이는 것보다 어려우므로 인자의 개수를 줄이고, 대신 부가 함수를 만들어서 사용하는 방식이 더 좋은 방식이라고 생각한다.

// 기본적으로 우리는 함수의 이름에서 많은 것을 유추한다.
function sum (a, b) {
  return a + b;
}
// sum이라는 이름에서 함수의 기능도 유추하고, 두 개의 인자를 더하는 로직도 유추할 수 있다.

// 대문자로 시작된 함수는 생성자 함수이고, 함수 이름을 봤을때 유저 정보를 생성한다는 것을 유추 할 수 있다.
// 일반적인 유저 정보로 이메일, 비밀번호, 사용자이름 등을 유추할 수 있으므로 3개 이상의 인자가 와도 괜찮다고 생각한다.
function User (email, password, nickname) {
  this.email = email;
  this.password = password;
  this.nickname = nickname;
}

Default Value

함수를 선언할 때 파라미터를 받는다면, 파라미터에 대한 기본적인 디폴트 값을 설정해 놓는 것이 좋다. 이는 잘못된 인자가 들어왔을때나 들어오지 않았을 때 발생할 수 있는 에러를 방어하는데도 도움이 된다.

// parameter로 받는 date 객체의 default 값을 사전에 정의한다.
function timeStamp (date = new Date(Date.now())) {
  // date값이 없거나 date 객체가 아닐 때 undefined 에러를 방어할 수 있다.
  const hours = date.getHours();
  const minutes = date.getMinutes();
  return { hours, minutes }
}

Rest Parameters

가변 파라미터를 다루는 방식으로 파라미터의 마지막 순서에 ...args 와 같은 형식으로 표현한다.

function sumTotal (initialValue, ...args) {
  // rest parameter는 initialValue 다음부터 들어오는 매개변수들을 배열로 정리해준다.
  return args.reduce((acc, cur) => acc + cur, initialValue);
};

void & return

함수의 반환 타입에 대한 분류. 자바스크립트 함수는 기본적으로 리턴 값이 없을 때 undefined를 리턴한다.

function alertMessage(message) {
  alert(message)
}
// alert 함수가 리턴하는 값은 없다. 불필요하게 return alert(message)를 붙이면,
// alertMessage 함수는 불필요한 undefined를 리턴하게 된다.

function 그럴리없는함수(num) {
  let arr = [];
  return arr.push(num);
}
// 그럴리 없는 함수는 우리의 예상과는 다르게 숫자 1을 리턴한다.
// array.push 메서드가 리턴 값으로 array.length를 가지고 있기 때문이다.

핵심은 불필요한 리턴을 줄이고, 메서드나 함수를 사용할 때 명세를 잘 살펴보는 습관을 가지는 것으로 볼 수 있다.

Arrow Function

this, Array.from(arguments), call, apply, bind 등을 사용할 수 없다.
객체 내에서 객체의 키값을 사용하는 메서드를 생성하는 경우, this.name과 같은 키워드로 접근할 수 없다.
함수 내부에서 this 키워드를 사용해야 하는 경우나 class 문법을 사용하는 경우에는 함수 선언식을 사용해야 한다.

Callback Function

함수를 명시적으로 호출하는 방식이 아니라 특정 이벤트가 발생했을 때 시스템에 의해 호출되는 함수. 함수가 1급 객체이므로 다른 함수의 매개 변수로 전달이 가능하고 전달받은 함수 내부에서 어느 특정한 시점에 실행된다.

// 가장 익숙한 addEventListener 함수 또한 두 번째 인자로 콜백 함수를 받는다.
var button = document.getElementById("submit-button");
button.addEventListener('click', function (e) {
	// something to do ...
});

콜백 함수는 주로 비동기식 처리 모델에서 사용된다. 이는 처리가 종료되면 호출될 함수(콜백함수)를 미리 매개변수에 전달하고 처리가 종료되면 콜백함수를 호출하는 것이다.

또한 콜백 함수는 중복된 함수를 리팩터링 하는데에도 도움이 된다.

로그인 결과에 따라 다른 메시지와 동작을 하는 함수가 있다고 해보자.

function loginSuccess () {
  const confirm = window.confirm("로그인에 성공했습니다.");
  
  if(confirm) {
    afterLoginAPI();
  }
}

function loginFail () {
  const confirm = window.confirm("로그인에 실패했습니다");
  
  if(confirm) {
    afterLoginFailAPI();
  }
}

위 두 함수는 메시지를 출력하고 이후 다른 함수를 호출하는 동일한 작업을 반복하고 있다.
메시지와 다른 함수를 인자로 전달받는 함수로 만들어 보자.

function loginAPI (message, cbFunc) {
  const confirm = window.confirm(message);
  
  if(confirm) {
    cbFunc();
  }
}

// 로그인 성공시
loginAPI("로그인에 성공했습니다.", afterLoginAPI());

// 로그인 싪패시
loginAPI("로그인에 실패했습니다.", afterLoginFailAPI());

Pure Function

동일한 input에 동일한 output. side effect가 없는 함수.
함수 내부에서 사용하는 값을 외부에 존재하는 값에 의존하지 않고, 인자로 받도록 변경하거나 함수 내부에 위치시켜 항상 같은 결과를 리턴하도록 한다.

var num1 = 10;
var num2 = 20;

function sum(num) {
  return num1 + num;
}

누군가 num1의 값을 변경하게 되면 sum은 항상 같은 결과를 리턴하지 않는다. 순수함수가 아니게 된다.

function sum(num) {
  return 10 + num;
}

외부 변수에 의존하던 값을 함수 내부로 위치시키면 sum 함수는 어떤 상황에도 입력값에 + 10을 더한 값을 리턴하는 순수 함수가 된다.

또한 side effect를 일으키지 않는 함수가 되어야 한다. 객체를 다룰 때 주의해야 하는 부분이다.

const obj = { one: 1 }

function changeObj (targetObj) {
  targetObj.one = 100;
  return targetObj;
}

changeObj(obj);

changeObj 함수의 호출 결과로 인해 리턴값 뿐만아니라 obj 객체의 one의 값도 100으로 바뀌게 된다. 객체가 참조값이기 때문이다.
이러한 문제를 해결하기 위해서는 항상 복사된 값을 리턴하도록 해야 한다.

function changeObj (targetObj) {
  return { ...targetObj, one = 100 };
}

changeObj(obj);

더이상 changeObj 함수는 원본 객체인 obj의 값을 변경시키지 않는다. 이러한 side effect를 일으키지 않는 함수를 작성해서 동일한 input에 동일한 output를 리턴하는 함수를 작성하도록 해야 한다.

Closure

MDN에서 정의하고 있는 Closure는 다음과 같다.

“A closure is the combination of a function and the lexical environment within which that function was declared.”
클로저는 함수와 그 함수가 선언됐을 때의 렉시컬 환경(Lexical environment)과의 조합이다.

무슨 소리인지 모르겠으니 예제를 떠와서 살펴보자.

function outerFunction() {
  var x = 10;
  return function innerFunction() {
    console.log(x);
  }
}

const inner = outerFunction();
inner();	// 10

innerFunctionouterFunction 내부에서 생성되었다. 생성된 시점의 scope를 기억하고 있는 함수이다. 그러므로 스코프 체인과 this가 바인딩 될 outerFunction 객체를 기억하고 있다. 때문에 outerFunction의 변수인 x값에 접근이 가능하다.

// closure는 함수를 반환하는 함수에서 자주 볼 수 있다.
function add(num1) {
  return function(num2) {
    return num1 + num2
  }
}

const addOne = add(1);
const addTwo = add(2);
// addOne, addTwo는 각각 자신이 생성되었을 시점의 정보인 num1의 값을 기억하고 있다.
addOne(4)	// 5
addTwo(4)	// 6

이처럼 자신을 포함하고 있는 외부 함수보다 내부 함수가 더 오래 유지되는 경우, 외부 함수 밖에서 내부 함수가 호출되더라도 외부 함수의 지역 변수에 접근할 수 있는데 이런 함수를 Closure라고 부른다.

MDN 정의에서 말하고 있는 함수란 반환된 내부 함수를 의미하고 그 함수가 선언될 때의 렉시컬 환경은 내부 함수가 선언되었을 때의 스코프를 의미한다.
즉 클로저는 반환된 내부함수가 자신이 선언됐을 때의 환경인 스코프를 기억하여 자신이 선언됐을 때의 환경(스코프) 밖에서 호출되어도 그 환경(스코프)에 접근할 수 있는 함수를 말한다.

profile
당근같은사람

0개의 댓글