[JavaScript] 모던 자바스크립트 Deep Dive 19장

ubin·2023년 10월 14일

JavaScript

목록 보기
21/21
post-thumbnail

19.0

  • JS는 명령형, 함수형, 프로토타입 기반 객체지향 프로그래밍을 지원하는 멀티 패러다임 프로그래밍 언어
  • JS는 클래스 기반 객체지향 프로그래밍 언어보다 효율적이고 더 강력한 프로토타입 기반의 객체지향 프로그래밍 언어
  • 객체 기반의 프로그래밍 언어로, 원시 타입의 값을 제외한 나머지 ‘모든 것’이 객체임

19.1 객체지향 프로그래밍

여러 개의 독립적 객체의 집합으로 프로그램을 표현하려는 프로그래밍 패러다임

  • 실세계의 실체를 인식하는 철학적 사고 (속성을 통해 실체를 인식하거나 구별하는 것) 를 프로그래밍에 접목하려는 시도에서 시작됨
  • 추상화 : 다양한 속성 중에서 프로그램에 필요한 속성만 간추려 내어 표현하는 것
  • 객체지향 프로그래밍은 객체의 상태를 나타내는 데이터와 상태 데이터를 조작할 수 있는 동작을 하나의 논리적인 단위로 묶어서 구별함
  • 객체는 고유의 기능을 갖는 독립적인 부품이지만, 기능을 수행하면서 다른 객체와 관계성을 가질 수 있음 (상속, 메세지 주고받는 등)
  • 객체는 (상태 데이터 + 동작) 하나의 논리적인 단위로 묶은 복합적인 자료구조

19.2 상속과 프로토타입

상속은 어떤 객체의 프로퍼티 또는 메서드를 다른 객체가 상속받아 그대로 사용할 수 있는 것

  • 동일한 생성자 함수에 의해 생성된 모든 인스턴스가 동일한 메서드를 중복 소유하면 메모리 낭비
  • 상속을 기반으로 상속을 구현하여 기존의 코드를 재사용하여 불필요한 중복 제거
  • 생성자가 생성한 모든 인스턴스가 동일한 메서드를 공유해서 사용할 수 있도록 프로토타입을 생성자 함수 프로토타입 프로퍼티에 바인딩시킴
function Circle(radius) {
	this.radius = radius;
}
Cricle.prototype.getArea = function() { //프로토타입을 생성자 함수 프로토타입 프로퍼티에 바인딩
	return Math.PI * this.radius ** 2;
};
  • 이때 Circle 생성자 함수가 생성하는 모든 인스턴스는 하나의 getArea 메서드를 공유함
  • 자신의 상태를 나타내는 radius 프로퍼티만 개별적으로 소유하고 메서드는 상속을 통해 공유하여 사용
💡 모든 인스턴스가 공통적으로 사용할 프로퍼티나 메서드를 프로토타입에 미리 구현해 두면 생성자 함수가 생성할 모든 인스턴스는 별도의 구현없이 부모 객체인 프로토타입의 자산을 공유하여 사용할 수 있다.

19.3 프로토타입 객체

프로토타입 객체는 객체지향 프로그래밍의 근간을 이루는 객체 간 상속을 구현하기 위해 사용된다.

  • 프로토타입 : 어떤 객체의 부모 객체의 역할을 하는 객체로서 다른 객체에 공유 프로퍼티(메서드 포함)을 제공함
  • 모든 객체는 [[Prototype]] 이라는 내부 슬롯을 가지고 값은 프로토타입의 참조로 이루어짐
  • [[Prototype]]에 저장되는 값은 객체가 생성될 때 객체 생성 방식에 따라 결정되고 저장됨
  • __proto__ 접근자 프로퍼티를 통해 [[Prototype]] 내부 슬롯을 간접적으로 접근 가능
  • 프로토타입은 constructor 프로퍼티를 통해 생성자 함수에 접근 가능
  • 생성자 함수는 property 프로퍼티를 통해 프로토타입에 접근 가능
