저를 너무 괴롭혔다 챕터입니다.하나의 챕터를 완전히 이해하고 볼것이 거의 2~3주일 걸리셨어요.개념적 이해하지 못하거나 같은 말을 다르게 사용하는 등 혼란시킬 여지가 많아 더 혼란한 챕터입니다.우선 JS는 ES6에서 클래스가 도입되고 원래 함수 기반 언어입니다.그리고 이 학급은 “모델 패턴”을 이용하고 클래스와 함께 승계를 실현하고 있다는 점입니다.프로토 타입 패턴이 있다는 것만 알고 있어 실제로 적용된 코드를 경험 안 해서 몰랐지만, JS에서는 이를 바탕으로 구현되어 있습니다.그러면 기존의 C++는 어떻게 다른지 대충 봅시다.상속과 프로토 타입
기본적으로 상속으로 만들어진 클래스는 일반적으로 각 클래스의 인스턴스마다 함수에 대한 메모리 정보를 갖게 됩니다. 그런데 효율적으로 단순하게 생각해봤을 때 같은 정보를 갖게 되는 형태이기 때문에 완전히 Low로 가면 비효율적인 면이 없다고 할 수 있습니다. JS에서는, 이것들을 재활용하기 위한 방법으로서 선택한 것이 「프로토타입」입니다.

일반적인 언어에서의 생성자에 의한 생성 이후의 메모리 상태
일반적인 언어는 붕어빵 틀(가장 많이 쓰는 표현이니까…)… 동일한 선언부가 있으며 인스턴스를 생성하면 해당 선언부에 맞게 실제 메모리 할당을 한 후 변수 이름을 Symbol Table에 등록하도록 처리됩니다. 이때 생성자는 부모가 있을 경우 부모의 생성자를 먼저 호출하여 나머지 부분을 처리하게 되어 있습니다. 어떻게 보면 상당히 비효율적으로 보일 수 있습니다.

Js는 그 함수 객체나 반(ES6)이 사용하는 공통의 내용을 prototype라는 객체에 저장하고 이를 참조하고 사용하고 있습니다.이는 일반적인 언어가 가진 중복된 데이터를 생성하지 않기로 효율성이 높아지지만 동시에 복잡성이 증가한 게 아닌가 생각됩니다.어쨌든 모든 객체는__proto__라는 접근을 제공하고 있습니다.그러나 constructor속성을 갖는 함수 객체는 승계를 위해서 보다 많은 것에 접근할 수 있도록 prototype을 노출시키는 형태로 보입니다.그러므로 Circle에서 일단 함수 객체를 만들고 이에 new를 사용함으로써 생성자 함수처럼 작동하게 되는 형태입니다.즉(함수)객체 명세 중에 함수 선언부를 둔 형태로 두면(객체 속에 객체나 방법 등을 가질 수 있다)실제로는 클래스처럼 쓸 수 있습니다.제 장점은 잃지 않고 같이 클래스를 구현한 형태라고 생각합니다.
무엇이든 개체가 생성되면 일단”객체”이니 마땅히__proto__을 가지게 되고 이로 인한 간접적으로 프로토 타입에 접속할 수 있습니다.그러나 이 프로토 타입은 결과적으로 함수 객체의 프로토 타입이어서 constructor속성을 갖게 되고 이는 생성자 함수 객체를 참조하게 됩니다.그리고 생성자 함수도 객체이다, 이는 함수 객체라 prototype이라는 속성을 갖게 되면서 당연히 전술의 프로토 타입을 참조할 약간의 순환 참조의 같은 형태로 됩니다.

