본문 바로가기

Web/JavaScript

ES6 와 syntactic sugar, prototype에 관하여

728x90

출처

https://ding-co.tistory.com/58

 

[모던 JS Deep Dive] 19장 - 프로토타입

19.0 프로토타입 - JS는 멀티 패러다임 프로그래밍 언어 (명령형, 함수형, 프로토타입 기반 객체 지향 프로그래밍 지원) - ES6 클래스 도입 - 클래스도 함수임, 기존 프로토타입 기반 패턴의 syntactic s

ding-co.tistory.com

 

 

내 블로그 연관 내용 링크

https://delightpip.tistory.com/291

 

 

 

syntactic sugar 

- Specifically, a construct in a language is called syntactic sugar if it can be removed from the language without any effect on what the language can do: functionality and expressive power will remain the same.

 

어떤 언어에서 제거되더라도 제공하는 기능이나 표현을 똑같이 유지하는데 문제가 없는 구조

 

 

 

 

Javascript 는 멀티 패러다임의 프로그래밍 언어이다. (명령형, 함수형, 프로토타입 기반 객체 지향 프로그래밍 모두 지원)

ES6 클래스의 도입으로 기존 프로토 타입 기반 패턴의 syntactic sugar를 다른 새로운 시간으로 보게 된다.

- 클래스와 생성자 함수는 모두 프로토타입 기반의 인스턴스 생성을 하지만 정확히 동일한 동작이 아니며,

클래스는 생성자 함수보다 엄격하고, 생성자 함수에서 제공하지 않는 기능도 제공하고 있다.

 

 

객체지향 프로그래밍이란

- 명령형 프로그래밍의 절차지향적 관점에서 프로그램을 명령어 or 함수의 목록으로 바라봄

- 객체 지향 프로그래밍: 여러 개의 독립적 단위, 독립적인 객체의 집합으로 프로그램 표현

- 객체: 상태 데이터(프로퍼티), 동작(메소드)을 하나의 논리적 단위로 묶은 복합적인 자료구조

- 속성 (attribute/property); 실체(사물, 개념)를 인식하거나 구별 가능, 객체의 상태를 나타내는 데이터

- 메소드: 객체의 상태 데이터를 조작할 수 있는 동작

- 추상화: 다양한 속성 중에서 프로그램에 필요한 속성만 간추려 내어 표현

 

 

 

상속이란?

어떤 객체의 프로퍼티/메소드를 다른 객체가 상속받아 그대로 사용하는 것

javascript는 프로토타입 기반으로 상속을 구현하여 코드 재사용을 허용한다

 

생성자 함수: new 연산자로 인스턴스 생성한다

객체의 프로퍼티 값은 일반적으로 인스턴스마다 다르다

메소드는 모든 인스턴스가 동일한 내용의 메소드를 사용하므로 단 하나만 생성하여

모든 인스턴스가 공유해서 사용하는 것이 바람직하다.

상속을 통해 불필요한 중복 제거 한다. (프로토타입 기반의 상속)

- 모든 인스턴스가 메소드 공유해서 사용할 수 있도록 프로토타입에 추가

 

(프로토타입은 Circle 생성자 함수의 prototype 프로퍼티에 바인딩되어 있음)

ex. Circle.prototype.getArea = function() { ... };

- 상속은 코드의 재사용 관점에서 매우 유용하다

 

 

프로토타입 객체

OOP 의 기본이 되는 객체 간 상속을 구현하기 위해 사용된다

타 객체의 상위(부모) 객체의 역할을 하는 객체로 공유 프로퍼티 및 메소드를 제공한다

- 모든 객체는 [[Prototype]] 내부 슬롯을 가지고, 내부 슬롯의 값이 프로토타입의 참조가 된다(nullable)

- [[Prototype]]에 저장되는 프로토타입은 객체 생성 방식에 의해 결정된다

- 객체 리터럴에 의해 생성된 객체의 프로토타입 => Object.prototype

- 생성자 함수에 의해 생성된 객체의 프로토타입 => 생성자 함수의 prototype 프로퍼티에 바인딩 되어 있는 객체

- 모든 객체는 하나의 프로토타입 가짐, 모든 프로토타입은 생성자 함수와 연결되어 있다

 

(객체 - 프로토타입 - 생성자 함수)

프로토타입은 자신의 constructor 프로퍼티를 통해 생성자 함수에 접근 가능,

생성자 함수는 자신의 prototype 프로퍼티를 통해 프로토타입에 접근 가능

 

 

 

proto 접근자 프로퍼티

