함수 객체

함수도 객체이기 때문에 다른 값들처럼 배열에 저장하거나 인수, 반환값으로 사용할 수 있다.

함수 객체는 ``js Function.prototype``에 연결된다. ( ``js Function``이 ``js Object.prototype``에 연결되어 있다. )

모든 함수는 두 개의 숨겨진 속성 ``context``과 ``code``를 가지고 있다.

또한, 모든 함수 객체는 ``prototype`` 객체를 속성으로 가지고 있다. 이 객체는 다시 함수 자기 자신(``this``) 자체를 값으로 갖는 ``constructor``라는 속성을 가지고 있다.

 

* 함수 객체가 만들어질 때, 함수를 생성하는 Function 생성자는 다음과 같은 코드를 실행한다.

```js

this.prototype = {constructor: this};

```

 

Call pattern

모든 함수는 명시되어 있는 매개변수에 더해서 ``this``와 ``arguments``라는 추가적인 매개변수 두 개를 받게 된다.

함수 호출 패턴으로는 다음 네 가지가 존재하며 각각의 패턴에 따라 ``this`` 다르게 초기화되기 때문에 매우 주의해야 한다.

 

method call pattern

``js func.method()``

단순히 메소드를 호출하는 경우라고 생각하면 안되고, 세부지정( ``.``이나 ``[]``)을 이용하여 호출하는 경우를 말한다.

``this``는 메소드를 포함하고 있는 객체에 바인딩된다. (``this``는 객체 자체가 된다.)

``this``와 객체의 바인딩은 호출 시에 일어난다.

 

function call pattern

``js func();``

세부지정 없이 함수 이름만으로 호출하는 경우. 

``this``는 전역 객체에 바인딩된다.

메소드나 어떤 함수 안에 위치한 내부 함수 일지라도 이렇게 호출하면 함수 호출 패턴이 적용된다.

이런 특성은 언어 설계 단계에서의 실수라고 한다. 원래는 내부 함수를 호출할 때 이 함수의 ``this``는 외부 함수의 ``this`` 변수에 바인딩되어야 맞는거라고.

이 때문에 ``new`` 없이 그냥 함수를 호출하는 경우 ``this``가 전역 객체에 바인딩되어 알 수 없는 결과가 초래된다.

 

이를 해결하는 방법은 메소드에서 변수(관례상 ``that``)를 정의한 후 여기에 ``this``를 할당하고, 이 변수를 통해서 ``this``에 접근하는 방법이다. 

```js

var bObject = {

value: 0,

add_value: function ( ){

var that = this;

var helper = function ( ) {

that.value += 3;

}

helper();

}

};

bObject.add_value();

======

bObject.value  : 3

(global) value : error ( not defined )

```

만약 여기서 ``that``을 안쓰고 ``this``로 접근한다면 

``helper();``를 호출할 때 전역 객체에 바인딩되어 ``js bObject.value : 0``이 된다.

 

* ``js typeof``가 ``function``이 아니라 ``object``여야만 제대로 동작한다. 아래와 같은 상황에서는 ``that``을 사용해도 제대로 동작하지 않는다. 

```js

aFunc = function ( ){

    var value = 0;

var that = this;

    function helper () {

        that.value = 3;

    };

    helper();

    return value;

}();

======

(aFunc's) value : 0

(global)  value : 3

```

이는 `` bObject, aFunc`` 모두 내부의 ``js helper()``에는 function call pattern이 적용되지만 

``js bObject.add_value()``는 method call pattern이 적용되어 ``this``에 ``bObject``가 바인딩되고,

``js aFunc``는 function call pattern이 적용되어 ``this``에 전역 객체가 바인딩되기 때문이다.

그래서 후자의 경우 ``that``에 전역 객체가 바인딩되기 때문에 제대로 동작하지 않는다.

 

apply call pattern

함수는 ``js apply(bind_to_this, arg_array)``라는 메소드를 가지고 있으므로 이를 호출해 ``this``와 ``arguments``를 지정할 수 있다.

이를 이용하면 어떤 객체에 다른 객체의 메소드를 적용할 수 있다. 근데 아마 ``.prototype``에 연결된 메소드에 대해서만 사용할 수 있는 듯.

* ``js apply()``는 ``this``를 포함하고 있는 어떤 메소드를 빌려쓸 수 있도록 한다. 객체 생성 시점에 prototype을 지정하는 메소드는 ``js apply()``가 아니라 ``js Object.prototype()``이다. 

 

constructor call pattern

JS에는 클래스가 없다. 프로토타입을 이용해 상속한다.

``new`` 문법은 클래스 기반 언어에서 사용하는 생성자 호출 방식이다. 

이는 JS의 프로토타입적 속성을 애매하게 만들기 때문에, new와 constructor function을 사용하는 스타일은 권장 사항이 아니다. 아래에 대안이 있다.

 

``new``와 함께 사용하도록 만든 함수를 생성자(constructor)라고 하며 보통 파스칼 표기법으로 표기한다.

``new``를 사용하면 호출된 함수의 ``prototype`` 속성의 값에 연결되는 (숨겨진) 링크를 갖는 객체가 생성되고, 이 새로운 객체는 ``this``에 바인딩된다.

데이터는 생성자 함수 안에, 메소드는 ``prototype``으로 지정하는 방식이다.

```js

var Cobject = function (v){

this.value = v;

};

// function도 Object 이기 때문에, 아래 처럼 Cobject라는 function이 prototype이라는 field를 가질 수 있다!

Cobject.prototype.get_value = function ( ) {

return this.value;

};

var c = new Cobject(9);

alert(c.get_value());

```

 

