본문으로 건너뛰기

JS 학습노트 2_객체지향

무료2015-04-06#JS#js面向对象

JS 의 OOP 는 Java 등과 크게 다르며, 그 핵심은 prototype 입니다. 본고에서는 JS 의 상속과 OOP 의 기타 부분에 대해 상세히 분석합니다

##1.객체의 정의##

ECMAScript 에서 객체는 순서 없는 프로퍼티의 집합이며, 여기서의 "프로퍼티"란 기본값, 객체 또는 함수일 수 있습니다

##2.데이터 프로퍼티와 접근자 프로퍼티##

  • 데이터 프로퍼티란 값을 가진 프로퍼티로, 프로퍼티를 읽기 전용, 삭제 불가, 열거 불가 등으로 설정할 수 있습니다

  • 접근자 프로퍼티는 getter 와 setter 를 설정하는 데 사용되며, 프로퍼티 이름 앞에"_"(밑줄) 를 붙여 해당 프로퍼티는 접근자를 통해서만 액세스 가능함 (프라이빗 프로퍼티) 을 나타내지만, 밑줄을 붙였다고 해서 프로퍼티가 프라이빗이 되는 것은 아닙니다. 이는단순히 관습적인 명명 방식 중 하나일 뿐입니다. 접근자 프로퍼티는 별로 쓸모가 없습니다. 이유는 다음과 같습니다:

     var book={
       _year:2004,
       edition:1
     }
     Object.defineProperty(book,"year",{
            get:function(){
               return this._year;
            },
            set:function(newValue){
               if(newValue>2004){
                 this._year=newValue;
                 this.edition+=newValue-2004;
               }
            }
     });
     book.year=2005;
     alert(book.edition);
     /*
     for(var attr in book){
       showLine(attr + ' = ' + book[attr]);
     }
     */
     
    

高程에서는 위의 샘플 코드를 사용했으며, 원리는 book 객체의 프로퍼티 중_year 는 데이터 프로퍼티이고 year 는 접근자 프로퍼티이며, getter 와 setter 를 이용해 읽기 쓰기 제어를 삽입할 수 있다는 것으로, 듣기에는 좋습니다.

하지만 문제는_year 와 edition 은 모두 열거 가능하여, 즉 for...in 루프로 볼 수 있지만, 접근자 프로퍼티 year 는 열거 불가합니다. 공개해야 할 접근자는 공개되지 않고, 숨겨야 할 프라이빗 프로퍼티가 공개되어 버립니다.

그 외에도, 이렇게 접근자를 정의하는 방식은 전체 브라우저 호환이 아니며, [IE9+] 에서만 완전히 지원됩니다. 물론, 구형 브라우저용 방법 (__defineGetter__() 과__defineSetter__()) 도 있지만, 상당히 번거롭습니다.

总之、접근자 프로퍼티는 별로 쓸모가 없습니다.

##3.생성자##

function Fun(){} var fun = new Fun();여기서 Fun 은 생성자이며, 일반 함수와 선언 방식의 차이는 전혀 없고, 호출 방식만 다를 뿐입니다 (new 연산자로 호출)

생성자는 커스텀 타입을 정의하는 데 사용할 수 있습니다. 예를 들어:

function MyClass(){
  this.attr = value;//멤버 변수
  this.fun = function(){...}//멤버 함수
}

Java 의 클래스 선언과 다소 비슷하지만, 몇 가지 차이점도 있습니다. 예를 들어 this.fun 은 단지 함수 포인터일 뿐이므로, 완전히 다른 액세스 가능한 함수 (글로벌 함수 등) 를 가리키도록 할 수 있지만, 이렇게 하면 커스텀 객체의 캡슐성을 파괴합니다

##4.함수와 프로토타입 prototype##

  1. 함수를 선언함과 동시에 프로토타입 객체가 생성되며, 함수명이 해당 프로토타입 객체에 대한 참조를保持합니다 (fun.prototype)

  2. 프로토타입 객체에 프로퍼티를 추가할 수도 있고, 인스턴스 객체에 프로퍼티를 추가할 수도 있습니다. 차이는 프로토타입 객체의 프로퍼티는 해당 타입의 모든 인스턴스가 공유하는 반면, 인스턴스 객체의 프로퍼티는 비공유라는 점입니다

  3. 인스턴스 객체의 프로퍼티에 액세스할 때, 먼저 해당 인스턴스 객체의 스코프 내에서 검색하고, 찾지 못한 경우에만 프로토타입 객체의 스코프 내에서 검색하므로, 인스턴스의 프로퍼티는 프로토타입 객체의 동명 프로퍼티를쉐도우할 수 있습니다

  4. 프로토타입 객체의 constructor 프로퍼티는 함수 포인터이며, 함수 선언을 가리킵니다

  5. 프로토타입을 통해 네이티브 참조 타입 (Object, Array, String 등) 에 커스텀 메서드를 추가할 수 있습니다. 예를 들어 String 에 Chrome 에서는 지원되지 않지만 FF 에서는 지원되는 startsWith 메서드를 추가:

    var str = 'this is script';
    //alert(str.startsWith('this'));//Chrome 에서 에러
    String.prototype.startsWith = function(strTarget){
      return this.indexOf(strTarget) === 0;
    }
    alert(str.startsWith('this'));//에러가 나지 않음
    