생성자 함수 (prototype) → ←생성자 함수.prototype ← 객체

__proto__ 접근자 프로퍼티

모든 객체는 __proto__ 접근자 프로퍼티를 통해 자신의 프로토타입인 내부 슬롯에 간접적으로 접근할 수 있다.

  • 내부 슬롯은 프로퍼티가 아니기 때문에 간접적인 접근만 가능
  • __proto__ 프로퍼티는 객체가 직접 소유하는 프로퍼티가 아닌 Object.prototype의 프로퍼티이므로 상속을 통해 사용 가능
  • 접근자 프로퍼티를 사용해 프로토타입에 접근하는 이유는 상호 참조에 의한 순환 참조하는 프로토타입 체인 생성을 방지하기 위해서
  • 프로토타입 체인은 무조건 단방향 링크드 리스트로 구현되야 함!!
  • 순환 참조하는 프로토타입 체인은 프로퍼티 검색시 체인 종점이 없어 무한 루프에 빠지게됨
  • __proto__ 를 코드 내에서 직접 사용하는 것은 비추
    • 모든 객체가 접근자 프로퍼티를 사용할 수 있는 것이 아님
    • 접근자 프로퍼티 대신 프로토타입의 참조 할 경우 Object.getPrototypeOf() 메서드 사용
    • 프로토타입 교체시 Object.setPrototypeOf() 메서드 사용

함수 객체의 prototype 프로퍼티

함수 객체만이 소유하는 prototype 프로퍼티는 생성자 함수가 생성할 인스턴스의 프로토타입을 가리킨다.

  • non-constructor 함수인 화살표 함수와 메서드 축약 표현으로 정의한 메서드는 prototype 프로퍼티를 소유하지 않고, 생성하지도 않음
  • 함수 객체는 prototype 프로퍼티 소유함
  • 일반 객체는 prototype 프로퍼티 소유하지않음
  • 모든 객체가 가지고 있는 __proto__ 접근자 프로퍼티와 함수 객체만이 가지고 있는 prototype 프로퍼티는 동일한 프로토타입 가리킴

프로토타입의 constructor 프로퍼티와 생성자 함수

  • 모든 프로토타입은 constructor 프로퍼티를 가짐
  • constructor 프로퍼티는 자신을 참조하고 있는 생성자 함수를 가리킴
  • 인스턴스 객체는 프로토타입의 constructor 프로퍼티 상속받아 사용 가능

19.4 리터럴 표기법에 의해 생성된 객체의 생성자 함수와 프로토타입

  • 명시적으로 new 연산자와 함께 생성자 함수를 호출하여 인스턴스를 생성하지 않는 객체 생성 방식
  • 리터럴 표기법에 의해 생성된 객체도 프로토타입이 존재하지만 constructor 프로퍼티가 가리키는 생성자 함수가 객체를 생성한 생성자 함수가 아닐 수도 있음
  • Object 생성자 함수에 인수를 전달하지 않거나 undefiend, null이 인수일 경우 내부적으로 추상 연산(OrdinanryObjectCreate)을 호출하여 Object.prototype을 프로토타입으로 갖는 빈 객체 생성
  • Object 생성자 함수 호출과 객체 리터럴의 평가는 추상 연산을 호출하여 빈 객체를 생성하는 것은 동일하지만 세부 내용은 다름
  • Object 생성자 함수가 생성한 객체 ≠ 객체 리터럴에 의해 생성된 객체
  • 하지만 리터럴 표기법에 의해 생성된 객체도 상속을 위한 프로토타입이 필요하므로 가상의 생성자 함수를 가짐
💡 프로토타입과 생성자 함수는 단독으로 존재할 수 없고 언제나 쌍으로 존재함

