20210706 JavaSciprt DeepDive 04 : 프로토타입 객체, 생성시점, 생성방식, 교체, 체인, 프로퍼티 섀도잉, 교체, instanceof 연산자, 직접상속, 정적 프로퍼티/메서드, 프로퍼티 존재 확인(in 연산자, hasOwnProperty메서드), 프로퍼티 열거(for…in, Object.keys/values/entries 메서드)
JavaScript Deep Dive 04
용어 및 중요사항 정리
프로토타입
멀티 패러다임 프로그래밍 언어
: 자바스크립트는 명령형, 함수형, 프로토 타입기반 객체지향 프로그래밍을 지원하는 언어임객체지향 프로그래밍
: 독립적인 객체의 집합으로 객체의 상태 데이터(프로퍼티), 상태를 조작하는 동작(메서드)을 하나의 논리적 단위로 묶어 프로그램을 표현하려는 프로그래밍 패러다임추상화
: 필요한 속성만 간추려 내어 표현하는 것객체
: 속성을 통해 여러개의 값을 하나의 단위로 구성한 복합적인 자료구조
상속
: 어떤 객체의 프로퍼터, 메서드를 다른 객체가 상속받아 그대로 사용할 수 있는 것- 자바스크립트는 프로토 타입을 기반으로 상속을 구현
- 생성자 함수의 prototype이 인스턴스들의 부모 객체 역할을 하게 하여 메서드를 넣고 상속받게 하여 다른 인스턴스가 사용할 수 있게함
- 불필요한 중복 제거 -> 코드 재사용
메서드 중복 생성
: 생성자 함수를 통해서 인스턴스를 찍어 낼 때마다 같은 메서드를 생성하게됨 -> 메모리 낭비 (한번 생성해서 공유하여 사용하는게 바람직함)- 프로퍼티의 경우에는 값이 달라지니까 인스턴스마다 다르지만, 메서드는 동작방식이 같기 때문에 중복이 발생함
프로토타입 객체
생성자 함수의 prototype 프로퍼티
: 생성자 함수는 자신의 prototype 프로퍼티로 프로토타입 객체에 접근 가능
프로토타입(프로토타입 객체)
: 어떤 객체의 부모 객체 역할을 하는 객체로 다른 객체에 공유 프로퍼티,메서드를 제공함으로서 객체간 상속을 구현하기 위해 사용됨- 프로토타입은 생성자 함수와 연결되어 있음
constructor 프로퍼티
: 프로토타입은 자신의 constructor를 통해서 생성자 함수에 접근 가능
객체(인스턴스)
[[Prototype]] 내부 슬롯
: 모든 객체에 있는 내부 슬롯으로 객체생성방식에 따라 프로토타입이 결정되면서 이 슬롯에 프로토타입이 저장됨__proto__ 접근자 프로퍼티
: [[Prototype]] 내부 슬롯이 가리키는 프로토타입에 간접적으로 접근할수 있는 접근자- 모든 객체가 직접 소유하는게 아니라 Object.prototype의 프로퍼티에게 상속 받아 사용함
- __proto__ 접근자 프로퍼티로 프로토타입 접근시
get __proto__ 함수
가 호출 - __proto__ 접근자 프로퍼티에 새로운 프로토타입 할당시
set __proto__ 함수
가 호출 - __proto__ 접근자 프로퍼티 사용은 상호참조에 의해 프로토타입 체인이 생성되는 것을 방지 하기 위함 (프로토타입 체인은 단방향 링크드 리스트로 구현되어야 함, 순환참조로 되면 프로퍼티 검색시 무한 루프에 빠짐)
프로토타입의 참조 얻기, 교체하기
:- __proto__ 접근자 프로퍼티 사용은 권장하지 않음(사용불가한 객체도 있기 때문)
- 대신,
Object.getPrototypeOf(), Object.setPrototypeOf()
사용 권장
함수 객체(생성자 함수)의 prototype 프로퍼티
:- 함수객체만 가진 프로퍼티로 생성자 함수가 생성할 인스턴스의 프로토타입을 가르킴
- non-constructor인 화살표 함수와 메서드 축약 표현으로정의한 메서드는 prototype을 소유하지 않고, 프로토타입도 없다.
- 모든 객체가 가진 __proto__ 접근자 프로퍼티와 함수 객체만이 가지고 있는 prototype 프로퍼티는 사용주체만 다르고 동일한 프로토타입을 가르킴
- 생성자함수의 프로토타입 = 프로토타입(객체) = 인스턴스의 __proto__ 접근자 프로퍼티
프로토타입의 constructor 프로퍼티
: 모든 프로토 타입은 constructor 프로퍼티를 갖고, constructor 프로퍼티는 prototype 프로퍼티로 자신을 참조하고 있는 생성자 함수를 가르킴- 프로토타입과 생성자 함수는 단독으로 존재할 수 없고 언제나 쌍으로 존재함
리터럴 표기법에 의해 생성된 객체의 생성자 함수와 프로토 타입
- new 연산자와 함께 생성자 함수를 호출하지 않고 객체 인스턴스를 생성하는 리터럴 표기법에 의해 생성된 객체도 프로토타입이 존재함
- 리터럴 표기법에 의해 생성된 객체의 프로토타입의 constructor는 Object 생성자 함수를 가르킴
- 하지만, 리터럴 표기법으로 객체를 생성하는게 Object 생성자로 객체가 생성되는 것은 아니고
추산연산 OrdinaryObjectCreate()
를 호출하여 Object.prototype을 프로토 타입으로 갖는 빈객체를 생성함 - Object 생성자 또한 인자를 갖지 않고 생성하는 경우에는 추산연산을 사용해서 빈객체를 생성함
- Function 생성자 함수도 똑같은 매커니즘으로 함수 선언문과 함수 표현식을 통해 만든 함수객체도 Function 생성자로 만들지 않았지만, contructor는 Function 생성자 함수를 가르킴
- Function 생성자 함수의 함수객체 vs 함수 선언문과 함수표현식의 함수객체 -> 생성과정과 스코프, 클로저 등의 차이를 갖게됨
프로토타입의 생성시점
- 프로토타입은 생성자 함수가 생성되는 시점에 더불어 생성됨(언제나 쌍으로 존재하기 때문)
사용자 정의 생성자함수의 프로토타입 생성 시점
:- 생성자 함수가 정의되어 런타임 전에 평가되어 함수객체가 생성 -> 프로토타입 생성됨(consturctor에 생성자 함수 바인딩) -> 함수객체의 prototype 프로퍼티에 프로토타입 바인딩
- non-constructor는 프로토타입이 생성되지 않음 (화살표 함수)
- 생성된 프로토타입의 프로토타입은 언제나 Object.protype
빌트인 생성자 함수의 프로토타입 생성 시점
:전역객체
: 코드가 실행되기 이전 단계에 생성되는 특수 객체 (window, global)- 전역객체가 생성되는 시점에 빌트인 생성자 생성 -> 빌트인 생성자의 프로토타입이 생성 -> 빌트인 생성자 함수의 prototype 프로퍼티에 프로토타입이 바인딩
- 공통적으로, 해당 객체가 생성되기전에 생성자함수, 프로토타입은 객체화 되어 생성됨
- 그리고 이 프로토타입은 생성자함수 또는 리터럴 표기법으로 인스턴스를 생성하면, 생성된 인스턴스 객체의 [[Prototype]] 슬롯에 할당됨
객체 생성 방식과 프로토타입의 결정
객체 생성 방식
- 객체리터럴, Object 생성자함수, 사용자 생성자 함수, Object.create메서드, 클래스
- 해당 방식 모두 OrdinaryObjectCreate에 의해 생성됨
OrdinaryObjectCreate
는 두가지 인수를 받아 처리하여 객체를 생성함- 생성할 객체의 프로토타입을 인수로 받음 -> 생성한 객체의 [[Prototype]]슬롯에 할당
- 생성할 객체에 추가할 프로퍼티 목록을 인수로 받음 -> 객체에 프로퍼티를 추가함
- OrdinaryObjectCreate가 생성자함수, 프로토타입 - 객체 사이의 연결을 담당
객체리터럴에 의해 생성된 객체의 프로토타입
- JS엔진이 객체리터럴 평가하여 객체 생성시 OrdinaryObjectCreate를 호출
- 인수로 Object.prototype을 프로토타입으로 전달
- Object 생성자 함수 - Object.prototype - 객체
- 프로퍼티 추가시 객체 리터럴 내부에서 추가(빈 객체인 상태 없이)
Object 생성자 함수에 의해 생성된 객체의 프로토타입
- Object 생성자 함수 호출시 OrdinaryObjectCrate를 호출
- Object.prototype을 프로토타입으로 전달
- Object 생성자 함수 - Object.prototype - 객체
- 객체리터럴에 의해 생성된 객체와 동일한 구조
- 프로퍼티 추가시 빈객체인 상태에서 프로퍼티를 추가
사용자 생성자 함수에 의해 생성된 객체의 프로토타입
- new 연산자와 함께 생성자 함수를 호출하여 인스턴스 생성시 OrdinaryObjectCrate를 호출
- 생성자 함수의 prototype 프로퍼티에 바인딩 되어 있는 객체가 프로토타입으로 전달됨
- 사용자 생성자 함수 - 사용자 생성자 함수.prototype - 인스턴스 객체
- 해당 사용자 생성자 함수.prototype은 constructor 프로토타입만 가지고, 빌트인 메서드는 없음 (하지만, 프로토타입 체인으로 사용할 수 있음)
- 그래서 사용자 생성자 함수.prototype에 프로퍼티를 추가하여 하위 인스턴스가 상속받아 사용할 수 있는 프로퍼티, 메소드를 구현할 수 있고 구현시 프로토타입 체인에 즉각 반영됨
프로토타입 체인
프로토타입 체인
: 객체의 프로퍼티(메서드)에 접근시 해당 객체에 접근하려는 프로퍼티가 없으면 [[Prototype]] 내부 슬롯의 참조를 따라서 자신의 부모역할을 하는 프로토타입의 프로퍼티를 순차적으로 검색해 올라가는 것을 말함 (상속과 프로퍼티 검색을 위한 매커니즘)프로토타입 체인의 종점
: 프로토타입 체인의 최상위의 객체는 언제나 Object.prototype이기 때문에 모든 객체는 상속받아 사용이 가능하고 이를 종점이라고 함- Object.prototype의 [[Prototype]] 내부슬롯은 null이고 종점까지 프로퍼티 검색을 못하면 undefined를 반환 받음 (에러가 없음에 주의)
- 스코프 체인은 식별자를 찾는 매커니즘으로, 스코프체인과 프로토타입체인이 협력하여 식별자와 프로퍼티를 검색하는데 사용됨
call 메서드
: this로 사용할 객체를 전달하면서 함수를 호출함(사용못하는 함수를 연결해주는 느낌)
오버라이딩과 프로퍼티 섀도잉
프로토타입 프로퍼티
: 프로토타입이 가진 프로퍼티인스턴스 프로퍼티
: 인스턴스가 가진 프로퍼티오버라이딩
: 상위 클래스가 가지고 있는 메서드를 하위 클래스가 재정의하여 사용하는 방식- 프로토타입 프로퍼티와 같은 이름의 프로퍼티를 인스턴스에 추가하는 경우 프로토타입 프로퍼티를 덮어쓰지 않고, 인스턴스 프로퍼티에 추가되어 검색시 인스턴스 프로퍼티를 먼저 참조하는 현상
- 다만, 하위 객체에서 프로토타입의 프로퍼티 변경 또는 삭제는 불가능함(get은 가능하나, set은 불가능함)
- 변경,삭제하고 싶다면, 직접 프로토타입에 접근해야함
프로퍼티 섀도잉
: 오버라딩이에 의해서 프로토타입 프로퍼티가 가려지는 현상오버로딩
: 함수의 이름은 동일하지만 매개변수의 타입 또는 개수가 다른 메서드를 구현하고 매개변수에 의해 메서드를 구별하여 호출하는 방식(자바스크립트에서는 arguments 객체를 사용하여 구현가능)
프로토타입 교체
프로토타입 교체
: 프로토타입(부모 객체)을 동적으로 임의의 다른 객체로 변경가능함- 생성자함수 또는 인스턴스에 의해 교체 할수 있음
생성자 함수에 의한 교체
: 미래에 생성할 인스턴스의 프로토타입 교체- 생성자함수로 prototype 프로퍼티에 접근해서 객체리터럴을 할당
- constructor 프로퍼티 없는 객체 리터럴을 할당하여 교체하면, 해당 객체리터럴이 암묵적으로 가진 프로토타입인 Object.prototype이 상위 프로토타입이 되어 생성자함수와의 연결이 끊어지고, Object.prototype의 contructor인 Objcect 생성자가 연결됨
- 생성자 함수를 가르키도록하는 constructor 프로퍼티를 추가하여 생성자 함수와 연결해주어야 함
인스턴스에 의한 교체
: 이미 생성된 인스턴스의 프로토타입 교체Object.setPrototypeOf(인스턴스. 객체리터럴)
를 활용해 인스턴스의 __proto__ 접근자로 접근하여, 교체할 객체 리터럴을 할당함- 인스턴스 방식 또한 constructor 프로퍼티가 없는 객체리터럴을 할당시키면, 생성자 함수와의 연결이 끊어짐으로 생성자 함수를 가르키는 constructor 프로퍼티를 추가한 객체리터럴을 할당해야한다.
- 프로토타입 교체를 통한 상속 관계를 동적으로 변경하는 것은 번거러우니, 직접교체하지 말고
직접 상속
방법을 사용해야 더 편리하고 안전함
instanceof 연산자
객체 instanceof 생성자 함수
- 우변에 함수가 아니면 typeError 발생
- 우변에 생성자 함수의 prototype에 바인딩된 객체가 좌변의 객체의 프로토타입 체인상에 존재하면 true, 아니면 false로 평가
- 사용자 생성자 함수의 프로토타입의 경우 cunstructor로 사용자 생성자함수와 연결되기었기도 하고, 프로토타입도 객체라서 __proto__ 프로퍼티로 Object 생성자 함수에도 연결 되어 있음 -> instnaceof 확인하면 사용자 생성자함수, Object 생성자함수 둘다 true임
- instanceof는 프로토타입 체인을 확인할 수 있게 도와줌 (양방향 연결을 조건으로 하지 않고 단방향 연결을 조건으로 함)
- 교체된 프로토타입의 constructor에 사용자 생성자함수가 연결되지 않아도, 사용자 생성자함수에 교체된 프로토타입이 연결되어 있다면, instanceof는 true임
직접 상속
Object.create에 의한 직접 상속
- Object.create 호출시 OrdinaryObjectCreate를 호출함
- 명시적으로 프로토타입을 지정하여 새로운 객체를 생성함
Object.create(상속 받을 프로토타입 객체, {프로퍼티 키: 프로퍼티 디스크립터 객체})
- 두번째 인수는 옵션으로 생략 가능
- 상속 받을 프로토타입 객체의 프로토타입 체인에 속하는 객체를 생성함
- 객체를 생성하면서, 동시제 직접적으로 상속을 구현할 수 있음
- 상속받을 프로토타입을 null을 넣게 되면 프로토타입의 종점이 되어 Object.prototype의 빌트인 메서드를 사용할 수 없음
- ESLint에서는 객체가 Object.prototype의 빌트인 메서드를 직접 호출하는 것을 권장하지 않음 -> Object.create를 생성해서 종점 객체를 생성 함으로서 편하게 Object.prototype 빌트인 메서드를 사용할수 없는 상황을 만들기 때문에
- 그래서 간접 호출로
call
을 활용함
Object.create의 장점
- new 연산자 없이 객체 생성
- 프로토타입 지정하면서 객체생성
- 객체 리터럴에 의해 생성된 객체도 상속 받을 수 있음
객체 리터럴 내부에서 __proto__에 의한 직접 상속
- Object.create 메서드를 통해서 두번째 인자로 프로퍼티를 정의하는 것은 번거로움
- ES6에서는 객체 리터럴 내부에서 __proto__접근자 프로퍼티를 사용해서 직접 상속 구현가능
- 객체 리터럴 방식으로 만들려는 객체에 직접적으로 __proto__접근자 프로퍼티에 상속받을 프로토타임을 할당하여 객체를 생성하면서 프로토타입을 지정할 수 있음
정적 프로퍼티/메서드
정적(static)프로퍼티/메서드
: 생성자 함수로 인스턴스를 생성하지 않아도 참조/호출 할수 있는 프로퍼티/메서드를 말함- 사용자 생성자 함수는 객체이므로 자신의 프로퍼티/메서드를 소유할 수 있는데 이렇게 사용자 생성자 함수가 가진 프로퍼티/메서드를 말함
- 생성자 함수 정의시 this를 사용한 프로퍼티/메서드는 인스턴스가 가질 프로퍼티/메서드로서 들어가는 것을 정의하는 것임
- 생성자 함수 정의시 this를 사용하지 않은 프로퍼티/메서드는 사용자 생성자 함수 자신의 프로퍼티/메서드로 정적 프로퍼티/메서드임
- 사용자 생성자 함수만 참조/호출가능하고 인스턴스는 참조/호출이 불가함
- 예시)
Object.create vs Object.prototype.hasOwnProperty
- Object.create : Object의 정적 메서드
- Object.prototype.hasOwnProperty : Object.prototype으로 객체의 종점이므로 모든 객체에서 사용가능
- 예시)
- 표기법 만으로도 정적메서드인지, 프로토타입 프로퍼티/메서드인지 알수 있음
프로토타입 프로퍼티/메서드 축약 표기
: .prototype. -> #
프로퍼티 존재 확인
in 연산자
'프로퍼티 키' in 객체
: 객체 내에 특정 프로퍼티가 존재하는지 여부를 확인함- 상속받은 모든 프로토타입의 프로퍼티를 확인하므로 주의
Reflect.has() 메서드
: in 연산자와 동일하게 작동Reflect.has(객체, '프로퍼티 키')
Object.prototype.hasOwnProperty() 메서드
: 고유 프로퍼티 인지 존재 확인 가능인스턴스.hasOwnProperty('프로퍼티키')
프로퍼티 열거
for...in 문
: 객체의 모든 프로퍼티를 순회하여 열거함for (변수선언문 in 객체) {...}
- 객체의 프로퍼티 개수만큼 순회하며 변수 선언문에서 선언한 변수에 프로퍼티키를 할당
- 상속받은 프로토타입의 프로퍼티까지 열거 (Object.prototype의 프로퍼티중 Enumerable이 false인것을 제외하고)
- 즉, for …in 문은 객체의 프로토타입 체인상에 존재하는 모든 프로토타입의 프로퍼티 중에서 프로퍼티 어트리뷰트 [[Enumerable]] 값이 true인 프로퍼티를 순회하며 열거함
- 열거시 순서를 보장하지 않음 (최신 브라우저의 경우만 보장, 숫자형태의 문자열인 프로퍼티 키만 정렬실시)
- 프로퍼티 키가 심벌인 프로퍼티는 열거하지 않음
- 프로토타입 체인이 아닌 자신만 가지고 있는 프로퍼티를 열거하고 싶다면, if 문과 hasOwnProperty를 활용하여 걸러내며 열거하게 하면 됨 (Object.key/value,entries 메서드 권장)
- 배열인 자료구조인 경우에는 일반적인 for, for…of문, forEach메서드를 권장함(요소인 프로퍼티만 다루기 위함)
for ... of
: 선언한 변수에 배열에서 가져온 키가 아닌 값을 할당함
Object.keys/values/entries 메서드
: 객체 자신의 고유 프로퍼티만 열거하는데 사용하는 메서드로 권장함Object.keys()
: 객체 자신의 열거 가능한 프로퍼티 키를 배열로 반환함Object.values()
: 객체 자신의 열거 가능한 프로퍼티 값을 배열로 반환함Object.entries()
: 객체 자신의 열거 가능한 프로퍼티 키와 프로퍼티 값의 쌍을 배열로 반환함- forEach와 사용시 좋음