6. 객체 지향 프로그래밍

· ☕ 4 min read · 👀... views

다루는 내용

  1. 객체 프로퍼티의 이해
  2. 객체의 이해와 생성
  3. 상속의 이해

1. 객체 프로퍼티의 이해

  • 객체는 특별한 순서가 없는 값의 배열 (key-value 쌍의 그룹)
  • 가장 단순한 방법은
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    
    var person = new Object();
    person.name = "snack";
    person.sayName = function() {
        console.log(this.name)
    };
    
    var person = {
        name : 'snack',
        sayName: function() {
            console.log(this.name);
        }
    };
    
  • 프로퍼티에는 데이터 프로퍼티와 접근자 프로퍼티 두 가지가 있음.
  • 데이터 프로퍼티에는
    1. [[Configurable]] - 프로퍼티의 속성을 바꾸거나 접근자 프로퍼티로 변환할 수 있음을 의미 (default : true)
    2. [[Enumerable]] - for-in 루프에서 해당 프로퍼티를 반환함을 나타냄 (default : true)
    3. [[Writable]] - 프로퍼티의 값을 바꿀 수 있음을 나타냄 (default : true)
    4. [[Value]] - 프로퍼티의 실제 데이터 값을 포함 (default : undefined)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    
    var person = {};
    Object.defineProperty(person, "name", {
        writable: false,
        value: "snack"
    });
    
    console.log(person.name); // snack
    person.name = "gray";
    console.log(person.name); // snack
    
  • 접근자 프로퍼티는
    1. [[Configurable]] - 프로퍼티의 속성을 바꾸거나 데이터 프로퍼티로 변환할 수 있음
    2. [[Enumerable]] - for-in 루프에서 해당 프로퍼티를 반환함을 나타냄
    3. [[Get]] - 프로퍼티를 읽을 때 호출 (default : undefined)
    4. [[Set]] - 프로퍼티를 바꿀 때 호출 (default : undefined)
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    
    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 = this.edition + newValue - 2004;
            }
        }
    });
    
    book.year = 2005;
    console.log(book.edition); // 2
    
  • 다중 프로퍼티를 사용할 경우에는 Object.defineProperties() 를 사용
  • 각 프로퍼티의 속성값을 알고 싶은 경우 Object.getOwnPropertyDescription() 을 사용

2. 객체의 이해와 생성

  • 팩토리 패턴 : 특정 객체를 생성하는 과정을 추상화 하는 것
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    
    function createPerson(name) {
        var o = new Object();
        o.name = name;
        return o;
    }
    
    var person1 = createPerson('snack');
    var person2 = createPerson('gray');
    
    // 코드의 중복은 해결했으나, 객체가 어떤 타입인지는 알 수 없음
    
  • 생성자 패턴
    1
    2
    3
    4
    5
    6
    
    function Person(name) {
        this.name = name;
    }
    
    var person1 = new Person('snack');
    var person2 = new Person('gray');
    
  • 생성자 패턴의 주요 문제는 인스턴스마다 메소드가 생성된다는 점 (함수 이름이 같아도 인스턴스가 다르면 다른 함수로 취급)
  • 프로토 타입 패턴
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    
    function Person() {
    }
    
    Person.prototype.name = "snack";
    Person.prototype.sayName = function() {
        console.log(this.name);
    }
    
    var person1 = new Person();
    person1.sayName(); // snack
    
    var person2 = new Person();
    person2.sayName(); // snack
    
    console.log(person1.sayName == person2.sayName); // true
    
  • 프로토타입 프로퍼티와 같은 이름의 프로퍼티를 인스턴스에 추가하면 인스턴스에 추가될 뿐 프로토타입까지 올라가지 않음
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    
    function Person() {
    }
    
    Person.prototype.name = "snack";
    Person.prototype.sayName = function() {
        console.log(this.name);
    }
    
    var person1 = new Person();
    var person2 = new Person();
        
    person.name = "gray";
    console.log(person1.name);  // gray - 인스턴스
    console.log(person2.name);  // snack - 프로토타입
    
  • hasOwnProperty() 를 통해 인스턴스의 프로퍼티인지 프로토타입의 프로퍼티인지 확인 가능
  • 프로토 타입은 리터럴로 생성이 가능
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    
    function Persion() {
    }
    
    Person.prototype = {
        // constructor : Person - 생성자 타입이 중요하다면 명시적으로 설정 가능
        name : "snack",
        sayName: function() {
            console.log(this.name);
        }
    };
    
  • 인스턴스는 프로토타입을 가리키는 포인터를 가질 뿐 생성자와 연결된 것은 아니기에, 프로토타입을 다른 객체로 바꾸면 연결이 깨짐
  • 네이티브 타입(Object, Array 등)의 메서드 역시 생성자의 프로토타입에 정의 (ex:Array.prototype.sort)
  • 프로토 타입에 존재하는 프로퍼티는 모두 인스턴스에 공유되는데, 참조 값을 포함하게 될 경우 문제가 생김
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    
    function Person() {
    
    }
    
    Person.prototype = {
        constrouctor : Person,
        name: "snack",
        friends : ["gray", "berry"]
    }
    
    var person1 = new Person();
    var person2 = new Person();
    
    person1.friends.push("cheese");
    
    console.log(person1.friends); // "gray,berry,cheese"
    console.log(person2.friends); // "gray,berry,cheese"
    
  • 생성자와 프로토타입 패턴의 장점만 취한 패턴
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    
    function Person(name) {
        this.name = name;
        thus.friends = ["cheese", "berry"]
    }
    
    Person.prototype = {
        constructor: Person,
        sayName: function() {
            console.log(this.name);
        }
    }
    
    var person1 = new Person("snack");
    var person2 = new Person("muz");
    
    person1.friends.push("cake");
    
    console.log(person1.friends); // cheese, berry, cake
    console.log(person2.friends); // cheese, berry
    
  • 프로퍼티에 직접 접근할 수 없게 하는 durable 생성자 패턴이 있음(캡슐화?)
    1
    2
    3
    4
    5
    6
    7
    
    function Person(name) {
        var o = new Object();
        o.sayName = function() {
            console.log(name);
        };
        return o;
    }
    

