본문 바로가기
개발 관련/javascript

6. 객체 지향 프로그래밍

by lazysnack 2022. 7. 13.

다루는 내용

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

1. 객체 프로퍼티의 이해

  • 객체는 특별한 순서가 없는 값의 배열 (key-value 쌍의 그룹)

  • 가장 단순한 방법은

    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)
    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)
    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. 객체의 이해와 생성

  • 팩토리 패턴 : 특정 객체를 생성하는 과정을 추상화 하는 것

    function createPerson(name) {
        var o = new Object();
        o.name = name;
        return o;
    }
    
    var person1 = createPerson('snack');
    var person2 = createPerson('gray');
    
    // 코드의 중복은 해결했으나, 객체가 어떤 타입인지는 알 수 없음
  • 생성자 패턴

    function Person(name) {
        this.name = name;
    }
    
    var person1 = new Person('snack');
    var person2 = new Person('gray');
  • 생성자 패턴의 주요 문제는 인스턴스마다 메소드가 생성된다는 점 (함수 이름이 같아도 인스턴스가 다르면 다른 함수로 취급)

  • 프로토 타입 패턴

    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
  • 프로토타입 프로퍼티와 같은 이름의 프로퍼티를 인스턴스에 추가하면 인스턴스에 추가될 뿐 프로토타입까지 올라가지 않음

    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() 를 통해 인스턴스의 프로퍼티인지 프로토타입의 프로퍼티인지 확인 가능

  • 프로토 타입은 리터럴로 생성이 가능

    function Persion() {
    }
    
    Person.prototype = {
        // constructor : Person - 생성자 타입이 중요하다면 명시적으로 설정 가능
        name : "snack",
        sayName: function() {
            console.log(this.name);
        }
    };
  • 인스턴스는 프로토타입을 가리키는 포인터를 가질 뿐 생성자와 연결된 것은 아니기에, 프로토타입을 다른 객체로 바꾸면 연결이 깨짐

  • 네이티브 타입(Object, Array 등)의 메서드 역시 생성자의 프로토타입에 정의 (ex:Array.prototype.sort)

  • 프로토 타입에 존재하는 프로퍼티는 모두 인스턴스에 공유되는데, 참조 값을 포함하게 될 경우 문제가 생김

    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"
  • 생성자와 프로토타입 패턴의 장점만 취한 패턴

    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 생성자 패턴이 있음(캡슐화?)

    function Person(name) {
        var o = new Object();
        o.sayName = function() {
            console.log(name);
        };
        return o;
    }

3. 상속의 이해

  • 함수 시그너쳐가 없으므로 인터페이스 상속은 불가능하고, 구현 상속만 지원 (대게 프로토타입 체인)

  • 프로토 타입 체인

    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 를 상속

  • 프로토타입 체인 상속 역시 참조값을 포함한 경우 문제가 될 수 있음

    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'(생성자 훔치기) 를 사용

    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
  • 생성자 훔치기의 경우, 메소드를 생성자 내부에서만 정의해야 하므로 함수 재사용이 불가능. 또한 상위 타입에서 정의된 메소드는 하위 타입에서 접근 불가

  • 프로토 타입 체인과 생성자 훔치기 패턴을 조합한 조합 상속이 있음

    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
  • 프로토 타입 상속

    var person = {
        name: "snack"
    };
    
    var anotherPerson = object(person);
    anotherPerson.name = "gray"
    
    // EcmaScript 5
    var anotherPerson2 = Object.create(person);
    anthoerPerson2.name = "chees";
  • 기생 상속

    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
  • 조합상속은 자주 쓰이는 상속 패턴이지만, 상위 타입 생성자가 항상 두번 호출된다는 비효율적인 면도 있음

  • 기생 상속 조합은 생성자 훔치기를 통해 프로퍼티 상속을 구현하지만, 메소드 상속에는 프로토타입 체인을 혼용

    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);
    };

'개발 관련 > javascript' 카테고리의 다른 글

8. 브라우저 객체 모델  (0) 2022.07.13
7. 함수 표현식  (0) 2022.07.13
5. 참조 타입  (0) 2022.07.13
4. 변수, 스코프, 메모리  (0) 2022.07.13
3. 언어의 기초  (0) 2022.07.13