상세 컨텐츠

본문 제목

자바스크립트 함수

자바스크립트/바닐라 자바스크립트

by 모든지 신나게 2020. 4. 20. 11:08

본문

함수

함수를 정의 하는 방법 4가지

  1. 함수 선언문으로 정의하는 방법
function square(x) {return x*x};
  1. 함수 리터럴로 정의하는 방법
var square = function(x){return x*x};
  1. Function 생성자로 정의하는 방법
var square = new function("x", "return x*x")
  1. 화살표 함수 표현식으로 정의하는 방법(ES6)
var square = x => x*x;

함수 호이스팅으로 인해 자바스크립트 엔진은 함수 선언문을 프로그램 첫머리 또는 함수 첫 머리로 끌어올린다.

하지만 2번 3번 4번으로 정의한 함수는 변수에 함수를 할당해야 사용할 수 있기 때문에 호출하는 코드보다 앞으로 나와야한다.

중첩함수

자바스크립트는 함수 내부에 함수를 쓰는 중접함수(지역함수 or 내부함수)를 사용할 수 있다.

자바스크립트에서의 중첩함수는 최상위 레벨에만 작성이 가능하다 if나 while 문 등의 문장에서는 작성할 수 없다.,

ex) 배열 요소의 제곱합에 대한 제곱근을 구하는 함수

function norm(x){
    var sum2 = sumSquare();
    return Math.sqrt(sum2)//제곱근 리턴
    function sumSquare(){
        sum = 0;
        for(var i = 0; i < x.length; i++){
            sum += x[i]*x[i];    //제곱합
        }
        return sum;
    }
}

var a = [2, 1, 3, 5, 7];
var n = norm(a);
console.log(n)    //9.3808315196

중첩 함수의 경우는 중첩 함수를 감싼 함수의 내부에서만 사용이 가능하다

또한 중첩 함수는 자신을 감싼 함수의 인수와 지역 변수에 접근이 가능하다.

즉시 실행 함수

익명 함수를 정의하고 곧바로 실행하는 구문으로 2가지 방법이 있다.

(function(){})();
(function(){}());

즉시 실행 함수는 전역 유효 범위를 오염시키지 않는 이름 공간을 생성할 때 사용한다.

함수의 인수

자바스크립트에서는 함수를 호출 할 때 인수를 생량할 수 있고 반대로 인수를 추가로 넘겨서 함수를 실행할 수 있다.

//인수의 생략
function f(x, y){
    console.log("x = " + x + ", y = " + y);
}
f(2)    //x = 2, y = undefined

//인수의 생략(초기값)
function multiply(a, b){
    b = b||1;    //||의 경우 왼쪽이 true면 왼쪽값을 반환하고 false면 오른쪽 값을 반환한다.
    return a*b;
}
multply(2); // a = 2, b = 1

인수를 생략할 경우 undefined가 되고 만약 초기값을 설정하고 싶으면 단축 평가식을 사용하여 초기값은 간단하게 설정이 가능하다.

//가변 인수 처리
function f() {
  console.log(arguments[0]);    //5
  console.log(arguments[1]);    //2
  console.log(arguments[2]);    //3
  console.log(arguments[3]);    //4
  console.log(arguments[4]);    //5
}
f(5, 2, 3, 4, 5);

arguments는 모든 함수에서 사용이 가능하며 해당 함수에 들어온 인수의 정보를 가지고 있다.

속성으로 length(인수 개수), callee(현재 실행되고 있는 함수의 참조)

재귀 함수

재귀 함수는 자기 자신을 호출 하는 함수이다.

function fact(n){    //n의 팩토리얼을 반환하는 함수이다.
    if(n <= 1) return 1
    return n * fact(n-1)
}
fact(5); // 120

//익명 함수 재귀
var fact = function(n){
    if(n <= 1) return 1;
    return *arguments.callee(n - 1)
}

재귀 함수를 사용할 때 주의 점 2가지

  1. 재귀 호출은 반드시 멈춰야 한다.
    • 위의 fact 함수도 if문을 사용해서 조건에 맞을 경우 빠져 나올 수 있게 한다.
    • 그러지 않은 경우 무한정 재귀를 하기 때문에 메모리를 무지막지하게 먹는다
  2. 재귀 호출로 간단하게 해결 할 수 있을 때만 해결한다.
    • 재귀 호출로 호출한 함수는 각각 메모리의 다른 영역을 차지하기 때문에 소비량이 어마어마하다.
    • for나 while같은 반복문으로 처리하는게 빠르고 간결하다.

*재귀 호출의 유명한 알고리즘 *

하노이의 탑