19.5 프로토타입의 생성 시점

  • 모든 객체는 생성자 함수와 연결되어 있음
  • 프로토타입은 생성자 함수가 생성되는 시점에 더불어 생성됨 (프로토타입-생성자함수 한 쌍)
  • 사용자 정의 생성자 함수와 빌트인 생성자 함수 구분으로 프로토타입 생성 시점을 확인할 수 있음

사용자 정의 생성자 함수와 프로토타입 생성 시점

생성자 함수로서 호출할 수 있는 함수인 constructor는 함수 정의가 평가되어 함수 객체를 생성하는 시점에 프로토타입도 더불어 생성됨

  • non-constructor 함수는 프로토타입이 생성되지 않음
  • 사용자 정의 생성자 함수는 자신이 평가되어 함수 객체로 생성되는 시점에 프로토타입도 동시에 생성됨
  • 프로토타입은 Object.prototype

빌트인 생성자 함수와 프로토타입 생성 시점

  • 빌트인 생성자 함수가 생성되는 전역 객체가 생성되는 시점에 프로토타입이 생성됨
  • 프로토타입은 빌트인 생성자 함수의 prototype 프로퍼티에 바인딩 됨
  • 객체가 생성되기 전에 생성자 함수와 프로토타입은 이미 객체화되어 존재
  • 이후 함수 또는 리터럴 표기법으로 객체 생성시 생성된 객체의 프로퍼티 내부 슬롯에 할당되며 프로토타입을 상속받음

19.6 객체 생성 방식과 프로토타입의 결정

  • 다양한 방식으로 생성된 객체는 모두 추상 연산에 의해 생성됨
  • 추상 연산은 필수적으로 자신이 생성할 객체의 프로토타입을 인수로 받음
  • 프로토타입은 추상 연산에 전달되는 인수에 의해 결정됨

객체 리터럴에 의해 생성된 객체의 프로토타입

  • 추상 연산에 전달되는 프로토타입은 Object.prototype
  • 객체 리터럴이 평가되면 추상 연산에 의해 Object 생성자 함수와 Object.prototype과 생성된 객체 사이에 연결이 만들어짐
  • 프로토타입을 상속받으면 상속받은 프로토타입의 프로퍼티와 메서드를 자유롭게 사용 가능

Object 생성자 함수에 의해 생성된 객체의 프로토타입

  • 추상 연산에 전달되는 프로토타입은 Object.prototype
  • 객체 리터럴에 의해 생성된 객체와 동일한 구조를 가짐
  • 객체 리터럴과 생성자 함수에 의한 객체 생성 방식의 차이는 프로퍼티를 추가하는 방식에 있음
  • Object 생성자 함수는 빈 객체 생성후 프로퍼티 추가
  • 객체 리터럴은 객체 리터럴 내부에 프로퍼티를 추가

생성자 함수에 의해 생성된 객체의 프로토타입

  • 추상 연산에 전달되는 프로토타입은 생성자 함수의 prototype 프로퍼티에 바인딩되어 있는 객체
  • 생성자 함수와 생성자 함수의 prototype 프로퍼티에 바인딩되어 있는 객체와 생성된 객체 사이에 연결이 생성됨
  • 프로토타입의 프로퍼티는 constructor 뿐임
  • 프로토타입도 객체이므로 프로퍼티를 추가/삭제할 수 있으며 프로토타입 체인에 즉시 반영됨

프로토타입 체인

객체의 프로퍼티에 접근하려고 할 때 해당 객체에 접근하려는 프로퍼티가 없다면 내부 슬롯의 참조를 따라 자신의 부모 역할을 하는 프로토타입의 프로퍼티를 순차적으로 검색하는 것

  • 프로토타입 체인은 자바스크립트가 객체지향 프로그래밍의 상속을 구현하는 매커니즘
  • call 메서드 : this로 사용할 객체를 전달하면서 함수를 호출하는 것
  • 프로토타입 체인의 최상위의 객체는 항상 Object.prototype이므로 모든 객체는 이를 상속받음
  • Object.prototype은 프로토타입 체인의 종점으로, 종점에서도 프로퍼티를 검색할 수 없는 경우 undefined 반환
  • Object.prototype의 프로토타입인 [[Prototype]] 내부 슬롯은 null
  • 프로퍼티가 아닌 식별자는 스코프 체인에서 검색