3. 상속의 이해

  • 함수 시그너쳐가 없으므로 인터페이스 상속은 불가능하고, 구현 상속만 지원 (대게 프로토타입 체인)
  • 프로토 타입 체인
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    
    function SuperType() {
        this.property = true;
    }
    SuperType.prototype.getSuperValue = function() {
        return this.property;
    };
    function SubType() {
        this.subproperty = false;
    }
    
    //SuperType 상속
    SubType.prototype = new SuperType();
    SubType.prototype.getSubValue = function() {
        return this.subproperty;
    };
    
    var instance = new SubType();
    console.log(instance.getSuperValue()); // true
    
  • 모든 참조 타입은 기본적으로 프로토 타입 체인을 통해 Object 를 상속
  • 프로토타입 체인 상속 역시 참조값을 포함한 경우 문제가 될 수 있음
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    
    function SuperType() {
        this.colors = ["red","green","blue"];
    }
    
    function SubType() {
    
    }
    
    SubType.prototype = new SuperType();
    
    var instance1 = new SubType();
    instance1.color.push("black");
    console.log(instance1.colors); // red, green, blue, black
    
    var instance2 = new SubType;
    console.log(instance2.colors); // red, green, blue, black
    
  • 참조 값에 얽힌 문제를 해결하고자 ‘constructor stealing’(생성자 훔치기) 를 사용
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    
    function SuperType() {
        this.colors = ["red","green"];
    }
    
    function SubType() {
        SuperType.call(this);
    }
    
    var instance1 = new SubType();
    instance1.colors.push("black");
    
    console.log(instance1.colors); // red, green, black
    
    var instance2 = new SubType();
    console.log(instance2.colors); // red, green
    
  • 생성자 훔치기의 경우, 메소드를 생성자 내부에서만 정의해야 하므로 함수 재사용이 불가능. 또한 상위 타입에서 정의된 메소드는 하위 타입에서 접근 불가
  • 프로토 타입 체인과 생성자 훔치기 패턴을 조합한 조합 상속이 있음
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    
    function SuperType(name) {
        this.name = name;
        this.colors = ["red", "green"];
    }
    
    SuperType.prototype.sayName = function() {
        console.log(this.name);
    }
    
    function SubType(name, age) {
        SuperType.call(this, name);
        this.age = age;
    }
    
    SubType.prototype = new SuperType();
    
    SubType.prototype.sayAge = function() {
        console.log(this.age);
    }
    
    var instance1 = new SubType("snack", 29);
    instance1.colors.push("black");
    console.log(instance1.colors); // red, green, black
    instance1.sayName(); // snack
    instance1.sayAge(); // 29
    
    var instance2 = new  SubType("gray", 28);
    console.log(instance2.colors); // red, green
    instance2.sayName(); // gray
    instance2.sayAge(); // 28
    
  • 프로토 타입 상속
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    
    var person = {
        name: "snack"
    };
    
    var anotherPerson = object(person);
    anotherPerson.name = "gray"
    
    // EcmaScript 5
    var anotherPerson2 = Object.create(person);
    anthoerPerson2.name = "chees";
    
  • 기생 상속
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    
    function createAnother(original) {
        var clone = object(original);
        clone.sayHi = function() {
            console.log("hi");
        };
        return clone;
    }
    
    var person = {
        name : "snack"
    };
    
    var anotherPerson = createAnother(person);
    anotherPerson.sayHi(); // hi
    
  • 조합상속은 자주 쓰이는 상속 패턴이지만, 상위 타입 생성자가 항상 두번 호출된다는 비효율적인 면도 있음
  • 기생 상속 조합은 생성자 훔치기를 통해 프로퍼티 상속을 구현하지만, 메소드 상속에는 프로토타입 체인을 혼용
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    
    function inheritPrototype(subType, superType) {
        var prototype = object(superType.prototype); // 객체 생성
        prototype.constructor = subType;  // 객체 확장 
        subType.prototype = prototype;  // 객체 할당
    }
    
    function SuperType(name) {
        this.name = name;
        this.colors = ["red", "green"];
    }
    
    SuperType.prototype.sayName = function() {
        console.log(this.name);
    };
    
    function SubType(name, age) {
        SuperType.call(this, name);
        this.age = age;
    }
    
    inheritPrototype(SubType, SuperType);
    
    SubType.prototype.sayAge = function() {
        console.log(this.age);
    };
    
Share on

snack
WRITTEN BY
snack
Web Programmer


What's on this Page