Web/모던 자바스크립트 Deep Dive
[모던 자바스크립트 Deep Dive] 19. 프로토타입
동띵
2023. 9. 11. 20:51
객체지향 프로그래밍
- 자바스크립트는 프로토타입 기반의 객체지향 프로그래밍 언어
- 원시 타입 값을 제외한 나머지 값들 (함수, 배열, 정규 표현식 등) 모두 객체
- 객체지향 프로그래밍 : 프로그램을 명령어 또는 함수의 목록으로 보는 전통적인 명령형 프로그래밍의 절차지향적 관점에서 벗어나 여러 개의 독립된 단위인 객체의 집합으로 프로그램을 표현하려는 프로그래밍
- 추상화 : 다양한 속성 중 필요한 속성만 가추려 내어 표현하는 것
- 객체 : 속성을 통해 여러 개의 값을 하나의 단위로 구성한 복합적인 자료구조 (상태 데이터와 동작을 하나의 논리적인 단위로 묶은 복합적인 자료구조)
- 상태 데이터 : 객체의 상태를 나타내는 것 ⇒ 프로퍼티 (property)
- 동작 : 객체의 상태 데이터를 조작할 수 있는 것 ⇒ 메서드 (method)
- 객체는 자신의 고유 기능을 수행하며 다른 객체와 관계성을 가질 수 있고, 다른 객체와 메시지를 주고받거나 데이터를 처리할 수 있으며, 다른 객체의 상태 데이터나 동작을 상속받아 사용하기도 함
상속과 프로토타입
- 상속 : 다른 객체의 프로퍼티나 메서드를 다른 객체가 상속받아 그대로 사용할 수 있는 것
- 자바스크립트는 프로토타입을 기반으로 상속을 구현하여 불필요한 중복 제거 (기존 코드 재사용)
- ex. 동일한 생성자 함수에 의해 생성된 모든 인스턴스가 동일한 메서드를 중복 소유 ⇒ 메모리 낭비 ⇒ 상속을 통해 불필요한 중복 제거 가능
// 생성자 함수 function Circle(radius) { this.radius = radius; } // Circle 생성자 함수가 생성한 모든 인스턴스가 getArea 메서드를 // 공유해서 사용할 수 있도록 프로토타입에 추가 // 프로토타입은 Circle 생성자 함수의 prototype 프로퍼티에 바인딩 Circle.prototype.getArea = function() { return Math.PI * this.radius ** 2; } // Circle 생성자 함수가 생성하는 모든 인스턴스는 하나의 getArea 메서드를 공유 const circle1 = new Circle(1); const circle2 = new Circle(2); console.log(circle1.getArea === circle2.getArea); // true
- 상속은 코드의 재사용이란 관점에서 매우 유용
- 생성자 함수가 생성할 모든 인스턴스가 공통적으로 사용할 프로퍼티나 메서드를 프로토타입에 미리 구현해두면 생성자 함수가 생성할 모든 인스턴스는 별도의 구현 없이 상위 (부모) 객체인 프로토타입의 자산을 공유하여 사용
프로토타입 객체
- 프로토타입 객체 : 객체 간 상속을 구현하기 위해 사용
- 프로토타입 : 어떤 객체의 상위 객체 역할을 하는 객체로서 다른 객체에 공유 프로퍼티(메서드 포함) 제공
- 프로토타입을 상속받은 하위 객체는 상위 객체의 프로퍼티를 자신의 것처럼 자유롭게 사용 가능
- 객체 리터럴에 의해 생성된 객체의 프로토타입은 Object.prototype
- 생성자 함수에 의해 생성된 객체의 프로토타입은 생성자 함수의 prototype 프로퍼티에 바인딩
__proto__ 접근자 프로퍼티
- 모든 객체는 __proto__ 접근자 프로퍼티를 통해 자신의 프로토타입인 [[Prototype]] 내부 슬롯에 간접적으로 접근 가능
__proto__는 접근자 프로퍼티다.
- __proto__는 getter/setter 함수라고 불리는 접근자 함수 [[Get]], [[Set]]를 통해 프로토타입을 취득하거나 할당
- __proto__를 통해 프로토타입에 접근하면 내부적으로 [[Get]] 호출
- __proto__를 통해 새로운 프로토타입 할당하면 내부적으로 [[Set]] 호출
const obj = {}; const parent = {x : 1}; // getter 함수인 get __proto__가 호출되어 obj 객체의 프로토타입 취득 obj.__proto__; // setter 함수인 set __proto__가 호출되어 obj 객체의 프로토타입 교체 obj.__proto__ = parent; console.log(obj.x); //1
__proto__ 접근자 프로퍼티는 상속을 통해 사용된다.
- __proto__ 접근자 프로퍼티는 객체가 직접 소유하는 프로퍼티가 아닌 Object.prototype의 프로퍼티 ⇒ 모든 객체는 상속을 통해 Object.prototype.__proto__ 접근자 프로퍼티 사용
__proto__ 접근자 프로퍼티를 통해 프로토타입에 접근하는 이유
- 상호 참조에 의해 프로토타입 체인이 생성되는 것을 방지하기 위해
const parent = {};
const child = {};
// child의 프로토타입을 parent로 설정
child.__proto__ = parent;
// parent의 프로토타입을 child로 설정
parent.__proto__ = child; // TypeError: Cyclic __proto__ value
- 프로토타입 체인은 단방향 링크드 리스트로 구현되어야 함
__proto__ 접근자 프로퍼티를 코드 내에서 직접 사용하는 것은 권장하지 않는다.
- 모든 객체가 __proto__ 접근자 프로퍼티를 사용할 수 있는 것은 X
- 직접 상속을 통해 Object.prototype을 상속받지 않는 객체를 생성할 경우 __proto__ 접근자 프로퍼티 사용할 수 X
- __proto__ 접근자 프로퍼티 대신 프로토타입 참조 취득하고 싶은 경우 Object.getPrototypeOf 메서드 사용 / 프로토타입 교체하고 싶은 경우 Object.setPrototypeOf 메서드 사용
const obj = {};
const parent = {x : 1};
Object.getPrototypeOf(obj); // obj.__proto__;
Object.setPrototypeOf(obj, parent); // obj.__proto__ = parent;
console.log(obj.x); // 1
함수 객체의 prototype 프로퍼티
- 함수 객체만이 소유하는 prototype 프로퍼티는 생성자 함수가 생성할 객체의 프로토타입을 가리킴
- 생성자 함수로서 호출할 수 없는 non-constructor 인 화살표 함수와 ES6 메서드 축약 표현으로 정의한 함수는 prototype 프로퍼티를 소유하지 않으며 프로토타입도 생성하지 않음
// 함수 객체는 prototype 프로퍼티 소유
(function () {}).hasOwnProperty('prototype'); // true
// 일반 객체는 prototype 프로퍼티 소유 X
{}.hasOwnProperty('prototype'); // false
프로토타입의 constructor 프로퍼티와 생성자 함수
- 모든 프로토타입은 constructor 프로퍼티를 가짐
- 이 constructor 프로퍼티는 prototype 프로퍼티로 자신을 참조하고 있는 생성자 함수 가리킴
// 생성자 함수 function Person(name) { this.name = name; } const me = new Person('Lee'); // me 객체의 생성자 함수는 Person console.log(me.constructor === Person); // true
리터럴 표기법에 의해 생성된 객체의 생성자 함수와 프로토타입
- 생성자 함수에 의해 생성된 인스턴스는 프로토타입의 constructor 프로퍼티에 의해 생성자 함수와 연결
- 리터럴 표기법에 의해 생성된 객체의 경우 프로토타입의 constructor 프로퍼티가 가리키는 생성자 함수가 반드시 객체를 생성한 생성자 함수라고 단정 지을 수 X
// 객체 리터럴로 생성 (new 키워드를 사용해 생성자 함수로 생성 X)
const obj = {};
// 객체 obj의 생성자 함수는 Object
console.log(obj.constructor === Object); // true
- 생성자 함수가 아닌 리터럴 표기법에 의해 객체를 생성했는데, 이 객체의 constructor 프로퍼티는 Object를 가리킴 ⇒ 객체 리터럴에 의해 생성된 객체는 사실 Object 생성자 함수로 생성되는 것이 아닐까? 라는 의문 생김
- Object 생성자 함수 호출과 객체 리터럴의 평가는 추상 연산 OrdinaryObjectCreate를 호출하여 빈 객체를 생성하는 점에서 동일하지만, new.target 확인이나 프로퍼티 추가 등 세부 내용은 다름 ⇒ 객체 리터럴에 의해 생성된 객체는 Object 생성자 함수가 생성한 객체가 아님
- 프로토타입과 생성자 함수는 단독으로 존재할 수 없고 언제나 쌍으로 존재
리터럴 표기법 생성자 함수 프로토타입
객체 리터럴 | Object | Object.prototype |
함수 리터럴 | Function | Function.prototype |
배열 리터럴 | Array | Array.prototype |
정규 표현식 리터럴 | RegExp | RegExp.prototype |
프로토타입의 생성 시점
- 프로토타입은 생성자 함수가 생성되는 시점에 생성
- 생성자 함수
- 사용자가 직접 정의한 사용자 정의 생성자 함수
- 자바스크립트가 기본 제공하는 빌트인 생성자 함수
사용자 정의 생성자 함수와 프로토타입 생성 시점
- 함수 정의 (constructor, non-constructor)가 평가되어 함수 객체를 생성하는 시점에 프로토타입 생성
빌트인 생성자 함수와 프로토타입 생성 시점
- 빌트인 생성자 함수가 생성되는 시점에 프로토타입 생성
객체 생성 방식과 프로토타입의 결정
- 객체의 생성 방법
- 객체 리터럴
- Object 생성자 함수
- 생성자 함수
- Object.create 메서드
- 클래스 (ES6)
- 위 생성 방식으로 생성된 모든 객체의 공통점 : 추상 연산 OrdinaryObjectCreate에 의해 생성
- OrdinaryObjectCreate
- 자신이 생성할 객체의 프로토타입을 인수로 전달받음
- 자신이 생성할 객체에 추가할 프로퍼티 목록을 옵션으로 전달
- OrdinaryObjectCreate
⇒ 프로토타입은 추상 연산 OrdinaryObjectCreate에 전달되는 인수에 의해 결정 (인수는 객체가 생성되는 시점에서 객체 생성 방식에 의해 결정)
객체 리터럴에 의해 생성된 객체의 프로토타입
- 객체 리터럴에 의해 생성되는 객체의 프로토타입은 Object.prototype
- 객체 리터럴로 생성된 객체는 constructor 프로퍼티와 hasOwnProperty 메서드 등을 소유하지 X 하지만 Object.prototype의 constructor 프로퍼티와 hasOwnProperty 메서드를 자신의 자산인 것처럼 자유롭게 사용 가능
- 객체 리터럴로 생성된 객체가 자신의 프로토타입인 Object.prototype 객체 상속받았기 때문
Object 생성자 함수에 의해 생성된 객체의 프로토타입
- Object 생성자 함수에 의해 생성되는 객체의 프로토타입은 Object.prototype
- 객체 리터럴과 다른 점 : 프로퍼티 추가 방식
- 객체 리터럴 : 객체 리터럴 내부에 프로퍼티 추가
- Object 생성자 함수 : 일단 빈 객체 생성 후 프로퍼티 추가
생성자 함수에 의해 생성된 객체의 프로토타입
- 생성자 함수에 의해 생성되는 객체의 프로토타입은 생성자 함수의 prototype 프로퍼티에 바인딩되어 있는 객체
프로토타입 체인
- 프로토타입 체인 : 자바스크립트가 객체지향 프로그래밍의 상속을 구현하는 메커니즘
- 자바스크립트가 객체의 프로퍼티 (메서드 포함)에 접근하려고 할 때 해당 객체에 접근하려는 프로퍼티가 없다면 [[Prototype]] 내부 슬롯의 참조를 따라 자신의 부모 역할을 하는 프로토타입의 프로퍼티를 순차적으로 검색
function Person(name) { this.name = name; } // 프로토타입 메서드 Person.prototype.sayHello = function() { console.log(`Hi! My name is ${this.name}`); } const me = new Person('Lee'); // hasOwnProperty는 Object.prototype의 메서드 console.log(me.hasOwnProperty('name')); // true
- 프로토타입 체인의 최상위에 위치하는 객체는 언제나 Object.prototype ⇒ 모든 객체는 Object.prototype 상속받음
- Object.prototype : 프로토타입 체인의 종점 (end of prototype chain)
- Object.prototype에서도 프로퍼티를 검색할 수 없는 경우 undefined 반환 (에러 발생 X)
⇒ 프로토타입 체인 : 상속과 프로퍼티 검색을 위한 메커니즘 / 스코프 체인 : 식별자 검색을 위한 매커니즘
me.hasOwnProperty('name');
- 스코프 체인에서 me 식별자 검색 (전역에서 선언되었으므로 전역 스코프에서 검색) → me 식별자 검색 후 me 객체의 프로토타입 체인에서 hasOwnProperty 메서드 검색
⇒ 스코프 체인과 프로토타입 체인은 서로 연관없이 별도로 동작하는 것이 아닌 서로 협력하여 식별자와 프로퍼티 검색하는 데 사용
오버라이딩과 프로퍼티 섀도잉
- 프로토타입 프로퍼티와 같은 이름의 프로퍼티를 인스턴스에 추가하면 덮어쓰는 것이 아닌 인스턴스 프로퍼티로 추가 ⇒ 인스턴스 메서드는 프로토타입 메서드를 오버라이딩하고, 프로토타입 메서드는 가려짐
⇒ 상속 관계에 의해 프로퍼티가 가려지는 현상 : 프로퍼티 섀도잉
- 오버라이딩 : 상위 클래스가 가지고 있는 메서드를 하위 클래스가 재정의하여 사용
- 오버로딩 : 함수 이름은 동일하지만 매개변수에 의해 메서드를 구별하여 호출하는 방식 (자바스크립트는 지원하지 않지만 arguments 객체 ㅅ용하여 구현 가능)
- 하위 객체를 통해 프로토타입의 프로퍼티를 변경 또는 삭제하는 것은 불가능
- 하위 객체를 통해 프로토타입에 get 액세스는 허용 but set 액세스는 허용 X
- 프로토타입 프로퍼티를 변경 또는 삭제하려면 직접 접근해야 됨
프로토타입의 교체
- 부모 객체인 프로토타입을 동적으로 변경 가능
- 위 특징을 활용하여 객체 간의 상속 관계를 동적으로 변경 가능
- 프로토타입은 생성자 함수 또는 인스턴스에 의해 교체 가능
생성자 함수에 의한 프로토타입의 교체
- 생성자 함수로 프로토타입 교체하는 것은 미래에 생성할 인스턴스의 프로토타입 교체하는 것
const Person = (function() {
function Person(name) {
this.name = name;
}
Person.prototype = {
sayHello() {
console.log(`Hi! My name is ${this.name}`);
}
};
return Person;
}());
const me = new Person('Lee');
- Person.prototype에 { … }로 객체 리터럴을 할당 → Person 생성자 함수가 생성할 객체의 프로토타입을 객체 리터럴로 교체 → 객체 리터럴에는 constructor 프로퍼티 X ⇒ me 객체의 생성자 함수는 Person이 아닌 Object
console.log(me.constructor === Person) // false
console.log(me.constructor === Object) // true
- 프로토타입 교체 시 constructor 프로퍼티와 생성자 함수 간의 연결 파괴 ⇒ 이 연결을 되살리기 위해 프로토타입으로 교체한 객체 리터럴에 constructor 프로퍼티 추가
Person.prototype = {
// constructor 프로퍼티와 생성자 함수 간의 연결 설정
constructor: Person,
sayHello() {
console.log(`Hi! My name is ${this.name}`);
}
};
console.log(me.constructor === Person) // true
console.log(me.constructor === Object) // false
인스턴스에 의한 프로토타입의 교체
- proto 접근자 프로퍼티를 통해 프로토타입 교체하는 것은 이미 생성된 객체의 프로토타입 교체하는 것
- 인스턴스의 proto 접근자 프로퍼티나 Object.setPrototypeOf 메서드를 사용해 프로토타입 교체 가능
function Person(name) {
this.name = name;
}
const me = new Person('Lee');
const parent = {
sayHello() {
console.log(`Hi! My name is ${this.name}`);
}
}
// Object.setPrototypeOf 사용
Object.setPrototypeOf(me, parent);
// __proto__ 접근자 프로퍼티 사용
me.__proto__ = parent;
- 프로토타입으로 교체한 객체에는 constructor 프로퍼티가 없음 ⇒ constructor 프로퍼티와 생성자 함수 간의 연결 파괴 ⇒ 프로토타입의 constructor 프로퍼티로 me 객체의 생성자 함수 검색하면 Object 나옴
instanceof 연산자
객체 instanceof 생성자 함수
- 이항 연산자로 좌변에 객체의 식별자, 우변에 생성자 함수의 식별자
- 만약 우변의 피연산자가 함수가 아닌 경우 TypeError
- 우변의 생성자 함수의 prototype에 바인딩된 객체가 좌변의 객체의 프로토타입 체인 상에 존재하면 true, 아니면 false로 평가
- 프로토타입의 constructor 프로퍼티가 가리키는 생성자 함수를 찾는 것이 아닌, 생성자 함수의 prototype에 바인딩된 객체가 프로토타입 체인 상에 존재하는지 확인 ⇒ 생성자 함수에 의해 프로토타입이 교체되어 constructor 프로퍼티와 생성자 함수 간의 연결이 파괴되어도 생성자 함수의 prototype 프로퍼티와 프로토타입 간의 연결은 파괴되지 않으므로 instanceof 에 영향 X
직접 상속
Object.create에 의한 직접 상속
- Object.create 메서드는 명시적으로 프로토타입 지정하여 새로운 객체 생성
- 다른 객체 생성 방식과 마찬가지로 추상 연산 OrdinaryObjectCreate 호출
- 첫 번째 매개변수 : 생성할 객체의 프로토타입으로 지정할 객체
- 두 번째 매개변수 (옵션) : 생성할 객체의 프로퍼티 키와 프로퍼티 디스크립터 객체로 이뤄진 객체
let obj = Object.create(null);
obj = Object.create(Object.prototype, {
x: {value: 1, writable: true, enumerable: true, configurable: true}
})
console.log(obj.x); // 1
console.log(Object.getPrototypeOf(obj) === Object.prototype); // true
// 임의의 객체 직접 상속
const myProto = {x: 10};
obj = Object.create(myProto);
console.log(obj.x); // 10
console.log(Object.getPrototypeOf(obj) === myProto); // true
// 생성자 함수
function Person(name) {
this.name = name;
}
obj = Object.create(Person.prototype);
obj.name = 'Lee';
console.log(obj.name); // Lee
console.log(Object.getPrototypeOf(obj) === Person.prototype); // true
- 객체를 생성하며 직접적으로 상속 구현
- new 연산자 없이도 객체를 생성할 수 있다는 장점
- 프로토타입을 지정하며 객체를 생성할 수 있다는 장점
- 객체 리터럴에 의해 생성된 객체도 상속받을 수 있다는 장점
- Object.create 메서드를 통해 프로토타입 체인의 종점에 위치하는 객체를 생성할 수 있으므로 Object.prototype의 빌드인 메서드를 객체가 직접 호출하는 것은 권장 X
- 프로토타입 체인의 종점에 위치하는 객체는 Object.prototype의 빌트인 메서드 사용 X
⇒ Object.prototype의 빌트인 메서드는 간접적으로 호출하는 것이 좋음const obj = Object.create(null); obj.a = 1; console.log(Object.getPrototypeOf(obj) === null); // true console.log(obj.hasOwnProperty('a')); // TypeError: obj.hasOwnProperty is not a function
- const obj = Object.create(null); obj.a = 1; console.log(Object.prototype.hasOwnProperty.call(obj, 'a')); // true
객체 리터럴 내부에서 __proto__에 의한 직접 상속
- Object.create 메서드의 두 번째 인자로 프로퍼티 정의하는 것은 번거로움 ⇒ ES6에서는 객체 리터럴 내부에서 proto 접근자 프로퍼티 ㅅ용하여 직접 상속 구현 가능
const myProto = {x: 1};
const obj = {
y: 20,
__proto__: myProto
}
/*
위 코드는 아래와 동일하다.
const obj = Object.create(myProto, {
y: {value: 20, writable: true, enumerable: true, configurable: true}
});
*/
console.log(obj.x, obj.y); // 10 20
console.log(Object.getPrototypeOf(obj) === myProto); // true
정적 프로퍼티/메서드
- 정적 프로퍼티/메서드 : 생성자 함수로 인스턴스 생성하지 않아도 참조/호출할 수 있는 프로퍼티/메서드
- 생성자 함수가 생성한 인스턴스로 참조/호출 불가능
- 생성자 함수에 추가한 정적 프로퍼티/메서드는 생성자 함수로 참조/호출
- 프로토타입 메서드를 호출하려면 인스턴스를 생성해야됨 but 정적 메서드는 인스턴스 생성하지 않아도 호출 가능
- 인스턴스/프로토타입 메서드 내에서 this를 사용하지 않으면 그 메서드는 정적 메서드로 변경 가능
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
+) 프로토타입 메서드 표기 시 prototype을 #로 표기하기도 함 (Object.prototype.isPrototypeOf을 Object#isPrototypeOf로 표기)
프로퍼티 존재 확인
in 연산자
// key : 프로퍼티 키를 나타내는 문자열
// object : 객체로 평가되는 표현식
key in object
- 객체 내에 특정 프로퍼티가 존재하는지 여부 확인
const person = {
name = 'Lee',
address = 'Seoul'
}
console.log('name' in person); // true
console.log('address' in person); // true
console.log('age' in person); // false
- in 연산자는 확인 대상 객체가 상속받는 모든 프로토타입의 프로퍼티를 확인함
- in 연산자가 person 객체가 속한 프로토타입 체인 상에 존재하는 모든 프로토타입에서 toString 프로퍼티를 검색하여 true로 평가됨 (toString은 Object.prototype의 메서드)
- console.log('toString' in person); // true
- ES6에 도입된 Reflect.has 메서드도 in 연산자와 동일하게 동작
const person = {
name = 'Lee',
address = 'Seoul'
}
console.log(Reflect.has(person, 'name')); // true
console.log(Reflect.has(person, 'toString')); // true
Object.prototype.hasOwnProperty 메서드
- 객체에 특정 프로퍼티가 존재하는지 확인 가능
const person = {
name = 'Lee',
address = 'Seoul'
}
console.log(person.prototype.hasOwnProperty('name')); // true
console.log(person.prototype.hasOwnProperty('age')); // false
- 인수로 전달받은 프로퍼티 키가 객체 고유의 프로퍼티 키인 경우에만 true 반환
- 상속받은 프로토타입의 프로퍼티 키일 경우 false 반환
console.log(person.prototype.hasOwnProperty('toString')); // false
프로퍼티 열거
for … in 문
for (변수선언문 in 객체) {...}
- 객체의 모든 프로퍼티를 순회하며 열거
- 프로퍼티 키가 심벌인 키는 열거하지 X
const person = {
name = 'Lee',
address = 'Seoul'
}
for (const key in person) {
console.log(person[key]);
}
- 상속받은 프로토타입의 프로퍼티까지 열거
- 단, [[Enumerable]] 이 true여야 됨 ([[Enumerable]] : 프로퍼티의 열거 가능 여부)
⇒ for … in 문은 객체의 프로토타입 체인 상에 존재하는 모든 프로토타입의 프로퍼티 중에서 [[Enumerable]]의 값이 true인 프로퍼티를 순회하며 열거
- 상속받은 프로퍼티는 제외하고 객체 자신의 프로퍼티만 열거하려면 Object.prototype.hasOwnproperty 메서드를 사용하여 객체 자신의 프로퍼티인지 확인
const person = {
name = 'Lee',
address = 'Seoul'
}
for (const key in person) {
if (!person.prototype.hasOwnProperty(key)) continue;
console.log(person[key]);
}
- for … in 문은 프로퍼티 열거 시 순서 보장하지 X
- but 대부분의 모던 브라우저는 순서를 보장하고 프로퍼티 키가 숫자인 경우 정렬 실시
- 배열의 경우 for 문, for … of, Array.prototype.forEach 메서드 사용
Object.keys/values/entries 메서드
- for … in 문은 상속받은 프로퍼티도 열거 ⇒ 객체 자신의 고유 프로퍼티만 열거하기 위해 Object.keys/values/entries 메서드 사용
Object.keys
- 객체 자신의 열거 가능한 프로퍼티 키를 배열로 반환
const person = {
name = 'Lee',
address = 'Seoul',
__proto__ = {age: 20}
}
console.log(Object.keys(person)); // ["name", "address"]
Object.values
- 객체 자신의 열거 가능한 프로퍼티 값을 배열로 반환
console.log(Object.values(person)); // ["Lee", "Seoul"]
Object.entries
- 객체 자신의 열거 가능한 프로퍼티 키와 값의 쌍의 배열을 배열에 담아 반환
console.log(Object.entries(person)); // [["name", "Lee"], ["address", "Seoul"]]
Object.entries(person).forEach(([key, value) => console.log(key, value));
/*
name Lee
address Seoul
*/