a에 있는 원반을 c로 옮기는 알고리즘이다.

  1. 막대기 a의 가장 아래 깔린 원반을 제외한 나머지 n-1개의 원방을 c를 거쳐 b로 옮긴다.
  2. 막대기 a의 가장 아래에 깔린 원반을 c로 옮긴다.
  3. 막대기 b에 있는 원반을 a를 거쳐 c로 옮긴다.
function hanoi(n, a, b, c) {
  if (n < 1) return;
  hanoi(n - 1, a, c, b);
  console.log(n + "번째 원반: " + a + "->" + c);
  hanoi(n - 1, b, a, c);
}
hanoi(3, "a", "b", "c")

퀵 정렬

평균 실행 시간이 O(nlogn)으로 빠른 정렬 알고리즘이다.

  1. 적당한 값 p를 고른다. p를 고를 때는 가능한 그 값의 크기가 p 값 이상인 요소의 개수와 p값 이하인 요소의 개수가 같게 한다.
  2. 배열 앞 부분에는 p값 이상인 요소를 옮기고 배열 뒷부분에는 p값 이하인 요소를 옮긴다.
  3. 배열 앞 부분의 길이가 2 이상이면 그 부분을 대상으로 퀵 정렬을 한다.
  4. 배열 뒷 부분이 길이가 2 이상이면 그 부분을 대상으로 퀵 정렬을 한다.
function quicksort(x, first, last){
    var p = x[Math.floor((first+last)/2)];
    for(var i = first, j =last; i++, j--){
        while(x[i] < p) i++; //왼쪽부터 p이상의 요소를 검색
        while(p<x[j]) j--;    //오른쪽 부터 p이하인 요소를 검색
        if(i>=j) break; //i와 j가 교차하면 다음으로 이동
        var w = x[i]; x[j]; x[j] = w;    //발견하면 x[i]와 x[j]을 교환
    }
    if(first<i-1) quicksort(x, first, i - 1)//왼족에 두개 이상 남아 있으면 원쪽을 다시 정렬
    if(j+1 < last) quicsort(x, j+1, last) //오른족에 2개 이상 남아있으면 다시 정렬
}

var a = [7, 2, 5, 1, 8, 9, 3]
quicksort(a, 0, a.length-1);
console.log(a)    //1, 2, 3, 5, 7, 8, 9

실행 문맥(Execution context)

소스 코드의 타입

ECMAScript 사양은 소스 코드(ECMAScript code)를 4가지 타입으로 구분한다.

소스 코드의 타입 설명
전역 코드(Global code) 전역에 존재하는 소스 코드를 말한다. 전역에 정의된 함수, 클래스 등의 내부 코드는 포함되지 않는다.
함수 코드(Function code) 함수 내부에 존재하는 소스 코드를 말한다. 함수 내부에 중첩된 함수, 클래스 등의 내부 코드는 포함되지 않는다.
eval 코드(Eval code) 빌트인 전역 함수인 eval 함수에 인수로 전달되어 실행되는 소스 코드를 말한다.
모듈 코드(Module code) 모듈 내부에 존재하는 소스 코드를 말한다. 모듈 내부의 함수, 클래스 등의 내부 코드는 포함되지 않는다.

4가지 타입으로 나누는 이유는 각 타입마다 실행 문맥을 생성하는 과정과 관리 내용이 다르기 때문이다.

소스 코드의 평가와 실행

모든 소스 코드는 실행에 앞서 평가 과정을 거친다.

자바스크립트 엔진은 소스 코드를 유효 범위 안에 있는 식별자와 그 식별자가 가르키는 값을 키와 값의 쌍으로 바인딩해서 렉시컬 환경 컴포넌트에 기록한다.

var x;    //실행 환경{x : undefined}
x = 1;    //실행 환경{x : 1}

렉시컬 환경 컴포넌트는 환경 레코드와 외부 렉시컬 환경 참조 컴포넌트로 구성되어 있다.

  • 환경 레코드
    • 환경 레코드는 유효 범위 안에 포함된 식별자를 기록하고 실행하는 역역으로 유효 범위 안의 식별자와 결괏값을 바인드해서 환경 레코드에 기록한다.
    • 환경 레코드는 선언적 환경 레코드와 객체 환경 레코드로 나눠진다.
    • 선언적 환경 레코드는 실제로 함수와 변수, catch 문의 식별자와 실행 결과가 저장되는 영역이다.
    • 객체 환경 레코드는 실행 문맥 외부에 별도로 저장된 객체의 참조에서 데이터를 읽거나 쓰거나 한다.
  • 외부 렉시컬 환경 참조
    • 자바스크립트는 함수 안에 함수를 중첩해서 정의할 수 있는 언어이다. 따라서 자바스크립트 엔진은 유효 범위 너버의 유효 범위도 검색할 수 있어야한다.
    • 결국 외부 렉시컬 환경 참조는 함수를 둘러싸고 있는 코드가 속한 렉시컬 환경 컴포넌트의 참조가 저장이 되고 중첩된 함수 안에서 바깥 코드에 정으된 변수를 읽거나 써야할 때 한 단계씩 거슬러 올라가 그 변수를 검색한다.
    • 이를 통해 단방향 링크드 리스트인 스코프 체인을 구현한다.
  1. 전역 환경 및 전역 객체 생성

