[JavaScript] 2-6. JavaScript Function

JavaScript Function


함수 (function)

프로그래밍에서의 함수란, 큰 프로그램을 잘게 쪼개어 특정 코드 뭉치를 반복해서 사용할 수 있도록 묶어놓은 코드 뭉치의 단위입니다.
함수를 어떻게 작성하느냐에 따라서 코드의 유지보수성 및 가독성이 크게 달라집니다.
(함수를 비효율적으로 작성하면 리팩토링을 해야하는 번거로움 및 비용소모(시간,체력등..)가 발생합니다.)
또한 JS 에서 함수는 많은 기능을 가지고 있는데, JS 에서는 중요도가 높아서
함수를 안다고 하면 JS 의 거진 모든 부분을 알고 있다고 해도 과언이 아닙니다.



함수의 구성요소

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

/* 함수의 기본 문법
function 함수명 (파라미터) {
    return 식 or 실행할 구문
}
*/

JS 의 함수기능을 이용하여 sum 이라는 함수를 작성하였습니다.
괄호 안에 a, b 를 매개변수(인자, parameter)라고 하며, return 뒤에 오는 값을 반환값(return value)라고 합니다.
함수를 정의했다면, 함수를 실행하기 위해서는 호출(function call)을 해야합니다.

sum(2, 3); // 5

함수명 뒤에 인수(argument)라는 계산하고자 하는 값을 넣어 호출을 하여 함수를 실행하고 실행결과에 해당하는 반환값을 받습니다.
이 것이 함수의 호출입니다.



함수의 실행 순서

JS 에서는 많은 부분이 위에서 아래로 차례대로 내려가면서 실행되지만 함수의 호출은 예외입니다.
함수 호출 코드를 만나면 코드의 실행 흐름이 호출된 함수로 옮겨집니다.
함수가 다시 값을 반환하면 다시 이전 위치부터 코드가 실행됩니다.

// (1) - 함수의 정의
function multiply(a, b) {
  return a * b; // (3) - 함수의 실행
}

let result = multiply(23, 34); // (2) - 함수의 호출 / (4) - 함수의 실행결과를 변수 result에 대입

console.log(result); // (5) - 나머지 구문 실행

함수는 절대 정의하는 것만으로 실행되지 않는 점 꼭 유의해야 합니다.



매개변수와 인수

위의 함수의 a, b 를 매개변수라고 합니다.
매개변수는 변수의 일종으로 함수의 호출시마다 인수가 매개변수의 값으로 대입됩니다.
위의 코드에서 multiply(23, 34)와 같이 호출을 하면 매개변수에는 a = 23, b = 34 의 값이 대입되고 나머지 코드가 실행됩니다.
함수를 호출할 때에도 반드시 인수의 값이 매개변수로 들어가는 것이기에, 함수의 인수값을 적어줘야합니다.
인수에 변수를 써주고, 매개변수에 새로운 값을 대입해도 인수로 써준 변수의 값은 변경되지 않습니다.

function test(b) {
  b = 3;
  return b;
}

const a = 1;
const testresult = test(a); // a라는 변수에 있는 값인 1이 함수의 매개변수로 들어가는 것이므로, 에러가 발생하지 않습니다.

console.log(a); // 1
console.log(testresult); // 3

반환값

return 구문은 함수의 반환값을 결정합니다.
return 키워드 바로 다음에 오는 값이 함수 호출의 결과값으로 반환되며, 반환즉시 함수의 실행은 끝납니다.

function sum(x, y) {
  return x + y;
  console.log("이 구문은 실행될까요?");
}

sum(2, 3); //  5 - 결과값으로는 5만 출력이 됩니다.

return 뒤에 아무 값도 없거나, 아예 return 이 없을 경우에는 undefined 가 반환됩니다.

function test() {
  return;
}

function test2() {}

test(); //  undefined
test2(); //  undefined



스코프 (Scope)

function add(x, y) {
  //  변수 x와 y를 정의하였습니다.
  return x + y;
}

add(x, y);
console.log(x); // Uncaught SyntaxError: missing ) after argument list