함수 객체의 경우, 프로토 타입이 노출된 것은 전의 방법 등을 공유하기 위해서 필요합니다.여러개를 만들지 않고 공용 객체로서의 역할을 하는 프로토 타입에 추가하여 해당 오브젝트를 사용하거나 상속한 객체가 이를 함께 공유할 수 있습니다.왜냐하면 프로토 타입도 결국 객체이기 때문에 속성을 동적으로 추가, 삭제할 수 있습니다.그러나 이를 통해서 만들어진 인스턴스가 이에 직접 언급할 수 없도록 막았는데, 그게__proto__입니다.
이렇게 복잡하게 만들어 놓은 이유는 간단합니다.일반적인 상속의 경우 다이아몬드 상속 등의 다중 상속이 문제가 되었고 각종 문제를 일으킬 가능성이 있습니다.그래서 최근의 언어는 단일 상속, 그리고 멀티 interface등을 통해서 처리하도록 되어 있습니다.생성자 함수를 통한 프로토 타입에 접근할 경우, 하고 싶은 것이 나름대로 뚜렷하지만, 인스턴스를 통해서 쉽게 조정할 수 있다고 코드의 가독성을 보증하기 어렵습니다.기타 상호 참조 등 여러 문제를 사전에 막기 위해서 제한을 둔 것으로 보면 좋겠습니다.__proto__, prototype

모든 개체가 가지고 있는 (정확하게는 Object.prototype에서 상속됨) __proto__액세스자 속성과 함수 객체만을 가지고 있는 prototype 속성은 동일한 프로토타입을 참조하지만 약간의 차이가 있습니다.
구분소유값 사용주체 사용목적 __proto__접근프로퍼티 모든 객체 프로토타입 참조 모든 객체가 자신의 프로토타입에 접근하거나 값을 변경하기 위해 사용 prototype 속성 constructor 프로토타입 참조 생성자 함수 생성자 함수가 자신이 생성하는 객체(인스턴트)의 프로토타입을 할당하기 위해 사용

구분소유값 사용주체 사용목적 __proto__접근프로퍼티 모든 객체 프로토타입 참조 모든 객체가 자신의 프로토타입에 접근하거나 값을 변경하기 위해 사용 prototype 속성 constructor 프로토타입 참조 생성자 함수 생성자 함수가 자신이 생성하는 객체(인스턴트)의 프로토타입을 할당하기 위해 사용
결과적으로 보면 객체가 가지고 있는 __proto______ 생성자 함수의 prototype 속성은 결국 실제로 Person.prototype이라고 하는 객체를 참조하고 있는 형태입니다.