모든 객체는 proto 접근자 프로퍼티를 통해 자신의 프로토타입, 즉 [[Prototype]] 내부 슬롯에 간접 접근 가능

(Object.prototype)

- __proto__는 접근자 프로퍼티다.

- 내부 슬롯은 프로퍼티가 아님, javascript에서 내부 슬롯/메소드 직접 접근, 호출 방법 제공하지 않는다.

일부 내부 슬롯과 메소드에 한해서 간접 접근 수단 제공

[[Prototype]] 내부 슬롯 직접 접근 불가, proto 접근자 프로퍼티 통해 간접적으로 접근 가능 (프로토타입)

- 접근자 프로퍼티는 [[Value]] 없고 [[Get]], [[Set]] 프로퍼티 어트리뷰트로 구성된 프로퍼티

- proto 접근자 프로퍼티는 상속을 통해 사용된다.

**- proto 접근자 프로퍼티는 객체가 직접 소유하는 프로퍼티가 아니라 Object.prototype의 프로퍼티임

- 모든 객체는 상속을 통해 Object.prototype.proto 접근자 프로퍼티 사용 가능

- Object.prototype (모든 객체의 프로토타입 객체)

모든 객체는 프로토타입의 계층 구조인 프로토타입 체인에 묶여 있음

프로토타입 체인의 종점 => Object.prototype

- proto 접근자 프로퍼티를 통해 프로토타입에 접근하는 이유

- 상호 참조에 의해 프로토타입 체인이 생성되는 것 방지 위함

- 프로토타입 체인은 단방향 링크드 리스트로 구현되어야 함

순환 참조하는 프로토타입 체인이 만들어지면 프로토타입 체인 종점이 존재하지 않기 때문에

프로토타입 체인에서 프로퍼티 검색 시 무한 루프에 빠짐

- proto 접근자 프로퍼티를 코드 내에서 직접 사용하는 것은 권장하지 않는다.

**- 브라우저 호환성 고려하여 ES6에서 proto 표준이 됨

모든 객체가 proto 접근자 프로퍼티 사용할 수 있는 것은 아님

직접 상속을 통해 Object.prototype 상속받지 않는 객체 생성 가능 => 접근자 프로퍼티 사용 불가

ex. Object.create(null) => 프로토타입 체인의 종점, 접근자 프로퍼티 상속 불가

proto 보다 Object.getPrototypeOf() 메소드 사용 권장 (프로토타입 참조 취득) // ES5

- 프로토타입 교체 시에는 Object.setPrototypeOf() // ES6

- 함수 객체의 prototype 프로퍼티

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

함수 객체는 prototype 프로퍼티 소유 O

일반 객체는 prototype 프로퍼티 소유 X

non-constructor인 화살표 함수, ES6 메소드 축약 표현 => prototype 프로퍼티 소유 X, 프로토타입 생성 X

- 모든 객체(인스턴스)가 가지고 있는 (Object.prototype으로부터 상속 받은) proto 접근자 프로퍼티와

함수 객체만이 가지고 있는 prototype 프로퍼티는 동일한 프로토타입 가리킴

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

- 모든 프로토타입은 constructor 프로퍼티를 가짐

- constructor 프로퍼티는 자신을 참조하는 생성자 함수 가리킴

- 인스턴스 객체는 프로토타입의 constructor 프로퍼티 상속받아 사용 가능

 

 

 

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

리터럴 표기법에 의한 객체 생성 방식 (new 연산자 사용하여 생성자 함수 호출하면서 인스턴스 생성 X)

- 리터럴 표기법에 의해 생성된 객체도 프로토타입 존재

프로토타입의 constructor 프로퍼티가 가리키는 생성자 함수가 반드시 객체를 생성한 생성자 함수로 단정 불가

Object 생성자 함수에 인수 전달 X, undefiend or null 을 인수로 전달하면서 호출 시

내부적으로 추상 연산 OrdinanryObjectCreate 호출하여 Object.prototype을 프로토타입으로 갖는 빈 객체 생성

객체 리터럴 평가 시 추상 연산 OrdinaryObjectCreate 호출하여 빈 객체 생성하고 프로퍼티 추가하도록 정의됨

- 객체 리터럴에 의해 생성된 객체는 Object 생성자 함수가 생성한 객체가 아님

- 리터럴 표기법에 의해 생성된 객체도 가상적인 생성자 함수 가짐

- 프로토타입과 생성자 함수는 단독으로 존재할 수 없고 언제나 쌍으로 존재함

 

 

 

