3 Apr 2019

ECMAScript6) 함수가 무엇인지?

자바스크립트 함수의 모든 것

목차

함수 정의하기
함수 호출하기
함수의 인수
재귀 함수
프로그램의 평가와 실행과정
클로저
이름 공간
객체로서의 함수
고차 함수
콜백 함수
ES6부터 추가된 함수 기능

함수 정의하기

1. 함수를 정의하는 방법

  1. 함수 선언문으로 정의
    • function square(x) {return x*x;}
  2. 함수 리터럴로 정의
    • var square = function(x) {return x*x;}
  3. Function() 생성자로 정의
    • var square = new Function("x", "return x*x");
  4. 화살표 함수 표현식으로 정의 : ES6에 추가
    • var square = x => x*x;
  • Javascript 엔진은 함수 선언문을 프로그램 첫머리 또는 함수의 첫머리로 끌어올린다.
  • 따라서, 함수 선언문으로 정의한 함수 (1번) 는 호출문이 그보다 앞에 위치해도 호출할 수 있다.
  • 하지만, 함수 리터럴, Function생성자, 화살표 함수 표현식으로 정의한 함수는 그 함수의 참조를 할당해야만 사용할 수 있음.

2. 중첩 함수

  • 특정 함수 내부에 선언된 함수
  • 지역함수, 내부함수 라고 부르기도 함.
  • 파이썬과 비슷하게, 함수안에서 함수를 선언하고, 그 선언된 함수 안에서만 사용이 가능한 함수임.
  • 신기한 점은 자신을 둘러 싼 외부 함수의 인수와 지역 변수에 접근할 수 있다는 점이다. 이 규칙은 클로저의 핵심적인 구성요소가 된다.

      function func1(arg1) {
          var a = 1;
          function innerFunc() {
              console.log(arg);  // 외부 함수의 인수에 접근 가능
              console.log(a);  // 외부 함수의 변수에 접근 가능
          }
      }
    

함수 호출하기

1. 함수 호출하는 방법

  1. 함수 호출

     var s = square(5);
    
  2. 메서드 호출

     obj.m = function() {};
     obj.m();
    
  3. 생성자 호출
    • new 키워드를 추가하면 함수가 생성자로 동작한다.
     var obj = new Object();
    
  4. call, apply 를 사용한 간접 호출
    • 객체 부분에서 자세히

2. 즉시 실행 함수 IIFE(immediately invoked function expression)

  • 익명함수의 뒤에 인수를 넘기며 곧바로 실행

      (function(a,b) {console.log(a,b)})(1,2);
    

함수의 인수

1. 인수의 생략

  • 함수를 호출할 때 인수를 생략할 수 있다.
  • 반대로 함수 정의시에 작성된 인자 개수보다 더 많은 개수의 인수를 넘길 수도 있다.

      // 생략된 인수는 undefined
      function f(x, y) {
          console.log(x, y);
      }
      f(2);  // x=2, y=undefined
    
      // 인자 초기값 설정하는 방법
      function multiply(a, b) {
          b = b || 1;
          return a * b;
      }
      multiply(2, 3);  // -> 6
      multiply(2);  // -> 2
    
  • 논리합 연산자인 ||는 왼쪽 피연산자가 true로 평가되면 왼쪽 피연산자를 반환하고, 왼쪽 피연산자가 false로 평가되면 오른쪽 피연산자를 반환한다.
  • 따라서 인수를 생략하여 bundefined가 되고, false이므로 b=1이 된다.
  • ES6 에서는 함수의 기본값을 지정할 수 있다. 파라미터(인자)를 정의할 때 = 연산자를 통해 기본값을 지정한다.(파이썬의 그것과 동일)

2. 가변 길이 인수 목록 (Arguments 객체)

  • 모든 함수에는 arguments 변수가 지역변수로 있다.
  • arguments 변수의 값은 Arguments 객체이다. (배열 객체가 아닌 유사 배열 객체)
  • 함수에 인수를 n개 넘겨서 호출하면 인수 값이 0부터 시작하는 인덱스와 함께 argments에 저장된다.
  • Argments 객체는 length(인수 개수), callee(현재 실행되고 있는 함수의 참조) 프로퍼티를 가지고 있다.
  • arguments 의 요소의 값을 바꾸면 함수의 입력된 값이 바뀐다.

      function f(a, b) {
          arguments[1] = 3;
          console.log(a, b);
      }
      f(1, 2);  // -> x = 1, y = 3
    
  • arguments 변수를 활용하면 인수 개수가 일정하지 않은 가변 인수 함수를 정의할 수 있다.

      function myConcat(separator) {
          var s = "";
          for(var i=1; i<arguments.length; i++){
              s += arguments[i];
              if(i<arguments.length-1) s += separator;
          }
          return s;
      }
      console.log(myConcat("/", "apple","orange","peach"));
    

재귀함수

  • 함수가 자기 자신을 호출하는 행위를 가리켜 재귀호출이라고 함.
  • 이러한 호출을 수행하는 함수를 재귀함수라고 한다.
  • 재귀함수는 문제를 재귀함수로 간단히 해결 가능할 때에만 사용하자.
  • 재귀호출은 반드시 멈추어야 한다.
  • 온애드에서는 되도록이면 쓰지 않는 것이 좋겠다.

프로그램의 평가와 실행과정

  • 자바스크립트의 내부 구조에 대한 설명을 시작할 것임.
  • 자바스크립트 프로그램을 평가하는 과정과 실행하는 과정
  • 순서적으로 설명함

1. 실행 가능한 코드 (Executable code)

  • 자바스크립트 엔진은 실행 가능한 코드(Executable code) 를 만나면 그 코드를 평가(Evaluate) 해서 실행 문맥(Execution Contaxt) 으로 만든다.
  • 실행 가능한 코드는 다음과 같다.
    • 전역 코드
      • 전역 객체 Window 아래에 정의된 함수
    • 함수 코드
      • 함수
    • eval 코드
      • eval() 함수를 말함.
      • 이는 렉시컬환경(lexical Environment)이 아니라 별도의 동적 환경에서 실행된다.

