top bar

글 목록

2017년 7월 5일 수요일

[Javascript] 생성자의 이해

시작에 앞서 잡설..



 과거 웹개발을 공부할때나, 아니면 커리어 초기에 개발업무를 진행 할때 '자바스크립트' 라는 언어는 그저 'onclick' 따위의 이벤트를 핸들링 하는 것과 Ajax를 이용한 비동기 요청을 구현하는 용도로 밖에 사용하지 않았다. 좀 더 활용했다라고 한다면 'JQuery UI' 를 이용해 캘린더 붙이는 정도...?

 하지만 새로운 회사로 이직하여 시작한 프로젝트는 npm 환경을 기반으로 하는 그야말로 트렌디한 온갖 자바스크립트 도구, 라이브러리, 프레임워크의 향연이었다. 또한 ECMAScript6 이후부터의 문법적 발전을 통해서, 객체의 해체할당, let과 const의 block scope, class 선언 방식 등장, 달라진 모듈 import와 export 문법 등등... 여러가지 새로운 개념을 익혀야 했다.

이러한 변화에 흐름에 당당히 맞서서(!?) 나는 요즈음 나비책, 부엉이책, 이름을알수없는새책 등을 탐독하고 있다 (코뿔소책은 그 두께에 엄두가 안나서 pass.. 그래도 필요한 부분은 틈틈히 보고 있다..)
모두 적어도 몇년전에 출판된, 비교적 과거의 문법과 이론을 담고 있는 책들이다.

그렇다고 변화에 흐름을 미련하게 역행 하겠다는 건 절대 아니다. 다만 우리가 역사를 연구 함으로 미래에 대한 통찰력을 얻는 것처럼, 자바스크립트의 과거 이론을 통해서 현재의 변화를 더 잘 이해하고 싶은 것 뿐이다.

생성자에 관해 정리하려다가 잡설이 길어졌다. 정리에 앞서, 자바스크립트 생성자에 대한 더 깊은 이해를 할 수 있도록 도와주신 실력자 'javarouka'님께 감사 드린다.


생성자 문법 정리



자바스크립트의 생성자는 다름아닌 '함수' 다. 자바의 경우 생성자는 일반 메소드와 시그니쳐가 비슷하지만, 자바의 생성자는 메소드처럼 호출 할 수 없다. 반드시 'new' 키워드와 함께 사용해야 한다.

하지만 자바스크립트의 생성자 함수는 'new'와 함께 사용할 수도, 그냥 일반적인 함수처럼 사용할 수도 있다. 아래의 예제 처럼 말이다.

function MyConstructor(prop) {
    this.prop = prop;
}

var obj1 = MyConstructor("prop value");
var obj2 = new MyConstructor("prop value");

하지만 obj1과 obj2 할당된 값은 아래와 같은 차이가 있다.

console.log(obj1) // undefined
console.log(obj2) // MyConstructor
console.log(obj2.prop) // "prop value"

obj1은 값이 정의되지 않은 'undefined' 값이고, obj2는 정상적으로 MyConstructor의 객체와 prop 속성값인 "prop value" 문자열이 할당 된 것을 볼 수 있다.

'new' 키워드와 함께 사용된 생성자 함수는 묵시적으로 객체를 생성하여 'this' 에 바인딩한다. 그리고 동적으로 속성을 정의한 뒤에 역시나 묵시적으로 'this' 를 반환한다. 따라서 obj2에는 정상적인 객체가 생성되어 메모리에 할당 되는 것이다.

하지만 생성자를 'new' 키워드와 사용하지 않으면 일반 함수 처럼 호출이 되고, 이때, 'this'는 브라우져의 자바스크립트 실행 환경에서 '머리객체(Head Object)'인 'window' 이다. 따라서  MyConstructor 생성자를 일반 함수로서 호출 하게 되면 머리객체인 'window'에 동적으로 'prop'속성이 생성되며, 아무것도 반환하지 않으므로 obj1는 'undefined'가 된다.

'window' 객체에 'prop' 속성이 생성되었는지 확인해보자.




.
.
.









 'window' 객체의 속성을 나열했더니 추가된 prop 이 보인다

이러한 생성자 함수를 'new'키워드와 사용하지 않을때 주의 사항은, 바로 위의 예제에서 'window' 머리객체에 'prop'이 추가 된 것처럼 절대 전역 유효범위를(global scope)를 어지럽혀선 안된다는 것이다. 이유는 자명하다. 전역 머리객체에 개발자가 의도하지 않은 속성이 추가 될 수 있고, 이는 찾아내기 어려운 버그 발생으로 이어지기 때문이다.


한편, 생성자 함수를 'new' 키워드와 함께 사용한다고 무조건 해당 생성자의 객체를 반환할까?
그것도 아닌것 같다.

function ReturnByObj() {
    this.key = "hello";
    return new Object();
}

function ReturnByString() {
    this.key = "hello";
    return "ret value";
}

const returnByObj = new ReturnByObj();
const returnByString = new ReturnByString();

console.log(returnByObj); // Object 객체
console.log(returnByObj.key); // undefined
console.log(returnByString); // ReturnByString 객체
console.log(returnByString.key); // "hello"

생성자 함수(처럼 보이는) ReturnByObj와 ReturnByString 에 'return' 문을 추가했다. 이 두 함수를 'new' 키워드와 함께 사용한 코드이다.

위의 console 문 주석을 보면 알 수 있듯이 'new' 키워드와 함께 사용해도, return 문의 값이 '객체' 라면,
해당 객체가 반환된다. 'ReturnByObj' 객체가 반환 되지 않는다는 말이다. 하지만 'ReturnByString' 생성자 함수 처럼 원시값(primitive type)을 리턴한다면, 우리가 기대했던대로 해당 생성자내부에서 생성된 'this' 객체('ReturnByString' 객체)가 반환된다.

중요한 사실은, 이러한 문법을 이해해야 생성자 함수를 일반 함수처럼 호출하거나, 일반 함수를 'new' 키워드와 같이 호출하는 등의 오용과 실수를 줄일 수 있다는 사실이다.



댓글 없음:

댓글 쓰기