💡 스코프 체인과 프로토타입 체인은 서로 협력하여 식별자와 프로퍼티를 검색하는데 사용됨

19.8 오버라이딩과 프로퍼티 섀도잉

  • 프로토타입 프로퍼티와 같은 이름의 프로퍼티를 인스턴스에 추가하면 프로토타입 체인을 따라 프로토타입 프로퍼티를 검색하여 덮어쓰는 것이 아닌 인스턴스 프로퍼티로 추가됨 (오버라이딩)
  • 상속 관계에 의해 프로퍼티가 가려지는 현상을 프로퍼티 섀도잉이라고 함
  • 오버라이딩 : 상위 클래스가 가지고 있는 메서드를 하위 클래스가 재정의하여 사용하는 방식
  • 오버로딩 : 함수 이름은 동일하지만 매개변수의 타입 또는 개수가 다른 메서드를 구현하고 매개변수에 의해 메서드를 구별하여 호출하는 방식 (JS에선 지원하지 않음)
  • 하위 객체를 통해 프로토타입의 프로퍼티를 변경/삭제는 불가능 직접 변경/삭제만 가능

19.9 프로토타입의 교체

  • 부모 객체인 프로토타입을 동적으로 변경할 수 있음
  • 객체 간의 상속 관계를 동적으로 변경할 수 있음
  • 프로토타입은 생성자 함수 또는 인스턴스에 의해 교체할 수 있음

생성자 함수에 의한 프로토타입의 교체

  • 생성자 함수의 prototype 프로퍼티를 통해 프로토 타입을 교체할 수 있음
  • 프로토타입 교체시 constructor 프로퍼티와 생성자 함수 간의 연결이 끊어지기 때문에 프로토타입의 체인에 따라 Object.prototype constructor가 검색됨
  • 연결을 되살리려면, 프로토타입으로 교체한 객체 리터럴에 constructor 프로퍼티를 추가하면 됨
Person.prototype = {
	constructor: Person, //constructor 프로퍼티와 생성자 함수 간의 연결 설정
	sayHi() {
		console.log('Hi');
	}
}

인스턴스에 의한 프로토타입의 교체

  • 프로토타입은 인스턴스의 __proto__ 접근자 프로퍼티를 통해 접근 가능하기 때문에 교체도 가능
  • 생성자 함수에 의한 교체는 미래에 생성할 인스턴스의 프로토타입을 교체하는 것
  • __proto__ 접근자 프로퍼티에 의한 교체는 이미 생성된 객체의 프로토타입을 교체하는 것
  • Object.setPrototypeOf()로 객체의 프로토타입을 교체
  • 프로토타입은 직접 교체하지 않는 것이 좋음

19.10 instanceOf 연산자

  • 우변의 생성자 함수의 prototype에 바인딩된 객체가 좌변의 객체의 프로토타입 체인 상에 존재하면 true로 평가
💡 instanceOf 연산자는 생성자 함수의 prototype에 바인딩된 객체가 프로토타입 체인 상에 존재하는지 확인

19.11 직접 상속

Object.create에 의한 직접 상속

  • Object.create()는 명시적으로 프로토타입을 지정하여 새로운 객체를 생성
  • 이 또한 추상 연산을 호출함
  • Object.create()첫번째 매개변수는 생성할 객체의 프로토타입으로 지정할 객체를 전달
    • 객체를 생성하면서 직접적으로 상속을 구현함
    • new 연산자 없이도 객체 생성 가능
    • 프로토타입을 지정하면서 객체 생성 가능
    • 객체 리터럴에 의해 생성된 객체도 상속받을 수 있음
  • 두번째 매개변수는 옵션으로, 생성할 객체의 프로퍼티 키와 프로퍼티 디스크립터 객체로 이루어진 객체를 전달함
  • Object.prototype의 빌트인 메서드를 직접 호출하는 것은 체인의 종점에 객체를 생성할 수 있으므로 권장 X
    • Function.prototype.call() 사용해서 직접적으로 호출하는걸 추천