2. 실행 문맥 (Execution Context)

  • 실행 문맥(Execution Context)은 실행 가능한 코드가 실제로 실해오디고 관리되는 영역
  • 실행 문맥은 실행에 필요한 모든 정보를 컴포넌트 여러 개가 나누어 관리하도록 만들어져있다.
  • 실행 문맥의 구성요소
    • 렉시컬 환경 컴포넌트(Lexical Environment)
      • 환경 레코드 (Environment Record)
      • 외부 렉시컬 환경 참조 (Outer Lexical Environment Reference)
    • 변수 환경 컴포넌트 (Variable Environment)
      • 렉시컬 환경 컴포넌트와 타입이 같음 (with 문 사용시를 제외)
    • 디스 바인딩 컴포넌트 (This Binding)
      • 함수를 호출한 객체의 참조가 저장되는 곳.
      • 이것이 가리키는 값이 this가 된다.

3. 렉시컬 환경 컴포넌트의 구성

  • 자바스크립트 엔진이 자바스크립트 코드를 실행하기 위해 자원을 모아 둔 곳
  • 자바스크립트 코드의 유효 범위 안에 있는 식별자와 그 식별자가 가리키는 값을 키와 값의 쌍으로 묶어(바인드하여) 렉시컬 환경 컴포넌트에 기록한다.

  • 환경 레코드 (Environment Record)

    • 유효범위 안에 포함된 식별자를 기록하고 실행하는 영역
    • 자바스크립트 엔진은 유효 범위 안의 식벌자와 결괏값을 바인드하여 환경 레코드에 기록함.
  • 외부 렉시컬 환경 참조 (Outer Lexical Environment Reference)
    • 함수 안에 함수를 중첩해서 정의할 수 있는 언어이기 때문에 자바스크립트 엔진은 유효 범위를 넘어서는 유효범위도 검색할 수 있어야 하기 때문에 존재하는 렉시컬환경 컴포넌트의 구성요소
    • 여기에는 함수를 둘러싸고 있는 코드가 속한 렉시컬 환경 컴포넌트의 참조가 저장된다.

4. 환경 레코드의 구성

  • 렉시컬 환경 안의 식별자와 그 식별자가 가리키는 값의 묶음이 실제로 저장되어 있는 영역.
  • 환경 레코드의 구성 (저장하는 값의 유형에 따라)
    선언적 환경 레코드 (Declarative Environment Record)
    • 실제 함수와 변수, catch문의 식별자와 실행 결과가 저장되는 영역
    • 키와 값 쌍으로 저장된다.
      객체 환경 레코드 (Object Environment Records)
    • 실행문맥 외부에 별도로 저장된 객체의 참조에서 데이터를 읽거나 쓴다.
    • 키와 값 쌍을 복사해 오는 것이 아니라, 그 객체 전체의 참조를 가져와서
    • 객체 환경 레코드의 bindObject라는 프로퍼티에 바인드하도록 만들어져 있다.

5. 전역 환경과 전역 객체의 생성

  • 자바스크립트 인터프리터는 시작과 동시에 렉시컬 환경 타입의 전역환경(Global Environment)을 생성한다.
  • 이후 전역 객체 window 를 생성한 다음, 전역 환경의 객체 환경 레코드전역 객체의 참조를 대입한다.
  • 이를 의사 코드로 표현시 다음과 같다.

      // 전역 환경
      전역 환경 = {
          객체 환경 레코드: {
              bindObject: window
          },
          외부 렉시컬 환경 참조: null
      }
      // 전역 실행 문맥
      실행 문맥 = {
          렉시컬 환경 컴포넌트: 전역 환경,
          디스 바인딩 컴포넌트: window
      }
    
      // 따라서 전역의 this는 (클라이언트측 자바스크립트의 전역 객체)
      this  === window // -> true
    
  • 웹 브라우저의 자바스크립트 실행 환경에서는 Window 객체가 전역 객체 이므로 객체 환경 레코드의 bindObejct 프로퍼티에는 Window의 참조가 할당되어, 전역 환경의 변수와 함수를 Window 안에서 검색하게 된다.
  • 전역 실행 문맥의 디스 바인딩 컴포넌트에도 Window의 참조가 할당되어 thisWindow를 가리키게 되고, this의 프로퍼티는 window의 프로퍼티가 된다.
  • 추가적으로, window 객체는 다음과 같은 프로퍼티를 기본적으로 가지고 있다.

    분류 프로퍼티
    전역 프로퍼티 undefined, NaN, Infinity
    생성자 Object(), String() , Number()
    전역 함수 parseInt() , parseFloat() , isNaN()
    내장 객체 Math , JSON, Refect, console

6. 프로그램의 평가와 전역 변수

  • 전역 환경과 전역 객체를 생성한 후, 자바스크립트는 프로그램을 읽고, 평가한다.
  • var를 이용해 선언한 전역 변수들은 전역 환경의 객체 환경 레코드의 프로퍼티로 추가된다.
  • 변수의 이름은 프로퍼티의 이름이 되고, 그 프로퍼티의 값은 undefined가 된다.
  • 함수 선언자를 이용해 선언한 전역 함수들은 함수 객체(function object)로 생성하여 객체 환경 레코드의 프로퍼티로 추가된다.
  • 즉, 전역 변수의 실체는 전역 객체의 프로퍼티 (전역 객체의 실행 문맥에 들어있는 객체 환경 레코드의 프로퍼티 - window의 프로퍼티)
  • 마찬가지로, 함수 안에서 선언된 지역 변수와 중첩 함수의 참조 또한 그 함수가 속한 실행 환경의 환경 레코드(선언적 환경 레코드)의 프로퍼티이다.
  • 이처럼 자바스크립트의 모든 변수를 객체의 프로퍼티로 간주하면 쉽게 이해할 수 있다.

    끌어올림(hoisting) 현상의 실체
    - 최상위 레벨에 선언된 변수와 함수는 프로그램을 평가하는 시점에서 환경 레코드에 추가됨. - 최상위 레벨에 선언된 함수와 변수는 이미 환경 레코드에 추가된 상태이기 때문에 코드 어느 위치에서도 참조가 가능한 것. - 그래서 함수안의 함수에서 상위 함수의 변수, 전역 변수에 접근할 수 있는 것.

  • var 문과 함수 선언문으로 선언한 전역 변수는 [[Configurable]] 프로퍼티가 false로 설정되어 있어 delete 연산자로 삭제할 수 없다.
  • 하지만 var 문을 사용하지 않고 변수를 선언해서 값을 할당하면 프로그램을 실행하는 도중에 this 바인딩 컴포넌트가 가리키는 객체의 프로퍼티로 추가된다. 전역 객체의 this 바인딩 컴포넌트는 전역 객체(window)를 가리키기 때문에 전역 객체의 프로퍼티가 되는 것이다.
  • 이 경우에는 [[configurable]] 프로퍼티의 값이 true가 되어 delete연산자로 삭제가 가능하다.