프로토타입의 생성시점

결국 모든 객체는 생성자 함수와 연결되어 있음

- 프로토타입은 생성자 함수가 생성되는 시점에 더불어 생성됨

프로토타입과 생서자 함수는 언제나 쌍으로 존재

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

생성자 함수로서 호출 가능한 함수 (constructor)는 함수 정의가 평가되어 함수 객체 생성 시점에

프로토타입도 더불어 생성됨

생성된 프로토타입은 생성자 함수의 prototype 프로퍼티에 바인딩됨

- 생성된 프로토타입은 오직 constructor 프로퍼티만 갖는 객체임

생성된 프로토타입의 프로토타입은 Object.prototype (모든 객체는 프로토타입 가짐)

non-constructor는 프로토타입이 생성되지 않음

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

- 빌트인 생성자 함수가 생성되는 시점에 프로토타입이 생성됨

- 모든 빌트인 생성자 함수는 전역 객체가 생성되는 시점에 생성됨

빌트인 객체 Object는 전역 객체의 프로퍼티임

- 생성자 함수 or 리터럴 표기법으로 객체 생성 시 프로토타입은 생성된 객체의 [[Prototype]] 내부 슬롯에 할당됨

(생성된 객체는 프로토타입 상속 받음)

 

 

객체의 다양한 생성 방법

  • 객체 리터럴
  • Object 생성자 함수
  • 생성자 함수
  • Object.create 메소드
  • 클래스(ES6)

- 프로토타입은 추상 연산 OrdinaryObjectCreate에 전달되는 인수에 의해 결정됨

(인수는 객체가 생성되는 시점에 객체 생성 방식에 의해 결정됨)

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

  • JS 엔진이 객체 리터럴 평가하여 객체 생성 시 추상 연산 OrdinaryObjectCreate 호출
  • 객체 리터럴에 의해 생성된 객체의 프로토타입 => Object.prototype (상속 받음)

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

  • Object 생성자 함수를 인수 없이 호출 시 빈 객체 생성됨
  • Object 생성자 함수 호출 시 추상 연산 OrdinaryObjectCreate가 호출됨
  • Object 생성자 함수에 의해 생성된 객체의 프로토타입 => Object.prototype

- 다양한 빌트인 메소드(hasOwnProperty, propertyIsEnumerable 등) 가지고 있음

- 객체 리터럴과의 차이는 프로퍼티 추가 방식의 차이

  • 객체 리터럴 방식; 객체 리터럴 내부에 프로퍼티 추가
  • Object 생성자 함수 방식; 일단 빈 객체 생성 후 프로퍼티 추가

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

  • new 연산자 사용하여 생성자 함수 호출하여 인스턴스 생성 시 추상 연산 OrdinaryObjectCreate 호출됨
  • 생성자 함수에 의해 생성된 객체의 프로토타입 => 생성자 함수의 prototype 프로퍼티에 바인딩 되어 있는 객체

- 생성된 프로토타입의 프로퍼티는 constructor 뿐임

- 표준 빌트인 생성자 함수에 의해 생성된 객체의 프로토타입에는 다양한 빌트인 메소드 존재

 

 

 

 

프로토타입 체인

모든 객체는 Object.prototype 상속 받음 (프로토타입의 프로토타입은 언제나 Object.prototype)

- 프로토타입 체인: javascript의 OOP 상속을 구현하는 메커니즘 (상속과 프로퍼티 검색을 위한 메커니즘)

  • JS는 객체의 프로퍼티/메소드에 접근하려고 할 때 해당 객체에 접근하려는 프로퍼티가 없다면

[[Prototype]] 내부 슬롯의 참조를 따라 자신의 부모 역할을 하는 프로토타입의 프로퍼티 순차적으로 검색

- Object.prototype은 프로토타입 체인의 종점임 ([[prototype]] 내부 슬롯의 값은 null)

- 만약 최상위 프로토타입에서 프로퍼티 검색 못하면 undefined 반환 (에러 발생 X)

- 프로퍼티가 아닌 식별자는 스코프 체인에서 검색!

(스코프 체인은 식별자 검색을 위한 메커니즘)

  • ex. const me = new Person('Lee');

me.hasOwnProperty('name');

  • 스코프 체인에서 me 식별자 검색, me 식별자는 전역에서 선언되었으므로 전역 스코프에서 검색됨
  • me 식별자 검색한 다음 me 객체의 프로토타입 체인에서 hasOwnProperty 메소드 검색

 

 

 

Overriding, Property shadowing