자바스크립트의 인터프리터는 새로운 웹이 시작하자마자 렉시컬 환경 타입의 전역 환경을 생성하고 전역 객체를 생성한다. 전역 환경의 객체 환경 레코드에 전역 객체의 참조를 대입한다.

  1. 전역 코드 평가

그 후 자바스크립트 프로그램을 읽은 후 프로그램을 평가한다. 이때 최상위 레벨에 작성된 var을 사용한 전역 변수를 전역 환경의 객체 환경 레코드에 이름은 식별자 이름 값은 undefined로 저장이 되고 최상위 레벨의 함수는 함수 객체로 생성해서 전역 환경의 객체 환경 레코드에 기록이 된다.

이렇듯 평가할 때 객체 환경 레코드에 기록이 되므로 우리는 전역 변수나 함수를 코드 어느 위치에 작성을 해도 프로그램이 참조를 할 수 있다. 이것이 바로 끌어올림(호이스팅)이다.

  1. 전역 코드 실행

그 후 프로그램이 실행 되는데 실행을 할 때 변수에 값을 넣고 함수를 호출하게 된다.

프로그램은 실행 문맥 안에서 실행이 된다.

실행 문맥은 스택(stack)구조로 관리 된다. 전역코드는 순차적으로 스택에 push되며 함수를 만나면 그 함수를 실행하기 위해 실행 문맥에 push를 한다.

  1. 함수 코드 평가

push된 함수는 실행을 하기 전 평가를 하게 된다.

이때 매개 변수와 지역 변수 선언문이 실행되고 그 결과 생성된 매개 변수와 지역 변수가 선언적 환경 레코드에 등록하여 관리한다. 이때 arguments가 생성되어 선언적 환경 레코드에 등록된다.

  1. 함수 코드 실행

함수 코드를 실행하는 도중 다른 함수를 호출하면 그 함수의 실행 문맥도 스택에 push한다. 이 방식은 중첩 함수 및 재귀 호출을 할 경우도 똑같이 적용한다. 그 후 실행 된 함수가 return으로 종료되어 제어권이 호출된 코드로 돌아가면 스택에서 pop된다.

this에 대해서

함수의 실행 문맥, 렉시컬 환경, 환경 레코드가 생성되면 실행 문맥에 있는 디스 바인딩 컴포넌트에 그 함수를 호출한 객체의 참조를 저장하며 저장한 값이 this가 된다.

this 는 동적으로 함수를 호출한 객체에 따라 바뀌게 된다.

var tom ={
    name: "Tom", 
    sayHello: function(){
        console.log("Hello!" + this.name)
    }
}
//이때의 this는 tom 객체로 호출했기 때문에 this가 Tom이 된다.
console.log(tom.sayHello)        //Hello! Tom
//이떄의 this는 호출한 객체가 huck로 바뀌었기 때문에 this의 name도 바뀌게 된다.
var huck = {name: "huck"};
huck.sayHello = tom.sayHello;
console.log(huck.sayHello());    //Hello! huck

다양한 상황에서의 this가 어떠한 객체를 가리키는지 정리한다.

  1. 최상위 레벨 코드의 this

최상위 레벨 코드의 this는 전역 객체를 가르킨다. 실행 문맥이 초기화될 때 그 안의 디스 바인딩 컴포넌트가 전역 환경을 가르키도록 초기화 되기 때문이다.

console.log(this); //Window
  1. 이벤트 처리기 안에 있는 this

이벤트 처리기 안에 있는 this는 이벤트가 발생한 요소 객체(이벤트 처리기가 등록된 객체)를 가르킨다.

  1. 생성자 함수 안에 있는 this

사용자가 정의한 생성자 함수 안에 있는 this는 그 생성자로 생성한 객체를 가르킨다.

  1. 생성자의 prototype 메서드 안에 있는 this

생성자의 prototype 메서드 안에 있는 this는 그 생성자로 생성한 객체를 가르킨다.

  1. 직접 호출한 함수 안에 있는 this

함수를 최상위 레벨의 코드에서 호출하면 함수 안에 있는 this는 전역 객체를 가르킨다. 이는 f(); 코드 앞에 객체가 없으므로 디스 바인딩 컴포넌트가 전역 객체를 가르키기 때문이다.

  1. apply와 call 메서드로 호출한 함수 안에 있는 this