7. 프로그램의 실행과 실행 문맥(Execution Context)

  • 프로그램이 평가된 다음에는, 프로그램은 실행 문맥(Execution Context) 안에서 실행된다.
  • 실행 문맥은 실행 가능한 코드(전역 코드, 함수코드, eval코드)만 실행한다.
  • 실행 문맥은 Stack(마지막 들어온 놈부터 나가는 자료구조 push/pop)의 구조로 관리된다.
  • 이를 call stack 이라고 한다.
  • 전역 코드를 시작으로 함수를 호출할 때마다 function은 콜 스택에 쌓인다. (재귀함수의 경우 지속적으로 쌓인다)
  • 쌓인 함수들은 마지막으로 들어온 (가장 scope가 내부인) 함수부터 실행을 시작하고, 실행 완료 후 call stack에서 pop한다.
  • Javascript 는 싱글 스레드 방식으로 하나의 작업이 끝나면 다음 하나의 작업을 실행한다. (이벤트 처리와 비동기 처리의 경우 실행하기에 앞서 이벤트 큐에 대기열을 만들고, 현재 실행중인 함수가 끝나면, 들어온 순서대로 call stack에 push된다.)

8. 환경 레코드와 지역 변수

  • 함수를 호출하면 현재 실행중인 코드의 작업은 일시적으로 멈추고, 호출된 함수의 실행문맥을 생성한다.
  • 프로그램의 실행 흐름도 새롭게 생성된 실행 문맥으로 이동한다.
  • 다음으로 함수의 실행 문맥이 call stack에 push 되고, 실행 문맥 안에 렉시컬 환경을 생성한다,
  • 렉시컬 환경환경레코드를 가지고 있고, 환경 레코드 안에는 현재의 함수 안에서 선언된 변수, 중첩함수를 기록한다. (함수의 인자, 함수 안에서 중첩된 하위 함수의 참조, 함수 내부의 지역변수(var로 선언된))
  • 렉시컬 환경의 환경 레코드는 사용자가 읽거나 쓸 수 없다.
  • 이후 함수 실행되며 함수의 선언적 환경 레코드 에는 인수 값이 설정되고, 없다면 undefined로 설정된다.
  • 함수의 실행 문맥, 그 안에 렉시컬 환경의 환경레코드가 생성되면 this 바인딩 컴포넌트함수를 호출한 객체의 참조(전역 함수인경우 전역 객체) 의 참조가 저장된다.
  • 환경레코드의 생성, this값의 결정 이후에는 함수 안의 코드가 순서대로 실행된다. 함수가 실행되는 시점에는 지역변수, 함수 선언문 등은 모두 환경 레코드에 기록이 되어있는 상태이므로, 지역변수나 함수 선언문이 함수 안의 어느 부분에 위치하더라도 사용할 수 있다.
  • 일반적으로 함수는 사용된 이후 렉시컬 환경 컴포넌트와 실행 문맥이 메모리에서 지워지는데, 함수의 바깥에 위치한 함수의 참조환경 레코드에 유지되는 경우에는 렉시컬 환경 컴포넌트가 메모리에서 지워지지 않는다. 이와 클로저(Closure)가 매우 관련이 깊다.

9. this!?

  • 함수가 호출된 시점에 this 값은 결정된다.
  • this 값은 함수가 호출되었을 때 그 함수가 속해 있던 객체의 참조 이며 실행 문맥의 디스 바인딩 컴포넌트가 참조하는 객체이다.

      var tom = {
          name: "Tom",
          sayHello: function() {
              console.log("Hello " + this.name);
          }
      };
    
      tom.sayHello();  // -> Hello Tom
    
  • sayHello() 메소드는 tom 객체에 속한 메소드이므로, 해당 실행 문맥의 경우 this 바인딩 컴포넌트가 가리키는 객체가 tom 이다. 따라서 this 값이 "Tom"이 된다.
  • tom.sayHello() 의 값은 함수의 참조이다. 따라서 다음이 가능하다

      // huck 객체를 생성
      var huck = {name: "huck"};
      // 함수의 참조를 대입
      huck.sayHello = tom.sayHello;
    
      huck.sayHello();  // -> Hello huck
    
  • sayHello 함수가 속한 객체가 huck 이므로 this 바인딩 컴포넌트가 가리키는 객체가 tom 에서 huck 으로 바뀌어 this 값이 huck 객체를 가리킨다.
  • 다양한 상황에서의 this
    1. 최상위 스코프에서 this
      • window
    2. 이벤트 처리기 안에서의 this
      • 이벤트가 발생한 요소 객체
    3. 생성자 함수 안에서의 this
      • 생성자로 생성한 객체
    4. 생성자의 prototype 메서드 안에서의 this
      • 생성자로 생성한 객체
    5. 직접 호출한 함수 안에 있는 this
      • 함수 앞에 객체를 붙여 호출한 경우(객체의 메소드), 그 객체
    6. apply와 call 메서드로 호출한 함수 안의 this
      • 함수 객체가 실행되는 실행 문맥의 this 바인딩 컴포넌트가 가리키는 객체