주의: 네이티브 객체에 프로토타입 프로퍼티를 추가하는 것은 권장되지 않습니다. 이렇게 하면 네이티브 메서드를 의도치 않게 재작성하여 다른 네이티브 코드 (해당 메서드를 호출하는 네이티브 코드) 에 영향을 줄 수 있기 때문입니다

  1. 프로토타입을 통해 상속을 구현할 수 있습니다. 아이디어는 서브클래스의 prototype 프로퍼티가 슈퍼클래스의 인스턴스를 가리키도록 하여 서브클래스가 액세스 가능한 프로퍼티를 늘리는 것입니다. 따라서 프로토타입 체인으로 연결한 후

    서브클래스가 액세스 가능한 프로퍼티
    = 서브클래스 인스턴스 프로퍼티 + 서브클래스 프로토타입 프로퍼티
    = 서브클래스 인스턴스 프로퍼티 + 슈퍼클래스 인스턴스 프로퍼티 + 슈퍼클래스 프로토타입 프로퍼티
    = 서브클래스 인스턴스 프로퍼티 + 슈퍼클래스 인스턴스 프로퍼티 + 슈퍼슈퍼클래스 인스턴스 프로퍼티 + 슈퍼슈퍼클래스 프로토타입 프로퍼티
    = ...
    

최종적으로 슈퍼슈퍼...슈퍼클래스 프로토타입 프로퍼티는 Object.prototype 이 가리키는 프로토타입 객체의 프로퍼티로 대체됩니다

구체적인 구현은 SubType.prototype = new SuperType();이며, 이를 심플 프로토타입 체인 방식의 상속이라고 할 수 있습니다

##5.커스텀 타입을 생성하는 최선의 방법 (생성자 패턴 + 프로토타입 패턴)##

인스턴스 프로퍼티는 생성자로 선언하고, 공유 프로퍼티는 프로토타입으로 선언합니다. 구체적인 구현은 다음과 같습니다:

    function MyObject(){
      //인스턴스 프로퍼티
      this.attr = value;
      this.arr = [value1, value2...];
    }
    MyObject.prototype = {
      constructor: MyObject,//서브클래스가 올바른 생성자 참조를保持하도록 보장。否则 서브클래스 인스턴스의 constructor 는 Object 의 생성자를 가리키게 됩니다. 프로토타입을 익명 객체로 변경했기 때문
      //공유 프로퍼티
      fun: function(){...}
    }
    

##6.상속을 구현하는 최선의 방법 (패러시틱 콤비네이션 상속)##

function object(obj){//프로토타입이 obj 이고 인스턴스 프로퍼티가 없는 객체를 반환
  function Fun(){}
  Fun.prototype = obj;
  return new Fun();
}
function inheritPrototype(subType, superType){
  var prototype = object(superType.prototype);//프로토타입 체인 구축, 슈퍼클래스 프로토타입 프로퍼티 상속. 커스텀 함수 object 로 처리하는 이유는 서브클래스 프로토타입이 되는 슈퍼클래스 인스턴스가 인스턴스 프로퍼티를 가지지 않도록 하기 위함. 간단히 말해,余分한 인스턴스 프로퍼티를 잘라내기 위함. 콤비네이션 상속과 비교하여 이해할 수 있음
  prototype.constructor = subType;//생성자가 올바르도록 보장. 프로토타입 체인은 서브클래스가 保持하는 생성자 참조를 변경하므로, 프로토타입 체인 구축 후 다시 되돌려야 함
  subType.prototype = prototype;
}

function SubType(arg1, arg2...){
  SuperType.call(this, arg1, arg2...);//슈퍼클래스 인스턴스 프로퍼티 상속
  this.attr = value;//서브클래스 인스턴스 프로퍼티
}
inheritPrototype(SubType, SuperType);

구체적인 설명은 코드 주석 참조. 이 방식은 SubType.prototype = new SuperType(); 심플 프로토타입 체인의 단점을 회피합니다:

  • 서브클래스 인스턴스가 슈퍼클래스 인스턴스 참조 프로퍼티를 공유하는 문제 (프로토타입 참조 프로퍼티는 모든 인스턴스가 공유하므로, 프로토타입 체인을 구축하면 슈퍼클래스의 인스턴스 프로퍼티가 자연스럽게 서브클래스의 프로토타입 프로퍼티가 됨).

  • 서브클래스 인스턴스 생성 시 슈퍼클래스 생성자에 파라미터를 전달할 수 없음

##7.상속을 구현하는 가장 일반적인 방식 (콤비네이션 상속)##

위의 inheritPrototype(SubType, SuperType);문을 다음과 같이 교체:

SubType.prototype = new SuperType();
SubType.prototype.constructor = SubType;

이 방식의 단점은: 슈퍼클래스의 컨스트럭트 메서드를 2 번 호출하므로, 余分한슈퍼클래스 인스턴스 프로퍼티가 존재한다는 점입니다. 구체적인 이유는 다음과 같습니다:

첫 번째 SuperType.call(this);문에서 슈퍼클래스에서 슈퍼클래스 인스턴스 프로퍼티를 한 부 복사하여 서브클래스의 인스턴스 프로퍼티로 하고, 두 번째 SubType.prototype = new SuperType();에서 슈퍼클래스 인스턴스를 생성하여 서브클래스 프로토타입으로 합니다.此时이 슈퍼클래스 인스턴스에는 또 하나의 인스턴스 프로퍼티가 있지만, 이는 첫 번째에 복사된 인스턴스 프로퍼티에 의해 쉐도우되어 버리므로, 余分합니다.

댓글

아직 댓글이 없습니다

댓글 작성