함수 객체의 apply와 call 메서드를 사용하면 함수를 호출할 때 this가 가르키는 객체를 바꿀 수 있다. 즉 명시적으로 설정이 가능하다.

스코프(__scope__)

스코프는 렉시컨 환경 컴포넌트 안의 외부 렉시컬 환경참조와 환경 레코드를 사용해서 구현을 하게 된다.

var a = "A"
function f(){
    var b = "B"
    function g(){
        var c = "C"
        console.log(a + b + c)
    }
    g();
}
f() //ABC
  1. var c

변수 c는 함수 g안에 선언된 지역 변수이므로 함수가 선언적 환경 레코드에서 찾을 수 있다.

  1. var b

변수 b는 함수 g의 바깥에서 선언되었다. 그렇기 때문에 선언적 환경 레코드에서 찾을 수 없고 외부 렉시컬 환경 참조를 따라 함수 g를 호출한 함수 f가 속한 실행 문법의 선언적 환경 레코드를 검색한다. 함수 f의 선언적 환경 레코드 안에서 변수를 찾으면 함수 g의 환경 레코드는 함수 f의 렉시컬 환경을 참조하게 된다.

  1. var a

변수 a는 전역 변수이기 때문에 함수 f의 선언적 환경 레코드의 한단계 위인 전역 실행 문맥의 객체 환경 레코드 안에 찾을 수 있다.

이렇게 각각 참조를 하면서 연결된것을 __scope__라고 부른다.

#클로저

자바스크립트의 모든 함수는 클로저를 정의한다.

var a = "A";                            //
function f(){                            //
    var b = "B";                        //
    function g(){            //            //
        var c = "C";        //함수g       //함수 g가 정의된 렉시컬 환경
        console.log(a+b+c);    //            //
    }                        //            //
    g();                                //
}                                        //
f();                                    //
  1. 함수 f를 호출할 때 함수 f의 렉시컬 환경 컴포넌트가 생성된다.
  2. 그 후 함수 g의 함수 선언문을 평가해서 함수 객체를 생선한다. 이 함수 객체의 렉시컬 환경 컴포넌트에는 함수 g의 코드, 함수 f의 렉시컬 컴포넌트 참조(이 안에 변수 b가 들어있음), 전역 객체의 참조(이 안에 변수 a가 들어 있음)가 저장된다.
  3. 함수 g를 호출해서 실행하면 그 시점에 함수 g의 렉시컬 환경 컴포넌트를 생성한다. 이와 동시에 함수 g의 실행 문맥의 회부 렉시컬 환경 참조(__scope__)를 마치 체인처럼 거슬러 올라가 자유 변수 a와 b를 참조한다.
  4. 그렇기 때문에 함수 g에서 함수 밖에 있는 a, b를 가져와서 출력이 가능하다.

사용 예

var arr = []
for(var i = 0; i < 5; i++){
    arr[i] = function(){
        return i;
    }
}

for문을 살펴보면 arr이라는 배열에 총 5개의 값을 넣을건데 익명 함수로 return i로 한다. 이럴경우 배열에 들어가는 값은 arr[5, 5, 5, 5, 5]가 된다.

var arr = []
for(var i = 0; i < 5; i++){
    arr[i] = function(id) {
        return function(){
            return id;
        }
    }(i);
}
var arr = []
for(let i = 0; i < 5; i++){
    arr[i] = function(){
        return i;
    }
}

원하는대로 0, 1, 2, 3, 4가 나오게 할려면 즉시 실행 함수에 카운터 변수를 전달하는 방식으로 해결이 가능하고 또는 var이 아닌 es6에서 새롭게 나온 let을 사용하면 된다.

let는 블록 유효 범위를 가지고 있기 때문에 반복문이 실행될 때 마다 새롭게 선언해 값을 대입한다.

클로저의 성질

function makeCounter(){
    var count = 0;
    return f;
    function f(){
        return count++;
    }
}

var counter = makeCounter();
var counter1 = makeCounter();
console.log(counter());    //0
console.log(counter()); //1
console.log(counter()); //2
console.log(counter1()); //0
console.log(counter1()); //1
console.log(counter1()); //2

이 예제는 counter()을 호출할 때마다 함수 makeCounter의 지역변수인 count값을 출력 후 증가시킨다. 이 예제로 알 수 있는 점

  1. 외부 함수 makeCounter는 중첩 함수 f의 참조를 반환한다.
  2. 중첩 함수 f는 외부 함수 makeCounter의 지역 변수 count를 참조한다.

1로 인해 f의 ㅎ마수 객체를 전역 변수 counter이 참조하고 2로 인해 함수 makeCounter의 렉시컬 환경 컴포넌트를 f의 함수 객체가 참조한다. 고로 makeCounter의 렉시컬 환경 컴포넌트를 f의 함수 객체가 참조하기 때문에 가비지 컬렉션이 makeCounter을 지우지 않는다.

