티스토리 뷰

728x90
반응형

리터럴과 생성자- javascript pattern 참고 및 정리
 
 자바스크립트에서 객체라고 하면 단순히 이름-값 쌍의 해시 테이블을 생각하면 된다.
 다른 언어에서 연관배열이라 불리는 것과 유사하다.  원시데이터타입과 객체 모두 값이 될 수 있다.
 함수도 값이 될 수 있으며 이런 함수는 메서드라고 부른다.
 
 var dog={} 등 빈 객체라는 표현을 쓸 수 있는데 간결성을 위한 것일 뿐 자바스크립트에 빈 객체란 없다는 사실을 알아두어야 한다.
 가장 간단한 {} 객체조차도 이미 Object.prototype에서 상속받은 프로퍼티와 메서드를 가진다.
'비어있다'는 말은 어떤 객체가 상속받은 것 외에는 자신의 프로퍼티를 갖고 있지 않다는 뜻으로 이해하면 된다.



사용자 정의 생성자 함수

직접 생성자 함수를 만들어 객체를 생성할 수도 있다.
var adam=new Person("Adam");
adam.say(); //"I am Adam"

var Person=function(name){
     this.name=name;
     this.say=function(){
           return "I am"+this.name;
     }
}
자바스크립트에는 클래스라는 것이 없으며 Person은 그저 보통의 함수일 뿐이다.

new와 함께 생성자 함수를 호출하면 함수 안에서 다음과 같은 일이 일어난다.
1. 빈객체가 생성된다. 이 객체는 this라는 변수로 참조할 수 있고, 해당 함수의 프로토타입을 상속받는다.
2. this로 참조되는 객체에 프로퍼티와 메서드가 추가된다.
3. 마지막에 다른 객체가 명시적으로 반환되지 않을 경우, this로 참조된 이 객체가 반환된다.

즉 이면에서는 다음과 같이 진행된다고 할 수 있다.
var Person=function(name){
   //객체 리터럴로 새로운 객체를 생성한다.
   //var this={};
   
   //프로퍼티와 메서드를 추가한다.
   this.name=name;
   this.say=function(){
      return "I am"+this.name;
   };
   
   //this를 반환한다.
   //return this;
}
이 예제에서는 간단히 say()라는 메서드를 this에 추가했다. 
결과적으로 new Person()을 호출 할때마다 메모리에 새로운 함수가 생성된다.
say()라는 메서드는 인스턴스별로 달라지는게 아니므로 이런 방식은 명백히 비효율적이다.
이 메서드는 Person의 프로토타입에 추가하는게 더 낫다.
Person.prototype.say=function(){
    return "I am"+this.name;
}

한가지 더 언급할 만한 사실이 있다.
먼저 생성자 내부에서 다음과 같은 일이 벌어진다고 했다.
//var this={};
이 객체는 Person의 프로토타입을 상속받는다. 즉 다음 코드에 더 가깝다.
//var this=Object.create( Person.prototype );

tip- Object.create()는 ECMAScript 5에서 프로토타입을 활용한 상속 패턴이 공식 요소가 되었다. 
두번째 선택적 매개변수로 객체를 받는다. 전달된 객체의 프로퍼티는 반환하는 객체 자신이 추가된다.
한번의 메서드 호출로 객체의 상속과 정의가 가능하므로 편리하게 쓸 수 있다. 



생성자의 반환 값

생성자 함수를 new와 함께 호출하면 항상 객체가 반환된다. 기본값은 this로 참조되는 객체다.
생성자 함수내에서 아무런 프로퍼티나 메서드를 추가하지 않았다면
 '빈'( 즉 생성자의 프로토타입에서 상속된 것 외에는 '비어있는' )객체가 반환될 것이다. 
 
함수내에 return 문을 쓰지 않았더라도 생성자는 암묵적으로 this를 반환한다. 그러나 반환 값이 될 객체를 따로 정할 수도 있다.