객체 리터럴 내부에서 __proto__ 에 의한 직접 상속

  • 객체 리터럴 내부에서 __proto__ 접근자 프로퍼티를 사용하여 직접 상속 구현 가능
const obj = {
	y: 20,
	__proto__: myProto
};

19.12 정적 프로퍼티/메소드

정적 프로퍼티/메소드는 생성자 함수로 인스턴스를 생성하지 않아도 프로토타입 메서드를 참조/호출할 수 있는 프로퍼티/메서드를 의미함

  • 정적 프로퍼티/메소드는 생성자 함수가 생성한 인스턴스로 참조/호출할 수 없음
  • 생성자 함수가 생성한 인스턴스는 자신의 프로토타입 체인에 속한 객체의 프로퍼티/메서드에 접근 가능
  • 정적 프로퍼티/메소드는 인스턴스의 프로토타입 체인에 속한 객체의 프로퍼티/메소드가 아니므로 인스턴스로 접근 불가
  • 인스턴스/프로토타입 메서드에서 this 생략시 → 정적 프로퍼티/메소드로 변경
  • 프로토타입 프로퍼티/메서드 표기시 prototype을 #으로 표기

19.13 프로퍼티 존재 확인

in 연산자

in 연산자는 객체 내에 특정 프로퍼티가 존재하는지 여부를 확인함

key in object 
//key = 프로퍼티 키를 나타내는 문자열
//object = 객체로 평가되는 표현식
  • in 연산자는 확인 대상 객체의 프로퍼티뿐만 아니라 확인 대상 객체가 상속받은 모든 프로토타입의 프로퍼티를 확인하므로 주의 !!

Object.prototype.hasOwnProperty 메서드

  • 객체에 특정 프로퍼티가 존재하는지 확인하는 메서드
  • 인수로 전달받은 프로퍼티 키가 객체 고유의 프로퍼티 키인 경우에만 true 반환

19.14 프로퍼티 열거

for … in 문

  • 객체의 모든 프로퍼티를 순회하며 열거함
for (변수선언문 in 객체) {....}
  • 객체의 프로퍼티 개수만큼 순회하며 변수선언문에서 선언한 변수에 프로퍼티 키를 할당함
  • in 연산자처럼 순회 대상 객체의 프로퍼티뿐만 아니라 상속받은 프로토타입의 프로퍼티까지 열거함
  • toString 메서드는 열거할 수 없도록 정의되어 있는 프로퍼티임
  • 프로퍼티 키가 Symbol인 프로퍼티도 열거에서 제외됨
  • 프로퍼티 열거시 순서가 보장되지 않음
  • 배열에는 일반적인 for문이나 for .. of 문 또는 forEach 문 사용 추천
💡 for .. in 문은 객체의 프로토타입 체인 상에 존재하는 모든 프로토타입의 프로퍼티 중에서 프로퍼티 어트리뷰트 [Enumerable]의 값이 true인 프로퍼티를 순회하며 열거함

Object.keys/values/entries 메서드

  • for … in 문은 상속받은 프로퍼티까지 열거 → hasOwnProperty()로 고유 프로퍼티인지 확인 필요
  • Object.keys/values/entries 메서드는 고유 프로퍼티만 열거해줌
  • Object.keys/values()는 객체 자신의 열거 가능한 프로퍼티 키를 배열로 반환
  • Object.entries()는 객체 자신의 열거 가능한 프로퍼티 키와 값의 쌍의 배열을 배열에 담아 반환
profile
프론트엔드 개발자가 되고싶은 코린이⌨️

0개의 댓글