'2015/12/06'에 해당되는 글 2건

  1. 2015.12.06 [JavaScript] 자바스크립트 함수에 대하여 1
반응형

<자바스크립트 함수 개요>


자바스크립트에서 함수는 일급 객체(first-class object)이다. 일급 객체는 다음의 특징을 가진다.

    • 리터럴에 의해 생성 가능하다.
    • 변수나 배열의 요소, 객체의 프로퍼티 등에 할당 가능하다.
    • 함수의 인자로 전달 가능하다.
    • 함수의 반환값으로 사용할수 있다.
    • 동적으로 프로퍼티를 생성하고 할당 가능하다.

일급 객체에 대한 내용은 https://en.wikipedia.org/wiki/First-class_citizen 에서 자세히 살펴볼수 있다. 일급 객체의 특성을 가지기 떄문에 C나 Java와는 다른 특성들을 제공하고 있으며, 다른 언어에 비해서 모듈화 처리, 클로저, 객체 생성등 자바스크립트의 여러 기능으로 활용된다.


1. 함수 생성

자바스크립트에서 함수를 생성하는 방법은 다음의 3가지가 있다. 이들 방법에 따라 함수 동작이 조금씩 차이가 있으며 활용 방법도 다르다.

    • 함수 선언문 방식(Function Declaration)
    • 함수 표현식 방식(Function Expression)
    • Function() 생성자 함수 이용

함수 선언문 방식은 다음과 같이 함수를 생성한다.

1
2
3
function hello(name){
    return 'Hello '+name;
}
cs

function 키워드에 hello라는 함수명, name이라는 매개변수, {} 안의 함수 몸체로 구성된다. 함수명은 선택사항으로 함수명이 없는 경우 익명함수라고 칭한다. 함수 선언문 방식으로 생성한 함수는 자바스크립트의 실행 컨텍스트가 생성될 때 바로 초기화되고 변수 객체(Variable Object)에 저장되기 때문에 함수 선언 위치와 관계 없이 어디에서나 호출 가능하다. (함수 호이스팅)


함수 표현식 방식은 다음과 같이 생성한다. 표현식은 변수에 함수를 할당하는 것과 같다. 자바스크립트 함수가 일급 객체이기 떄문에 변수에 할당 가능한 것이다. 표현식에 의한 방법으로 다음과 같이 함수를 생성한다.

1
2
3
4
var hello=function(name){
    return 'hello '+name;
};
var halo=hello;
cs

hello에 익명의 함수를 할당하고, halo에 hello를 할당한다. hello와 halo는 동일한 익명함수를 참조하고 있어서 halo 또는 hello로 함수를 호출해도 동일한 결과를 넘겨준다. 함수 표현식에서 익명의 함수를 이용하지 않고 함수 이름을 표현하여 생성 가능하지만 그 이름으로 외부에서 호출할수 없다. (단 생성하는 함수 내에서는 재귀적으로 호출 가능하다.) 참고로 함수 이름이 포함된 함수 표현식은 기명 함수 표현식 이라고 한다. 다음의 코드에서 이 차이를 나타내고 있다.