또한 makeCounter의 객체를 새롭게 만들면 그 객체의 count는 0부터 다시 시작한다.

지역변수인 count는 밖에서 접근이 불가능하지만 함수 f가 클로저의 내부 상태를 바꾸는 메서드의 역활을 하고 있다. 객체 지향 프로그래밍 관점으로 보면 캡슐화 한 상태이다.

즉 클로저는 캡슐화된 객체라 볼 수 있다.

클로저를 사용할 때는 익명함수를 많이 쓴다. 위의 함수 f를 익명으로 바꿔서 다시 만들어 봤다.

function makeCounter(){
    var count = 0;
    return function(){
        retunr count++;
    }
}

핵심 사항

  • 외부 함수를 호출하면 그 함수의 렉시컬 환경 컴포넌트가 생성된다. 그리고 그 안에 중첩된 중첩 함수의 함수 객체를 생성해서 반환한다. 그 결과 외부 함수의 렉시컬 환경 컴포넌트를 참조하는 중첩 함수가 정의한 클로저가 생성된다. 즉 외부 함수는 클로저를 생성하는 팩토리 함수이다.
  • 외부 함수가 속한 렉시컬 환경 컴포넌트는 클로저 내부 상태 자체이다. 외부 함수가 호출될 때마다 새로 생성된다.
  • 중첩 함수의 함수 객체가 있는 한 외부 함수가 속한 렉시컬 환경 컴포넌트는 지워지지 않습니다. 외부 함수의 함수 객체가 사라져도 지워지지 않습니다.
  • 클로저 내부 상태(외부 함수의 지역변수, 선언적 환경 레코드)는 외부로부터 은폐되어 있으며 중첩 함수 안에서만 읽거나 쓸 수 있습니다.

이름공간

전역 유효 범위의 오염을 방지하기 위한 수단으로서 객체를 이름 공간으로 사용하는 방법과 함수를 이름 공간으로 사용하는 방법이 있다.

전역 이름 공간의 오염

전역 변수와 전역 함수를 객체에 선언하는 행위를 가르켜 '전역 유효 범위를 오염시킨다'라고 한다.

문제점으로 다음과 같은 상황 일때 변수 이름과 함수 이름이 겹칠 수 있다.

  • 라이브러리 파일을 여러 개 읽어 들여 사용할 때
  • 규모가 큰 프로그램을 만들 때
  • 여러 사람이 한 프로그램을 만들 때

자바스크립트 엔진은 이름이 같은 전역 변수나 함수를 끌어올려서 변수 또는 함수를 단 하나만 생성하므로 원하는 동작을 하지 않을 수 있다.

해결법

  • 객체를 이름 공간으로 활용하기

    • 이름 공간이란 변수 이름과 함수 이름을 한곳에 모아 두어 충돌을 방지하고 변수와 함수를 쉽게 가져다 쓸 수 있게 만든 메커니즘이다.

    • var myApp = myApp || {};
      myApp.name = "Tom"
      myApp.showName = function(){};
      myApp.view = {};
    • 위 처럼 변수를 선언 후 해당 변수에 변수와 함수를 넣으면 myApp이라는 나만의 공간을 가지고 사용 할 수 있다.

    • 또한 객체를 이용하여 서브 이름 공간을 만들 수 있다.

  • 함수를 이름 공간으로 활용하기

    • 함수 안에 선언된 변수의 유효 범위는 함수 내부이다. 이를 이용하여 해당 변수를 함수 안에 선언하여 안에서만 읽거나 쓰게 만드는 성질을 이용하여 이름공간으로 활용한다.

    • var x = "global x"
      (function(){
          var x = "local x"
          var y = "local y"
      }());
      console.log(x)    //global x
      console.log(y)    //not defined
    • 즉 전체 프로그램을 즉시 함수 안에 넣어 사용이 가능하다.

객체로서의 함수

자바스크립트에서는 함수도 일종의 객체이다.

그러므로 함수는 값을 처리할 수 있으며 프로퍼티와 메서드도 가지고 있다.

1. 함수는 객체

자바스크립트의 함수는 Function 객체이므로 다른 객체와 마찬가지로 다음 특징을 가지고 있다.

  • 함수는 변수나 프로퍼티나 배열 요소에 대입할 수 있다.
  • 함수는 함수의 인수로 사용할 수 있다.
  • 함수는 함수의 반환값으로 사용할 수 있다.
  • 함수는 프로퍼티와 메서드를 가질 수 있다.
  • 함수는 이름 없는 리터럴로 표현할 수 있다.(익명 함수)
  • 함수는 동적으로 생성 할 수 있다.

