[JS] 함수 #2. Call pattern, 생성자 대안(함수형 패턴), 상속
함수 객체
함수도 객체이기 때문에 다른 값들처럼 배열에 저장하거나 인수, 반환값으로 사용할 수 있다.
함수 객체는 ``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``으로 받기 때문에 이렇게 사용한 듯.
이런 식으로 사용한다.
```
``superior``를 사용하지 않고 직접 ``super_get_name() = that.get_name``한다면, 이 것이 ``that.get_name``을 다시 정의하는 부분 보다 위에 있으면 잘 동작한다. 그러나 재정의하는 부분 보다 아래에 위치하는 경우에는 재귀 호출이 되어 무한 루프에 빠진다.
``superior`` 메소드를 사용하면 위치에 상관 없이 제대로 super 메소드를 가져온다.
'JS Stack > JS' 카테고리의 다른 글
[JS] char <> code <> hex / unicode 공백 / Unpack (0) | 2017.06.04 |
---|---|
[JS] memoization, currying (0) | 2017.06.02 |
[JS] 함수 #1. 유효범위(scope), callback, 클로저(closure), 접근 제어 (0) | 2017.05.31 |
[JS] 객체, 프로토타입, 기본 타입에 기능 추가 (0) | 2017.05.29 |
[Regex] JS (0) | 2017.05.28 |