매개변수와 같이 함수 안에서 정의된 변수는 함수 바깥에서는 접근할 수 없기 때문에 함수 안에서만 사용할 수 있습니다.
즉, 변수는 코드의 일정 범위 안에서만 유효하다는 성질이 있는 것입니다.
이렇게, 특정 변수가 유효한 코드 상의 유효 범위를 가지고 스코프라고 합니다.



스코프 - 스코프 연쇄 (scope chain)

함수 내부 코드에서 매개변수 혹은 그 함수 안에서 정의된 변수만 사용할 수 있는 것은 아닙니다.

function a() {
  var a = 1;
  function b() {
    console.log(a); //  1
  }
  b();
}
a();

위의 b 함수를 보면 a 함수의 변수 a 의 값을 가져와 사용하려고 합니다.
결과는 변수 a 의 값인 1 이 나옵니다.
함수 b 에 선언도 안되어 있는 외부 변수 a 의 값을 어떻게 출력했을까요?
자세히 살펴보면 코드의 실행 흐름이 식별자에 도달하여, 먼저 그 식별자와 같은 이름을 갖는 변수를 현재 스코프에서 찾아보고,
변수가 존재하면 그것을 그대로 사용합니다.
만약 현재 스코프에서 변수를 찾지 못하면 바깥쪽 스코프에서 변수를 찾아봅니다.
있으면 사용하고 없으면 바깥쪽에서 찾는 이러한 과정을 스코프 연쇄라고 합니다.
이 과정은 가장 바깥쪽에 있는 스코프를 만날 때까지 반복됩니다.
가장 바깥쪽 스코프까지 찾아봤는데도 없을경우 그제서야 에러가 발생합니다.

가장 바깥에 있는 스코프를 최상위 스코프, 전역 스코프라고 부릅니다.



스코프 - 변수 가리기 (variable shadowing)

const a = 5;

function aPlusFive(a) {
  function add(a, b) {
    return a + b;
  }
  return add(a, 5);
}

aPlusFive(a); //  10;

단일 스코프에서는 같은 이름을 갖는 서로 다른 변수가 존재할 수 없습니다.
하지만, 위와 같이 바깥쪽 스코프에 존재하는 변수와 같은 이름을 갖는 변수를 안쪽 스코프에서 정의할 수 있습니다.
안쪽 스코프에서 재정의할 경우 바깥쪽 스코프에 있는 이름은 무시됩니다.
이런 현상을 변수 가리기라고 합니다.



스코프 - 어휘적 스코핑 (lexical scoping)

스코프는 코드가 작성된 구조에 의해서 결정되는 것이지, 함수 호출의 형태에 의해 결정되는 것이 아닙니다.

function multiply2(x) {
  const two = 2;
  return multiply(x);
}

multiply2(3);

function multiply(x) {
  return two * x; // ReferenceError: two is not defined - two는 함수 multiply2에서만 유효하기 때문입니다.
}

multiply 라는 함수가 multiply2 라는 함수 안에서 호출되었다고 해서, multiply 함수 내부에서 multiply2 함수의 스코프 안에
있는 변수에 접근할 수 있는 것은 아닙니다. 스코프는 코드가 작성된 구조에 의해 결정되는 성질입니다.
위 코드를 동작시키려면 아래처럼 해야합니다.

function multiply2(x) {
  const two = 2;
  function multiply(x) {
    return two * x;
  }
  return multiply(x);
}

multiply(3); //  6



클로저

보통의 경우, 안쪽 스코프에서 정의된 변수는 바깥 스코프에서 접근할 수 없습니다.
그러나 안쪽 스코프에서 만들어진 함수에서 바깥 스코프의 변수를 사용하고 있다면, 이 함수를 통해서 변수를 계속 사용할 수 있습니다.
심지어 바깥 스코프에 해당하는 코드의 실행이 끝난 뒤라도 말입니다.

function makeFunc() {
  var name = "FDS";
  function displayName() {
    alert(name);
  }
  return displayName;
}

var myFunc = makeFunc();
myFunc();