프로토타입이 소유한 프로퍼티/메소드 => 프로토타입 프로퍼티

인스턴스가 소유한 프로퍼티 => 인스턴스 프로퍼티

- Property shadowing: 상속 관계에 의해 프로토타입의 프로퍼티 가려지는 현상 (인스턴스 메소드 오버라이딩에 의해)

  • 하위 객체를 통해 프로토타입 체인을 거쳐 프로토타입의 메소드가 삭제되지는 않음
  • 하위 객체를 통해 프로토타입에 get 액세스는 허용되지만 set 엑세스는 허용되지 않음

- 프로토타입 프로퍼티에 직접 접근해야 프로토타입의 프로퍼티/메소드 삭제 또는 변경 가능

 

 

프로토타입 교체

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

  • 생성자 함수의 prototype 프로퍼티를 통해 프로토타입 교체
  • 프로토타입 교체 시 constructor 프로퍼티와 생성자 함수 간의 연결 파괴됨
  • 프로토타입의 constructor 프로퍼티로 인스턴스의 생성자 함수 검색 시 Object 나옴
  • 프로토타입으로 교체한 객체 리터럴에 constructor 프로퍼티 추가 시 프로토타입의 constructor 프로퍼티 되살림

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

- 생성자 함수의 prototype 프로퍼티에 다른 임의의 객체 바인딩하는 것은

미래에 생성할 인스턴스의 프로토타입 교체하는 것을 의미

  • 프로토타입 교체 시 constructor 프로퍼티와 생성자 함수 간의 연결 파괴됨

- 생성자 함수에 의한 프로토타입의 교체와의 차이

- 생성자 함수에 의한 프로토타입 교체; 생성자 함수의 prototype 프로퍼티가 교체된 프로토타입 가리킴

- 인스턴스에 의한 프로토타입 교체; 생성자 함수의 prototype 프로퍼티가 교체된 프로토타입 가리키지 X

  • 프로토타입 교체를 통해 객체 간의 상속 관계를 동적으로 변경하는 것은 많이 번거로움
  • 프로토타입 직접 교체 권장 X

- 프로토타입 직접 상속이 더 편리하고 안전

- ES6 클래스 사용해도 직관적으로 상속 구현 가능

 

 

instanceof 연산자

- 객체 instanceof 생성자 함수

  • 이항 연산자, 좌변: 객체 가리키는 식별자 / 우변: 생성자 함수 가리키는 식별자
  • 우변의 피연산자가 함수가 아니면 TypeError 발생

- 우변의 생성자 함수의 prototype 프로퍼티에 바인딩된 객체가 좌변의 객체의 프로토타입 체인 상에 존재하면

true로 평가되고, 그렇지 않으면 false로 평가됨

instanceof 연산자는 프로토타입의 constructor 프로퍼티가 가리키는 생서자 함수를 찾는 것이 아니라,

생성자 함수의 prototype 프로퍼티에 바인딩된 객체가 프로토타입 체인 상에 존재하는지 확인한다

 

 

 

직접상속

- Object.create에 의한 직접 상속

Object.create(): 명시적으로 프로토타입 지정하여 새로운 객체 생성

지정된 프로토타입 및 프로퍼티 갖는 새로운 객체 생성하여 반환

  • 추상 연산 OrdinaryObjectCreate 호출

- 첫번째 매개변수: 생성할 객체의 프로토타입으로 지정할 객체

  • 두번쨰 매개변수: 생성할 객체의 프로퍼티 키와 프로퍼티 디스크립터 객체로 이뤄진 객체 [옵션]

- 첫번째 매개변수에 전달한 객체의 프로토타입 체인에 속하는 객체 생성

(객체 생성하면서 직접적으로 상속 구현)

- 프로토타입이 null인 객체 => 프로토타입 체인상 종점에 위치 (Object.prototype 상속받지 못함)

- Object.create() 메소드 장점

  • new 연산자 없이 객체 생성 가능
  • 프로토타입을 지정하면서 객체 생성 가능
  • 객체 리터럴에 의해 생성된 객체도 상속받을 수 있음
  • ESLint 에서 Object.prototype의 빌트인 메소드를 객체가 직접 호출하는 것 권장 X

(Object.create 메소드를 통해 프로토타입 체인 종점에 위치하는 객체를 생성할 수 있음

=> 프로토타입 체인 종점에 위치하는 객체는 Object.prototype 빌트인 메소드 사용 불가)

- Function.prototype.call 메소드 이용하여 간접 호출하는 것이 좋음

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