일반적으로 이러한 작업이 가능한 객체를 일급객체라고 한다.

  • 평가란 코드가 계산 되어 값을 만드는 것

  • 일급이란

    • 값으로 다룰 수 있고
    • 변수에 담을 수 있고
    • 함수의 인자로 사용할 수 있고
    • 함수의 결과로 사용될 수 있다.
  • 일급 함수

    • 함수를 값으로 다룰 수 있다.

    • 조합성과 추상화의 도구이다.

    • //함수는 변수에 담을 수 있고
      const add5 = a => a + 5;
      console.log(add5); //a => a + 5
      console.log(add5(3)); // 8
      //함수는 함수의 결과로 사용될 수 있다.
      const f1 = () => () => 1;
      console.log(f1()); //() => 1
      const f2 = f1();
      console.log(f2); //() => 1
      console.log(f2()); // 1

일급 객체인 함수는 일급 함수라 하는데 C나 JAVA같은 프로그래밍 언어의 함수는 일급 함수가 아니다.

이 일급 함수를 가지고 함수형 프로그래밍을 할 수 있다.

2. 함수의 프로퍼티

  • 함수의 프로퍼티
프로퍼티 이름 설명
caller 현재 실행 중인 함수를 호출한 함수
length 함수의 인자 개수
name 함수를 표시할 때 사용하느 ㄴ이름
prototype 프로토타입 객체의 참조
  • prototype

함수의 Function 생성자의 prototype 객체의 프로퍼티를 상속받아서 사용한다.

프로퍼티 이름 설명
apply() 선택한 this와 인수를 사용하여 함수를 호출한다. 인수는 배열
call() apply()와 똑같다. 단 인수는 쉼표로 구분한 값이다.
bind() 선택한 this와 인수를 적용한 새로운 함수를 반환한다.
contructor Function 생성자의 참조
toString() 함수의 소스 코드를 문자열로 만들어 반환한다.
  • 함수에 프로퍼티 추가하기

다른 객체와 마찬가지로 함수에도 프로퍼티를 추가할 수 있다.

function f(x){...};
f.p = a;
f.g = function(){...};

Function 객체에 추가된 프로퍼티는 그 함수를 실행하지 않아도 읽거나 쓸 수 있습니다. 함수의 프로퍼티에는 일반적으로 그 함수의 작업과 관련된 데이터와 메서드를 저장합니다. 물론 전역 변수에 저장해도 똑같은 역활을 할 수 있지만 전역 유효 범위를 오염시켜 버리므로 주의해야 하지만 함수의 프로퍼티로 작업하면 오염되지 않는다.

function fibonacci(n) {
  if (n < 2) return n;
  if (!(n in fibonacci)) {
    fibonacci[n] = fibonacci(n - 1) + fibonacci(n - 2);
  }
  return fibonacci[n];
}

for (var i = 0; i <= 20; i++) {
  console.log((" " + i).slice(-2) + ":" + fibonacci(i));
}

함수 fibonacci는 스스로 반환값을 함수의 프로퍼티에 저장하면서 피보나치 수열을 계산하는 재귀함수이다. 프로퍼티 이름이 인수 값이므로 대괄호를 사용하여 프로퍼티를 읽고 씁니다. 피보나치수열의 경우는 0항부터 20항까지를 계산하는데 28635번이 필요하지만 위의 함수는 19번이면 충분하다.

3.고차 함수

고차 함수란 함수를 인수로 받는 함수 또는 함수를 반환하는 함수를 말한다.

함수형 프로그래밍을 할 때 자주 사용한다.

간단한 예제와 그걸 응용한 예제를 살펴보자.

const apply1 = f => f(1);
const add2 = a => a + 2;
console.log(apply1(add2)); //3

const times = (f, n) => {
  let i = -1;
  while (++i < n) f(i);
};

times(console.log, 3); //0, 1, 2
times(a => console.log(a + 10), 3); // 10, 11, 12

또한 클로저를 이용한 예제를 보겠다.

const addMaker = a => b => a + b; //해당 함수는 a를 기억하는 클로저이다.
const add10 = addMaker(10);
console.log(add10(0));  //10
console.log(add10(5));  //15
console.log(add10(10)); //20

  1. 메모이제이션

메모이제이션이란 함수를 호출했을 때의 인수와 반환값을 한 쌍으로 만들어 저장해 두는 기법을 말합니다.

함수에 메모이제이션을 적용해 두면 한 번 건네받은 이력이 있는 결괏값으로 저장해 둔 결과를 반환하므로 추가적인 계산을 생략할 수 있습니다.