위와 같이, 바깥 스코프에 있는 변수를 가져다 사용하는 함수와 변수가 저장되는 저장소를 클로저라고 합니다.
클로저의 성질을 이용하여 고차 함수를 흥미롭게 활용할 수 있습니다.

// 고차 함수의 인수로 함수를 넘길 때, 해당 함수에서 바깥 스코프에 있는 변수를 사용할 수 있습니다.
const food = [
  { name: "sushi", price: 12000 },
  { name: "hamburger", price: 5300 },
  { name: "beef", price: 15000 }
];

function foodExpensiveThan(name, threshold) {
  return food.filter(food => food.price > threshold);
}

foodExpensiveThan(name, 9000); //  0: {name: "sushi", price: 12000} 1: {name: "beef", price: 15000}
// 특정한 방식으로 동작하는 함수를 만들어내는 고차 함수를 작성할 수 있습니다.
function makeAdder(x) {
  return function(y) {
    return x + y;
  };
}

[1, 2, 3].map(makeAdder(2)); //  [3,4,5]



스코프 - let, const 변수의 블록 스코프

함수에서 매개변수 scope 외에도 JS 에는 다른 종류의 스코프도 있습니다.
ES2015 에서 도입된 let,const 와 이전의 변수인 var 과는 다른 몇 가지 특징이 있습니다.
let 과 const 는 같은 일름을 갖는 변수의 재선언을 하지 않습니다.

var foo = 1;
var foo = 2;  // ES2015 이전의 변수 var는 변수의 재선언이 가능했습니다.

let foo = 2;  // error : Duplicate declaration "foo"
const foo = 3; // error : Duplicate declaration "foo"

function (param) {
  let param = 1;  // error : Duplicate declaration "param"
}

또한 let 과 const 는 변수가 선언되기 전에 참조하려고 하면 에러가 납니다.

console.log(foo);
let foo = 1;

당연해 보일 수도 있지만 ES2015 이전에는 위와 같이 변수를 사용해도 에러가 나지 않았습니다.
호이스팅이라는 특성을 이용하여 프로그래밍을 하기도 했지만, 호이스팅이 생각지도 못한 곳에서 발생할 경우 심각한 버그의 원인이기도 하고
가독성을 해치고, 유지보수를 어렵게 만든다는 이유 때문에, ES2015 에 들어와서 let 과 const 가 등장하게 되었습니다.
let 과 const 는 블록 스코프를 갖기에 호이스팅이 일어나지 않습니다.

// 블록 안에서 선언된 변수는 외부에서 접근할 수 없습니다.
if (true) {
  let i = 0;
}
console.log(i); // ReferenceError: i is not defined

if, for, while, function 등의 구문을 사용하면 블록이 형성되어,
그 안에서 let 또는 const 를 통해 선언된 변수는 외부에서 접근할 수 있습니다.

for (let i = 0; i < 10; i++) {
  console.log(i);
}
console.log(i); // ReferenceError: i is not defined

또는 특별한 기능이 없는 블록을 만들 수 있습니다.
객체와 유사하게 중괄호로 코드의 일부분을 둘러싸면 됩니다.

{
  let i = 1;
}
console.log(i); // ReferenceError: i is not defined



스코프 - var 변수와 함수 스코프

ES2015 에서 let 과 const 가 등장하기 전까지 모든 변수는 var 키워드를 통해 선언되었습니다.
var 는 let 과 유사하게 값을 다시 대입할 수 있습니다. 그리고 var 는 함수의 매개변수와 유사하게,
함수 스코프를 갖습니다.

function func() {
  var foo = 1;
}
func();
console.log(foo); // ReferenceError: foo is not defined

var 를 통해 선언된 변수는 let 과 const 로 선언된 변수와 다른 특징을 갖습니다.
먼저, var 를 통한 변수 선언은 같은 이름의 변수 재선언을 허용합니다.

// 아무런 에러가 발생하지 않습니다.
var foo = 1;
var foo = 1;