10. Scope chain

  • 자바스크립트는 어휘적 유효범위(Lexical scope) 를 가지고 있다.
  • 지역 변수와 전역변수 등 변수의 이름이 같으면 충돌이 발생하는데, 이때 해당 변수가 어디에서(어느 함수에서) 선언된 변수인지를 결정하는 작업을 식별자 결정 이라고 한다.
  • 자바스크립트의 식별자 결정은 좀 더 안쪽 코드에 선언된 변수를 사용한다는 규칙을 가진다.

      var a = "a";
      function f(){
          var b = "b";
          function g(){
              var c = "c";
              console.log(a + b + c);
          }
          g();
      }
      f();
    
  • 함수의 인수와 지역 변수를 속박 변수 라고 한다.
  • 그 외의 변수를 자유 변수 라고 한다.
  • 위의 예에서는 c가 속박변수, a와 b는 자유 변수이다.
  • 또한, 속박 변수만 포함한 함수를 닫힌 함수, 자유 변수를 가지고 있는 함수를 열린 함수 라고 한다.
  • 위의 예에서는 f가 닫힌 함수고, g가 열린 함수이다.
  • 자바스크립트의 경우 식별자를 찾을 때, 외부 렉시컬 환경의 참조를 따라가는 방식을 취한다.

      // 콜스택 상 가장 위의 함수 g 의 렉시컬 환경
      함수 g  렉시컬 환경: {
          선언적 환경 레코드 : {
              c: "c";
          },
          외부 렉시컬 환경 참조 : 함수 f  렉시컬 환경
      }
      // 함수 g가 가리키고 있는 f 의 렉시컬 환경
    
      함수 f  렉시컬 환경: {
          선언적 환경 레코드 : {
              b: "b";
          },
          외부 렉시컬 환경 참조 : 전역  렉시컬 환경
      }
    
      // 함수 f가 가리키고 있는 전역 의 렉시컬 환경
      전역  렉시컬 환경: {
          Object 환경 레코드 : {
              bindObejct:{
                  a: "a";
              }
          },
          외부 렉시컬 환경 참조 : null
      }
    
  • 이러한 연결고리를 scope chain(ECMAScript 3 기준의 명명) 이라고 한다.
  • 이 스코프 체인에 대한 이해가 있어야 이후의 Closure에 대한 이해가 가능하다.

11. 가비지 컬렉션

  • 사용하지 않는 객체의 메모리는 가비지 컬렉터가 자동으로 해제한다.
  • 사용하지 않는 객체란 다른 객체의 프로퍼티와 변수가 참조하지 않는 객체를 말한다.

      var p = {x:1, y:2};
      console.log(p);  // -> Object {x:1, y:2}
      // p 가 {x:1, y:2} 객체의 참조를 가지고 있음
    
      // p 에 null 로 참조를 해제
      p = null;  // {x:1, y:2} 를 참조하지 않음. -> 메모리 해제
    
  • 가비지 컬렉터는 자동으로 동작함.

클로저

1. 클로저 Closure

  • 자바스크립트의 모든 함수는 클로저를 정의한다. 이는 자바스크립트가 가진 강력한 기능으로, 이를 활용하면 변수를 은닉하여 지속성을 보장하는 등의 기능을 구현할 수 있다.
  • 클로저(함수 폐포) 를 프로그래밍 언어적인 관점에서 보면, 자기 자신이 정의된 환경에서 함수 안에 있는 자유 변수의 식별자 결정을 실행 하는 기능과 그 기능을 구현한 자료 구조의 모음이라고 할 수 있다.
  • 말로 보면 어렵고, 앞서의 예를 통해 살펴보겠다.

      var a = "a";
      function f(){
          var b = "b";
          function g(){
              var c = "c";
              console.log(a + b + c);
          }
          g();
      }
      f();
    
  • g가 정의된 렉시컬 환경은 함수 g 를 둘러싸고 있는 바깥 영역 전체.
  • g() 함수가 실행 되는 경우, 이 렉시컬 환경 안에서 자유 변수 ab 의 식별자 결정을 한다.
  • 실행 순서는 다음과 같다.
    1. 함수 f를 호출하면, 함수 f 의 렉시컬 환경이 생성된다.
    2. 함수 g의 선언문을 평가해 함수객체를 생성한다. 이 렉시컬 환경에는 함수g의 코드, 함수f의 선언적 환경 레코드(변수 b가 들어 있다.), 전역 객체의 참조(변수 a가 들어 있다.)
    3. 함수 g를 호출하면, 함수 g 의 렉시컬 환경이 생성되고, console.log(a + b + c) 가 실행되자 마자 함수 g의 외부 렉시컬 환경 참조(scope)를 따라 체인처럼 거슬러 올라가 변수 a와 b의 값을 참조한다.
  • 즉, 함수 g의 함수 객체와 렉시컬 환경 컴포넌트가 자유 변수 ab의 식별자 결정을 위한 자료구조이다
  • 이 자료구조(함수객체 + 그 함수의 렉시컬 환경 컴포넌트)는 상위 함수인 f가 호출되어 함수 g가 평가되는 시점에 생성된다.
  • 이 예에서, 함수 g 의 함수 객체가 있는 동안에는 클로저는 가비지 컬렉션의 대상이 되지 않는다. 따라서, 함수 g의 함수객체가 있는 한 클로저는 메모리에서 사라지지 않는다.

    closure

  • 중첩함수 g는 열린 함수(자유변수를 사용하는)이다. 그러나 유효 범위 체인으로 주변 환경의 변수 b와 a를 들여와서 실질적으로는 닫힌함수가 되었다.

