자바스크립트는 프로토타입 기반의 강력한 객체지향 프로그래밍 언어이다.
객체지향 프로그래밍 은 명령형 프로그래밍(Imperative programming)
의 절차지향적 관점에서 벗어나 여러개의 독립적 단위 객체(Object)
의 집합으로 프로그램을 표현하려는 프로그래밍 패러다임을 말한다.
- 객체(object) - 상태 데이터와 동작을 하나의 논리적인 단위로 묶은 복합적인 자료구조
- 프로퍼티(property) - 객체의 상태
- 메서드(method) - 객체의 동작
상속(inheritance) 은 어떤 객체의 프로퍼티 또는 메서드를 다른 객체가 상속받아 그대로 사용할 수 있는 것을 말한다.
-
자바스크립트는 프로토타입(prototype) 을 기반으로 상속을 구현한다.
-
상속은 공통적으로 사용할 프로퍼티나 메서드를 한 번만 구현한 후 상속하여 사용하므로 코드 재사용 관점에서 유용하다.
function Circle(radius) { this.radius = radius; } Circle.prototype.getArea = function () { return Math.PI * this.radius ** 2; };
프로토타입 은 어떤 객체의 상위(부모) 객체의 역할을 하는 객체로서 다른 객체에 공유 프로퍼티, 메서드를 제공한다.
- 모든 객체는
[[Prototype]]
이라는 내부 슬롯을 가지며, 이 값은 프로토타입의 참조 이다. - 프로토타입은 객체 생성 방식에 의해 결정된다.
- 객체와 프로토타입과 생성자 함수는 서로 연결되어있다.
- __proto__는 접근자 프로퍼티 이다.
- 모든 객체는 __proto__ 접근자 프로퍼티 를 통해 자신의 프로토타입, 즉 [[Prototype]] 내부 슬롯에 간접적으로 접근할 수 있다.
obj.__proto__; // get __proto__ obj.__proto__ = parent; // set __proto__
- 모든 객체는 __proto__ 접근자 프로퍼티 를 통해 자신의 프로토타입, 즉 [[Prototype]] 내부 슬롯에 간접적으로 접근할 수 있다.
- __proto__ 접근자 프로퍼티는 상속을 통해 사용된다.
- __proto__는 객체가 직접 소유하는 프로퍼티가 아니라 Object.prototype의 프로퍼티이다.
- 모든 객체는 상속을 통해
Object.prototype.__proto__
접근자 프로퍼티를 사용할 수 있다.
- __proto__ 접근자 프로퍼티를 통해 프로토타입에 접근하는 이유
- 순환 참조(circular reference) 하는 프로토타입 체인이 만들어지면 체인 종점이 존재하지 않아 프로퍼티 검색시 무한 루프 에 빠진다.
- 상호 참조에 의해 프로토타입 체인이 생성되는 것을 방지하기 위해 __proto__ 접근자 프로퍼티로 체크 후 접근 및 교체할 수 있도록 구현되어있다.
child.__proto__ = parent; parent.__proto__ = child; // TypeError: Cyclic __proto__ value
- __proto__ 접근자 프로퍼티를 코드 내에서 직접 사용하는 것은 권장하지 않는다.
- __proto__는 ES6에서 표준으로 채택되었다.
- 하지만 직접 상속을 통해 Object.prototype을 상속받지 않는 객체를 생성하여 __proto__를 사용할 수 없는 경우가 있다.
- 따라서
Object.getPrototypeOf
와Object.setPrototypeOf
메서드를 사용하는 것이 좋다.const obj = Object.create(null); obj.__proto__; // undefined Object.getPrototypeOf(obj); // null, obj.__proto__; 와 동일한 동작 Object.setPrototypeOf(obj, parent); // obj.__proto__ = parent; 와 동일한 동작
함수 객체만이 소유하는 prototype 프로퍼티 는 생성자 함수가 생성할 인스턴스의 프로토타입 을 가리킨다.
non-constructor
함수 객체(화살표 함수, ES6 메서드 축약 표현)는 생성자 함수로서 호출할 수 없으므로,prototype 프로퍼티
를 소유하지 않으며 프로토타입도 생성하지 않는다.- 생성자 함수가 아닌 일반 함수도
prototype 프로퍼티
를 소유하지만 의미가 없다. - 모든 객체가 가지고 있는
__prototype__
접근자 프로퍼티와 함수 객체만이 가지고 있는 prototype 프로퍼티는 결국 동일한 프로토타입을 가리킨다. 하지만 사용 주체가 다르다.
구분 | 소유 | 값 | 사용 주체 | 사용 목적 |
---|---|---|---|---|
__proto__ 접근자 프로퍼티 | 모든 객체 | 프로토타입의 참조 | 모든 객체 | 객체가 자신의 프로토타입에 접근 또는 교체하기 위해 사용 |
prototype 프로퍼티 | constructor | 프로토타입의 참조 | 생성자 함수 | 생성자 함수가 자신이 생성할 객체(인스턴스)의 프로토타입을 할당하기 위해 사용 |
- 모든 프로토타입은
constructor
프로퍼티를 갖는다. - 프로토타입의
constructor
프로퍼티는 자신을 참조하고 있는 생성자 함수 를 가리킨다. - 프로토타입을 상속받은 객체 역시
constructor
프로퍼티로 생성자 함수에 접근 할 수 있다.
function Person(name) {
this.name = name;
}
const me = new Person('Lee');
console.log(me.constructor === Person); // true
- 리터럴 표기법에 의한 객체 생성과 같이, 생성자 함수를 명시적으로 호출하지 않고 객체를 생성할 수 있다.
- 리터럴 표기법에 의해 생성된 객체도 상속을 위해 프로토타입이 필요한데, 프로토타입과 생성자 함수는 언제나 쌍으로 존재하며 연결되어으므로 가상적인 생성자 함수 를 갖는다.
- 이 경우 객체를 직접 생성하지 않은 생성자 함수 가 프로토타입의
constructor
프로퍼티의 값이 된다. - 결과적으로는 해당 생성자 함수로 직접 생성한 객체와, 직접 생성한 객체는 본질적인 특성 면에서 큰 차이가 없다.
const obj = {};
obj.constructor === Object; // true, Object 생성자 함수로 생성하지 않았음에도 constructor가 Object를 가리킨다.
리터럴 표기법 | 생성자 함수 | 프로토타입 |
---|---|---|
객체 리터럴 | Object | Object.prototype |
함수 리터럴 | Function | Function.prototype |
배열 리터럴 | Array | Array.prototype |
정규 표현식 리터럴 | RegExp | RegExp.prototype |
프로토타입 은 생성자 함수가 생성되는 시점 에 더불어 생성된다.
- 생성자 함수로서 호출할 수 있는 함수, 즉
constructor
는 함수 객체를 생성하는 시점에 프로토타입도 생성된다. - 함수 선언문은 런타임 이전 에 호이스팅되어 객체가 되므로, 해당 시점에 사용자 정의 생성자 함수의 프로토타입도 생성된다.
- 사용자 정의 생성자 함수의 프로토타입의 프로토타입은 언제나
Object.prototype
이다.
Object
,String
,Number
,Function
,Array
,RegExp
,Date
,Promise
등은 빌트인 생성자 함수이다.- 빌트인 생성자 함수도 함수 생성 시점에 프로토타입이 생성된다.
- 모든 빌트인 생성자 함수는 전역 객체*가 생성되는 시점 에 생성된다.
* 전역 객체 - 코드 실행 이전 자바스크린트 엔진에 의해 생성되는 특수한 객체. 브라우저에서는 window
, Node에서는 global
객체를 의미.
생성자 함수와 프로토타입은 이미 객체화되어 존재한다.
객체를 생성하면 프로토타입이 생성된 객체의 [[Prototype]] 내부 슬롯에 할당된다.
객체 생성 방법에는 객체 리터럴
, Object 생성자 함수
, 생성자 함수
, Object.create 메서드
, 클래스(ES6)
가 있다.
- 각 객체 생성 방법은 내부적으로 추상 연산
OrdinaryObjectCreate
에 의해 생성된다는 공통점이 있다. OrdinaryObjectCreate
는 생성할 객체의 프로토타입을 인수로 전달받고, 객체를 생성하여 [[Prototype]] 내부 슬롯에 할당하여 반환한다.- 따라서 프로토타입은 추상 연산
OrdinaryObjectCreate
에 전달되는 인수에 의해 결정되고, 인수는 객체가 생성되는 시점에 객체 생성 방식 에 의해 결정된다.
객체 리터럴을 평가하여 객체를 생성할 때 추상연산 OrdinaryObjectCreate
를 호출하며, Object.prototype
이 프로토타입으로 전달된다.
const obj = { x: 1 };
// 객체 리터럴에 의해 생성된 obj 객체는 Object.prototype을 상속받는다.
console.log(obj.constructor === Object); // true
console.log(obj.hasOwnProperty('x')); // true
Object 생성자 함수를 호출하면 객체 리터럴과 마찬가지로 OrdinaryObjectCreate
를 호출하며, Object.prototype
이 프로토타입으로 전달된다.
- 결과적으로 객체 리터럴에 의해 생성된 객체와 동일한 구조를 갖는다.
- 객체 리터럴은 리터럴 내부에서 프로퍼티를 추가하지만, Object 생성자 함수 방식은 빈 객체를 생성한 이후 프로퍼티를 추가한다는 차이점이 있다.
const obj = new Object(); obj.x = 1;
new
연산자와 함께 생성자 함수를 호출하여 인스턴스를 생성하면 다른 객체 생성 방식과 마찬가지로 OrdinaryObjectCreate
를 호출한다.
이때 OrdinaryObjectCreate
에 전달되는 프로토타입은 생성자 함수의 prototype 프로퍼티 에 바인딩 되어있는 객체이다.
- Object.prototype은 다양한 빌트인 메서드를 갖고있다. 하지만 사용자 정의 생성자 함수와 그 프로토타입은 이를 포함하지 않는다.
- 프로토타입에 프로퍼티를 추가/삭제 할 수 있으며, 이는 프로토타입 체인에 즉각 반영 된다.
function Person(name) { this.name = name; } // 프로토타입 메서드 추가 Person.prototype.sayHello = function () { console.log(`Hi! My name is ${this.name}`); };
- 프로토타입 체인 은 자바스크립트가 객체의 프로퍼티 및 메서드에 접근하려고 할 때, 해당 객체에 접근하려는 프로퍼티가 없을 경우 [[Prototype]] 내부 슬롯의 참조를 따라 부모 역할을 하는 프로토타입의 프로퍼티를 순차적으로 검색하는 것이다.
- 프로토타입 체인은 자바스크립트가 객체지향 프로그래밍의 상속을 구현하는 매커니즘이다.
function Person(name) {
this.name = name;
}
const me = new Person('Lee');
// hasOwnProperty는 Object.prototype의 메서드다.
console.log(me.hasOwnProperty('name')); // true
me.hasOwnProperty('name')과 같이 메서드를 호출하면 자바스크립트 엔진은 다음과 같은 과정을 거쳐 메서드를 검색한다.
- 메서드를 호출한 me 객체에서
hasOwnProperty
메서드를 검색한다. me 객체에 없으므로 프로토타입 체인을 따라, [[Prototype]] 내부 슬롯에 바인딩되어 있는 프로토타입Person.prototype
으로 이동하여 검색한다. Person.prototype
에도hasOwnProperty
메서드가 없으므로 프로토타입 체인을 따라, [[Prototype]] 내부 슬롯에 바인딩되어있는Object.prototype
로 이동하여 검색한다.Object.prototype
에는hasOwnProperty
가 존재한다.
Object.prototype.hasOwnProperty
를 호출한며, 아래와 같이 this에 me 객체가 바인딩된다.Object.prototype.hasOwnProperty.call(me, 'name');
- 프로토타입 체인의 최상위에 위치한 객체는 언제나
Object.prototype
이다. 따라서Object.prototype
를 프로토타입 체인의 종점(end-of-prototype chain) 이라한다. - 프로토타입 체인의 종점인
Object.prototype
에서도 프로퍼티를 검색할 수 없는 경우undefined
를 반환하며, 에러가 발생하지 않는다.
- 프로토타입 체인의 최상위에 위치한 객체는 언제나
- 프로토타입 체인 은 상속과 프로퍼티 검색을 위한 메커니즘이다.
- 스코프 체인 은 식별자 검색을 위한 메커니즘이다.
- 스코프 체인과 프로토타입 체인은 별도로 동작하는 것이 아니라 서로 협력하여 식별자와 프로퍼티를 검색하는 데 사용된다.
ex) 예시에서 me.hasOwnProperty('name')을 호출하면 스코프체인에서 me를 먼저 찾은 후 프로토타입에서 hasOwnProperty를 찾는다.
- 프로토타입 프로퍼티 - 프로토타입이 소유한 프로퍼티(메서드 포함)
- 인스턴스 프로퍼티 - 인스턴스가 소유한 프로퍼티(메서드 포함)
- 프로토타입 프로퍼티와 같은 이름의 프로퍼티를 인스턴스에 추가하면 덮어쓰는 것이 아니라 새로운 인스턴스 프로퍼티로 추가된다.
- 이때 인스턴스 메서드가 프로토타입 메서드를 오버라이딩 했다고 한다.
- 프로토타입 메서드는 인스턴스 메서드에 가려지며 이러한 현상을 프로퍼티 섀도잉(property shadowing) 라고 한다.
const Person = (function () {
function Person(name) {
this.name = name;
}
// 프로토타입 메서드
Person.prototype.sayHello = function () {
console.log(`Hi! My name is ${this.name}`);
};
return Person;
})();
const me = new Person('Lee');
// 인스턴스 메서드
me.sayHello = function () {
console.log(`Hey! My name is ${this.name}`);
};
// 인스턴스 메서드가 호출된다. 프로토타입 메서드는 인스턴스 메서드에 의해 가려진다.
me.sayHello(); // Hey! My name is Lee
- 프로퍼티 삭제 역시 인스턴스 프로퍼티를 삭제하며, 프로토타입 프로퍼티에는 영향을 끼치지 않는다.
- 프로토타입의 프로퍼티를 삭제하려면 프로토타입에 직접 접근해야한다.
// 인스턴스 메서드를 삭제한다.
delete me.sayHello;
me.sayHello(); // Hi! My name is Lee
// 프로토타입 체인을 통해 프로토타입 메서드가 삭제되지 않는다.
delete me.sayHello;
me.sayHello(); // Hi! My name is Lee
delete Person.prototype.sayHello;
me.sayHello(); // TypeError: me.sayHello is not a function
프로토타입은 임의의 다른 객체로 변경할 수 있다. 따라서 객체 간의 상속 관계를 동적으로 변경 할 수 있다.
프로토타입은 생성자 함수 또는 인스턴스 에 의해 교체할 수 있다.
- 생성자 함수의 prototype 프로퍼티 를 통해 프로토타입을 교체할 수 있다.
- 자바스크립트 엔진은 프로토타입을 생성할 때
constructor
프로퍼티를 암묵적으로 추가하지만, 직접 프로토타입을 생성하여 교체할 경우constructor
프로퍼티가 없어, 프로퍼티와 생성자 함수간의 연결이 파괴될 수 있다. - 아래와 같이
constructor
프로퍼티를 추가하여 연결을 되살릴 수 있다.const Person = (function () { function Person(name) { this.name = name; } Person.prototype = { // constructor 프로퍼티와 생성자 함수 간의 연결을 설정 constructor: Person, sayHello() { console.log(`Hi! My name is ${this.name}`); }, }; return Person; })();
- 인스턴스의 __proto__ 접근자 프로퍼티(혹은 Object.setPrototype 메서드)를 통해 프로토타입을 교체할 수 있다.
- 생성자 함수에 의한 프로토타입 교체와 같이
constructor 프로퍼티
의 연결이 파괴되므로 연결을 설정해주어야한다. - 인스턴스에 의한 프로토타입의 교체는 생성자함수의
prototype
프로퍼티가 교체된 프로토타입을 가리키지 않는다. 따라서prototype
프로퍼티와의 연결도 설정해주어야한다.const parent = { constructor: Person, sayHello() { console.log(`Hi! My name is ${this.name}`); }, }; // 생성자 함수의 prototype 프로퍼티와 프로토타입 연결 Person.prototype = parent; // me 객체의 프로토타입을 parent 객체로 교체한다. // me.__proto__ = parent; 와 동일하게 동작한다. Object.setPrototypeOf(me, parent);
프로토타입 교체를 통해 객체간의 상속 관계를 동적으로 변경하는 것은 번거로우므로 지양하는 것이 좋다.
후의 직접 상속
이나 ES6에서 추가된 class
를 사용하는 것이 좋다.
-
instanceof
연산자는 이항 연산자로서 좌변에 객체 를 가리키는 식별자, 우변에 생성자 함수 를 가리키는 식별자를 피연산자로 받는다.객체 instanceof 생성자함수;
-
우변의 피연산자가 함수가 아닌경우
TypeError
가 발생한다. -
우변의 생성자 함수의 prototype 에 바인딩된 객체가 좌변의 객체의 프로토타입 체인 상에 존재하면
true
로 평가되고, 그렇지 않으면false
로 평가된다.constructor 프로퍼티
와 생성자 함수간의 연결이 파괴되어도, 생성자함수의 prototype 프로퍼티와 프로토타입 간의 연결만 유효하면 instanceof는 영향을 받지 않는다.
Object.create
메서드는 명시적으로 프로토타입을 지정하여 새로운 객체를 생성한다.Object.create
역시 다른 객체 생성 방식처럼 추상 연산OrdinaryObjectCreate
를 호출한다.Object.create
의 첫 번째 매개변수에 생성할 객체의 프로토타입으로 지정할 객체를 전달하며 이는OrdinaryObjectCreate
로 전달된다.
// obj → Person.prototype → Object.prototype → null
function Person(name) {
this.name = name;
}
// obj = new Person('Lee')와 동일하다.
obj = Object.create(Person.prototype);
obj.name = 'Lee';
Object.create
메서드는 객체를 생성하면서 직접적으로 상속을 구현한며 장점 은 다음과 같다.
- new 연산자 없이도 객체를 생성할 수 있다.
- 프로토타입을 지정하면서 객체를 생성할 수 있다.
- 객체 리터럴에 의해 생성된 객체도 상속받을 수 있다.
Object.create
메서드는 프로토타입 체인의 종점에 위치하는 객체를 생성할 수 있다.
-
이러한 객체는
Object.prototype
의 빌트인 메서드를 사용할 수 없고 오류 를 야기한다. -
따라서
ESLint
에서는 빌트인 메서드를 객체가 직접 호출하는 것을 권장하지 않고 아래와 같이 간접적으로 호출하도록 권장한다.const obj = Object.create(null); obj.a = 1; console.log(obj.hasOwnProperty('a')); // TypeError: obj.hasOwnProperty is not a function console.log(Object.prototype.hasOwnProperty.call(obj, 'a')); // true
Object.create
메서드에 의한 직접 상속은 여러 장점이 있지만, 두 번째 인자로 프로퍼티를 정의하는 것이 번거롭다.- 객체를 생성한 이후 프로퍼티를 추가할 수 있으나 이 또한 깔끔하지 않다.
const obj = Object.create(myProto, {
y: { value: 20, writable: true, enumerable: true, configurable: true },
});
ES6 에서는 객체 리터럴 내부에서 __proto__ 접근자 프로퍼티 를 사용하여 직접 상속을 구현할 수 있다.
// obj → myProto → Object.prototype → null
const myProto = { x: 10 };
const obj = {
y: 20,
__proto__: myProto,
};
정적(static) 프로퍼티/메서드 는 생성자 함수 객체가 소유한 프로퍼티/메서드를 말한다.
- 정적 프로퍼티/메서드는 생성자 함수 접근을 통해 바로 참조 및 호출할 수 있다.
- 생성자 함수가 생성한 인스턴스로는 참조 및 호출할 수 없다.
- 생성자 함수가 생성한 인스턴스는 자신의 프로토타입 체인에 속한 객체의 프로퍼티/메서드에만 접근할 수 있으며, 정적 프로퍼티/메서드는 프로토타입 체인에 속하지 않기 때문이다.
Object.create는 Object 생성자 함수의 정적 메서드이고 Object.prototype.hasOwnProperty 메서드는 프로토타입 메서드이다.
따라서 Object 생성자로 생성한 객체는 create는 호출할 수 없지만 hasOwnProperty 메서드는 호출할 수 있다.
// 생성자 함수
function Person(name) {
this.name = name;
}
Person.staticProp = 'static prop'; // 정적 프로퍼티
Person.staticMethod = function () {
console.log('staticMethod');
}; // 정적 메서드
const me = new Person('Lee');
Person.staticMethod(); // staticMethod
me.staticMethod(); // TypeError: me.staticMethod is not a function
- 인스턴스/프로토타입 메서드 내에서
this
를 사용하지 않는다면, 그 메서드는 정적 메서드로 변경하여도 동작한다.
function Foo() {}
// 프로토타입 메서드
Foo.prototype.x = function () {
console.log('x');
};
// 프로토타입 메서드를 호출하려면 인스턴스를 생성해야 한다.
const foo = new Foo();
foo.x(); // x
// 정적 메서드
Foo.x = function () {
console.log('x');
};
Foo.x(); // x
- 문서들에서
Object.prototype.isPrototype
를Object#isPrototype
로 표기하는 경우도 있다.
in
연산자는 객체 내에 특정 프로퍼티가 존재하는지 여부 를 확인한다.
key in object;
-
in
연산자는 확인 대상 객체의 프로퍼티뿐만 아니라 상속받은 모든 프로토타입의 프로퍼티를 확인 한다.console.log('toString' in person); // true, toString은 Object.prototype의 메서드이다.
-
in
연산자 대신 ES6에서 도입된Reflect.has
메서드를 사용할 수 있으며 동일하게 동작한다.console.log(Reflect.has(person, 'toString')); // true
- Object.prototype.hasOwnProperty 메서드를 사용하여도 객체에 특정 프로퍼티가 존재하는지 확인할 수 있다.
- 앞의
in
연산자,Reflect.has
메서드와의 차이점은 프로퍼티 키가 객체 고유의 프로퍼티 키인 경우에만true
를 반한한다는 것이다.console.log(person.hasOwnProperty('name')); // true console.log(person.hasOwnProperty('age')); // false
객체의 모든 프로퍼티를 순회하며 열거(enumeration) 할 때 사용한다.
for (변수선언문 in 객체) {...}
for...in
문은 객체의 프로토타입 체인 상에 존재하는 모든 프로토타입의 프로퍼티 중에서 프로퍼티 어트리뷰트 [[Enumerable]]의 값이 true인 프로퍼티를 순회하며 열거(enumeration)한다.for...in
문은 객체의 프로퍼티 개수만큼 순회하며for...in
문의 변수 선언문에서 선언한 변수에 프로퍼티 키를 할당한다.const sym = Symbol(); const obj = { name: 'Lee', address: 'Seoul', __proto__: { age: 20 }, [sym]: 10, }; for (const key in obj) { console.log(key + ': ' + obj[key]); } // name: Lee // address: Seoul // age: 20
- 프로퍼티 키가 심벌인 프로퍼티는 열거하지 않는다.
for...in
문은 in 연산자처럼 순회 대상 객체의 프로퍼티뿐만 아니라 상속받은 프로토타입의 프로퍼티까지 열거한다.- 하지만
Object.prototype
의 프로퍼티는 열거되지 않는다. 해당 프로퍼티들의 프로퍼티 어트리뷰터 [[Enumerable]]의 값이false
로 열거 할 수 없도록 정의되어있기 때문이다.console.log(Object.getOwnPropertyDescriptor(Object.prototype, 'toString')); // {value: ƒ, writable: true, enumerable: false, configurable: true}
- 상속받은 프로퍼티를 제외하고 객체 자신의 프로퍼티만 열거하려면
Object.prototype.hasOwnProperty
메서드를 사용하여 객체 자신의 프로퍼티인지 확인해야한다.for (const key in person) { // 객체 자신의 프로퍼티인지 확인한다. if (!person.hasOwnProperty(key)) continue; console.log(key + ': ' + person[key]); }
for...in
문은 프로퍼티를 열거할 때 순서를 보장하지 않으므로 주의해야한다.- 대부분의 모던 브라우저는 순서를 보장하고, 숫자 형태인 프로퍼티 키에 대해서는 정렬을 실시한다.
- 배열에는
for...in
보다는 후에 나올for...of
문 또는Array.prototype.forEach
를 사용하는 것을 권장한다.
객체 고유의 프로퍼티만 열거하기 위해서는 for...in
문과 hasOwnProperty
메서드를 조합하여 사용하는 것 보다, Object.keys/values/entries
메서드를 사용하는 것을 권장한다.
- Object.keys 메서드는 객체 자신의 열거 가능한(enumerable) 프로퍼티 키 를 배열로 반환한다.
- ES8에서 도입된 Object.values 메서드는 객체 자신의 열거 가능한 프로퍼티 값 을 배열로 반환한다.
- ES8에서 도입된 Object.entries 메서드는 객체 자신의 열거 가능한 프로퍼티 키와 값의 쌍의 배열 을 배열에 담아 반환한다.
const person = {
name: 'Lee',
address: 'Seoul',
__proto__: { age: 20 },
};
console.log(Object.keys(person)); // ["name", "address"]
console.log(Object.values(person)); // ["Lee", "Seoul"]
console.log(Object.entries(person)); // [["name", "Lee"], ["address", "Seoul"]]