그리고, var 로 선언된 변수는 내부적으로 함수 혹은 파일의 맨 위로 끌어올려지는 과정을 거치기 때문에,
같은 스코프 안에만 있다면 변수가 선언된기 전에도 해당 변수에 접근할 수 있습니다.
이를 호이스팅이라고 합니다.
단, 호이스팅이 일어나도 변수의 선언만 위로 끌어올려질뿐 대입하는 과정은 순서에 맞게 되므로,
대입이 일어나기 전에 변수의 값을 읽으면 undefined 가 불러와 집니다.

function showName() {
  console.log("First Name : " + name);
  var name = "Ford";
  console.log("Last Name : " + name);
}
showName();
// First Name : undefined
// Last Name : Ford
// First Name이 undefined인 이유는 지역변수 name이 호이스트 되었기 때문입니다.

마지막으로 var 변수는 함수 스코프를 갖습니다.
즉, 함수가 아닌 블록에서 정의된 var 변수는 해당 블록 바깥에서도 유효할 수 있다는 말입니다.

function func() {
  for (var i = 0; i <= 10; i++) {
    //...
  }
  console.log(i); //  10
}

func();

이 특징에 주의하지 않으면, 다중 for 문과 같은 블록이 중첩된 코드에서 의도치 않은 동작을 할 수 있습니다.