- ES6 객체 리터럴 내부에 proto 접근자 프로퍼티 사용하여 직접 상속 구현 가능

 

 

 

정적 프로퍼티/메소드

생성자 함수로 인스턴스 생성하지 않아도 참조/호출할 수 있는 프로퍼티/메소드

  • 생성자 함수쪽에 생김

- 생성자 함수를 이용하여 호출

  • 생성자 함수가 생성한 인스턴스로 참조/호출 불가

- 인스턴스로 참조/호출할 수 있는 프로퍼티/메소드 프로토타입 체인 상에 존재해야 함

  • ex. Object.create() 에서 create() 메소드는 Object의 정적 메소드,

Object.prototype.hasOwnProperty() 에서 hasOwnProperty() 메소드는 프로토타입 메소드

  • 인스턴스/프로토타입 메소드 내에서 this 사용하지 않으면 정적 메소드로 변경 가능
  • MDN 문서: 정적 프로퍼티/메소드, 프로토타입 프로퍼티/메소드 구분
  • 프로토타입 프로퍼티/메소드 앞에 # 붙여서 표기하는 경우도 있다

 

프로퍼티 존재 확인하기

- in 연산자

- 객체 내에 특정 프로퍼티가 존재하는지 여부 확인

- key in object

- key: 프로퍼티 키를 나타내는 문자열

in 연산자는 확인 대상 객체의 프로퍼티뿐만 아니라

확인 대상 객체가 상속받은 모든 프로토타입의 프로퍼티 확인함! (주의 필요)

  • ES6 Reflect.has(객체, 키) 메소드 사용 가능

- Object.prototype.hasOwnProperty 메소드

  • 객체에 특정 프로퍼티 존재하는지 확인 가능

- Object.prototype.hasOwnProperty(키)

- 인수로 전달받은 키가 객체 고유의 프로퍼티 키인 경우에만 true 반환,

상속받은 프로토타입의 프로퍼티 키면 false 반환

 

 

 

프로퍼티 열거방식

- for ... in 문

**  - 객체의 모든 프로퍼티 순회하며 열거하기 위해 사용 (객체의 프로퍼티 개수만큼 순회)

- for (변수 선언문 in 객체) { ... }

  • 순회 대상 객체의 프로퍼티뿐만 아니라 상속받은 프로토타입의 프로퍼티까지 열거함,

하지만 Object.prototype.toString() 와 같이 프로퍼티 어트리뷰트 [[Enumerable]]가 false인 것은 열거 안함

- for ... in 문은 객체의 프로토타입 체인 상에 존재하는 모든 프로토타입의 프로퍼티 중에서

프로퍼티 어트리뷰트 [[Enumerable]]의 값이 true인 프로퍼티를 순회하며 열거함

- 프로퍼티 키가 심벌인 프로퍼티는 열거 안함

- 객체 자신의 프로퍼티만 연결하려면 Object.prototype.hasOwnProperty() 메소드 사용

- for ... in 문은 프로퍼티 열거 시 순서 보장하지 않음!

- 대부분의 모던 브라우저는 순서 보장함 (문자열인 숫자에 대해서 정렬 실시)

- 배열에 for ... in 문 말고 일반적인 for 문이나 for ... of 문 또는 Array.prototype.forEach() 메소드 사용 권장!

  • 배열도 객체이므로 프로퍼티와 상속받은 프로퍼티가 포함될 수 있음

- forEach() 메소드는 요소가 아닌 프로퍼티는 제외함

- for ... of 문은 변수 선언문에서 선언한 변수에 키가 아닌 값을 할당함

- Object.keys/values/entries 메소드

  • 객체 자신의 고유 프로퍼티만 열거하기 위해 Object.keys/values/entries 메소드 사용 권장

Object.keys() 메소드 : 객체 자신의 열거 가능한 프로퍼티 키를 배열로 반환

Object.values() 메소드 : ES8, 객체 자신의 열거 가능한 프로퍼티 값을 배열로 반환

Object.entries() 메소드 : ES8, 객체 자신의 열거 가능한 프로퍼티 키와 값의 쌍의 배열을 배열에 담아 반환

  • cf. Object.entries(person).forEach((key, value) => { ... })

 

728x90

'Web > JavaScript' 카테고리의 다른 글

[Design Pattern] 1. singleton pattern  (0) 2023.11.10
내가 쓰려고 정리하는 javascript  (0) 2023.11.01
[event handler]  (1) 2023.10.30
[mozilla javascript] MutationObserver  (1) 2023.10.29
[first-class object]  (1) 2023.10.29