``js new``연산자를 생성자를 포함하는 객체의 메소드라고 가정하면 이런 식으로 표현된다.

```js

Function.method('new', function ( ) {

//생성자 함수가 속해있는 객체의 prototype 속성(this.prototype)을

//프로토타입으로 하는 새로운 객체를 만들고 ( == 상속 )

var that = Object.create(this.prototype);

//이를 this에 바인딩한 다음

var other = this.apply(that, arguments);

//새로운 객체를 리턴

return (typeof other === 'object' && other) || that;

});

```

이 방식은 보편적으로 쉽게 이해할 수 있다는 장점이 있다.

그러나 ``new``를 빼먹어도 런타임 에러가 발생하지 않는데, ``new``를 빼먹는 경우 ``this``가 전역 객체와 바인딩되기 때문에 의도한 대로 동작하지 않아 실수할 수 있는 여지가 있다.

 

ECMAScript 6 에서 ``js class`` 키워드가 추가되어서, 위 과정을 class 키워드를 통해서 익숙한 방법으로 처리할 수 있다.

다만, 그냥 키워드만 추가된 것이고 내부적으로는 위와 같이 처리하게 된다.

 

생성자 대안

JS에서 객체를 찍어내는 다양한 방법이 있다.

  • 객체 리터럴
  • ``new``와 ``Constructor``
  • ``js Object.create()`` 메소드를 이용한 프로토타입 방식
  • (추천, 함수형 패턴) 객체를 생성 및 반환하는 함수를 호출하는 방법

 

가장 추천하는 방식은 프로토타입 방식(``js Object.create()``)을 응용한 객체를 생성 및 반환하는 함수를 사용한 방식이다.

 

객체를 생성 및 반환하는 함수는 다음 4 단계로 진행된다.

#1 필요한 ``private`` 변수와 메소드를 정의한다.

#2 ``that``에 새로운 객체를 생성해 할당한다.

#3 ``that``에 메소드를 추가한다.

#4 ``that``을 반환한다.

 

``new``를 사용하지 않기 때문에 대문자로 시작하는게 아니라 소문자로 시작하도록 정의한다.

var constructor = function (spec, my) {

    var that, private 변수와 메소드;

    my = my || {};

    공유할 변수와 메소드를 my에 추가

    that = 새로운 객체

    that에 메소드 추가

    return that;

}

일반적으로 ``constructor``를 호출할 때 넘기게 되는 데이터는 ``spec``을 통해 넘기면 된다.

``my``는 선택적으로 사용하면 되며 상속 연결상에서 생성자와 공유하게 되는 비밀을 담는 컨테이너로 사용한다.

 

이런 식으로 사용하면 된다.

var createMammal = function (spec) {

    var that = {};
    that.get_name = function ( ) {
        return spec.name;
    }

    /*이런 식으로 일단 private 메소드로 만든 다음 that에 할당할 수도 있다.
    이렇게 되면 내부에서는 that.says이 아니라 says로 호출할 수 있고,
    that.says가 변경되더라도 says는 그대로이므로 
    says를 호출하는 메소드는 같은 작업을 수행할 수 있게 된다.*/

    says = function ( ) {
        return spec.saying || '';
    }
    that.says = says;
    return that;
};

var myMammal = createMammal({name: 'Herb'});

* function call pattern이기 때문에 ``var that = this``로 써도 소용없다.

 

또는 이런 식으로...

export const createX = () => {
    private 변수와 메소드;
    
    return {
        field: {},
        method() {
            ...
        }
    }
}

 

상속

#2 ``that``에 새로운 객체를 생성해 할당한다.

부분에서, ``that``에 상속 대상 객체를 생성해 할당하는 방식으로 처리한다.

```js

var cat = function (spec) {

spec.saying = spec.saying || 'meow';

var that = mamml(spec);

...

return that;

};

```

 

함수형 패턴을 사용하면 super 메소드에도 접근 가능하다.

다음은 super 메소드를 함수로 반환하는 ``superior`` 메소드다.

``superior`` 메소드가 반환하는 함수는 속성이 변경되더라도 원래의 super 메소드를 반환한다.

```js

Object.method('superior', function (name) {

    var that = this,

        method = that[name];

    return function ( ) {

        return method.apply(that, arguments);

    };

});

```

``superior``는 method call pattern이 적용되어 그냥 ``this``를 사용해도 되지만, 상속받는 함수에서 super 객체를 ``that``으로 받기 때문에 이렇게 사용한 듯.

 

이런 식으로 사용한다.

```js
var mammal = function (spec) {
    spec.saying = spec.saying || '';
var that = {};
    that.get_name = function ( ) {
        return spec.name;
    }
return that;
};
 
var cat = function (spec) {
    spec.saying = spec.saying || 'meow';
    var that = mammal(spec),
        super_get_name = that.superior('get_name');
    
    that.get_name = function (n) {
        return 'SUPER ' + super_get_name( );
    };
    return that;
};
 
var myCat = cat({name: 'Mint'});
alert(myCat.get_name( ));    // SUPER Mint

```

``superior``를 사용하지 않고 직접 ``super_get_name() = that.get_name``한다면, 이 것이 ``that.get_name``을 다시 정의하는 부분 보다 위에 있으면 잘 동작한다. 그러나 재정의하는 부분 보다 아래에 위치하는 경우에는 재귀 호출이 되어 무한 루프에 빠진다.

``superior`` 메소드를 사용하면 위치에 상관 없이 제대로 super 메소드를 가져온다.