for (var i=0; i<3; i++) {
  console.log('outer');
  // 위 아래 `i`변수는 같은 함수 스코프에서 정의된 같은 변수입니다.
  // 바깥쪽 루프 한 번 도는 동안, 안쪽 루프를 도느라 이미 i의 값은 3이 되어버렸습니다.
  // Uncaught SyntaxError: Unexpected identifier
  for (var i=0; i<3 i++) {
    console.log('inner');
}

var 는 위와 같이 코드의 문맥과 맞지 않는 행동을 하기 때문에, 가급적 const 와 재대입이 필요할 경우에만 let 을 쓰는 것이 좋습니다.



전역 변수, 전역 스코프 (global variable, global scope)

전역 스코프는 스코프 체인의 가장 바깥쪽에 있는 스코프입니다.

let foo; //  전역 스코프

if (true) {
  let bar; // 블록 스코프
}

위 코드에서 foo 와 같이 전역 스코프에서 선언된 변수를 전역 변수라고 합니다.
변수를 명시적으로 전역 스코프에서 선안하지 않아도, 한 번도 선언되지 않은 이름으로
안쪽 스코프에서 let, const, var 를 붙여주지 않고 변수를 선언하면 전역 스코프에서 변수가 만들어 집니다.
그냥 안쪽 스코프에서 let,const,var 를 붙여주지 않고 변수를 선언한다고 뚝딱하고 만들어지는 것이 아니라
바깥쪽 스코프에 같은 변수명을 가진 변수가 있는지 확인하면서 올라가다가 최종적으로 글로벌 영역에 도달하여 전역 변수가 만들어 지는 것입니다.

function func() {
  globalvar = 1;
}

func();
console.log(globalvar); //  1

전역변수는 코드의 어떤 부분에서든지 아무런 제한없이 접근하여 조작할 수 있습니다.
편하고 좋아보일 수 있지만 해킹이나 보안적인 측면에서 취약하며,
이러한 특성으로인해 프로그램이 커질수록 변수의 관리가 힘들어집니다.
또한 전역변수를 통해 프로그램의 많은 파일이 결합이 되어 문제되는 부분을 고쳤는데
아무 이상없던 다른 파일에서 문제가 생길수 있습니다.
변수를 선언할 때는 그 변수를 필요로 하는 작은 스코프 안에서만 접근할 수 있도록 하고, 공유되는 값에 접근할 수 있는 코드의 범위를 최소한으로 줄이며, 그 값은 약속된 방식으로만 변경을 할 수 있도록 합니다.



값으로서의 함수

JS 에서는 함수도 값입니다.

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

const plus = sum;
plus(2, 3); //  5

다른 값처럼 함수도 선언한 뒤 변수에 대입하여 호출할 수 있고, 혹은 배열이나 객체에 넣을 수도 있으며,
심지어는 함수를 다른 함수에 인수로 넘기거나 함수에서 함수를 반환할 수 있습니다.

// 함수를 배열이나 객체에 넣어보기
function sum(a, b) {
  return a + b;
}
[sum];
{
  addFunc: sum;
}

// 함수를 인수로 넘겨보기
function isOdd(x) {
  return x % 2 === 1; // x를 2로 나눴을때 나머지가 1이면 true, 아니면 false 반환
}
[1, 2, 3, 4, 5].filter(isOdd); // [1,3,5]

// 함수를 인수로 넘기는 예 - 콜백함수
function one() {
  return 1;
}

var two = function() {
  return 2;
};

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

add_one_two(one(), two());

// 함수에서 함수 반환해보기
function createEmptyFunc() {
  function func() {}
  return func;
}

값으로 사용할 수 있는 JS 함수는 1 급 객체입니다.

  1. 함수를 변수나 데이터에 할당할수 있으며,
  2. 함수를 인수로 전달할 수 있고,
  3. 함수를 리턴 할 수 있기 떄문입니다.

이러한 1 급 객체의 특징을 가지고 여러 가지 프로그래밍 기법으로 활용할 수 있습니다.



익명 함수 (anonymous function)

JS 에서 함수를 선언할 때 꼭 이름을 붙여줘야 하는 것은 아닙니다.
이름을 붙이지 않은 함수를 가지고 익명 함수 혹은 함수 리터럴이라고 합니다.

// 두 수를 곱해서 반환하는 익명 함수
function (x, y) {
  return x * y;
}

// 위의 함수는 이름이 없어서 호출을 할 수 없습니다.

// 호출을 하기 위해서는 변수를 저장한 후에 변수의 이름을 통해서 호출해야 합니다.
let multiply = function (x,y) {
  return x * y;
}

multiply(3,3);  //  9;

익명 함수는 함수를 만든 쪽이 아니라 다른 쪽에서 그 함수를 호출할 때 많이 사용됩니다.
대표적인 경우는 함수를 인수로 넘겨줄 때입니다.

[1, 2, 3, 4, 5].filter(function(x) {
  return x % 2 === 1;
}); //  [1,3,5]



화살표 함수(arrow function)

함수 정의를 위한 새로운 표기법인 화살표 함수는 ES2015 에서 도입되었습니다.

// arrow function을 사용하면 x + y가 바로 반환됩니다.
const add = (x, y) => x + y;

// 바로 반환시키지 않고 function 키워드를 통한 함수 정의처럼 여러 구문을 사용하려면 curly braces(중괄호) ({...})으로 둘러싸줘야 합니다.
// '=>' 다음 부분을 중괄호로 둘러싸면, 명시적으로 'return' 하지 않는 한 아무것도 반환되지 않습니다.
const add = (x, y) => {
  const result = x + y;
  return result;
};
// 매개변수가 하나밖에 없다면, 매개변수 부분의 괄호를 쓰지 않아도 무방합니다.
const negate = x => !x;

화살표 함수는 표기법이 간단하기 때문에 익명 함수를 다른 함수의 인자로 넘길 때 주로 사용됩니다.



2. Today I Found Out

ES2015에서부터 생긴 기능들(arrow function, let,const block scope..)을 공부하면서
신기했고, 처음이라 그런지 낯설었던 부분도 있었지만 공부하면서 이러한 기능들이
더더욱 우리가 JS를 통해 로직구현을 할 때 편리함을 줄 것 같다는 느낌을 많이 받았습니다.
예전에는 호이스팅, 스코프등.. 단어를 듣기만 해도 어렵고 피곤했는데
승하쌤의 helloworldjavascript.net 덕분에 한층 쉽게 공부할 수 있었고,
수업시간에 가르쳐주신 mdn 검색으로 필요한 부분을 추가적으로 더 살펴볼고
공부할 수 있어서 원하는 만큼 이해할 수 있었던 하루였습니다.



3. refer

https://helloworldjavascript.net/

https://www.zerocho.com/category/JavaScript/post/5740531574288ebc5f2ba97e

http://www.nextree.co.kr/p7363/

https://developer.mozilla.org/ko/docs/Web/JavaScript/Guide/Closures