- 자바스크립트는 명령형, 함수형, 프로토타입 기반, 객체지향을 지원하는 멀티 패러다임 프로그래밍 언어이다.
- 자바스크립트는 프로토타입 기반의 객체지향 프로그래밍 언어이다.
*클래스
-> ES6에서 클래스가 도입되었지만 기존 프로토타입 기반 객체지향 모델을 폐기하는것은 아니다.
-> 클래스도 함수이며 프로토타입 기반의 Syntatic sugar라고 볼 수 있다. (사람이 쉽게 이해하고 쓸 수 있게 만들어진 문법)
-> 생성자 함수와는 작동하는 방식이 동일하지 않기 때문에 새로운 객체 생성 메커니즘으로 보는것이 더욱 합당하다.
1. 객체지향 프로그래밍
- 명령형 프로그래밍의 절차지향적 관점에서 벗어나 여러개의 독립적 단위, 객체의 집합으로 프로그램을 표현하려는 프로그래밍 패러다임을 의미한다.
- 실체는 특징이나 성질을 나타내는 속성(attribute/property)를 가지고 있고, 이를 통해 실체를 인식하거나 구별한다.
- 여러 속성중에 우리가 필요한 속성만 간추려내 표현하는것을 추상화라고 한다.
- 속성을 통해 여러개의 값을 하나의 단위로 구성한 복합적인 자료구조를 객체라 하며, 독ㅊ립적인 객체의 집합으로 프로그램을 표현하려는 프로그램 패러다임이 객체지향 프로그래밍이다.
- 상태를 나타내는 데이터와 상태 데이터를 조작할 수 있는 동작을 하나의 단위로 묶어 생각한다.
- 객체의 상태 데이터를 프로퍼티, 동작을 메서드라고 부른다.
2. 상속과 프로토타입
- 상속은 어떤 객체의 프로퍼티 또는 메서드를 다른 객체가 상속받아 그대로 사용할 수 있는것을 의미한다.
- 자바스크립트는 프로토타입을 기반으로 상속을 구현해 중복을 제거한다.
- 생성자 함수를 이용해 만든 인스턴스는 동일한 메서드가 중복생성된다. 이를 방지하기위해 프로토타입 기반으로 상속을 구현해 공통된 메서드는 중복을 제거한다.
function Circle(radius){
this.radius = radius;
}
Circle.prototype.getArea = function(){ //Circle객체의 prototype에 getArea함수를 추가
return Math.PI * this.radius ** 2;
};
const circle1 = new Circle(1);
const circle2 = new Circle(2);
console.log(circle1.getArea === circle2.getArea); // true
3. 프로토타입 객체
- 어떤 객체의 상위(부모) 객체의 역할을 하는 객체로서 다른 객체에 공유 프로퍼티를 제공한다.
- 프로토타입을 상속받은 하위(자식) 객체는 상위 객체의 프로퍼티를 공유받아 자유롭게 사용할 수 있다.
- 모든 객체는 [[Prototype]]이라는 내부 슬롯을 가지며, 이 내부 슬롯의 값은 프로토타입의 참조이다.
- [[Prototype]]에 저장되는 프로토타입은 객체 생성 방식에 의해 결정된다.
- 객체가 생성될때 생성 방식에 따라 프로토타입이 결정되고 [[Prototype]]에 저장된다.
- 모든 객체는 하나의 프로토타입을 갖는다.
3.1 __proto__ 접근자 프로퍼티
- 모든 객체는 __proto__ 접근자 프로퍼티를 통해 자신의 [[Prototype]] 내부 슬롯에 간접적으로 접근할 수 있다.
__proto__는 접근자 프로퍼티다.
- 내부슬롯은 프로퍼티가 아니다.
- 내부슬롯에는 직접적으로 접근하거나 호출할 수 없고, 일부 내부슬롯과 내부 메서드에 한해 간접적으로 접근할 수단을 제공한다.
- [[Prototype]] 내부슬롯에도 직접 접근할 수 없으며 __proto__접근자 프로퍼티를 통해 간접적으로 내부슬롯의 프로토타입에 접근할 수 있다.
- 접근자 프로퍼티는 자체적으로 값을 갖지 않고 다른 데이터 프로퍼티의 값을 읽거나 저장할때 사용하는 [[Get]] [[Set]]프로퍼티 어트 리뷰트로 구성된 프로퍼티이다.
- __proto__접근자 프로퍼티를 통해 새로운 프로토타입을 할당하면 __proto__접근자 프로퍼티의 setter함수가 호출된다.
__proto__접근자 프로퍼티는 상속을 통해 사용된다.
- 접근자 프로퍼티는 객체가 직접 소유하는 프로퍼티가 아니라 Object.prototype의 프로퍼티이다.
- 모든 객체는 상속을 통해 Object.prototype.__proto__접근자 프로퍼티를 사용할 수 있다.
* Object.prototype
-> 모든 객체는 프로토타입의 계층 구조인 프로토타입 체인에 묶여 있다.
-> 자바스크립트 엔진은 객체의 프로퍼티에 접근하려고 할 때 해당 객체에 접근하려는 프로퍼티가 없다면 __proto__접근자 프로퍼티가 가리키는 참조를 따라 자신의 부모역할을 하는 프로토타입의 프로퍼티를 순차적으로 검색한다.
-> 프로토타입체인의 최상위 객체는 Object.prototype이다..
__proto__ 접근자 프로퍼티를 통해 프로토타입에 접근하는 이유
- [[Prototype]] 내부 슬롯의 값 (프로토타입) 에 접근하기 위해 접근자 프로퍼티를 사용하는 이유는 상호 참조에 의해 프로토타입 체인이 생성되는것을 방지하기 위해서이다.
- 프로토타입 체인은 단방향 링크드 리스트로 구현되어야 한다.
- 아무런 체크 없이 프로토타입을 교체할 수 없도록 __proto__ 접근자 프로퍼티를 통해 프로토타입에 접근하고 교체하도록 구현되어있다.
__proto__ 접근자 프로퍼티를 코드 내에서 직접 사용하는 것은 권장하지 않는다.
- 모든 객체가 __proto__ 접근자 프로퍼티를 사용할 수 있는 것은 아니기 때문에 권장하지 않는다.
- __proto__ 접근자 프로퍼티 대신 프로토타입의 참조를 취득하고 싶은 경우에는 Object.getPrototypeOf 메서드를 사용한다.
- 프로토타입을 교체하고 싶은 경우에는 Object.setPrototypeOf 메서드를 사용하는것을 권장한다.
const obj = {};
const parent = {x:1};
Object.getPrototypeOf(obj); // obj.__proto__;
Object.setPrototypeOf(obj.parent); // obj.__proto__ = parent;
3.2 함수 객체의 prototype 프로퍼티
- 함수 객체만이 소유하는 prototype 프로퍼티는 생성자 함수가 생성할 인스턴스의 프로토타입을 가리킨다.
- prototype 프로퍼티는 생성자 함수가 생성할 객체(인스턴스)의 프로토타입을 가리킨다.
- 생성자 함수로서 호출할 수 없는 (non-constructor) 화살표함수와 ES6메서드 축약표현으로 정의한 메서드는 prototype프로퍼티를 소유하지 않으며 프로토타입도 생성하지 않는다.
- 모든 객체가 가지고 있는 __proto___접근자 프로퍼티와 함수객체만이 가지고 있는 prototype 프로퍼티는 결국 동일한 프로토타입을 가리킨다. 하지만 프로퍼티를 사용하는 주체가 다르다.
구분 | 소유 | 값 | 사용주체 | 사용목적 |
__proto__ 접근자 프로퍼티 |
모든 객체 | 프로토타입의 참조 | 모든 객체 | 객체가 자신의 프로토타입에 접근 또는 교체하기 위해 사용 |
prototype 프로퍼티 |
constructor | 프로토타입의 참조 | 생성자 함수 | 생성자 함수가 자신이 생성할 객체(인스턴스)의 프로토타입을 할당하기 위해 사용 |
3.3 프로토타입의 constructor 프로퍼티와 생성자 함수
- 모든 프로토타입은 constructor 프로퍼티를 갖는다.
- constructor 프로퍼티는 prototype 프로퍼티로 자신을 참조하고 있는 생성자 함수를 가리킨다.
- 이 연결은 생성자 함수가 생성될 때 이루어진다.
4. 리터럴 표기법에 의해 생성된 객체의 생성자 함수와 프로토타입
- 생성자 함수에 의해 생성된 인스턴스는 프로토타입의 constructor 프로퍼티에 의해 생성자 함수와 연결된다.
- 리터럴 표기법에 의해 생성된 객체도 프로토타입이 존재하지만, 프로토타입의 constructor프로퍼티가 가리키는 생성자 함수가 반드시 객체를 생성한 생성자 함수라고 할 수 없다.
- 객체 리터럴이 평가될 때는 추상연산 OrdinaryObjectCreate를 호출해 빈 객체를 생성하고 프로퍼티를 추가하도록 정의되어 있다
- 따라서 객체 리터럴에 생성된 객체는 Object생성자 함수가 생성한 객체가 아니다.
- 리터럴 표기법에 의해 생성된 객체도 가상적인 생성자 함수를 갖는다.
- 프로토타입은 생성자함수와 더불어 생성되며 prototype, constructor 프로퍼티에 의해 연결되어 있기 떄문이다.
- 프로토타입과 생성자함수는 단독으로 존재할 수 없고 언제나 쌍으로 존재한다.
5. 프로토타입의 생성 시점
- 프로토타입은 생성자 함수가 생성되는 시점에 더불어 생성된다.
- 생성자 함수는 사용자가 직접 정의한 사용자 정의 생성자 함수와, 자바스크립트가 기본 제공하는 빌트인 생성자 함수로 구분할 수 있다.
5.1 사용자 정의 생성자 함수와 프로토타입 생성 시점
- 일반함수로 정의한 함수 객체는 new 연산자와 함께 생성자 함수로서 호출할 수 있다.
- 생성자 함수로서 호출할 수 없는 함수는 프로토타입이 생성되지 않는다.
- 함수 평가시점에 함수가 객체가 되면서 프로토타입도 같이 생성된다.
- 생성된 프로토타입은 생성자함수의 prototype프로퍼티에 바인딩된다.
- 생성된 프로토타입은 오직 constructor 프로퍼티만을 갖는 객체이다.
- 프로토타입도 객체이고 모든 객체는 프로토타입을 가지므로 프로토타입도 자신의 프로토타입을 갖는다.
5.2 빌트인 생성자 함수와 프로토타입 생성 시점
- 빌트인 생성자 함수도 생성되는 시점에 프로토타입이 생성된다.
- 모든 빌트인 생성자 함수는 전역 객체가 생성되는 시점에 생성된다.
- 생성된 프로토타입은 빌트인 생성자 함수의 prototype프로퍼티에 바인딩된다.
- 생성자 함수 또는 리터럴 표기법으로 객체를 생성하면 프로토타입은 생성된 객체의 [[Prototpye]] 내부 슬롯에 할당된다.
6. 객체 생성 방식과 프로토타입의 결정
- 객체 생성방법: 객체 리터럴 / Object 생성자 함수 / 생성자 함수 / Object.create메서드 / 클래스
- 이런 모든 객체는 추상연산 OrdinaryObjectCreate에 의해 생성된다는 공통점이 있다.
- 추상 연산 OrdinaryObjectCreate는 자신이 생성할 객체의 프로토타입을 인수로 전달받는다.
- 빈 객체를 생성한 후, 객체에 추가할 프로퍼티 목록이 인수로 전달된 경우 프로퍼티를 객체에 추가한다.
- 그리고 인수로 전달받은 프로토타입을 자신이 생성한 객체의 [[Prototype]] 내부 슬롯에 할당한 다음 생성한 객체를 반환한다.
- 즉 프로토타입은 추상연산 OrdinaryObjectCreate에 전달되는 인수에 의해 결정된다.
- 이 인수는 객체가 생성되는 시점에 객체 생성 방식에 의해 결정된다.
6.1 객체 리터럴에 의해 생성된 객체의 프로토타입
- 객체리터럴을 평가하여 생성할때 추상연산에 전달되는 프로토타입은 Object.prototype이다.
- 즉 객체 리터럴에 의해 생성되는 객체의 프로토타입은 Object.prototype이다.
6.2 Object 생성자 함수에 의해 생성된 객체의 프로토타입
- Object생성자함수를 인수 없이 호출하면 빈 객체가 생성된다.
- 추상연산에 전달되는 프로토타입은 Object.prototype이다.
- 객체 리터럴와 Object 생성자 함수에 의한 객체 생성 방식의 차이는 프로퍼티를 추가하는 방식이다.
- 객체 리터럴 방식은 객체 리터럴 내부에 프로퍼티를 추가하지만, Object 생성자 함수 방식은 빈 객체를 생성한 이후 프로퍼티를 추가해야 한다.
6.3 생성자 함수에 의해 생성된 객체의 프로토타입
- new연산자와 함꼐 생성자 함수를 호출해 인스턴스를 생성하면 추상연산 OrdinaryObjectCreate가 호출된다.
- 추상연산에 전달되는 프로토타입은 생성자 함수의 prototype 프로퍼티에 바인딩되어있는 객체다.
- Object.prototype은 다양한 빌트인 메서드를 가지고있찌만 사용자 정의 생성자 함수와 함꼐 생성된 프로토타입의 프로퍼티는 constructor뿐이다.
- 프로토타입은 객체이기때문에 프로퍼티를 추가해 자식객체가 상속받을 수 있게 된다.
7. 프로토타입 체인
- 생성자함수에 의해 생성된 객체는 prototype의 프로퍼티로 constructor만을 갖는다.
- prototype의 프로토타입은 언제나 Object.prototype이다.
- 프로토타입 체인으로 인해 생성자 함수에서도 Object.prototype을 사용할 수 있다.
- 자바스크립트는 객체의 프로퍼티에 접근하려고 할떄 객체에 접근하려는 프로퍼티가 없다면 [[Prototype]] 내부 슬롯의 참조를 따라 자신의 부모 역할을 하는 프로토타입의 프로퍼티를 순차적으로 검색한다.
- 이를 프로토타입 체인이라 한다.
- 프로토타입 체인은 자바스크립트가 객체지향프로그래밍의 상속을 구현하는 메커니즘이다.
- 프로토타입 체인의 최상위에 위치하는 객체는 언제나 Object.prototype이다.
- Object.prototype의 프로토타입, 즉 [[Prototype]]의 내부 슬롯의 값은null이다.
- Object.prototype에서도 프로퍼티를 검색할 수 없는 경우 undefined를 반환한다. (에러를 발생시키지 않는다)
- 프로토타입 체인은 상속과 프로퍼티 검색을 위한 메커니즘이고, 스코프 체인은 식별자 검색을 위한 메커니즘이다.
8. 오버라이딩과 프로퍼티 섀도잉
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();
- 프로토타입이 소유한 프로퍼티를 프로토타입 프로퍼티, 인스턴스가 소유한 프로퍼티를 인스턴스 프로퍼티라고 한다.
- 프로토타입 프로퍼티와 같은 이름의 프로퍼티를 인스턴스에 추가하면, 프로토타입 체인을 따라 프로토타입 프로퍼티를 검색해 프로토타입 프로퍼티로 덮었느ㅡㄴ것이 아니라 인스턴스 프로퍼티로 추가한다.
- 이때 인스턴스메서드 sayHello는 프로토타입 메서드 sayHello를 오버라이딩 했고 프로토타입 메서드 sayHello는 가려진다.
- 이처럼 상속관계에 의해 프로퍼티가 가려지는 현상을 프로퍼티 섀도잉이라 한다.
- 프로퍼티 섀도잉이 일어났을때 프로토타입 프로퍼티를 변경 또는 삭제하는것은 불가능하다.
9. 프로토타입의 교체
9.1 생성자 함수에 의한 프로토타입의 교체
- 프로토타입에 객체 리터럴을 할당하면 최초에 생기는 prototype객체가 새로만든 객체 리터럴로 교체된다
- 그래서 constructor메서드가 사라진다.
9.2 인스턴스에 의한 프로토타입의 교체
- 프로토타입은 생성자 함수의 prototype 프로퍼티 뿐만 아니라 인스턴스의 __proto__ 접근자 프로퍼티 (또는 Object.getProtoTypeOf메서드 ) 를 통해 접근할 수 있다.
- 인스턴스의 __poroto__ 접근자 프로퍼티를 통해 프로토타입을 교체할 수 있다.
- 가급적 프로토타입은 직접 교체하지 않는 것이 좋다.
10. instanceof 연산자
- instanceof연산자는 이항연산자로 좌변에 식별자, 우변에 생성자함수를 가리키는 식별자를 피연산자로 받는다.
- 우변의 피연산자가 함수가 아닌 경우 TypeError를 발생시킨다.
- 우변의 생성자함수의 prototype에 바인딩된 객체가 좌변의 객체의 프로토타입 체인상에 존재하면 true로 평가되고, 그렇지 않은경우 false로 평가된다.
- instanceof연산자는 prototype의 cunstructor프로퍼티가 가리키는 생성자 함수를 찾는것이 아니라 생성자 함수의 prototype에 바인딩 된 객체가 프로토타입 체인상에 존재하는지 확인한다.
11. 직접 상속
11.1 Object.create에 의한 직접 상속
- Object.create 메서드는 명시적으로 프로토타입을 지정해 새로운 객체를 생성한다.
- Object.create메서드도 추상연산 OrdinaryObjectCreate를 호출한다.
- Object.create 메서드의 첫번째 매개변수에는 생성할 객체의 프로토타입으로 지정할 객체를 전달한다.
- 두번째 매개변수에는 생성할 객체의 프로퍼티 키와 프로퍼티 디스크립터 객체로 이루어진 객체를 전달한다.
- 객체를 생성하면서 직접적으로 상속을 구현할 수 있다.
- 장점
- new 연산자 없이도 객체를 생성할 수 있다
- 프로토타입을 지정하면서 객체를 생성할 수 있다
- 객체 리터럴에 의해 생성된 객체도 상속받을 수 있다.
- ESLint에서는 Object.prototype의 빌트인 메서드를 객체가 직접 호출하는것을 권장하지 않는다.
- Object.prototype의 빌트인 메서드는 간접적으로 호출하는것이 좋다(call메서드를 이용)
11.2 객체 리터럴 내부에서 __proto__에 의한 직접 상속
- ES6에서 객체 리터럴 내부에서 __proto__ 접근자 프로퍼티를 사용해 직접 상속을 구현할 수 있다.
12. 정적 프로퍼티/메서드
- 정적 프로퍼티/메서드는 생성자 함수로 인스턴스를 생성하지 않아도 참조/호출할 수 있는 프로퍼티/메서드를 말한다.
- 생성자 함수에 추가한 정적 프로퍼티/메서드는 생성자 함수로 참조/호출할 수 있다.
- 정적 프로퍼티/메서드는 생성자 함수가 생성한 인스턴스로 참조/호출될 수 없다.
- 만약 인스턴스/프로토타입 매서드 내에서 this를 사용하지 않는다면, 그 메서드는 정적 메서드로 변경할 수 있다.
- 프로토타입 프로퍼티/메서드를 표기할때 프로토타입을 #으로 표기하는 경우도 있다(Object.prototype.isPrototypeOf를 Object#isPrototypeOf로 표기)
13. 프로퍼티 존재 확인
13.1 in연산자
- 객체 내에 특정 프로퍼티가 존재하는지 여부를 확인한다.
- key in object //존재하면 오브잭트 내에 key가 존재하면 true
- in연산자는 대상 객체의 프로퍼티 뿐만 아니라 모든 프로토타입 체인상의 키를 확인하므로 주의가 필요하다.
- in연산자 대신 ES6에서 도입된 Reflect.has 메서드를 사용해서도 가능하다.
13.2 Object.prototype.hasOwnProperty 메서드
- 객체에 특정 프로퍼티가 존재하는지 확이낳ㄹ 수 있다.
- 인수로 전달받은 프로퍼티 키가 객체 고유의 프로퍼티 키인 경우에만 true를 반환한다.
14. 프로퍼티 열거
14.1 for...in문
- 객체의 모든 프로퍼티를 순회하며 열거하려면 for ... in문을 사용한다.
- for ...in문은 객체의 프로퍼티 개수만큼 순회하며 변수선언문에서 선언한 변수에 프로퍼티 키를 할당한다.
- for ...in문은 in 연산자처럼 순회 대상 객체의 프로퍼티 뿐만 아니라 상속받은 프로토타입의 프로퍼티까지 열거한다.
- 하지만 프로퍼티 어트리뷰트의 [[Enumerable]]이 false로 정해진 프로퍼티들은 열거하지 않는다.
- 프로퍼티 키가 심벌인 프로퍼티도 열거하지 않는다.
- 상속받은 프로퍼티는 제외하고 객체 자신의 프로퍼티만 열거하려면 Object.prototype.hasoWnproperty메서드를 사용해 객체 자신의 프로퍼티인지 확인하는 작업이 필요하다.
- for ...in문은 프로퍼티를 열거할 때 순서를 보장하지 않지만 , 대부분의 브라우저들은 순서를 보장하고 숫자인 프로퍼티 키에 대해서는 정렬을 실시한다.
- 배열에서는 for ...in문을 사용하지 말고 for문이나 for ...of문 또는 Array.prototype.forEach메서드를 사용하기를 권장한다.
14.2 Object.keys/ values/ entries메서드
- 객체 자신의 고유 프로퍼티만 열거하기 위해서는 for...in문보다 Object.keys/values/entries메서드를 사용하는 것을 궈장한다.
- Object.keys는 객체 자신의 열거 가능한 프로퍼티 키를 배열로 반환한다.
- Object.values메서드는 객체 자신의 열거 가능한 프로퍼티 값을 배열호 반환한다.
- Object.entries는 객체 자신의 열거 가능한 프로퍼티 키와 값의 쌍의 배열을 배열에 담아 반환한다.
'JS > [책] 모던 자바스크립트 Deep Dive' 카테고리의 다른 글
21. 빌트인 객체 (0) | 2021.09.07 |
---|---|
20. strict mode (0) | 2021.09.07 |
18. 함수와 일급 객체 (0) | 2021.09.05 |
17. 생성자 함수에 의한 객체 생성 (0) | 2021.09.05 |
16. 프로퍼티 어트리뷰트 (0) | 2021.08.30 |