//인수로 받은 함수의 실행 결과를 객체 cache안에 저장하여 함수로 리턴한다.
//즉 memorize를 클로저로 만드는 함수이다.
function memorize(f) {    
  var cache = {};
  return function(x) {
    if (cache[x] === undefined) cache[x] = f(x);
    return cache;
  };
}
//인수가 소수면 true 함성수면 false를 반환한다.
function isPrime(n) {
  if (n < 2) return false;
  var m = Math.sqrt(n);
  for (var i = 2; i <= m; i++) if (n % i === 0) return false;
  return true;
}
//isPrime함수를 메모라이즈한다.
var isPrime_memo = memorize(isPrime);
var N = 1000;
for (var i = 2; i < N; i++) {
  isPrime(i);
}
//쌍둥이 소수의 목록을 출력한다.
for (var i = 2; i + 2 <= N; i++) {
  if (isPrime_memo(i) && isPrime_memo(i + 2)) console.log(i + "," + (i + 2));
}

4. 함수의 합성

함수 f(x)와 g(x)가 있을 때 함수f(g(x))를 f와 g의 함성 함수라고 합니다.

//단일
function compose(f, g) {
  return function(x) {
    return f(g(x));
  };
}
//다중
function compose(f, g) {
  return function(x) {
    return f.call(this, g.apply(this, arguments))
  };
}

var square = function(x) {
  return x * x;
};
var add1 = function(x) {
  return x + 1;
};
var h = compose(
  //h(x) = (x+1)*(x+1)
  square,
  add1
);
console.log(h(2)); //9

콜백 함수

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

f(g, ...);//g가 콜백 함수이다.
function f(callback, ...){
    ...
    callback();
    ...
}

콜백 함수는 함수를 호출할 때 무언가 새로운 일이 생기거나 그 함수의 실행이 끝나면 지정한 콜백 함수를 실행하 주도록 함수에 요청해야 할 때 사용한다. 이때 콜백 함수의 주체는 어디까지나 함수를 호출한 호출자이다.

호출자가 목적에 따라 어떠한 콜백 함수를 사용할 것인지 정한다. 호출된 함수는 콜백 함수를 실행하지만 그 콜백 함수가 작업하는 내용에는 관여하지 않는다.

*이벤트 처리기 *

이벤트 처리기는 특정 이벤트가 발생했을 때 실행하도록 등록시키는 함수이다.

함수를 호출할 때 무언가 사건이 발생하면 콜백함수를 실행하도록 인수로 넘기는 행위와 닮았다.

button.addEventListener("click", function(){...}, false);

타이머

타이머 메서드(setTimeout, setInterval)에 첫 번째 인수로 넘기는 함수가 콜백 함수이다.

ECMAScript 6부터 추가된 함수의 기능

화살표 함수, 나머지 매개변수, 인수의 기본 값, 이터레이터, 제너레이터, 템플릿 리터럴이 있다.

  • 화살표 함수 표현식으로 함수 정의하기

화살표 함수 표현식은 함수 리터럴(익명 함수)의 단축 표현이다. 하지만 함수 리터럴과는 다른점이 있으므로 주의해야한다.

작성법

//기본적인 사용법
var square = function(x) {return x*x}
var square = (x) => {return x*x};

//인수가 여러개 있을 시
var f =(x, y, z) =>{...};

//인수가 하나만 있을 시 괄호 생략
var squdare = x => {return x*x};

//return만 있을경우
//단 리턴값이 객체 리터럴이면 ()로 묶어야한다.
var squdare = x => x*x;
var f = (a, b) => ({x:a, b:b})

//즉시 실행 함수
(x=>x*x)(3);

차이점

  1. this의 값이 함수를 정의할 때 결정된다.

함수 리터럴로 정의한 함수의 this 값은 함수를 호출할 때 결정된다.

그러나 화살표 함수의 this 값은 함수를 정의할 때 결정이 된다. 즉 화살표 함수 바깥의 this값이 화살표 함수의 this값이 된다.

var obj = {
  say: function() {
    console.log(this);                //object Object
    var f = function() {
      console.log(this);
    };
    f();                            //object Window
    var g = () => console.log(this);
    g();                            //object Object
  }
};
obj.say();
//함수 f는 say라는 함수의 중첩 함수이며 this의 값은 전역 객체를 가르킨다.
//한편 화살표 함수인 g의 this값은 g를 정의함 익명 함수의 this의 값인 객체 obj를 가르킨다
  1. arguments 변수가 없다.

화살표 함수 안에는 arguments 변수가 정의되어 있지 않으므로 사용할 수 없다.

  1. 생성자로 사용할 수 없다.

화살표 함수 앞에 new 연산자를 붙여서 호출할 수 없습니다.

  1. yield 키워드를 사용할 수 없다.

화살표 함수 안에서는 yield 키워드를 사용할 수 없다. 그러므로 제너레이터로 사용할 수 없다.

  • 인수에 추가된 기능
  1. 나머지 매개변수

ECMAScript 6부터는 함수의 인자가 들어가는 부분에 ...을 입력하면 그만큼의 인수를 배열로 받을 수 있습니다. 이를 나머지 매개변수라고 부릅니다.