2. 클로저의 성질

  • 전형적인 클로저 사용의 예시

      function makeCounter() {
          var count = 0;
          return f;
          function f() {
              return count++;
          }
      }
      var counter = makeCounter();
      console.log(counter);  // -> 0
      console.log(counter);  // -> 1
      console.log(counter);  // -> 2
      // 함수가 끝이났는데도 지속적으로 메모리에 남아있는 지역변수.
      // makeCounter의 참조인 counter 가 남아있는 한, 클로저는 사라지지 않는다.
      // 외부로부터 은폐되어 있고, 중첩함수의 안에서만 읽거나 쓸 수 있다.
      // makeCounter가 새로이 호출되면 클로저가 새롭게 생성되어 초기화된다.
      // f 의 참조인 counter 를 통해 함수를 사용할 시, 클로저가 새롭게 생성되는 것이 아닌 내부함수가 사용된다.
    
  1. 외부 함수 makeCounter는 중첩 함수 f의 참조를 반환한다.
  2. 중첩 함수 f는 외부 함수 makeCounter의 지역 변수 count를 참조한다.
  • 1 로 인해 f의 함수 객체를 전역 변수 counter가 참조한다.
  • 2 로 인해 함수 makeCounter 의 렉시컬 환경 컴포넌트를 전역 변수 counterf의 함수 객체로 간접적으로 참조하게 되므로 가비지 컬렉션의 대상이 되지 않는다. (함수를 반환하는 함수 - makeCounter, 그것의 참조값을 가진 변수 - 전역변수counter)
  • 따라서, makeCounter 실행이 끝나도 makeCounter 의 렉시컬 환경 컴포넌트가 메모리에서 지워지지 않는다. 그렇기 때문에 지역 변수이자 makeCounter 렉시컬 환경 컴포넌트의 환경 레코드 프로퍼티중 하나인 counter 역시 메모리에서 지워지지 않는다.
  • 변수 counter 는 클로저의 내부 상태로 저장된다. 또한 지역 변수이기 때문에 외부에서 읽거나 쓸 수 없다.
  • 객체에 비유해 보면, 지역 변수 counter는 객체의 프로퍼티, f함수는 객체의 메써드에 해당한다고 볼 수 있다. 객체의 경우 외부에서 프로퍼티를 읽고, 쓰기가 가능하지만 클로저의 경우 외부로부터 숨겨진 상태이다.
  • 객체 지향 프로그래밍에서의 캡슐화(객체의 프로퍼티를 외부로부터 은폐하는 행위)가 이와 매우 비슷한 역할을 한다고 생각된다.
  • 따라서, 클로저는 캡슐화된 객체 라고 할 수 있다.

3. 클로저 예제

  • 클로저 사용시 데이터와 데이터를 조작하는 함수를 하나로 묶을 수 있다.
  • 여러 개의 내부 상태와 메서드를 가진 클로저

      function makePerson(name, age) {
          var _name = name;
          var _age = age;
          return {  // 메서드를 여러 개 가진 객체를 반환
              getName: function() {return _name;},
              getAge: function() {return _age;},
              setAge: function(x) {return _age = x;},
          };
      }
      var person = makePerson("Tom", 18);
      console.log(person.getName());  // -> "Tom"
      console.log(person.getAge());  // -> 18
      person.setAge(26);
      console.log(person.getAge());  // -> 26
    
  • 함수 팩토리 : 다양한 매개변수를 받는 함수를 여러 개 생성할 수 있다.

      function makeMultiplier(x) {
          return function(y) {
              return x * y;
          };
      }
      // 클로저를 
      var multi2 = makeMultiplier(2);
      var multi10 = makeMultiplier(10);
    
      console.log(multi2(3));  // -> 6
      console.log(multi10(3));  // -> 30
    
  • 잘못된 예 : 반복문 안에서 클로저 만들기

      <script>
          window.onload = function () {
              var elm = document.getElementsByTagName("input");
              for(var i=0; i<elm.length; i++) {
                  elm[i].onclick = function() {
                      console.log(i);
                  }
              }
          }
      </script>
      <body>
          <input type="button" value="0">
          <input type="button" value="1">
          <input type="button" value="2">
      </body>
    
  • 위 예제는 어느 버튼을 눌러도 3이 출력된다. 왜 일까?
  • onclick 이벤트 처리기로 등록한 함수 세 개가 바깥에 있는 변수 i를 참조하는 클로저가 되었기 때문이다.
  • onclick 이벤트 처리기가 실행되는 시점에는 for문의 실행이 끝나있기 때문에, 클로저가 공유하는 변수 i 의 값이 3 인 상태이다.
  • 이 경우에는 블록 스코프를 가지는 let을 사용하여 변수를 선언하면 올바르게 이용 가능하다.

이름 공간 (Name space)

전역 유효 범위의 오염을 방지하기 위한 수단.

1. 전역 이름 공간의 오염

  • 전역 변수와 전역 함수를 전역 객체에 선언하는 행위를 전역 스코프를 오염시킨다고 말한다.
  • 전역 유효 범위가 오염되면 다음 상황에서 변수 이름과 함수 이름이 겹칠 수 있다.
    • 라이브러리 파일을 여러개 읽어들일 때
    • 규모가 큰 프로그램을 만들 때
    • 여러 사람이 한 프로그램을 만들 때
  • 이름이 같은 변수나 함수를 선언하면 자바스크립트 엔진이 단 하나의 변수 또는 함수를 생성한다. 따라서 프로그램이 올바르게 동작하지 않으며, 오류로 표현되지 않기 때문에 디버깅도 힘들다.
  • 이러한 상황에 대한 방법이 이름 공간이다.

2. 객체를 이름 공간으로 사용하기

  • 이름 공간(name space) 이란 변수 이름과 함수 이름을 한 곳에 모아 두어 이름 충돌을 방지하고, 변수와 함수를 쉽게 가져다 쓰도록 하는 기법이다.
  • C++, Java 의 경우 기본적으로 이름공간을 제공하지만 자바스크립트는 그렇지 않다.
  • 객체를 값으로 가지는 전역 변수를 생성한 후, 그 객체에 프로그램 전체에서 사용하는 모든 변수와 함수를 프로퍼티로 정의한다.
  • 예시

      var myApp = || {};
      // myApp이 이미 정의되어 있을 때는 그것을 사용,
      // 정의되지 않은 경우는 빈 객체 생성
    
      // 사용하고자 하는 변수와 함수를 프로퍼티로 추가
      myApp.name = "Tom";
      myApp.showName = function() {};
    
      // 부분 이름 공간으로 사용할 수 있다.
      myApp.view = {};
      myApp.control = {};
    

3. 함수를 이름 공간으로 사용하기

  • 즉시 실행 함수를 사용하여 이름공간으로 활용
  • 라이브러리를 읽어들여 라이브러리 안의 함수 및 변수를 사용할 때에 충돌을 피하기 위해 전체 프로그램을 즉시 함수 안에 넣어 실행한다.

      var x = "global"
      (function() {
          // 프로그램을 작성한다
          var x = "local"
          console.log(x);
      })();  // -> "local"
      console.log(x);  // -> "global"
    