var Objectmaker=function(){ //생성자가 다른 객체를 대신 반환하기로 결정했기 때문에 //다음의 'name' 프로퍼티는 무시된다. this.name="This is it"; //새로운 객체를 생성하여 반환한다. var that={}; that.name="And that's that"; return that; }

어떤 객체라도 (객체이기만 하다면) 반환할 수 있다.
객체가 아닌것( 문자열, false값 )을 반환하려고 시도하면, 에러가 발생하진 않지만 
그냥 무시되고 this에 의해 참조된 객체가 대신 반환된다.

생성자에서 new를 빼먹게 되면 생성자 내부의 this는 전역 객체. (브라우저라면 this가 window를 가르키게 된다. )
생성자 내부에 this.member와 같은 코드가 있을 때 이 생성자를 new없이 호출하면, 전역 객체에 member라는 새로운 프로퍼티가 생성되고 이 프로퍼티는 window.member 또는 그냥 member를 통해 접근할 수 있다. 이런 동작 방식은 바람직하지 않다.
생성자 함수가 new 없이 호출되어도 항상 동일하게 동작하도록 보장하는 방법을 써야 한다

명명규칙
간단한 대안은 생성자 함수명의 첫글자를 대문자( MyConstructor )로 쓰고 '일반적인' 함수와 메서드의 첫글자는 소문자(myFunction)를 사용한다.

that 사용

생성자가 항상 생성자로 동작하도록 해주는 패턴을 살펴보면 this에 모든 멤버를 추가하는 대신, 
that에 모든 멤버를 추가한 후 that을 반환하는 것이다.
var Objectmaker=function(){
function Waffle(){
   var that={};
   that.tastes="yummy";
   return that;
}
//간단한 객체라면 that이라는 지역 변수를 만들 필요도 없이 객체 리터럴을 통해 객체를 반환해도 된다.
function Waffle(){
    return{
          tastes:"yummy"
    };
}
하지만 이 패턴의 문제는 프로토타입과의 연결고리를 잃어버리게 된다는 점이다.
즉 Waffle()프로토타입에 추가한 멤버를 객체에서 사용할 수 없다.



스스로를 호출하는 생성자

앞서 설명한 패턴의 문제점을 해결하고 인스턴스 객체에서 프로토타입의 프로퍼티들을 사용할 수 있게 하려면,
다음 접근 방법을 고려할 수 있다. 생성자 내부에서 this가 해당 생성자의 인스턴스인지를 확인하고, 그렇지 않은 경우
new와 함께 스스로를 재호출하는 것이다.
function Waffle(){
     if(!(this instanceof Waffle) ){
          return new Waffle();
     }
     this.tastes="yummy";
}
Waffle.prototype.wantAnother=true;

//호출 확인
var first=new Waffle(),
second=Waffle();
console.log( first.tastes );//"yummy"
console.log( second.tastes );//"yummy"

console.log( first.wantAnother );//true
console.log( second.wantAnother );//true


배열 리터럴

객체 리터럴과 마찬가지로 배열 리터럴 표기법이 더 간단하고 장점이 많다.
//안티패턴
var a=new Array("itsy", "bitsy", "spider" );

//위와 똑같은 배열
 var a=["itsy", "bitsy", "spider"];
 
 console.log(type a );//배열도 객체이기 때문에 "object"가 출력된다.
 console.log(a.constructor===Array); //true
배열 리터럴 문법
결국 배열이란 0에서 인덱스를 시작하는 값의 목록이다.
생성자와 new연산자를 가져오거나 코드를 장황하게 써서 일을 복잡하게 만들 이유가 전혀 없다.
var arr=[];//배열 생성 및 초기화
var arr2=[1, 2, 3]; //값지정
 
배열 생성자의 특이성
new Array()를 멀리해야 하는 또 다른 이유는 이 생성자가 품고 있는 함정을 피하기 위해서이다.
Array() 생성자에 숫자 하나를 전달할 경우, 이 값은 배열의 첫 번째 원소 값이 되는 게 아니라 배열의 길이를 지정한다.
즉 new Array(3)은 길이가 3이고 실제 원값은 가지지 않는 배열을 생성한다. 
원소가 존재하지 않기 때문에 어느 원소에 접근하든 undefined값을 얻게 된다. 
//한 개의 원소를 가지는 배열
var a=[3];
console.log( a.length ); //1 
console.log( a[0] ); //3

 //세개의 원소를 가지는 배열
 var a=new Array(3);
 console.log( a.length );//3
 console.log( typeof a[0] ); //"undefined"
 
//부동소수점을 가지는 수를 전달할 경우 부동소수점을 가지는 수는 배열의 길이로 유효한 값이 아니기 때문에 에러가 발생한다.
 
 //리터럴 사용
var a=[3.14];
console.log( a[0] ); //3.14

var a=new Array(3.14);// 배열 길이로 유효하지 않은 값이므로 RangeError가 발생
console.log( typeof a );//"undefined"

런타임에 동적으로 배열을 생성할 경우 배열의 리터럴표기법을 쓰는 것이 훨씬 안전하다.

tip- 그러나 Array()생성자를 독창적으로 활용하는 사례들도 있다. 
var white=new Array(256).join(" ");//255개의 공백문자로 이루어진 문자열을 반환한다.


배열인지 판별하는 법
Object.prototype.toString() 메서드를 호출하여 판별할 수 있다.
배열에 toString을 호출하면 "[object Array]" 라 는 문자열을 반환하게 되어 있다.
객체일 경우에는 문자열 "[object Object]"가 반환될 것이다.

if( typeof Array.isArray==="undefined"){
    Array.isArray=function( arg ){
          return Object.prototype.toString.call( arg )==="[object Array]";
    };
}


JSON
JSON은 그저 배열과 객체 리터럴 표기법의 조합일 뿐이다.
{"name":"value", "som":[1,2,3] }
객체 리터럴은 프로퍼티명이 식별자로서 유효하지 않은 경우에만 따옴표가 필요하다.
즉 {"first name": "Dave"} 에서처럼 프로퍼티명에 공배문자가 포함되어 있다면 따옴표로 감싸주어야 한다.
JSON문자열에는 함수나 정규식 리터럴을 사용할 수 없다.


JSON 다루기
eval을 사용하여 무턱대고 JSON문자열을 평가하면 보안 문제가 있을 수 있기 때문에 바람직하지 않다.
가능하면 JSON.parse()를 사용하는 것이 최선책이다.
이 메서드는 ES 5부터 언어에 포함되었고 구형 자바스크립트 엔진에서는 JSON.org의 라이브러리 (http://www.json.org/json2.js)를 쓸 수 있다.

//입력되는 JSON문자열
var jstr='{"mykey": "my value"}';

//안티패턴
var data=eval('(' +jstr+ ')');

//권장안
var data=JSON.parse(jstr);
console.log( data.mykey );// "my value"

리터럴 사용 정리표

 내장 생성자( 사용 자제 )

  리터럴과 원시 데이터타입( 권장안 )
 var o=new Object();     var o={}
 var a=new Array();    var a=[];
 var re=new RegExp();   var re=/[a-z]/g;
 var s=new String(); var s=""
 var n=new Number(); var n=0;
 var b=new Boolean();  var b=false;
 throw new Error("stop");

 throw{  name:"Error", message:"stop"};  
또는 throw Error("stop" );


728x90
반응형
댓글