function(a, b, ...args){
    console.log(a, b, args);
}
f(1, 2, 3, 4, 5, 6);

함수의 가변인수를 사용할려면 이전에는 arguments를 사용해야 했지만 이는 배열이 아닌 유사 배열이므로 forEach 등의 배열 메서드로 조작할려면 배열로 변환 시켜야하고 화살표 함수등에서는 사용이 불가능했지만 args는 사용이 가능하다.

  1. 인수의 기본값

ECMAScript 6부터는 함수의 인자에 대입(=) 연산자를 사용해서 기본값을 설정할 수 있다.

기본값을 설정한 인자에 호흥하는 인수를 생략하거나 undefined를 넘기면 대입 연산자 우변의 값이 기본값이 된다.

function multplay(a, b=1){
    return a*b;
}
multplay(3);    //3
multplay(3, 2)    //6
  • ES6에서의 리스트 순회

이터레이션

이터레이션은 반복 처리라는 뜻으로 데이터 안의 요소를 연속적으로 꺼내는 행위를 말합니다.

ES6 이전에는 단순히 반복문을 사용하여 숫자키로 리스트 안에 있는 값을 순회하는 방법이었다.

var a = [5, 4, 3];
for (var i = 0; i < a.length; i++) {
  console.log(a[i]);
}
var str = "abc"
for (var i = 0; i < str.length; i++) {
  console.log(str[i]);
}
//ES6버젼
for (const a of str){
    console.log(a)
}
for(const a of a){
    console.log(a)
}

하지만 ES6부터는 다른 방법으로 배열을 순회하기 시작했다. 이터러블/이터레이터 프로토콜을 사용하기 시작했다.

이터러블 : 이터레이터를 리턴하는 {Symbol.iterator}() 를 가진 값

이터레이터 : {value, done} 객체를 리턴하는 next() 를 가진 값, 이때 value는 꺼낸 값 done는 반복이 끝났는지를 뜻하는 논리값이 저장된다 done이 false면 반복, frue면 반복이 끝 .

이터러블/이터레이터 프로토콜 : 이터러블을 for ... of, 전개 연산자 등과 함께 동작하도록한 규약

const iterable = {            //이터러블의 동작 과정을 확인
  [Symbol.iterator]() {
    let i = 3;
    return {
      next() {
        return i === 0
          ? { value: undefined, done: true }
          : { value: i--, done: false };
      },
      [Symbol.iterator]() {    //이터러블을 참고하는 이터레이터도 이터러블이다. 
        return this;
      }
    };
  }
};

let iterator = iterable[Symbol.iterator]();
console.log(iterator.next());        //Object {value: 3, done: false}
console.log(iterator.next());        //Object {value: 2, done: false}
console.log(iterator.next());        //Object {value: 1, done: false}
console.log(iterator.next());        //Object {value: undefined, done: true}
for (const a of iterator) console.log(a);    //이미 위에서 next를 해서 done가 ture이므로 반복하지 않음

Symbol

Symbol 이란?

ES6에서 새롭게 추가된 7번째 타입(number, string, boolean, undefined, null, Symbol, object)으로 변경 불가능한 원시 타입의 값이다. 다른 값과 중복되지 않는 유일무이한 값으로 주로 이름의 충돌 위험이 없는 유일한 프로퍼티 키를 만들기 위해 사용된다.

const mySymbol1 = Symbol("일번");
const mySymbol2 = Symbol("이번");        
console.log(mySymbol1)            //Symbol();
console.log(typeof mySymbol1);    //symbol
console.log(mySymbol1 === mySymbol2);

제너레이터

이터레이터이자 이터러블을 생성하는 함수

  • 반복 가능한 이터레이터 값으로 반환한다.
  • 작업의 일시 정지와 재시작이 가능하며 자신의 상태를 관리한다.
function* gen() {
  yield 1;
  yield 2;
  yield 3;
  return 1000;        //return값은 done값이 true일 경우 반환한다.
                    //그렇기 때문에 for of문에서는 나오지 않는다. 
                    //for of는 done이 ture면 빠져 나오기 때문
}

let iter = gen();
console.log(iter.next());
console.log(iter.next());
console.log(iter.next());
console.log(iter.next());
for (const a of gen()) console.log(a);//1, 2, 3
//제너레이터는 그 자체로 이터레이터이다.

yield : 제너레이터 함수에서 제너레이터 프로토콜을 통해 반환할 값을 정의

즉 일시적으로 정지되는 위치이다.

'자바스크립트 > 바닐라 자바스크립트' 카테고리의 다른 글

자바스크립트 특징  (0) 2020.04.20
자바스크립트 기초  (0) 2020.04.20

관련글 더보기

댓글 영역