다루는 내용
- 객체 프로퍼티의 이해
- 객체의 이해와 생성
- 상속의 이해
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); } };
- 프로퍼티에는 데이터 프로퍼티와 접근자 프로퍼티 두 가지가 있음.
- 데이터 프로퍼티에는
- [[Configurable]] - 프로퍼티의 속성을 바꾸거나 접근자 프로퍼티로 변환할 수 있음을 의미 (default : true)
- [[Enumerable]] - for-in 루프에서 해당 프로퍼티를 반환함을 나타냄 (default : true)
- [[Writable]] - 프로퍼티의 값을 바꿀 수 있음을 나타냄 (default : true)
- [[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
- 접근자 프로퍼티는
- [[Configurable]] - 프로퍼티의 속성을 바꾸거나 데이터 프로퍼티로 변환할 수 있음
- [[Enumerable]] - for-in 루프에서 해당 프로퍼티를 반환함을 나타냄
- [[Get]] - 프로퍼티를 읽을 때 호출 (default : undefined)
- [[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); };