프로토타입03
*🔐Study Keyword :
**프로토타입 관련된 키워드들에 대해서 공부하자🔑
-오버라이딩과 프로퍼티 섀도잉
-프로토타입 교체
-instanceof 연산자
-생성자 함수에서 참조 호출할 수 있는 정적 프로퍼티와 메서드
-프로퍼티 존재를 확인과 열거
8. 오버라이딩과 프로퍼티 섀도잉
- 생성자 함수로 객체를 생성한 다음 인스턴스에 메서드를 추가한다.
<script> const Person = (function(){ // 생성자 함수 function Person(name){ this.name = name; } // 프로토타입 메서드 Person.prototype.sayHi = function(){ console.log(`Hi. bro! I'm ${this.name}`); }; // 생성자 함수를 반환 return Person; }()); // 즉시 실행 함수 // 인스턴스 생성 const me = new Person('jmaie'); // 인스턴스 메서드 me.sayHi = function(){ console.log(`Hi. bro! I'm ${this.name}`); }; // 프로토타입 메서드는 인스턴스 메서드에 의해 가려진다 = 프로퍼티 셰도잉 // 따라서 인스턴스 메서드가 호출된다. me.sayHi(); // "Hi. bro! I'm jmaie" </script>
- 프로토타입이 소유한 프로퍼티(메서드 포함)를 1>
프로토타입 프로퍼티
,
인스턴스가 소유한 프로퍼티(메서드 포함)를 2>인스턴스 프로퍼티
라 부른다.1>프로토타입 프로퍼티
와 같은 이름의 프로퍼티를 인스턴스에 추가하면프로토타입 체인
을 따라 프로토타입 프로퍼티를검색
하여프로토타입 프로퍼티를 덮어쓰는게 아닌2>인스턴스 프로퍼티
로 추가한다.- 🔑KEY1> 이처럼 인스턴스 메서드(sayHello)는 프로토타입 메서드(sayHello)를 📇
오버라이딩
하여 프로토타입 메서드(sayHello)가 가려졌는데 상속관계에 의해 프로퍼티가 가려지는 이러한 현상을 🥊프로퍼티 섀도잉
이라한다.
-📇오버라이딩
VS 📰오버로딩
- 📇
오버라이딩
은 상위 클래스가 가지고 있는 메서드를 하위 클래스가 재정의하여 사용하는 방식이며- 📰
오버로딩
은 함수의 이름은 동일하지만 매개변수의 타입 또는 개수가 다른 메서드를 구현하여 매개변수에 의해 메서드를 구별하여 호출하는 방식이다.자바스크립트는 📰오버로딩
을 지원하진 않지만 arguments 객체를 사용하여 구현할 순 있다.
- 프로퍼티를 삭제하는 경우 당연히 프로토타입 메서드가 아닌 인스턴스 메서드가 삭제되고 다시 한 번 삭제해도 여전히 프로토타입 메서드는 삭제가 안된다.
<script> // 인스턴스 메서드 삭제하면 delete me.sayHi(); // 인스턴스에 메서드가 없으니 프로토타입 메서드가 호출된다. me.sayHi(); // "Hi. bro! I'm // 다시 한 번 삭제해도 프로토타입 체인을 통해 프로토타입 메서드가 삭제되진 않는다. delete me.sayHi(); // 프로토타입 메서드가 호출된다. me.sayHi(); // "Hi. bro! I'm </script>
- 하위 객체를 통해 프로토타입의 프로퍼티를 변경 또는 삭제하는 것은 불가능하다.
즉 하위 객체를 통해 프로토타입에 get 엑세스는 허용⭕ set 엑세스는 허용되지❌
프로토타입 프로퍼티
를 변경 또는 삭제하려면 _하위 객체를 통해 프로토타입 체인으로 접근하는 것이 아닌프로토타입에 직접 접근해야한다.<script> // 프로토타입 메서드 변경시 직접 접근 Person.prototype.sayHi = function(){ console.log(`What's Up. bro! I'm ${this.name}`); }; me.sayHi(); // "What's Up. bro! I'm jmaie" // 프로토타입 메서드 삭제시 직접 접근 delete Person.prototype.sayHi; me.sayHi(); // "TypeError: me.sayHi is not a function" </script>
9. 프로토타입 교체
- 프로토타입은 임의의 다른 객체로 변경할 수 있다. 이는 부모 객체인 프로토타입을 동적으로 변경이 가능하다는 뜻이다.
- 이런 특징을 활용해 객체 간의
상속관계
를 동적으로 변경할 수 있다.
- 프로토타입은 1>
생성자 함수
또는 2>인스턴스
에 의해 교체 할 수 있다.
1>생성자 함수
에 의한 프로토타입의 교체
<script> const Person = (function(){ function Person(name){ this.name = name; } // 1> 생성자 함수의 prototype 프로퍼티를 통한 프로토타입 교체 Person.prototype = { sayHi() { console.log(`Hi. bro! I'm ${this.name}`); } }; return Person; }()); </script>
- 1번에서 Person.prototype에
객체 리터럴을 할당
했는데 이는 Person 생성자 함수가 생성할 객체의 프로토타입을객체 리터럴로 교체
한 것이다.
- 프로토타입으로 교체한
객체 리터럴
에는 constructor 프로퍼티가 없다. constructor 프로퍼티는 자바스크립트 엔진이 프로토타입을 생성할 때 암묵적으로 추가한 프로퍼티이므로 me 객체의 생성자 함수를 검색하면 Person이 아닌 Object가 나온다.<script> const me = new Person('jamie'); // 프로토타입 교체하면 constructor 프로퍼티와 생성자 함수 간의 연결 파괴 console.log(me.constructor === Person); // false // 프로토타입 체인을 따라 Object.의 constructor프로퍼티 검색 console.log(me.constructor === Object); // true </script>
- 이처럼 프로토타입을 교체하면 constructor 프로퍼티와 생성자 함수간의
연결
이 파괴된다.- 파괴된 constructor 프로퍼티와 생성자 함수간의 연결을 되살리기 위해서는 프로토타입으로 교체한 객체 리터럴에 constructor 프로퍼티를 추가하여 프로토타입의 constructor 프로퍼티를 되살릴 순 있다.
<script> const Person = (function(){ function Person(name){ this.name = name; } // 1> 생성자 함수의 prototype 프로퍼티를 통한 프로토타입 교체 Person.prototype = { // constructor 프로퍼티와 생성자 함수간의 연결 설정 constructor: Person, sayHi() { console.log(`Hi. bro! I'm ${this.name}`); } }; return Person; }()); // constructor 프로퍼티가 이젠 생성자 함수를 가리킨다. console.log(me.constructor === Person); // true console.log(me.constructor === Object); // false </script>
2>인스터스
에의한 프로토타입의 교체
- 프로토타입은 1>
생성자 함수
의 prototype 프로퍼티 뿐만 아니라 2_1>인스터스
의 __proto__ 접근자 프로퍼티(또는 Object.getPrototypeOf 메서드)를 통해 '접근'하고 2_2> 인스턴스의 __proto__ 접근자 프로퍼티(또는 Object.setPrototypeOf 메서드)를 통해 프로토타입을 '교체'할 수 있다- 앞서 살펴본 1>
생성자 함수
의 prototype 프로퍼티에 다른 임의의 객체를 바인딩하는 것은 미래에 생성할 인스턴스의 프로토타입을 교체하는 것이지만
2>인스터스
의 __proto__ 접근자 프로퍼티를 통해 접근 및 교체는이미 생성된 객체의 프로토타입을 교체하는 것이다.<script> function Person(name) { this.name = name; } const me = new Person('jmaie'); // 프로토타입으로 교체할 객체 const parent = { sayHi() { console.log(`Hi. bro! I'm ${this.name}`); } }; Object.setPrototypeOf(me, parent) // 1. me 객체의 프로토타입을 parent 객체로 교체한다. // 위코드는 me.__proto__ = parent 와 동일하게 동작 me.sayHi() // "Hi. bro! I'm jmaie" </script>
- 1번 에서 me 객체의 프로토타입을
parent로 교체
했다.
- 프로토타입으로 교체한 객체는 constructor 프로퍼티가 없으므로 constructor 프로퍼티와 생성자 함수간의
연결
이 파괴된다. 따라서 프로토타입의 constructor 프로퍼티로 me 객체의 생성자 함수를 검색하면 Person이 아닌 Object 가 나온다.<script> // 프로토타입 교체하면 constructor 프로퍼티와 생성자 함수 간의 연결 파괴 console.log(me.constructor === Person); // false // 프로토타입 체인을 따라 Object.의 constructor프로퍼티 검색 console.log(me.constructor === Object); // true </script>
- 1>
생성자 함수
와 2>인스턴스
에 의한 프로토타입 교체는 별 차이 없어보이지만 미묘한 차이가 있다.
- 프로토타입으로 교체한 객체 리터럴에 constructor 프로퍼티를 추가하고 생성자 함수의 prototype 프로토타입를 재설정하여 파괴된 생성자 함수와 프로토타입 간의
연결
을 되살릴 수 있다.<script> function Person(name){ this.name = name; } const me = new Person('jmaie'); // 프로토타입으로 교체할 객체 const parent = { // constructor 프로퍼티와 생성자 함수 간의 연결 설정 constructor : Person, sayHi() { console.log(`Hi. bro! I'm ${this.name}`); } }; // 생성자 함수의 prototype 프로퍼티와 프로토타입 간의 연결 설정 Person.prototype = parent; // me 객체의 프로토타입을 parent 객체로 교체한다. Object.setPrototypeOf(me,parent) // 이젠 constructor 프로퍼티가 생성자 함수를 가리킨다. console.log(me.constructor === Person); // true console.log(me.constructor === Object); // false // 생성자 함수의 prototype 프로퍼티가 교체된 프로토타입을 가리킨다, console.log(Person.prototype === Object.getPrototypeOf(me)); // true </script>
- 그러나 프로토타입 교체를 통해 객체간의
상속관계를 동적으로 변경
하는 건 꽤 번거로우니 직접 교체하지 않는게 좋지 않다.상속관계를 인위적으로 설정
하려면직접 상속
이 더 편리하고 안전하다. 또는클래스
를 사용하면 간편하고 직관적으로상속관계를 구현
할 수 도있다.
10. instanceof 연산자
-WHAT IS❓
객체 instanceof 생성자 함수
- instaneof 연산자는 이항 연산자로서 좌변에 객체를 가리키는 식별자, 우변에 생성자 함수를 가리키는 식별자를 피연산자로 받는다. 만약 우변의 피연산자가 함수가 아닌 경우 TypeError가 발생한다.
- 우변의 생성자 함수의 prototype에 바인딩된 객체(프로토타입 객체)가 좌변의 객체의
프로토타입 체인
상에 존재하면 true 평가되고 그렇지 않은 경우에는 false로 평가된다.<script> // 생성자 함수 function Person(name) { this.name = name; } const me = new Person('Kim'); // Person.prototype이 me 객체의 프로토타입 체인 상에 존재하므로 true 평가 console.log(me instanceof Person); // Object.prototype이 me 객체의 프로토타입 체인 상에 존재하므로 true 평가 console.log(me instanceof Object); </script>
- instaneof 연산자가 어떻게 동작는지 이해하기 위해 프로토타입을 교체해보자.
- me 객체는 비록 프로토타입이 교체되어 프로토타입과 생성자 함수간의 연결이 파괴되었지만 Person 생성자 함수에 의해 생성된 인스턴스임에는 틀림이 없다. 그러나
me instanceof Person
은 false로 평가된다.<script> // 생성자 함수 function Person(name) { this.name = name; } const me = new Person('Kim'); // 프토토타입으로 교체할 객체 const parent = {}; // 프토토타입의 교체 Object.setPrototypeOf(me, parent) // 이젠 Person 생성자 함수와 parent 객체는 연결되어있지 않음 console.log(Person.prototype === parent); // false console.log(parent.constructor === parent); // false // Persont.prototype이 me 객체의 프로토타입 체인 상에 존재하지 않아 false 평가 console.log(me instanceof Person); // false // Object.prototype은 me 객체의 프로토타입 체인 상에 존재하므로 true 평가 console.log(me instanceof Object); // true </script>
- 이는 Person.prototype이 me 객체의
프로토타입 체인
상에 더이상 존재하지 않기에 그러하다. 프로토타입으로 교체한 parent 객체를 Person 생성자 함수의 prototype 프로퍼티에 바인딩하면 me instanceof Person는 true로 평가될 것이다.<script> // 생성자 함수 function Person(name) { this.name = name; } const parent = {}; // 프토토타입의 교체 Object.setPrototypeOf(me, parent) Person.prototype = parent ; // 이제는 Persont.prototype이 me 객체의 프로토타입 상에 존재하므로 true 평가 console.log(me instanceof Person); // true // Object.prototype은 me 객체의 프로토타입 체인 상에 존재하므로 true 평가 console.log(me instanceof Object); // true </script>
- 이처럼 instaneof 연산자는
프로토타입의 construtor 프로퍼티가 가리키는 생성자 함수를 찾는게 아닌생성자 함수의 prototype에 바인딩된 객체가프로토타입 체인
상에 존재하는지 확인한다.
- 1>
me instanceof Person
의 경우프로토타입 체인
상에 Person.prototype에 바인딩된 객체가 존재하는지 확인한다.- 2>
me instanceof Object
의 경우도 마찬가지로 me 객체의프로토타입 체인
상에 Object.prototype에 바인딩된 객체가 존재하는지 확인한다.
=> 생성자 함수에 의해 프로토타입이 교체되어프로토타입의 constructor 프로퍼티와 생성자 함수 간의 연결
이 파괴되어도생성자 함수의 prototype 프로퍼티와 프로토타입 간의 연결
은 파괴지되지 않으므로 instaneof에는 아무런 영향을 받지 않는다.
11. 직접 상속
11.1>Object.create에 의한 직접 상속
- Object.create 메서드는 명시적으로 프로토타입을 지정하여 새로운 객체를 생성한다. Object.create 메서드도 다른 객체 생성 방식과 마찬가지로 추상 연산
ordinaryObjectCreate
를 호출한다.- Object.create 메서드의 1>첫 번째 매개변수에는 생성할 프로토타입으로 지정할 객체를 전달하고 2>두 번째 매개변수에는 생성할 객체의 프로퍼티 키와 프로퍼티 디스크립터 객체로 이뤄진 객체를 전달한다.
이 객체의 형식은 Object.defineProperties 메서드의 두 번째 인수와 동일하며 두번째 인수는 옵션으로 생략 가능하다.<script> // 프로토타입이 null인 객체를 생성한다. 생성된 객체는 프로토타입 체인의 종점에 위치 // obj -> null let obj = Object.create(null); console.log(Object.getPrototype(obj) === null); // true // Object.prototype을 상속받지 못한다. console.log(obj.toString()); // "TypeError: Object.getPrototype is not a function // obj -> Object.prototype -> null // obj = {} 동일 obj = Object.create(Object.prototype); console.log(Object.getPrototype(obj) === Object.prototype); // true // obj -> Object.prototype -> null // obj = {x :1} 동일 obj = Object.create(Object.prototype, { x : {value : 1, writable : true, enumerable: true, configurable: true} }); // 위 코드 아래와 동일 // obj = Object.create(Object.prototype) // obj = {x:1} 동일 console.log(obj.x) // 1 console.log(Object.getPrototype(obj) === Object.prototype); // true const myProto = {x:10}; // 임의의 객체를 직접 상속 // obj -> myProto -> Object.prototype -> null obj = Object.create(myProto); console.log(obj.x) // 10 console.log(Object.getPrototype(obj) === myProto); // true // 생성자 함수 function Person(name) { this.name = name; } // obj -> Person.prototype -> Object.prototype -> null // obj = new Person('kim')과 동일 obj = Object.create(Person.prototype) obj.name = 'kim'; console.log(obj.name) // kim console.log(Object.getPrototype(obj) === Person.prototype); // true </script>
- 이처럼 Object.create 메서드는 1>첫 번째 매개변수에 전달한 객체의 프로토타입 체인에 속하는 객체를 생성한다.
=> 객체를 생성하면서직접적으로 상속을 구현
하는 것으로 다음과 같은 장점을 가진다.
- new 연산자 없이도 객체를 생성할 수 있다.
- 프로토타입을 지정하면서 객체를 생성할 수 있다.
- 객체 릴터럴에 의해 생성된 객체도 상속받을 수 있다.
- 참고로 Object.prototype의 빌트인 메서드인 Object.prototype.hasOwnProperty, Object.prototype.isPrototypeof 등은 모든 객체의
프로토타입 체인
의 종점, Object.protototype의 메서드이므도 모든 객체가상속
받아 호출할 수 있다.<script> const obj = { a: 1}; console.log(obj.hasOwnProperty('a')); </script>
- 그러나 앞의 코드 처럼 Object.prototype의 빌트인 메서드를 객체가 직접 호출하는 것을 권장❌ !
Object.create 메서드를 통해프로토타입 체인의 종점
에 위치하는 객체를 생성할 수가 있기 때문에 이 객체는 Object.prototype의 빌트인 메서드를 사용❌<script> // 프로토타입이 null, 즉 프로토타입 체인의 종점에 위치하는 객체 생성 const obj = Object.create(null) obj.a = 1; console.log(Object.getPrototype(obj) === null); // true // obj는 Object.prototype의 빌트인 메서드를 사용할 수 없다. console.log(obj.hasOwnProperty('a')); // TypeError: Object.getPrototype is not a function </script>
=> 이 같은 에러 발생 위험을 없애기 위해 Object.prototype의 빌트인 메서드는 다음 처럼 간접적(Call 메서드)으로 호출을 권장⭕!
<script> // 프로토타입이 null, 즉 프로토타입 체인의 종점에 위치하는 객체 생성 const obj = Object.create(null) obj.a = 1; // console.log(obj.hasOwnProperty('a')); // TypeError: Object.getPrototype is not a function // Object.prototype의 빌트인 메서드는 객체로 직접 호출하지 않는다. console.log(obj.prototype.hasOwnProperty.call(obj, 'a')); // true </script>
11.2> 객체 리터럴 내부에서 __proto__ 에 의한 직접 상속
- Object.create 메서드에 의한 직접 상속은 앞에서 다룬 것과 같이 여러 장점이 있지만 두 번째 인자로 프로퍼티를 정의하는 것이 번거롭다. 일단 객체를 생성한 후 이후 프로퍼티를 추가할 수는 있지만 이 또한 깔끔한 방법은 아니다.
- ES6에서 객체 리터럴 내부에서 __proto__ 접근자 프로퍼티를 사용하여
직접 상속을 구현
할 수 있다.<script> const myProto = {x:10}; // 객체 리터럴에 의해 객체를 생성하면서 프로토타입을 지정해 직접 상속 const obj = { y:20, // 객체를 직접 상속받는다. // obj -> myProto -> Object.prototype -> null __proto__: myProto } /* 아래의 코드와 동일 const obj = Object.create(myProto, { y: {value:20, writable : true, enumerable: true, configurable: true} }); */ obj.a = 1; console.log(obj.x, obj.y); // 10 20 console.log(Object.getPrototypeOf(obj)===myProto); // true </script>
12. 정적 프로퍼티/메소드
- 정적 프로퍼티/메서드는 생성자 함수로 인스턴스를 생성하지 않아도 참조/호출 할 수 있는 프로퍼티/메서드를 말한다.
<script> // 생성자 함수 function Person(name){ this.name = name; } // 프로토 타입 멤서드 Person.prototype.sayHi = function() { console.log(`Hi! my name is ${this.name}`) }; // 정적 프로퍼티 Person.staticProp = 'static prop' // 정적 메서드 Person.staticMethod = function (){ console.log('staticMethod') } const me = new Person('kim'); // 생성자 함수에 추가된 정적 프로퍼티/메서드는 생성자 함수로 참조/호출 한다. Person.staticMethod() // "staticMethod" // 정적 프로퍼티/ 메서드는 생성자 함수가 생성한 인스턴스로 참조/호출할 수 없다 // 인스턴스로 참조/ 호출할 수 있는 프로퍼티/메서드는 프로토타입 체인상에 존재해야한다 me.staticMethod() // "TypeError: me.staticMethod is not a function </script>
- 생성자 함수가 생성한 인스턴스는 자신의
프로토타입 체인
에 속한 객체의 프로퍼티/메서드에 접근할 수 있지만 정적 프로퍼티/메서드는 인스턴스의 프로토타입 체인에 속한 객체의 프로퍼티/메서드가 아니므로 인스턴스로 접근 불가능하다.- 앞에서 살펴본 Object.create 메서드는 Object 생성자 함수의 정적 메서드고 Object.prototype.hasOwnProperty 메서드는 Object.prototype의 메서드다. 따라서 Object.create 메서드는 생성자 함수의 정적 메서드이므로, Object 생성자 함수가 생성한 객체로 호출할 수 없지만 Object.prototype.hasOwnProperty 메서드 모든 객체의
프로토타입 체인
의 종점 Object.prototype 메서드이므로 모든 객체가 호출 가능하다.<script> // Object.create는 정적 메서드 const obj = Object.create({name: 'kim'}); // Object.prototype.hasOwnProperty 메서드는 프로토타입 메서드 obj.hasOwnProperty('name'); // false </script>
- 만약 인스턴스/프로토타입 메서드 내에서 this를 사용하지 않는다면 그 메서드는 정적 메서드로 변경할 수 있다. 인스턴스가 호출한 인스턴스/프로토타입 메서드 내에서 this는 인스턴스를 가리킨다.
- 메서드 내에서 인스턴스를 참조할 필요가 없다면 정적 메스드로 변경해도 동작한다. 프로토타입 메서드를 호출하려면 인스턴스를 생성해야하지만 정적메서드는 인스턴스를 생성하지 않아도 호출할 수 있다.
<script> function Foo(){} // 프로토타입 메서드 // this를 참조하지 않는 프로토타입 메서드는 정적 메서드로 변경해도 동일한 효과 얻는다 Foo.prototype.x = function(){ console.log('x') }; const foo = new Foo(); // 프로토타입 메서드를 호출하려면 인스턴스를 생성해야한다. foo.x(); // x // 정적 메서드 Foo.x = function(){ console.log('x') }; // 정적 메서드는 인스턴스를 생성하지 않아도 호출가능하다. Foo.x()// x </script>
- MDN 문서를 봐도 정적 프로퍼티/메서드와 프로토타입 프로퍼티/메서드를 구분하여 소개해 표기법만으로도 정적 프로퍼티/메서드와 프로토타입 프로퍼티/메서드를 구별할 수 있다.
- 참고로 프로토타입 프로퍼티/메서드를 표기할 때 prototype을 #(Object.prototype.isPrototypeOf를 Object#isPrototypeOf으로 표기)으로 표기하는 경우도 있으니 알아두자
13. 프로퍼티 존재 확인
13.1>in 연산자
- in 연산자는 객체 내에 특정 프로퍼티가 존재하는 지 여부를 확인한다. 사용법은 다음과 같다.
<script> /** * key : 프로퍼티 키를 나타내는 문자열 * object : 객체로 평가되는 표현식 key in object */ const person = { name: 'Kim', address: 'Seoul' }; // person 객체에 name 프로퍼티가 존재한다. console.log('name' in person); // true // person 객체에 address 프로퍼티가 존재한다. console.log('address' in person); // true // person 객체에 age 프로퍼티가 존재한다. console.log('age' in person); // false console.log('toSring' in person); // true </script>
- in 연산자는 확인 대상 객체의 프로퍼티뿐만 아니라 확인 대상 객체가
상속받은 모든 프로토타입의 프로퍼티를 검색
하므로 주의가 필요하다.- 위코드에서 in 연산자는 person 객체가 속한
프로토타입 체인 상
에존재하는 모든 프로토타입에서 toString 프로퍼티를 검색
했기 때문에 Object.prototype 메서드인 toString 역시 true이다.- in 연산자 대신 ES6에 도입된 Reflect.has 메서드를 사용하면 in 연산자자와 동일하게 동작한다.
<script> const person = { name: 'Kim', address: 'Seoul' }; // person 객체에 name 프로퍼티가 존재한다. console.log(Reflect.has(person,'name')); // true // person 객체에 address 프로퍼티가 존재한다. console.log(Reflect.has(person,'address')); // true // person 객체에 age 프로퍼티가 존재한다. console.log(Reflect.has(person,'toString')); // true </script>
13.2>Object.prototype.hasOwnProperty 메서드
- Object.prototype.hasOwnProperty 메서드를 사용해도 객체에 특정 프로퍼티가 존재하는지 확인할 수 있다.
<script> console.log(person.hasOwnProperty('name')) //true console.log(person.hasOwnProperty('age')) // false </script>
- Object.prototype.hasOwnProperty 메서드는 이름에서 알수 있듯이 인수로 전달받은 프로퍼티 키가 객체 '고유의 프로퍼티' 키인 경우 true를 반환하고 상속받은 프로토타입의 프로퍼티 키인 경우 false를 반환한다.
<script> console.log(person.hasOwnProperty('toString')) //true </script>
14. 프로퍼티 열거
14.1 for...in 문
- 객체의 모든 프로퍼티를 순회하며 열거하려면
for...in 문
을 사용한다.<script> /** for (변수 선언문 in 객체) {...} */ const person = { name: 'Kim', address: 'Seoul' }; // for...in 문의 변수 prorp에 person객체의 프로퍼티 키가 할당된다. for (const key in person){ // 객체에 2개의 프로퍼티를 2번 순회하며 프로퍼티 키를 key 변수에 할당한 후 코드 블록 실행 console.log(`${key} : ${person[key]}`) } //"name : Kim" // "address : Seoul" </script>
for...in 문
은 객체의 프로퍼티 개수만큼 순회하며for...in 문
의 변수 선언문에서 선언한 변수에 프로퍼티 키를 할당한다.for...in 문
은 in 연산자처럼 순회 대상 객체의 프로퍼티뿐만 아니라상속받은 프로토타입의 프로퍼티
까지 열거한다.
- 하지만 위 코드의 경우 toString 같은 Object.prototype의 프로퍼티가 열거되진 않는데 이는 toString 메서드가 열거할 수 없도록 정의되어 있는 프로퍼티이기 때문이다.
다시 말해 Object.prototype.string 프로퍼티의 프로퍼티 어트리뷰트 [[Enumerable]]의 값이 false로 이 프로퍼티 어트리퓨트 [[Enumerable]]는 프로퍼티의 열거 가능 여부를 나타내며 불리언 값을 갖는다.<script> const person = { name: 'Kim', address: 'Seoul' }; // in 연산자는 객체가 상속받은 모든 프로토타입의 프로퍼티를 확인한다. console.log('toSring' in person); // true // for...in 문도 객체가 상속받은 모든 프로토타입의 프로퍼티를 열거한다. // 하지만 toString같은 Object.prototype의 프로퍼티가 열거되지는 않는다. for (const key in person){ console.log(`${key} : ${person[key]}`) }; // "name : Kim" // "address : Seoul" // Object.getOwnPropertyDescriptor 메서드는 프로퍼티 디스크립터 객체를 반환한다. // 디스크립터 객체는 프로퍼티 어트리뷰트 정보를 담고 있는 객체다 console.log(Object.getOwnPropertyDescriptor(Object.prototype,'toString')) // { configurable: true, enumerable: false, value: f, writable: true } </script>
=> 따라서
for...in 문
을 정확히 표현하면for...in 문
은 객체의 프로터타입 체인 상에 존재하는 모든 프로토타입의 프로퍼티 중에 프로퍼티 어트리 뷰트 [[Enumerable]]값이 true인 프로퍼티를 순회하며 열거한다.<script> const person = { name: 'Kim', address: 'Seoul', __proto__: {age:20} // 상위 객체의 프로퍼티 }; for (const key in person){ // 객체의 프로토체인 상 존재하는 // 모든 열거가능한 프로퍼티 순회 열거 console.log(`${key} : ${person[key]}`) }; //"name : Kim" //"address : Seoul" //"age : 20" </script>
for...in 문
은 프로퍼티 키가 심벌인 프로퍼티는 열거하지 않는다.<script> const sym = Symbol(); const obj = { a: 1, [sym] : 10 } for (const key in obj){ console.log(`${key} : ${obj[key]}`) }; // "a : 1" </script>
하고 객체 자신의 프로퍼티만 열거하려면 Object.prototype.hasOwnProperty 메서드를 사용하여 객체 자신의 프로퍼티인지 확인해야한다.상속
받은 프로퍼티는 제외<script> const person = { name: 'Kim', address: 'Seoul', __proto__: {age:20} }; for (const key in person){ // 객체 자신의 프로퍼티인지 확인한다. if(!person.hasOwnProperty(key)) continue; console.log(`${key} : ${person[key]}`) }; //"name : Kim" //"address : Seoul" </script>
for...in 문
은 프로퍼티를 열거할 때 순서를 보장하지 않으므로 주의하자. 하지만 대부분의 모던 브라우저는 순서를 보장하는데 숫자(사실을 문자열인)인 프로퍼티키에 대해서 정렬을 실시한다.<script> const obj = { 3: 3, 1: 1, 2: 2, b: 'b', a: 'a' }; for (const key in obj){ // 객체 자신의 프로퍼티인지 확인한다. if(!obj.hasOwnProperty(key)) continue; console.log(`${key} : ${obj[key]}`) }; /* "1 : 1" "2 : 2" "3 : 3" "b : b" "a : a" */ </script>
-배열에는
for...in 문
을 사용❌ 일반적인for문
이나for...of문
또는Arrray.prototype.forEach 메서드
를 사용하기를 권장⭕
왜냐면 배열도 객체이므로 상속받은 프로퍼티가 포함될 수 있다.<script> const arr = [1, 2, 3] arr.x = 10 // for (const i in arr){ // 프로퍼티 x도 출력된다. console.log(arr[i]) // 1, 2, 3, 10 }; // 일반적인 for문 for (let i = 0; i<arr.length; i++){ console.log(arr[i]) // 1, 2, 3 } //forEach 메서드는 요소가 아닌 프로퍼티는 제외 arr.forEach(v=>console.log(v)) // 1, 2, 3 // for...of는 변수 선언문에서 선언한 변수에 키가 아닌 값을 할당 for (const value of arr){ console.log(value) // 1, 2, 3 } </script>
14.2>Object.keys/valuse/entries 메서드
for...in 문
은 객체 자신의 고유 프로퍼티뿐 아니라 상속받은 프로퍼티도 열거한다. 따라서 Object.prototype.hasOwnProperty 메서드를 사용하여 객체 자신의 프로퍼인지 확인하는 추가 처리가 필요하다.
- 객체 자신의 고유 프로퍼티만 열거하기 위해서는
for...in 문
을 사용하는 것보다 Object.keys/valuse/entries 메서드를 사용하는 것을 권장한다.- Object.keys 메서드는 객체 자신의 열거 가능한 프로퍼티 키를 배열로 반환한다.
- ES8에서 도입된 Object.valus 메서드는 객체 자신의 열거 가능한 프로퍼티 키를 값을 배열로 반환한다.
- ES8에서 도입된 Object.entris는 메서드는 객체 자신의 열거 가능한 프로퍼티 키와 값의 쌍의 배열을 배열에 담아 반환한다.
<script> const person = { name : 'Kim', address : 'Seoul', __proto__ : { age : 20 } }; console.log(Object.keys(person)) // ["name", "address"] console.log(Object.values(person)) // ["Kim", "Seoul"] console.log(Object.entries(person)) // [["name", "Kim"], ["address", "Seoul"]] Object.entries(person).forEach(([key, value]) => console.log(key, value)); // "name" "Kim" "address" "Seoul" </script>
*💡conclusion
- 오버라이딩과 오버로딩의 차이를 알고 객체의 프로퍼티가 프로토탑인의 프로퍼티로 인해 가려지는 프로퍼티의 개념에 대해 인지하자.
- 프로토타입을 생성자 함수와 인스턴스로 교체할 수 있는 각각의 방법에 대해서 알고 두 방식의 미묘한 차이가 있다는 것을 알자
- 프로토타입 교체를 통해 상속관계를 동적으로 변경하기 보단 직접 상속이나 클래스를 사용하여 상속관계를 구혀하자
- instaneof 연산자는 생성자 함수의 prototype에 바인딩된 객체가 프로토타입 체인 상에 존재하는지 확인한다.
- 직접 상속에는 Object.create 메소드 혹은 객체 리터럴 내부에서 __proto__를 사용하여 구현할 수 있다.
- 생성자 함수에 있는 프로퍼티/ 메서드인 정적 프로퍼티/메소드와 프로토타입 프로퍼티/메서드를 구별하자.
- 프로퍼티 존재를 확인하는 in 연산자(또는 Reflaect.has 메서드)와
Object.prototype.hasOwnProperty 메서드의 차이점을 알자.- 상속받은 모든 객체의 프로퍼티를 열거하는 for..in문과 객체 자신 고유한 프로퍼티의 키, 키의값, 키와 키의값의 쌍을 배열로 반환하는 Object.keys/valuse/entries 메서드에 대해서 알아두자.
#📑Study Source
- 책 - 모던 자바스크립트 Deep Dive (272p-287p)
Author And Source
이 문제에 관하여(프로토타입03), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://velog.io/@minj9_6/자바스크립트-프로토타입03저자 귀속: 원작자 정보가 원작자 URL에 포함되어 있으며 저작권은 원작자 소유입니다.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)