ECMAScript에서의 Object 생성자에 대한 의사 코드입니다. 생성자 함수에 값을 전달하지 않거나 undefined 또는 null을 전달하면서 호출하면 결과적으로 추상 연산 Abstract Operation Ordinary Object Create를 호출하면서 Object.prototype을 프로토타입으로 가진 빈 객체가 생성됩니다.
//2.Object생성자 함수로 객체 생성//인수가 전달되지 않은 때, 추상 연산 OrdinaryObjectCreate를 호출하고 하늘의 객체를 생성한다.letobj=new Object()console.log(obj)//{}//1.new.target이 undefined와 Object가 아닌 경우//인스턴스 → Foo.prototype→ Object.prototype순으로 프로토 타입 체인이 생성되는 class Fooextends Object{}new Foo//Foo{}//3. 인수가 전달된 경우에는 인수를 객체로 변환한다.////Number객체생성 obj=new Object(123)console.log(obj)//Number{123}//String객체생성 obj=new Object(‘123’)console.log(obj)//String<‘123’}
// 2. Object 생성자 함수에 의한 객체 생성/인수가 전달되지 않았을 때 추상 연산 Ordinary Object Create를 호출하여 빈 객체를 생성한다.letobj = new Object ()console.log(obj) // {}// 1.new.target이 undefined나 Object가 아닌 경우 // 인스턴스 → Foo.prototype → Object.prototype 순으로 프로토타입 체인이 생성되는 class Fooextends Object {}new Foo() // Foo{}/ 3. 인수가 전달된 경우 인수를 객체로 변환한다.// // // Number 객체 생성obj = new Object(123)console.log(obj) // Number {123}// String 객체 생성obj = new Object(‘123’)console.log(obj) // String {‘123’}결과적으로 Object 생성자 함수 호출과 객체 리터럴은 추상 연산인 Ordinary Object Create를 호출하여 빈 객체를 생성한다는 점에서는 동일하지만 new.target 확인이나 속성을 추가하는 상세한 처리 내용은 조금 다릅니다.리터럴 표기법생성자 함수 프로토타입 객체 리터럴Object.prototype함수 리터럴 Function.prototype 배열 리터럴 Array.prototype 정규 표현식 리터럴 RegExpRegExp.prototype리터럴 표기법생성자 함수 프로토타입 객체 리터럴Object.prototype함수 리터럴 Function.prototype 배열 리터럴 Array.prototype 정규 표현식 리터럴 RegExpRegExp.prototype프로토타입 생성 시점결과적으로 모든 함수 객체는 생성자 함수와 자연스럽게 연결되게 되어 있습니다. 그럼 프로토타입은 도대체 언제 자동으로 생성되는지 그 타이밍이 궁금합니다. 책에서는 프로토타입은 생성자 함수가 생성될 때 함께 생성된다고 언급하고 있습니다.Person은 함수 선언문으로, 가장 먼저 함수 객체로 평가가 이루어진 후 log를 누르는 코드가 실행됩니다.(함수호이스팅이 일어나서 코드 실행 전에 미리 평가됨) 결과적으로 미리 함수 객체가 생긴 후 prototype을 눌러보면 결과값이 나온다는 것은 생성자 함수가 생겼을 때 같이 발생한다고 봐도 무방할 것입니다.생성된 프로토타입은 constructor 속성만을 가진 객체입니다. 앞서 말했듯이 프로토타입도 객체이기 때문에 프로토타입 프로토타입도 있고 항상 최상위 프로토타입은 결국 Object.prototype이 됩니다. 객체 생성 방식과 프로토타입 결정아까 말씀드린 것처럼 오브젝트 리터럴로 생성을 하더라도 결과적으로 Ordinary Object Create를 통해서 호출이 됩니다. 이때 인자로 전달되는 프로토타입은 Object.prototype이 기본값입니다. 그렇기 때문에 객체 리터럴에서 생성한 객체 프로토타입은 결국 Object.prototype이 될 수밖에 없습니다.이와 달리 Object 생성자 함수를 이용하여 객체를 생성하는 경우에는 추상 연산인 Ordinary Object Create가 호출됩니다.이때도 자동으로 Object.prototype이 묵시적으로 인자로 전달됩니다. 결과적으로 동일하게 생성된 Object의 프로토타입은 동일합니다.만약 생성자 함수를 통해 객체를 생성할 경우 Object.prototype이 없다면 결국 Object와 똑같은 형태로 생성됩니다. 물론 Person.prototype 프로토타입은 당연히 상위 객체의 프로토타입인 Object.prototype이 됩니다.functionPerson(name){this.name=name}//[Person.prototype.sayHello=함수]{console.log(`안녕하세요!제 이름은${this.name}`입니다)}consteme= 새로운 사람(‘Lee’)const you=newPerson(‘Kim’)me.SayHello///안녕하세요!제 이름은 이유ー입니다.안녕하세요//안녕하세요 김이라고 합니다functionPerson(name) {this.name = name}/ [Person.prototype.sayHello= 함수] {console.log(`안녕하세요! 제 이름은 ${this.name }`입니다)}consteme=새로운 사람(‘Lee’)const you=newPerson(‘Kim’) me.SayHello()////안녕하세요! 제 이름은 이유입니다.안녕하세요( ) //안녕하세요! 김이라고 합니다.아까 말씀 드렸지만 prototype도 결과적으로 “객체”이며 이에 속성을 추가할 수 있습니다.상기의 코드처럼 Person.prototype에 함수 객체이다 sayHello를 추가했습니다.그리고, you, me을 만들면 결과적으로 Person.prototype에 의해서 생성된 인스턴스인 것으로 모두 같은 sayHello를 공유할 수 있게 되는 형태입니다.가장 먼저 본 C++의 상속과는 조금 다른 방식으로 접근하고 있어요.다만__proto__을 사용하는 바가 많아지면서 ES6에서 표준으로서 포함되지만, 사용을 권장할 수는 없습니다.직접 상속 같은 독특한 경우에는 프로토 타입이 undefined일 가능성이 있습니다.//obj는 프로토 타입 체인의 종점이다.그러므로 Object.__proto__을 상속할 수 없습니다.constobj=Object.create(null)//obj는 Object.__proto__을 상속할 수 없었습니다.console.log(obj.__proto__)//undefined// 싶어__proto__의 Object.getPrototypeOf를 사용하는 것이 좋다.console.log(Object.getPrototypeOf(obj)//nullES6에서 함께 도입된 기능으로 set Prototype Of, get Prototype Of를 사용하는 것이 좋습니다. 프로토타입 체인functionPerson(name){this.name=name}Person.prototype.sayHello=function(){console.log(`Hi!제 이름은${this.name},)}constem=새 Person(‘Kim’)//hasOwnProperty는 Object.protype의 console.log(me.hasOwn’)//truefunctionPerson(name) {this.name = name}Person.prototype.sayHello = function() {console.log(`Hi! 私の名前は${this.name }`)}constem = 新しいPerson(‘Kim’)// hasOwnProperty는 Object.protype의 console.log(me.hasOwn’)) // truePerson인스턴스인 me이 hasOwnProperty를 호출하는 경우[Prototype]라는 내부 슬롯을 체인에 따라서 한 단계씩 올라가서 찾습니다.즉, Person.prototype을 먼저 찾는데 없어서 Object.prototype에 오르고 찾는 식입니다.이는 몇개를 상속 해도 한 형식을 따릅니다.이런 방법으로 다른 언어와는 조금 다른 상속을 실현하고 있습니다.오버 라이딩·앤드·속성·섀도잉const Person=(함수){//성이지않습니다. person.prototype.sayHello=함수){console.log(`안녕하세요!제 이름은${this.name},)}returnPerson;}();constem= 새로운 사람(‘Kim’)//소이지드 me.sayHello=함수{console.log(`안녕하세요!My name is${this.name},)}//인스턴스의메소드가 호출이된다. 즉, 프로토타입의메소드는 인스턴스의메소드에 의해가려짐. me.sayHello//Hello!제 이름은 김입니다const Person = (함수) {/ 성이지 않습니다.person.prototype.sayHello =関数) {console.log(`안녕하세요! 제 이름은 ${this.name }`)}returnPerson;}());constem=새로운 사람(‘Kim’)/ 소이지메.sayHello=함수{console.log(`안녕하세요! My name is ${this.name}`)}// 인스턴스의 메소드가 호출이 된다. 즉, 프로토타입의 메소드는 인스턴스의 메소드에 의해 가려짐.me.sayHello() // Hello! 제이름은 김입니다속성에 결과적으로 새로운 값을 올리게 됩니다.그러나 기존의 모델로 같은 값이므로, 프로토 타입 체인까지 소급 없이 본인의 객체 정보에서 해결할 수 있는 모습입니다.결과적으로 새로 할당함으로써 다른 언어가 가지고 있는 오버 라이딩 Overriding을 실현합니다.그리고 이처럼 숨겨진 속성이 발생합니다만, 이렇게 숨겨진 속성은 속성 섀도잉 PropertyShadowing이라고 부르신답니다.프로토 타입의 교환입니다const Person=(함수){function Person(name){this.name=name}//1.프로토 타입을 작성하려면 Person.prototype={sayhello(){console.log(`Hi!제 이름은${this.name},)}}}();constem=newPerson(‘Kim’)const Person = (関数) {function Person(name) {this.name = name}// 1. 프로토타입을 작성하려면 Person.prototype = {sayhello() {console.log(`Hi! 제 이름은 ${this.name }`)}}());constem = newPerson(‘Kim’)JS 엔진에서는 기본적으로 객체 함수는 프로토타입을 만들면서 constructor를 추가하도록 되어 있습니다. 하지만 커스텀에 직접 만든 것은 위 그림과 같이 constructor 속성이 존재하지 않게 됩니다. 그렇기 때문에 me의 객체 생성자 함수를 검색하면 Person이 아닌 Object가 나오는 기묘한 형태가 됩니다.그래서 실제로 실행할 경우 Person은 Constructor가 없기 때문에 실질적으로 실행되지 않을 수도 있습니다. 따라서 프로토타입에 대한 이해가 깊지 않으면 무리하게 코드를 바꾸는 것은 위험하다는 것을 알아야 합니다.constarent = {supervisor: 안녕하세요(´·ω·`)그러나 이것도 그 프로토타입에 constructor에 내가 원하는 생성자 함수를 대입함으로써 해결이 가능하게 되어 있습니다. 유연함이 분명 장점이지만 이 유연함이 굳이 필요 없어 보인다는 점에서는 오히려 독이 되는 부분이 아닐까 싶습니다.함수 Person(name){this.name=name}constme=ner Person(‘Kim’)//함수 person(‘kim’)/정수 constar=<say안녕하세요(){console.log(`Hi!제 이름은${this.name}입니다.`)}}//1.me객체의프로토타입을 parent객체로교환한다. 목적.setPrototypeOf(나, 부모)//me.__module__==parent와보기하니다, setPrototypeOf()더 보기 me.sayHello()함수 Person(name){this.name=name}constme=ner Person(‘Kim’)//함수 person(‘kim’)/정수 constar=<say안녕하세요(){console.log(`Hi!제 이름은${this.name}입니다.`)}}//1.me객체의프로토타입을 parent객체로교환한다. 목적.setPrototypeOf(나, 부모)//me.__module__==parent와보기하니다, setPrototypeOf()더 보기 me.sayHello()방금 생성자 함수를 통해 프로토타입을 교환한 것과 인스턴스를 이용하여 교환한 코드입니다.양쪽의 가장 큰 차이는 생성자 함수에서 만들어 생성자 함수 자체가 자신의 프로토 타입을 별개의 것으로 바꾼 상태가 됩니다.그러나 후자의 경우, 인스턴스 자체의 프로토 타입을 변경했습니다.위의 그림은 오히려 혼동을 불러일으킬 수 있다고 생각합니다만… 그렇긴 후자의 경우 Person을 통해서 작성은 했는데, 그 후 이 인스턴스의 프로토 타입을 보아 버린 것이 됩니다.그래서 Person생성자 함수는 당연히 이 인스턴스의 프로토 타입과는 아무 상관 없어요.instanceof연산자객체 instanceof생성자 함수의 형태로 사용되는데 이 경우 생성자 함수의 prototype에 바인딩 된 객체가 좌변 객체의 프로토 타입 체인상에 존재하는지를 체크하고 true, false를 리턴 하는 연산자라고 생각하세요.그래서, 인스턴스의 프로토 타입을 바꿀 경우”해당 객체의 프로토 타입 체인 상”에 존재하지 않으므로 false가 표시됩니다.정적 멤버, 정적 메서드일반적인 객체 지향을 지원하는 언어에서는 static에서 선언한 정적 변수나 정적 함수가 지원됩니다. 하지만 프로토타입을 이용하는 Js의 경우는 당연히 조금 다르게 처리합니다.기본적으로 생성자 함수 자체도 객체이기 때문에 생성자 함수 자체에 속성을 추가하면 실제로 다른 언어에서 정적 변수와 메서드를 제공하는 것과 같은 형태가 됩니다. 생성자 함수의 이름에 점을 찍어서 생성자 함수 객체가 가지고 있는 변수와 메서드에 접근하는 것이 똑같습니다.// // C++class Person {public:static std::string staticProp;static void staticMethod() {};}// Jsconst Person = {Person.prototype.staticProp’Person.prototype.staticMethod=関数({})오브젝트만으로도 이렇게 비슷한 기능을 만들어낸 아이디어가 대단하다고 생각합니다. 속성의 존재를 확인합니다.in 연산자, Reflect.has (ES6)const person={name:’Kim’, addr:’Seoul’,}console.log(‘name’in person)//trueconsole.log(addr’in person)//falseconsole.log(reflect.has(person,’name’)//trueconsole.log(`${key}:$person[key])//서울의 이름을 추가합니다in연산자와 Reflect.has연산자의 경우, 프로토 타입 체인을 더듬으며 해당 속성이 존재하는지를 체크하도록 되어 있습니다.그 때문에, 실제로 해당 인스턴스 및 해당 오브젝트가 본래 가지고 있는 속성이 아니더라도 부모가 가지고 있다면 갖고 있다고 판단하게 되어 있습니다.이는 for···in도 마찬가지입니다.같은 키워드를 사용하므로, 아마 같은 정책을 지킨 것처럼 보입니다.오히려 for···in에서는 거꾸로 되어 있으면 너무 혼란을 준 게 아닌가 싶습니다그런데 위의 코드로 재밌는 부분을 보면 person의 경우 toString이 검색되지만 정작 for···in에서는 부모의 것이 검색하지 못하는 기묘한 현상이 있습니다.이는 toString이[Enumrable] 아니라 속성이기 때문입니다.결과적으로 for···in은 프로토 타입 체인 상에서[Enumrable]가 true인 속성을 순회하는 것이라고 이해하는 것이 가장 완벽합니다.Object.prototype.hasOwnProperty방법, 즉 hasOwnProperty를 이용하면 내가 가지고 있지만 정보만 얻을 수 있습니다.Own이라는 말에서 명확하게 유추가 가능하므로, 메소드 이름 자체만 보면 얼마나 고민하고 만들었는지를 알 수 있습니다.Object.keys/values/entries메서드, 자신만이 가진 정보를 출력하기 위해서 각각 사용을 권장하는 방법입니다.정확히는 속성인 거죠.const person={name:’Kim’, addr:’서울’,__proto__:{age:20},}console.log(Object.keys(person)//[“name”,”addr”]console.log(Object.values(person)//[“Kim”,”서울”]console.log(Object.entries(person)//[[이름,”Kim”,”Kim”],[addr”]재미있는 것은 values, entries는 ES8이 되어서야 도입되었다는 점입니다. 그동안 불편해서 어떻게 작업했느냐 하는 기능인데 많이 늦게 도입된 것 같네요. 잡담분명히 저자가 정말 힘을 넣고 열심히 설명했지만 의외로 적절치 않은 그림과 prototype이라는 속성, 그리고 프로토 타입이라는 단어의 혼용에서 머릿속이 백지가 될 만큼 희게 한 챕터입니다.더 글을 잘 쓰지 않을까 하는데요, 근데 이거만 정말 Js을 딥으로 파헤친 책은 아직 보지 않아…이래봬도 언어의 원리를 더 중요하게 생각하는 나에게는 정말 마음에 드는 책임에는 분명하지만… 그렇긴 변함 없이 방황할 수밖에 없는 내 strict한 두뇌에 통탄하지 않을 수 없습니다. ww확실히 저자가 정말 공을 들여서 열심히 설명했지만 의외로 적절하지 않은 그림과 prototype이라는 속성, 그리고 프로토타입이라는 단어의 혼용으로 머릿속이 흰 바탕이 될 정도로 하얗게 만든 챕터입니다. 좀더 글을 잘 쓰지 않을까 생각합니다만, 그래도 이만큼 정말 Js를 딥하게 파고든 책은 아직 보지 못했기 때문에… 언어의 원리를 더 중요하게 생각하는 저에게는 너무나 마음에 드는 책임은 분명하지만… 여전히 망설일 수밖에 없는 저의 strict한 두뇌에 통탄하지 않을 수 없습니다. ㅎㅎ