4. 모듈 패턴

  • 즉시 실행 함수를 사용하여 모듈을 정의하는 방법.
  • 모듈은 기능 여러개(함수 여러개)를 하나로 묶은 것이다. 모듈을 다양한 곳에서 사용하면 모듈 안에서 사용하는 변수 이름이나 함수 이름이 사용하는 곳에서의 그것과 충돌할 가능성이 있다.
  • 이때, 모듈을 즉시 실행 함수(IIFE) 안에 작성하여 실행하면 충돌을 피할 수 있다.
  • 하지만 즉시 실행 함수는 외부에서는 접근하거나 사용할 수 없다. 그래서 즉시 실행 함수에 객체로구현한 이름공간을 전역 변수로 넘겨서, 공개할 함수를 이름공간에 추가하여 모듈의 내부는 은폐하고, 원하는 함수만 공개할 수 있다.
  • 전형적인 모듈 정의의 예이다.

      var Module = Module || {};  // 객체 이름공간 생성
      (function (_Module) {
          var name = "NoName";  // private 변수
          function getName() {
              return name;
          }  // private 함수
          _Module.showName = function() {
              console.log(getName());  // 프라이빗 함수를 사용하는 퍼블릭 함수
          };  // public 함수
          _Module.setName = function() {
              name = x;
          };  // public 함수
      })(Module);
      Module.setName("Tom");
      Module.showName();  // -> Tom
    
  • 위 예제에서 showName 메서드는 getName을 참조하고 있으며, setName 메서드는 name 을 참조하고 있다.
  • 이로 인해 클로저가 생성된다.
  • 즉시 실행 함수의 지역변수 name 과 지역함수 getName 은 클로저의 내부 상태로 저장된다.

객체로서의 함수

자바스크립트에서 함수도 객체의 일종이다. 따라서 함수는 값을 처리할 수 있고, 프로퍼티와 메서드를 가지고 있다.

1. 함수는 객체!

  • 자바스크립트의 함수는 Function 객체이다. 따라서 다음과 같은 특징을 지닌다.
    • 함수는 변수나 프로퍼티나 배열 요소에 대입할 수 있다.
    • 함수는 함수의 인수로 사용할 수 있다.
    • 함수는 함수의 반환값으로 사용할 수 있다.
    • 함수는 프로퍼티와 메서드를 가질 수 있다.
    • 함수는 이름없는 리터럴로 표현할 수 있다.(익명함수)
    • 함수는 동적으로 생성할 수 있다.
  • 일반적으로 이러한 작업이 가능한 객체를 일급 객체 라고 한다. 일급 객체인 함수는 일급 함수라고 한다.

2. 함수의 프로퍼티

프로퍼티 이름 설명
caller 현재 실행 중인 함수를 호출한 함수
length 함수의 인자 개수
name 함수를 표시할 때 사용되는 이름
prototype 프로토타입 객체의 참조
  • 또한 함수는 Function 생성자의 prototype 객체(Function.prototype) 의 프로퍼티를 상속 받아서 사용한다.
  • Function.prototype 의 프로퍼티
프로퍼티 이름 설명
apply() 선택한 this와 인수를 사용하여 함수를 호출한다. 인수는 배열 객체
bind() 선택한 this와 인수를 적용한 새로운 함수를 반환한다.
call() 선택한 this와 인수를 사용하여 함수를 호출한다. 인수는 쉼표로 구분한 값이다.
constructor Function 생성자의 기초
toString() 함수의 소스 코드를 문자열로 만들어 반환

3. apply와 call 메서드

  • this 값과 함수의 인수를 사용하여 함수를 실행하는 메서드 : apply(), call()
  • 예제

      function say(greetings, honorifics) {
          console.log(greetings + " " + honorifics + this.name);
      }
      var tom = {name: "톰 하디"};
      var jackson = {name: "마이클 잭슨"};
    
      say.apply(tom, ["hello", "Mr."])  // hello Mr.톰 하디
      say.apply(jackson, ["hi!", "Mr."])  // hi! Mr.마이클 잭슨
      say.call(tom, "hello", "Mr.")  // hello Mr.톰 하디
      say.call(jackson, "hi!", "Mr.")  // hi! Mr.마이클 잭슨
    

4. bind 메서드

  • bind()

      function say(greetings, honorifics) {
          console.log(greetings + " " + honorifics + this.name);
      }
      var tom = {name: "톰 하디"};
      var sayToTom = say.bind(tom);
      sayToTom("Hello", "Mr.");  // hello Mr.톰 하디
    
  • bind() 메서드를 통해 반환된 함수는 항상 인수로 받은 객체를 this 로 한다.

5. 함수에 프로퍼티 추가 및 메모이제이션

  • 다른 객체와 마찬가지로 함수에도 프로퍼티를 추가할 수 있다.
  • Function 객체에 추가된 프로퍼티는 그 함수를 실행하지 않아도 읽거나 쓸 수 있다.
  • Memoization : 함수를 호출했을 때의 인수값과 반환값을 한쌍으로 만들어 저장해두는 기법
  • 예시

      function fib(n) {
          if (n>2) return n;
          // fib 함수객체에 n프로퍼티가 없는 경우
          if(!(n in fib)) {
              // 함수의 프로퍼티로 값을 저장
              fib[n] = fib(n-1) + fib(n-2);
          }
          return fib[n];
      }
      for(var i=0;i<=20;i++) {
          console.log((" "+i).slice(-2) + ":" fib(i));
      }
    
      // 0:0
      // 1:1
      // 2:1
      // ...(중략)
      // 19:4181
      // 20:6765
    
  • 재귀함수의 경우 콜스택에 지속적으로 함수가 쌓여 실행 시간이 무지막지하게 길어지지만, 메모이제이션 기법을 사용하면 매우 빠른 속도로 재귀함수의 일을 처리할 수 있다.

고차 함수

고차 함수란 함수를 인수로 받는 함수 또는 함수를 반환하는 함수