1
2
3
4
5
6
var factorialFunc = function fact(n){
    if(n<=1return 1;
    return n*fact(n-1);
};
console.log(factorialFunc(10)); //3628800
console.log(fact(10));  // ReferenceError : fact is not defined
cs

fact 함수는 fact 함수 내부에서 참조 가능하지만 함수 외부에서는 참조가 불가능하다.


참고로 함수 선언문 방식으로 선언된 함수는 함수 끝에 세미콜론을 붙이지 않지만 함수 표현식 방식은 세미콜론을 붙이는게 좋다. (함수 선언 이후에 즉시 실행 함수를 실행할때 문제가 되는 경우가 있다.)


Function() 생성자 함수를 이용해서 함수를 생성할 수 있다. 실제로 함수 선언문이나 함수 표현식 방식도 내부적으로는 Function() 생성자 함수로 함수를 생성한다고 볼 수 있다. 하지만 자주 사용되지 않는다. 다음 예제는 Function() 생성자 함수로 함수를 생성한 예이다.

1
var hello=new Function('name','return \'hello \'+name;');
cs


즉시 실행 함수는 함수 선언과 동시에 실행하는 함수이다. 함수 리터럴을 ()로 감싸고 이 함수에 파라미터를 전달하고 호출하기 위해 ()를 추가한다. 즉시 실행 함수는 함수에 이름을 넣어도 넣이 않아도 상관 없으나, 즉시 실행함수는 함수가 실행되면 다시 호출할 수 없다. 


2. 즉시실행함수

즉시 실행 함수는 다음과 같은 방법으로 생성하고 즉시 실행할 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
(function(name){
    console.log('Hello '+name);
})('bar');
 
(function(name){
    console.log('Hello '+name);
}('bar'));
 
(function(name){
    console.log('Hello '+name);
}('bar'));
 
(function hello(name){
    console.log('Hello '+name);
}('bar'));
cs


즉시실행함수는 한번 실행되면 다시 호출할 수 없다. 이런 특징으로 한번만 실행할 필요가 있는 초기화 코드 등에 사용 가능하다. 또한 즉시실행함수를 이용하면 자바스크립트에서 글로벌 스코프에서 정의된 변수는 코드 내에서 어디에서든 접근 가능하다는 문제점을 해결하고 전역 변수에서 동일한 이름 명명에 따른 충돌을 방지할 수 있다. 즉 라이브러리 코드를 즉시 실행 함수 내부에 정의하면 함수 외부에서 접근할수 없다. 그럼으로 전역 변수의 네임스페이스를 오염시키지 않게 된다.


따라서 여러 라이브러리에서 즉시 실행 함수를 이용한다. 다음은 jQuery에서 사용된 즉시 실행 함수이다.

1
2
3
4
5
(function(window, undefined){
 
....
 
})(window);
cs

위의 ( .... ) 사이에 정의된 변수들은 전역 변수에 아무런 영향을 주지 않는다.


jQuery나 Prototype 라이브러리는 동일한 $라는 글로벌 변수를 이용한다. 이 두 라이브러리를 같이 사용한다면 $에 대한 충돌이 생길 것이다. 하지만 특정 즉시 실행함수의 코드 블록에서 jQuery를 위한 $변수를 이용하고자 한다면 다음과 같이 Prototype의 $에 대한 오염을 방지할 수 있다.

1
2
3
4
5
(function($){
    // 함수 스코프 내에서 $는 jQuery Object가 된다.
    ....
 
}(jQuery));
cs


즉시 실행함수의 함수 블록 코드에서 변수 접근은 객체를 return 함으로 접근할수 있다.

1
2
3
4
5
6
7
var hello=(function(){
    var privateStr='Hello World';
    return {
        hello: privateStr
    };
}());
console.log(hello.hello); //Hello World
cs


이런 특성으로 OOP의 Encapsulation 특성을 구현할수 있다. 다음은 그 예이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var hello=(function(){
    var privateStr='Hello World';
    var getString=function(){
        return privateStr;
    };
    var setString=function(str){
        privateStr=str;
    };
    return {
        get: getString,
        set: setString
    };
}());
console.log(hello.get()); // Hello World
hello.set('Hello Galaxy');
console.log(hello.get()); // Hello Galaxy
hello.privateStr; //undefined
cs


3. 내부함수

자바스크립트에서는 함수 내부에서 다시 함수 정의가 가능하다. 이렇게 함수 내부에 정의된 함수를 내부함수(inner function)이라고 한다.

1
2
3
4
5
6
7
8
9
10
11
12
function parent(){
    var f_str='Parent Function String';
    var p_str='Parent String';
    function child(){
        var f_str='Child Function String';
        console.log(f_str);
        console.log(p_str);
    }
    child();
}
parent();
child(); //ReferenceError: child is not defined
cs

상기 예제처럼 parent 함수 내에 생성한 child는 parent 함수 이외에서는 호출이 불가능하다. 즉 외부함수 밖에서는 이 외부함수의 내부함수를 접근할수 없다. 또한 내부 함수는 자신을 둘러싼 외부 함수에 접근이 가능하다. 이는 내부 함수가 호출될때 자신을 호출한 외부 함수를 스코프 체이닝상에서 연결되어 있고, 내부 함수에서 변수를 참조하지 못하면 스코프 체이닝 상의 외부 함수의 변수를 찾게 된다. 


내부 함수를 이용하면 클로저를 만들 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
function parent(){
    var a=0;
    var getIncreased=function(){
        a++;
        return a;
    };
    return getIncreased;
}
var incre=parent();
incre(); //1
incre(); //2
parent.a;//undefined
cs

위 코드 예제에서 parent 함수를 호출하면 getIncreased 라는 내부 함수를 리턴한다. 이 함수는 parent 함수의 a라는 변수를 1증가시키고 a를 리턴하는데 parent.a 와 같이 변수를 직접 접근할수 없다. 부모 함수의 호출이 끝났지만, 호출이 끝난 부모 함수의 변수, 즉 부모함수의 스코프의 변수를 참조하는 getIncreased 같은 함수를 클로저라고 한다. 클로저는 자바스크립트에 강력한 기능을 제공한다. (클로저는 다른 포스팅으로 상세 설명한다.)



<자바스크립트 함수 프로퍼티>


앞서 자바스크림트의 함수는 객체라고 설명하였다. 함수를 생성하면 함수 객체가 생성된다. 함수 객체는 객체이기 떄문에 [[prototype]] 프로퍼티를 가진다. (__proto__ 프로퍼티) 함수 객체의 [[prototype]]은 모든 함수의 부모 역활을 하는 Function.prototype 객체를 가르키는데 이 또한 함수 객체이다. 이 함수 객체의 [[prototype]]은 모든 객체와 마찮가지로 Object.prototype을 가르킨다. 다음의 예제를 보자.

1
2
3
4
5
function hello(name,greeting){
    var str=name+' '+greeting;
    console.log(str);
    return str;
}
cs

이 함수는 다음과 같은 프로퍼티들을 가지고 있다.

arguments 는 함수에 전달되는 파라미터에 대한 정보이다. caller는 이 함수를 호출한 함수를 나타낸다. length는 이 함수에 정의된 파라미터 개수를 의미한다.이 함수는 자바스크립트 객체가 가지고 있는 [[Prototype]] 프로퍼티를 가진다. 모든 함수는 함수 부모 역활을 하는 Function.prototype를 가진다. Function.prototype 역시 객체이므로 모든 객체의 부모 역활을 하는 Object.prototype을 가진다. 

위에서 보여지는 것처럼 hello.__proto__ 는 Function.prototype 과 같다는 것을 알수 있다. 그리고 Function.prototype.__proto__는 역시 Object.prototype과 같다는 것을 알수 있다. Function.prototype은 다음과 같은 프로퍼티를 가지고 있다.

따라서 apply, call, bind, toString 등과 같은 함수는 함수 생성시 별도로 추가하지 않아도 프로토타입 체인 특성에 따라 기본적으로 사용 가능하다.


그런데 특이한 것은 [[Prototype]] 특성 이외에 prototype 이라는 프로퍼티(이 함수는 hello라는)를 함수가 가지고 있다는 것이다. (이름 때문에 헷갈리지 말자. prototype 과 __prototype__은 엄연히 다르다.) 함수의 prototype은 이 함수의 프로토타입 객체를 가르킨다. hello.prototype은 다음의 프로퍼티를 가진다.

결국 함수 객체는 prototype 프로퍼티를 가지고 있는데 이 prototype 프로퍼티는 해당 함수를 constructor 로 가르키고 있는 객체를 가르킨다. hello.prototype.constructor 는 hello를 가르키고 hello.prototype은 hello.prototype을 가르키고 있는 것이다. 


prototype 프로퍼티는 생성자 함수의 동작과 관련이 있다. 어떤 함수를 new를 써서 생성자 함수로 호출하게 되면 새로운 객체를 생성하고 이 객체의 [[prototype]] 링크를 생성자 함수의 prototype 프로퍼티의 링크에 할당한다. 

위 코드 예처럼 hello2는 hello함수를 new를 써서 생성자 함수로 호출한 것이다. 이 객체는 __proto__를 hello.prototype을 가르키고 있다. 당연히 hello.prototype은 constructor로 hello 함수를 가르키고 있는 것이다.


참고자료

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

한빛미디어 인사이드 자바스크립트

반응형
Posted by alias
,