1. 고차함수의 사용

  • 자바스크립트의 함수는 일급 객체이므로 함수의 인수로 함수를 넘길 수 있으므로 고차함수를 쉽게 정의할 수 있다.
  • 고차 함수를 사용하면 처리 패턴이 같은 작업을 추상화하여 하나로 합칠 수 있다.
  • 판다스의 apply(), map(), 파이썬의 map 과 같은 예시를 볼 수 있다.
  • 예시

      //  수열을 표시하는 프로그램
      var digits = "";
      for(var i=0; i<10; i++) {
          digits += i;
      }
      console.log(digits);  // -> 0123456789
    
      // 무작위 알파벳을 표시하는 프로그램
      var randomChars = "";
      for(var i=0; i<8; i++) {
          randomChars += String.fromCharCode(Math.floor(Math.random() * 26) + "a".charCodeAt(0));
      }
      console.log(randomChars); // dizohqsf : 무작위 알파벳 뭉치
    
  • 위의 예제들은 하는 일은 다르지만 사용하는 로직이 같다. 따라서 공통 부분을 고차 함수로 만들어 사용할 수 있다.

      function joinStrings(n, f) {
          var s = "";
          for (var i=0; i<n; i++) {
              s += f(i);
          }
          return s;
      }
    
  • 만들어진 고차함수 joinStrings 를 사용해서 똑같은 두 작업을 하는 함수를 생성하면 다음과 같다.

      // 수열표시함수
      var digits = joinString(10, function(i) {return i;});
    
      // 무작위 알파벳 표시 함수
      var randomChars = joinString(8, function(i) {return String.fromCharCode(Math.floor(Math.random() * 26) + "a".charCodeAt(0));});
    

2. 커링 Curring function

  • 커링이란 인수 두 개 이상 받는 함수를 분해하여 인수가 하나인 함수의 중첩 함수로 변환하는 작업.
  • 말로 보면 어려우니 코드로 보자

      // 커링된 add()
      // 부분적인 인자의 목록을 받는다.
      function add(x, y){
          var old_x = x, old_y = y;
          if (typeof old_y === 'undefined') {
                  // 부분적인 적용
                  return function (new_y) { return old_x + new_y; }
              }
          // 전체 인자를 적용
          return x + y;
      }
      console.log(add(3, 5));
      console.log(add(10)(10));
      var add10 = add(10);
      console.log(add10(20));
    
  • 부분적으로 인자를 설정한 새로운 함수를 만들어 낼 수 있다.

콜백 함수

자바스크립트의 경우 콜백 함수를 매우 자주 사용한다.

1. 콜백함수 ?

  • 자바스크립트의 함수는 일급 객체이며 다른 함수에 인수로 넘겨질 수 있다. 다음과 같이 다른 함수에 인수로 넘겨지는 함수를 콜백 함수라고 부른다

      f(g, ...);
    
      function f(callback, ...) {
          ...
          callback();
          ...
      }
    
  • 함수 f의 인수로 넘겨진 함수 g가 콜백함수이다. 이를 통해 함수 f는 함수g 에 대한 제어권을 가질 수 있다.
  • 이벤트 처리기는 특정 이벤트가 발생 했을 때 실행하도록 등록하는 함수인데, 여기에서 사용되는 것이 콜백함수이다.

      button.onclick = function() { ... };
    
      // 또는 addEventListener 메서드를 사용
      button.addEventListener("click", function () {...});
    
      // 타이머 메서드의 경우에도 콜백함수를 인수로 받는다.
      setInterval(function() { ... }, 2000);
    

ES6부터 추가된 함수 기능

1. 화살표 함수 표현식

  • 함수 리터럴의 단축 표현
  • 함수 리터럴과 100% 같은 것은 아니므로 주의가 필요하다.

      var square = function(x) { return x*x; };
    
      // 화살표 함수 표현식으로 작성하면
      var square = (x) => {return x*x;};
    
      // 인수가 여러개라면
      var f = (x, y, z) => { ... };
    
      // 인수가 없다면
      var f = () => { ... };
    
      // 인수가 하나라면 인수묶는 괄호는 생략가능
      var f = x => { ... };
    
      // 즉시 실행 함수(IIFE)로 사용할 수 있다.
      ((x, y) => {return x*y})(1, 2);  // -> 2
    
  • 함수 리터럴과 화살표 함수의 차이점은 다음과 같다
    • 화살표 함수의 경우 this 의 값이 함수를 정의할 때 결정된다. (함수 리터럴의 경우 호출시 정의됨.)
    • arguments 변수가 없다.
    • 생성자로 사용할 수 없다. (화살표 함수 앞에 new 연산자를 붙여서 호출할 수 없다.)
    • yield 키워드를 사용할 수 없다.

2. 인수에 추가된 기능

  • 나머지 매개변수 ...args 의 추가
  • 기존에 가변인수에 대해 접근하기 위해서 arguments 프로퍼티를 통해 접근했다. 하지만 이는 list가 아니고 유사 배열 객체라서 배열로 바꾼 후 작업을 하는 경우가 많았다. 또한 ECMAScript6 에서 추가된 화살표함수에는 arguments 프로퍼티가 존재하지 않는다.
  • ...args는 배열이며, 화살표함수에서도 사용할 수 있다.

      var sum = (...args) => {
          for(var i=0, s=0; i<args.length; i++) {
              s += args[i]
          }
          return s;
      }
      sum(1, 2, 3, 4, 5);  // -> 15
    
  • 함수의 인자에 대입연산자(=) 를 통해 기본값을 설정할 수 있다. (파이썬의 그것과 같음)

      function multiply(a, b=1) {
          return a*b
      }
      multiply(3,2);  // -> 6
      multiply(3);  // -> 3
    
      // 다른 인자의 값도 기본값으로 사용할 수 있다.
      function add(a, b=a+1) {
          return a+b;
      }
      add(2, 1);  // -> 3
      add(2);  // -> 5
    

3. 이터레이터(iterator) 와 for/of 문

  • 이터레이터, 그리고 이후 설명할 제네레이터는 ECMAScript6 에서 추가된 기능 중 가장 강력한 기능이다.
  • for/of 문은 이터레이터를 제어하는 구문이다.

  • 이터레이션 iteration 은 반복 처리라는 뜻으로, 데이터 안의 요소를 연속적으로 꺼내는 행위를 말한다.
  • 예로, 배열의 forEach() 메서드가 있다. 배열의 요소를 순차적으로 검색하여 그 값을 함수의 인수로 넘기기를 반복한다.

      var a = [5, 4, 3];
      a.forEach(function(val) {console.log(val); });
    
      // 5
      // 4
      // 3
    
  • 이터레이터 iterator란 반복처리(iteration)가 가능한 객체를 말한다. 앞의 forEach()메서드는 배열의 요소를 꺼내 그 값을 함수의 인수로 넘기고, 그 작업이 끝나면 배열의 다음 요소를 꺼내 인수로 넘기기를 반복한다. 함수 내의 작업은 내부적으로 처리되기 때문에 개발자는 각 처리단계를 제어할 수 없다. 하지만 이터레이터의 경우 반복처리(iteration)을 단계별로 제어할 수 있다.
    이터레이터가 무엇인지 보자.

  • 배열은 Symbol.iterator() 메서드를 가지고 있다. 이는 이터레이터를 반환하는 함수이다.

      var a = [5, 4, 3];
      var iter = a[Symbol.iterator]();
      console.log(iter.next());  // Object {value:5, done: false}
      console.log(iter.next());  // Object {value:4, done: false}
      console.log(iter.next());  // Object {value:3, done: false}
      console.log(iter.next());  // Object {value:undefined, done: true}
    
  • 이처럼 iternext() 메서드를 호출할 때마다 valuedone 프로퍼티를 갖는 iterator result 객체가 반환된다
  • iterator result 객체에는 next() 메서드가 호출 될 때마다 value 프로퍼티에는 차례대로 꺼내진 배열 요소의 값이 들어가고, done 프로퍼티에는 요소의 열거가 끝났는지를 뜻하는 논리값이 들어간다.

      function makeIterator(array) {
          var index = 0;
          return {
              next: function() {
                  if(index < array.length) {
                      return {value: array[index++], done: false};
                  } else {
                      return {value: undefined, done: true};
                  }
              }
          }
      }
    
  • 이터레이터를 사용해서 이터레이션(반복처리) 를 하려면 개발자가 직접 처리를 작성해야 한다.

      var a = [5, 4, 3];
      var iter = a[Symbol.iterator]();
      while(true) {
          var iteratorResult = iter.next();
          if (iteratorResult.done == true) break;
          var v = iteratorResult.value;
          console.log(v);
      }
      // 5
      // 4
      // 3
    
  • 이러한 코드로서 실행할 수 있는데 이는 for/of 문을 사용하면 더욱 쉽게 표현할 수 있다.

      var a = [5, 4, 3];
      for(var v of a) {
          console.log(v);
      }
    
  • for/of 문은 a 이터레이터의 next 메서드를 순회할 때마다 매번 호출한다.
  • iterator result 객체의 done 프로퍼티 값이 false 가 아닌 동안 v에 대입한다.
  • for/of 문은 다음 조건을 만족하는 객체를 반복처리한다.
    • Symbol.iterator() 메서드를 갖고 있다.
    • Symbol.iterator() 메서드는 반환값으로 이터레이터를 반환한다.
  • Symbol.iterator() 메서드를 가진 객체를 반복가능(iterable)한 객체라고 한다. 이에는 Array, String, TypedArray, Map, Set 이 있다.
  • 반복가능한 객체는 for/of 문, 전개연산자, yield*, 비구조화 할당 등에 활용할 수 이있다.
  • 여기서, 이터레이터(iterator) 객체와 이터러블(iterable)한 객체는 다르다는 것을 유의해야 한다.

4. 제네레이터 Generator

  • 제네레이터는 다음과 같은 성질을 지닌 함수이다.
    • 반복 가능한 이터레이터를 값으로 반환한다.
    • 작업의 일시 정지와 재시작이 가능하며, 자신의 상태를 관리한다.
  • 제네레이터는 이터레이터의 반복 처리를 강력하게 지원한다.
  • 제네레이터는 function* 문으로 정의한 함수이며, 하나 이상의 yield 표현식을 포함한다.

      function* gen() {
          yield 1;
          yield 2;
          yield 3;
      }
      var iter = gen();
      console.log(iter.next());  // Object {value: 1, done: false}
      console.log(iter.next());  // Object {value: 2, done: false}
      console.log(iter.next());  // Object {value: 3, done: false}
      console.log(iter.next());  // Object {value: undefined, done: true}
    
  • 과정은 다음과 같다.
    1. 제네레이터 함수인 gen() 은 호출해도 바로 실행되지 않고, 대신 이터레이터를 반환한다.
    2. 이터레이터의 next() 메서드가 호출되면 함수의 첫 번째 yield 연산자의 위치까지 실행하고, 결괏값으로 iterator result 를 반환한다. 이터레이터 리절트의 value 값으로 yield 표현식에 지정한 값을 저장하고, done 프로퍼티 값으로 제네레이터 함수를 끝까지 실행했는지 저장한다. 이때, 여기서 함수의 실행이 일시정지된다.
    3. 또 다시 next() 메서드가 호출되면 일시 정지한 위치에 있는 처리를 재개한 이후, 이터레이터 리절트의 value 값으로 yield 표현식에 지정한 값을 저장하고, done 프로퍼티 값으로 제네레이터 함수를 끝까지 실행했는지 저장한다. 역시 이때, 함수의 실행이 일시정지된다.
    4. 이터레이터의 next() 메서드가 호출된 후 또 다시 재개한다.
    5. 마지막 yield 에 도착하면, valueundefineddone 프로퍼티가 true 인 이터레이터 리절트를 반환한다.
  • 제네레이터로 생성한 이터레이터는 반복가능하기 때문에 for/of 문으로 반복 처리 할 수 있다.
  • 제네레이터 함수는 return 을 yield 로 쓰고, 일시정지가 되는 것으로 이해하면 쉽다.

  • 제네레이터 함수 안에서 yield* 표현식을 사용하면 반복 가능한 객체를 지정할 수 있다.

      function* f() {
          yield "x";
          yield "y";
      }
      function g() {
          yield 0;
          yield* [2, 4];
          yield* "AB";
          yield* f();
      }
      var iter = g();
      for(var v of iter) console.log(v);  // 0, 2, 4, A, B, x, y 순.
    

Tags: