일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | ||||
4 | 5 | 6 | 7 | 8 | 9 | 10 |
11 | 12 | 13 | 14 | 15 | 16 | 17 |
18 | 19 | 20 | 21 | 22 | 23 | 24 |
25 | 26 | 27 | 28 | 29 | 30 | 31 |
- 파이썬 #python #기초 #기본
- #코딩독학 #코딩인강 #코딩배우기 #개발자 #코딩이란 #코딩교육 #프론트엔드부트캠프 #백엔드부트캠프 #국비지원부트캠프 #개발자 #백엔드 #AI부트캠프 #개발자국비지원 #백엔드개발자
- #프론트엔드개발자
- Today
- Total
아자아자 화이팅이닷 !
Chapter 19 & 43 본문
* [19장 프로토타입] > [p.259 ~ p.312]
<19.1 객체지향 프로그래밍>
- 속성을 통해 여러개의 값을 하나의 단위로 구성한 복합적인 자료구조를 객체라고 하며, 객체지향 프로그래밍은 독립적인 객체의 집합으로 프로그램을 표현하려는 프로그래밍 패러다임이다.
- 객체지향 프로그래밍은 객체의 상태를 나타내는 데이터와 상태 데이터를 조작할 수 있는 동작을 하나의 논리적인 단위로 묶어 생각한다. 따라서 객체는 상태 데이터와 동작을 하나의 논리적인 단위로 묶은 복합적인 자료구조라고 할 수 있다. 이때 객체의 상태 데이터를 프로퍼티, 동작을 메서드라고 부른다.
<19.2 상속과 프로토타입>
// 생성자 함수
function Circle(radius) {
this.radius = radius;
this.getArea = function () {
// Math.PI는 원주율을 나타내는 상수다.
return Math.PI * this.radius ** 2;
};
}
// 반지름이 1인 인스턴스 생성
const circle1 = new Circle(1);
// 반지름이 2인 인스턴스 생성
const circle2 = new Circle(2);
// Circle 생성자 함수는 인스턴스를 생성할 때마다 동일한 동작을 하는
// getArea 메서드를 중복 생성하고 모든 인스턴스가 중복 소유한다.
// getArea 메서드는 하나만 생성하여 모든 인스턴스가 공유해서 사용하는 것이 바람직하다.
console.log(circle1.getArea === circle2.getArea); // false
console.log(circle1.getArea()); // 3.141592653589793
console.log(circle2.getArea()); // 12.566370614359172
- 여기에서 Circle 생성자 함수는 인스턴스를 생성할 때마다 getArea 메서드를 중복 생성하고 모든 인스턴스가 중복 소유한다.
- 자바스크립트는 프로토타입을 기반으로 상속을 구현한다.
- 예제)
// 생성자 함수
function Circle(radius) {
this.radius = radius;
}
// Circle 생성자 함수가 생성한 모든 인스턴스가 getArea 메서드를
// 공유해서 사용할 수 있도록 프로토타입에 추가한다.
// 프로토타입은 Circle 생성자 함수의 prototype 프로퍼티에 바인딩되어 있다.
Circle.prototype.getArea = function () {
return Math.PI * this.radius ** 2;
};
// 인스턴스 생성
const circle1 = new Circle(1);
const circle2 = new Circle(2);
// Circle 생성자 함수가 생성한 모든 인스턴스는 부모 객체의 역할을 하는
// 프로토타입 Circle.prototype으로부터 getArea 메서드를 상속받는다.
// 즉, Circle 생성자 함수가 생성하는 모든 인스턴스는 하나의 getArea 메서드를 공유한다.
console.log(circle1.getArea === circle2.getArea); // true
console.log(circle1.getArea()); // 3.141592653589793
console.log(circle2.getArea()); // 12.566370614359172
- 자신의 상태를 나타내는 radius 프로퍼티만 개별적으로 소유하고, 내용이 동일한 메서드는 상속을 통해 공유하여 사용한다.
- 상속에 의한 메서드 공유
<19.3 프로토타입 객체>
- 프로토타입은 어떤 객체의 상위(부모) 객체의 역할을 하는 객체로서 다른 객체에 공유 프로퍼티(메서드 포함)를 제공한다. 프로토타입을 상속받은 하위(자식) 객체는 상위 객체의 프로퍼티를 자신의 프로퍼티처럼 자유롭게 사용할 수 있다.
- 모든 객체는 하나의 프로토타입을 갖는다. 그리고 모든 프로토타입은 생성자 함수와 연결되어 있다.
- 모든 객체는 __proto__ 접근자 프로퍼티를 통해 자신의 프로토타입, 즉 [[prototype]] 내부 슬롯에 간접적으로 접근할 수 있다.
- Object.prototype의 접근자 프로퍼티인 __proto__는 getter/setter 함수라고 부르는 접근자 함수([[Get]],[[Set]] 프로퍼티 어트리뷰트에 할당된 함수)를 통해 [[Prototype]] 내부 슬롯의 값, 즉 프토로타입을 취득하거나 할당한다.
const obj = {};
const parent = { x: 1 };
// getter 함수인 get __proto__가 호출되어 obj 객체의 프로토타입을 취득
obj.__proto__;
// setter함수인 set __proto__가 호출되어 obj 객체의 프로토타입을 교체
obj.__proto__ = parent;
console.log(obj.x); // 1
- __proto__ 접근자 프로퍼티는 객체가 직접 소유하는 프로퍼티가 아니라 Object.prototype의 프로퍼티이다. 모든 객체는 상속을 통해 Object.prototype.__proto__ 접근자 프로퍼티를 사용할 수 있다.
- 예제)
const person = { name: 'Lee' };
// person 객체는 __proto__ 프로퍼티를 소유하지 않는다.
console.log(person.hasOwnProperty('__proto__')); // false
// __proto__ 프로퍼티는 모든 객체의 프로토타입 객체인 Object.prototype의 접근자 프로퍼티다.
console.log(Object.getOwnPropertyDescriptor(Object.prototype, '__proto__'));
// {get: ƒ, set: ƒ, enumerable: false, configurable: true}
// 모든 객체는 Object.prototype의 접근자 프로퍼티 __proto__를 상속받아 사용할 수 있다.
console.log({}.__proto__ === Object.prototype); // true
- 프로토타입 체인은 단방향 링크드 리스트로 구현되어야한다. 아래와 같이 순환 참조하는 프로토타입 체인이 만들어지면 안된다.
- [[Prototype]] 내부 슬롯의 값, 즉 프로토타입에 접근하기 위해 접근자 프로퍼티를 사용하는 이유는 상호 참조에 의해 프로토타입이 생성되는 것을 방지하기 위해서이다.
- 코드 내에서 __proto__ 접근자 프로퍼티를 직접 사용하는것을 지양하자.
- 직접 상속을 통해 Object.prototype을 상속받지 않는 객체를 생성할 수도 있기 때문에 __proto__ 접근자 프로퍼티를 사용할 수 없는 경우가 있다.
- 예제)
// obj는 프로토타입 체인의 종점이다. 따라서 Object.__proto__를 상속받을 수 없다.
const obj = Object.create(null);
// obj는 Object.__proto__를 상속받을 수 없다.
console.log(obj.__proto__); // undefined
// 따라서 __proto__보다 Object.getPrototypeOf 메서드를 사용하는 편이 좋다.
console.log(Object.getPrototypeOf(obj)); // null
- 따라서 프로토타입의 참조를 취득하고 싶을 경우 Object.getPrototypeOf 메서드를 사용하고, 프로토타입을 교체하고 싶을 경우 Object.setPrototypeOf 메서드를 사용할 것을 권장한다.
- 예제)
const obj = {};
const parent = { x: 1 };
// obj 객체의 프로토타입을 취득
Object.getPrototypeOf(obj); // obj.__proto__;
// obj 객체의 프로토타입을 교체
Object.setPrototypeOf(obj, parent); // obj.__proto__ = parent;
console.log(obj.x); // 1
- 함수 객체만이 소유하는 prototype 프로퍼티는 생성자 함수가 생성할 인스턴스의 프로토타입을 가리킨다.
- 예제)
// 함수 객체는 prototype 프로퍼티를 소유한다.
(function () {}).hasOwnProperty('prototype'); // -> true
// 일반 객체는 prototype 프로퍼티를 소유하지 않는다.
({}).hasOwnProperty('prototype'); // -> false
- 따라서 non-constructor인 화살표 함수와 ES6 메서드 축약 표현으로 정의한 메서드는 prototype 프로퍼티를 소유하지 않으며 프로토타입도 생성하지 않는다.
- 생성자 함수로 호출하기 위해 정의하지 않은 일반 함수(함수 선언문, 함수 표현식)도 prototype 프로퍼티를 소유하지만 아무런 의미가 없다.
- 모든 객체가 가지고 있는(엄밀히 말하면 Object.prototype으로부터 상속받은) __proto__ 접근자 프로퍼티와 함수 객체만이 가지고 있는 prototype 프로퍼티는 결국 동일한 프로토타입을 가리킨다.
구분 | 소유 | 값 | 사용 주체 | 사용 목적 |
__proto__ 접근자 프로퍼티 | 모든 객체 | 프로토타입의 참조 | 모든 객체 | 객체가 자신의 프로토타입에 접근 또는 교체하기 위해 사용 |
prototype 프로퍼티 | constructor | 프로토타입의 참조 | 생성자 함수 | 생성자 함수가 자신이 생성할 객체(인스턴스)의 프로토타입을 할당하기 위해 사용 |
- 예제)
// 생성자 함수
function Person(name) {
this.name = name;
}
const me = new Person('Lee');
// 결국 Person.prototype과 me.__proto__는 결국 동일한 프로토타입을 가리킨다.
console.log(Person.prototype === me.__proto__); // true
- 모든 프로토타입은 constructor 프로퍼티를 갖는다. 이 constructor 프로퍼티는 prototype 프로퍼티로 자신을 참조하고 있는 생성자 함수를 가리킨다. 이 연결은 함수 객체가 생성될 때 이뤄진다.
- 예제)
// 생성자 함수
function Person(name) {
this.name = name;
}
const me = new Person('Lee');
// me 객체의 생성자 함수는 Person이다.
console.log(me.constructor === Person); // true
- 생성된 me 객체는 프로토타입의 constructor 프로퍼티를 통해 생성자 함수와 연결된다. me 객체의 프로토타입인 Person.prototype에는 me 객체에 없는 constructor 프로퍼티가 있다. 따라서 me 객체는 constructor 프로퍼티를 상속받아 사용할 수 있다.
<19.4 리터럴 표기법에 의해 생성된 객체의 생성자 함수와 프로토타입>
- 리터럴 표기법에 의한 객체 생성 방식과 같이 명시적으로 new 연산자와 함께 생성자 함수를 호출하여 인스턴스를 생성하지 않는 객체 생성 방식도 있다.
- 예제)
// 객체 리터럴
const obj = {};
// 함수 리터럴
const add = function (a, b) { return a + b; };
// 배열 리터럴
const arr = [1, 2, 3];
// 정규표현식 리터럴
const regexp = /is/ig;
- 리터럴 표기법으로 생성된 객체도 물론 프로토타입이 존재한다. 하지만 프로토타입의 constructor 프로퍼티가 가리키는 생성자 함수가 반드시 객체를 생성한 생성자 함수라고 단정할 수는 없다.
- 예제)
// obj 객체는 Object 생성자 함수로 생성한 객체가 아니라 객체 리터럴로 생성했다.
const obj = {};
// 하지만 obj 객체의 생성자 함수는 Object 생성자 함수다.
console.log(obj.constructor === Object); // true
- Object 생성자 함수에 인수를 전달하지 않거나 undefined 또는 null을 인수로 전달하면서 호출하면 내부적으로는 추상 연산을 호출하여 Object.prototype을 프로토타입으로 갖는 빈 객체를 생성한다.
- 예제)
// 2. Object 생성자 함수에 의한 객체 생성
// 인수가 전달되지 않았을 때 추상 연산 OrdinaryObjectCreate를 호출하여 빈 객체를 생성한다.
let obj = new Object();
console.log(obj); // {}
// 1. new.target이 undefined나 Object가 아닌 경우
// 인스턴스 -> Foo.prototype -> Object.prototype 순으로 프로토타입 체인이 생성된다.
class Foo extends Object {}
new Foo(); // Foo {}
// 3. 인수가 전달된 경우에는 인수를 객체로 변환한다.
// Number 객체 생성
obj = new Object(123);
console.log(obj); // Number {123}
// String 객체 생성
obj = new Object('123');
console.log(obj); // String {"123"}
- 이처럼 Object 생성자 함수 호출과 객체 리터럴의 평가는 추상 연산 OrdinaryObjectCreate를 호출하여 빈 객체를 생성하는 점에서 동일하나 세부 내용은 다르다. 따라서 객체 리터럴에 의해 생성된 객체는 Object 생성자 함수가 생성한 객체가 아니다.
- 함수 선언문과 함수 표현식을 평가하여 함수 객체를 생성한 것은 Function 생성자 함수가 아니다. constructor 프로퍼티를 통해 확인해보면 foo 함수의 생성자 함수는 Function 생성자 함수다.
- 예제)
// foo 함수는 Function 생성자 함수로 생성한 함수 객체가 아니라 함수 선언문으로 생성했다.
function foo() {}
// 하지만 constructor 프로퍼티를 통해 확인해보면 함수 foo의 생성자 함수는 Function 생성자 함수다.
console.log(foo.constructor === Function); // true
- 리터럴 표기법에 의해 생성된 객체도 상속을 위해 프로토타입이 필요하다. 따라서 가상적인 생성자 함수를 갖는다.
- 프로토타입은 생성자 함수와 더불어 생성되며 prototype, constructor 프로퍼티에 의해 연결되어 있기 때문이다.
- 따라서 프로토타입과 생성자 함수는 단독으로 존재할 수 없고 언제나 쌍(pair)으로 존재한다.
- 프로토타입의 constructor 프로퍼티를 통해 연결되어 있는 생성자 함수를 리터럴 표기법으로 생성한 객체를 생성자 함수로 생각해도 큰 무리는 없다.
- 리터럴 표기법에 의해 생성된 객체의 생성자 함수와 프로토타입
리터럴 표기법 | 생성자 함수 | 프로토 타입 |
객체 리터럴 | Object | Object.prototype |
함수 리터럴 | Function | Function.prototype |
배열 리터럴 | Array | Array.prototype |
정규 표현식 리터럴 | RegExp | RegExp.prototype |
<19.5 프로토타입의 생성 시점>
- 객체는 리터럴 표기법 또는 생성자 함수에 의해 생성되므로 결국 모든 객체는 생성자 함수와 연결되어 있다.
- 프로토타입은 생성자 함수가 생성되는 시점에 더불어 생성된다.
- 생성자 함수는 사용자가 직접 정의한 사용자 정의 생성자 함수와 자바스크립트가 기본 제공하는 빌트인 생성자 함수로 구분할 수 있다.
- 생성자 함수로서 호출할 수 있는 함수, 즉 constructor는 함수 정의가 평가되어 함수 객체를 생성하는 시점에 프로토타입도 더불어 생성된다. non-constructor는 프로토타입이 생성되지 않는다.
- 예제)
// 함수 정의(constructor)가 평가되어 함수 객체를 생성하는 시점에 프로토타입도 더불어 생성된다.
console.log(Person.prototype); // {constructor: ƒ}
// 생성자 함수
function Person(name) {
this.name = name;
}
// 화살표 함수는 non-constructor다.
const Person = name => {
this.name = name;
};
// non-constructor는 프로토타입이 생성되지 않는다.
console.log(Person.prototype); // undefined
- 위 예제에서 볼 수 있듯이 생성자 함수는 어떤 코드보다 먼저 평가되어 함수 객체가 되고, 그 때 프로토타입도 더불어 생성된다. 생성된 프로토 타입은 Person 생성자 함수의 prototype 프로퍼티에 바인딩된다. 위 예제에서 생성된 프로토타입의 내부를 살펴보면 아래와 같다.
- 프로토타입도 객체이고 모든 객체는 프로토타입을 가지므로 프로토타입도 자신의 프로토타입을 갖는다. 생성된 프로토타입의 프로토타입은 Object.prototype이다.
- Object, String, Number, Function, Array, RegExp, Date, Promise 등과 같은 빌트인 생성자 함수도 생성되는 시점에 프로토타입이 생성된다.
- 모든 빌트인 생성자 함수는 전역 객체가 생성되는 시점에 생성되고 생성된 프로토타입은 빌트인 생성자 함수의 prototype 프로퍼티에 바인딩된다.
- 이처럼 객체가 생성되기 이전에 생성자 함수와 프로토타입은 이미 객체화 되어 존재한다.
- 이후 생성자 함수 또는 리터럴 표기법으로 객체를 생성하면 프로토타입은 생성된 객체의 [[Prototype]] 내부 슬롯에 할당된다.
<19.6 객체 생성 방식과 프로토타입의 결정>
- 객체는 다음과 깉이 다양한 생성 방법이 있다.
- 객체 리터럴
- Object 생성자 함수
- 생성자 함수
- Object.create 메서드
- 클래스(ES6)
- 이들의 공통점은 추상 연산 OrdinaryObjectCreate에 의해 생성된다는 것이다.
- 추상 연산은 빈 객체를 생성한 후, 객체에 추가할 프로퍼티 목록이 인수로 전달된 경우 프로퍼티를 객체에 추가한다. 그리고 인수로 전달받은 프로토타입을 자신이 생성한 객체의 [[Prototype]] 내부 슬롯에 할당한 다음 생성한 객체를 반환한다.
- 즉, 프로토타입은 추상 연산에 전달되는 인수에 의해 결정된다.
- 객체 리터럴에 의해 생성되는 객체의 프로토타입은 Object.prototype이다.
const obj = { x: 1 };
- 위 객체 리터럴이 평가되면 추상 연산에 의해 다음과 같이 Object 생성자 함수와 Object.prototype과 생성된 객체 사이에 연결이 만들어진다.
- 이처럼 객체 리터럴에 의해 생성된 obj 객체는 Object.prototype을 프로토타입으로 갖게 되며, 이로써 object.prototype을 상속받는다.
- 이로 인해 obj 객체는 constructor 프로퍼티와 hasOwnProperty 메서드를 자신의 자산인 것처럼 자유롭게 사용할 수 있다.
- Object 생성자 함수를 호출하면 객체 리터럴과 마찬가지로 추상 연산이 호출된다.
- 즉, Object 생성자 함수에 의해 생성되는 객체의 프로토타입은 Object.prototype이다.
- 예제)
const obj = new Object();
obj.x = 1;
- 위 예제를 실행하면 추상 연산에 의해 Object 생성자 함수와 Object.prototype과 생성된 객체 사이에 연결이 만들어진다. 객체 리터럴에 의해 생성된 객체와 동일한 구조이다.
- 이처럼 Object 생성자 함수에 의해 생성된 obj 객체는 Object.prototype을 프로토타입으로 갖게되며, 이로써 Object.prototype을 상속받는다.
- 객체 리터럴과 Object 생성자 함수에 의한 객체 생성 방식의 차이는 프로퍼티를 추가하는 방식에 있다.
- 객체 리터럴 방식 : 객체 리터럴 내부에 프로퍼티 추가
- Object 생성자 함수 방식 : 일반 빈 객체를 생성한 이후 프로퍼티 추가
- new 연산자와 함께 생성자 함수를 호출하여 인스턴스를 생성하면 다른 객체 방식과 마찬가지로 추상 연산이 호출된다.
- 생성자 함수에 의해 생성되는 객체의 프로토타입은 생성자 함수의 prototype 프로퍼티에 바인딩되어 있는 객체다.
- 예제)
function Person(name) {
this.name = name;
}
const me = new Person('Lee');
- 위 코드가 실행되면 추상 연산에 의해 생성자 함수와 생성자 함수의 prototype 프로퍼티에 바인딩되어 있는 객체와 생성된 객체 사이에 연결이 만들어진다.
- 생성된 프로토 타입 object.prototype은 다양한 빌트인 메서드를 갖고 있다.
- 하지만 사용자 정의 생성자 함수 Person과 더불어 생성된 프로토타입 Person.prototype의 프로퍼티는 constructor 뿐이다.
- 프로토타입은 객체이므로 일반 객체와 같이 프로토타입에도 프로퍼티를 추가/삭제할 수 있고, 이런 수정사항은 프로토타입 체인에 즉각 반영된다.
- 예제)
function Person(name) {
this.name = name;
}
// 프로토타입 메서드
Person.prototype.sayHello = function () {
console.log(`Hi! My name is ${this.name}`);
};
const me = new Person('Lee');
const you = new Person('Kim');
me.sayHello(); // Hi! My name is Lee
you.sayHello(); // Hi! My name is Kim
- Person 생성자 함수를 통해 생성된 모든 객체는 프로토타입에 추가된 sayHello 메서드를 상속받아 사용할 수 있다.
<19.7 프로토타입 체인>
- 예제)
function Person(name) {
this.name = name;
}
// 프로토타입 메서드
Person.prototype.sayHello = function () {
console.log(`Hi! My name is ${this.name}`);
};
const me = new Person('Lee');
// hasOwnProperty는 Object.prototype의 메서드다.
console.log(me.hasOwnProperty('name')); // true
- me 객체의 프로토타입은 Person.prototype이고, Person.prototype의 프로토타입은 Object.prototype이다.
- 프로토타입의 프로토타입은 언제나 Object.prototype이다.
Object.getPrototypeOf(me) === Person.prototype; // -> true
Object.getPrototypeOf(Person.prototype) === Object.prototype; // -> true
- 자바스크립트는 객체의 프로퍼티(메서드 포함)에 접근하려고 할 때 해당 객체에 접근하려는 프로퍼티가 없다면 [[Prototype]] 내부 슬롯의 참조를 따라 자신의 부모 역할을 하는 프로토타입의 프로퍼티를 순차적으로 검색한다. 이를 포로토타입 체인이라 한다. 프로토타입 체인은 자바스크립트가 객체지향 프로그래밍의 상속을 구현하는 메커니즘이다.
- Object.prototype을 프로토타입 체인의 종점(end of prototype chain)이라 한다.
- Object.prototype의 프로토타입, 즉 [[Prototype]] 내부 슬롯 값은 null 이다.
- 프로토타입 테인의 종점인 Object.prototype에서도 프로퍼티를 검색할 수 없는 경우 undefined를 반환하고, 이때 에러는 발생하지 않는다.
- 프로토타입 체인은 상속과 프로포티 검색을 위한, 스코프 체인은 식별자 검색을 위한 메커니즘이다.
- 스코프 체인과 프로토타입 체인은 서로 연관 없이 별도로 동작하는 것이 아니라 서로 협력하여 식별자와 프로퍼티를 검색하는 데 사용된다.
<19.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(); // Hey! My name is Lee
- 생성자 함수로 객체를 생성한 다음, 인스턴스에 메서드를 추가했다.
- 프로토타입이 소유한 프로퍼티를 포로토타입 프로퍼티, 인스턴스가 소유한 프로퍼티를 인스턴스 프로퍼티라고 부른다.
- 같은 이름의 프로퍼티를 인스턴스에 추가하면 덮어쓰는 것이 아니라 인스턴스 프로퍼티로 추가한다. 이때 인스턴스 메서드는 프로토타입 메서드를 오버라이딩했고 프로토타입 메서드는 가려진다.
- 이처럼 상속 관계에 의해 프로퍼티가 가려지는 현상은 프로퍼티 섀도잉(property shadowing)이라 한다.
- 하위 객체를 통해 프로토타입의 프로퍼티를 변경/삭제하는 것은 불가능하다. 다시말해, 하위 객체를 통해 프로토타입에 get 액세스는 허용되나 set액세스는 허용되지않는다.
- 프로토타입의 프로퍼티를 변경/삭제하여면 프로토타입에 직접 접근해야한다.
<19.9 프로토타입의 교체>
- 프로토타입은 임의의 다른 객체로 변경할 수 있다.
- 이러한 특징을 활용하여 객체 간의 상속 관계를 동적으로 변경할 수 있다.
- 프로토타입은 생성자 함수 또는 인스턴스에 의해 교체할 수 있다.
- 예제)
const Person = (function () {
function Person(name) {
this.name = name;
}
// ① 생성자 함수의 prototype 프로퍼티를 통해 프로토타입을 교체
Person.prototype = {
sayHello() {
console.log(`Hi! My name is ${this.name}`);
}
};
return Person;
}());
const me = new Person('Lee');
- ①에서 Person.prototype에 객체 리터럴을 할당했다. 이는 Person 생성자 함수가 생성할 객체의 프로토타입을 객체 리터럴로 교체한 것이다.
- 프로토타입으로 교체한 객체 리터럴에는 constructor 프로퍼티가 없다. 따라서 me 객체의 생성자 함수를 검색하면 Person이 아닌 Object가 나온다.
// 프로토타입을 교체하면 constructor 프로퍼티와 생성자 함수 간의 연결이 파괴된다.
console.log(me.constructor === Person); // false
// 프로토타입 체인을 따라 Object.prototype의 constructor 프로퍼티가 검색된다.
console.log(me.constructor === Object); // true
- 이처럼 프로토타입을 교체하게 되면 constructor프로퍼티와 생성자 함수간의 연결이 파괴되는데, constructor 프로퍼티를 추가하여 프로토타입의 constructor 프로퍼티를 되살린다.
const Person = (function () {
function Person(name) {
this.name = name;
}
// 생성자 함수의 prototype 프로퍼티를 통해 프로토타입을 교체
Person.prototype = {
// constructor 프로퍼티와 생성자 함수 간의 연결을 설정
constructor: Person,
sayHello() {
console.log(`Hi! My name is ${this.name}`);
}
};
return Person;
}());
const me = new Person('Lee');
// constructor 프로퍼티가 생성자 함수를 가리킨다.
console.log(me.constructor === Person); // true
console.log(me.constructor === Object); // false
- 인스턴스의 __proto__ 접근자 프로퍼티(또는 Object.setPrototypeOf 메서드)를 통해 프로토타입을 교체할 수 있다.
- 생성자 함수의 prototype 프로퍼티에 다른 임의의 객체를 바인딩하는 것은 미래에 생성할 인스턴스 프로토타입을 교체하는 것이다.
- __proto__ 접근자 프로퍼티를 통해 프로토타입을 교체하는 것은 이미 생성된 객체의 프로토타입을 교체하는 것이다.
- 예제)
function Person(name) {
this.name = name;
}
const me = new Person('Lee');
// 프로토타입으로 교체할 객체
const parent = {
sayHello() {
console.log(`Hi! My name is ${this.name}`);
}
};
// ① me 객체의 프로토타입을 parent 객체로 교체한다.
Object.setPrototypeOf(me, parent);
// 위 코드는 아래의 코드와 동일하게 동작한다.
// me.__proto__ = parent;
me.sayHello(); // Hi! My name is Lee
- ①에서 me 객체의 프로토타입을 parent 객체로 교체했다.
- 생성자 함수에 의한 프로토타입의 교체와 마찬가지로 constructor 프로퍼티와 생성자 함수 간의 연결이 파괴된다.
- 따라서 프로토타입의 constructor 프로퍼티로 me 객체의 생성자 함수를 검색하면 Person이 아닌 Object가 나온다.
- 생성자 함수와 인스턴스 각각의 프로토타입 교체는 차이가 없어보이지만 미묘한 차이가 있다.
- 생성자 함수 : Person 생성자 함수의 prototype 프로퍼티가 교체된 프로토타입을 가리킨다.
- 인스턴스 : Person 생성자 함수의 prototype 프로퍼티가 교체된 프로토타입을 가리키지 않는다.
- 프로토타입 교체를 통한 상속 관계를 동적으로 변경하는 것은 꽤나 번거로우니 직접 교체하지 않는 것이 좋다.
- 상속 관계를 인위적으로 설정하려면 후에 배울 직접 상속이 더 편리하고 안전하다. 또한 클래스를 사용하면 간편하고 직관적으로 상속 관계를 구현할 수 있다.
<19.10 instanceof 연산자>
- instanceof 연산자는 이항 연산자로서 좌변에는 '객체를 가리키는 식별자', 우변에는 '생성자 함수를 가리키는 식별자'를 피연산자로 받는다.
객체 instanceof 생성자 함수
- 우변의 생성자 함수의 prototype에 바인딩된 객체가 좌변의 객체의 프로토타입 체인 상에 존재하면 true로 평가되고, 그렇지 않은 경우에는 false로 평가된다.
- 예제)
// 생성자 함수
function Person(name) {
this.name = name;
}
const me = new Person('Lee');
// Person.prototype이 me 객체의 프로토타입 체인 상에 존재하므로 true로 평가된다.
console.log(me instanceof Person); // true
// Object.prototype이 me 객체의 프로토타입 체인 상에 존재하므로 true로 평가된다.
console.log(me instanceof Object); // true
// 생성자 함수
function Person(name) {
this.name = name;
}
const me = new Person('Lee');
// 프로토타입으로 교체할 객체
const parent = {};
// 프로토타입의 교체
Object.setPrototypeOf(me, parent);
// Person 생성자 함수와 parent 객체는 연결되어 있지 않다.
console.log(Person.prototype === parent); // false
console.log(parent.constructor === Person); // false
// Person.prototype이 me 객체의 프로토타입 체인 상에 존재하지 않기 때문에 false로 평가된다.
console.log(me instanceof Person); // false
// Object.prototype이 me 객체의 프로토타입 체인 상에 존재하므로 true로 평가된다.
console.log(me instanceof Object); // true
- 프로토타입의 교체 이후에 me instanceof Person이 false로 평가된 이유는 Person.prototype이 me 객체의 프로토타입 체인 상에 존재하지 않기 때문이다.
- 이는 프로토타입으로 교체한 parent 객체를 Parson 생성자 함수의 prototype 프로퍼티에 바인딩하면 true로 평가될 것이다.
- instanceof 연산자는 프로토타입의 constructor 프로퍼티가 가리키는 생성자 함수를 찾는 것이 아니라 생성자 함수의 prototype에 바인딩된 객체가 프로토타입 체인 상에 존재하는지 확인한다.
<19.11 직접 상속>
- Object.create 메서드는 명시적으로 프로토타입을 지정하여 새로운 객체를 생성한다. 그와 동시에 추상 연산 OrdinaryObjectCreate를 호출한다.
- Object.create 메서드의 "첫번째 매개변수"는 생성할 객체의 프로토타입으로 지정할 객체를 전달한다.
- "두번째 매개변수"에는 생성할 객체의 프로퍼티 키와 프로퍼티 디스크립터 객체로 이뤄진 객체를 전달한다.(생략 가능)
/**
* 지정된 프로토타입 및 프로퍼티를 갖는 새로운 객체를 생성하여 반환한다.
* @param {Object} prototype - 생성할 객체의 프로토타입으로 지정할 객체
* @param {Object} [propertiesObject] - 생성할 객체의 프로퍼티를 갖는 객체
* @returns {Object} 지정된 프로토타입 및 프로퍼티를 갖는 새로운 객체
*/
Object.create(prototype[, propertiesObject])
// 프로토타입이 null인 객체를 생성한다. 생성된 객체는 프로토타입 체인의 종점에 위치한다.
// obj → null
let obj = Object.create(null);
console.log(Object.getPrototypeOf(obj) === null); // true
// Object.prototype을 상속받지 못한다.
console.log(obj.toString()); // TypeError: obj.toString is not a function
// obj → Object.prototype → null
// obj = {};와 동일하다.
obj = Object.create(Object.prototype);
console.log(Object.getPrototypeOf(obj) === Object.prototype); // true
// obj → Object.prototype → null
// obj = { x: 1 };와 동일하다.
obj = Object.create(Object.prototype, {
x: { value: 1, writable: true, enumerable: true, configurable: true }
});
// 위 코드는 다음과 동일하다.
// obj = Object.create(Object.prototype);
// obj.x = 1;
console.log(obj.x); // 1
console.log(Object.getPrototypeOf(obj) === Object.prototype); // true
const myProto = { x: 10 };
// 임의의 객체를 직접 상속받는다.
// obj → myProto → Object.prototype → null
obj = Object.create(myProto);
console.log(obj.x); // 10
console.log(Object.getPrototypeOf(obj) === myProto); // true
// 생성자 함수
function Person(name) {
this.name = name;
}
// obj → Person.prototype → Object.prototype → null
// obj = new Person('Lee')와 동일하다.
obj = Object.create(Person.prototype);
obj.name = 'Lee';
console.log(obj.name); // Lee
console.log(Object.getPrototypeOf(obj) === Person.prototype); // true
- 이처럼 Object.create 메서드는 첫 번째 매개변수에 전달한 객체의 프로토타입 체인에 속하는 객체를 생성한다. 즉, 객체를 생성하면서 직접적으로 상속을 구현하는 것이다.
- 이 메서드의 장점이다.
- new 연산자 없이도 객체를 생성할 수 있다.
- 프로토타입을 지정하면서 객체를 생성할 수 있다.
- 객체 리터럴에 의해 생성된 객체도 상속받을 수 있다.
- 참고로 Object.prototype은 체인의 종점이므로 그의 빌트인 메서드인 hasOwnProperty, isPrototypeOf, propertyIsEnumerable 등은 모든 객체가 호출할 수 있다. 하지만 ESLint는 빌트인 메서드를 객체가 직접 호출하는 것을 권장하지 않는다.
- 그 이유는 Object.create 메서드를 통해 프로토타입 체인의 종점에 위치하는 객체를 생성할 수 있고, 체인의 종점에 위치하는 객체는 빌트인 메서드를 사용할 수 없기 때문이다.
- 따라서 다음과 같이 간접적으로 호출하는 것이 좋다.
// 프로토타입이 null인 객체를 생성한다.
const obj = Object.create(null);
obj.a = 1;
// console.log(obj.hasOwnProperty('a')); // TypeError: obj.hasOwnProperty is not a function
// Object.prototype의 빌트인 메서드는 객체로 직접 호출하지 않는다.
console.log(Object.prototype.hasOwnProperty.call(obj, 'a')); // true
- Object.create 메서드는 여러 장점이 있지만 두 번째 인자로 프로퍼티를 정의하는 것은 번거롭다.
- ES6에서는 객체 리터럴 내부에서 __proto__ 접근자 프로퍼티를 사용하여 직접 상속을 구현할 수 있다.
<19.12 정적 프로퍼티/메서드>
- 정적(static) 프로퍼티/메서드는 생성자 함수로 인스턴스를 생성하지 않아도 참조/호출할 수 있는 프로퍼티/메서드를 말한다.
- 예제)
// 생성자 함수
function Person(name) {
this.name = name;
}
// 프로토타입 메서드
Person.prototype.sayHello = function () {
console.log(`Hi! My name is ${this.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
- 정적 프로퍼티/메서드는 생성자 함수가 아닌 생성자 함수가 생성한 인스턴스로 참조/호출할 수 없다. 그 이유는 인스턴스의 프로토타입 체인에 속한 객체의 프로퍼티/메서드가 아니기 때문이다.
- 앞서 살펴본 Object.create 메서드는 Object 생성자 함수의 정적 메서드고 Object.prototype.hasOwnProperty 메서드는 Object.prototype의 메서드다.
- 인스턴스가 호출한 인스턴스/프로토타입 메서드 내에서 this는 인스턴스를 가리킨다. 이 this를 사용하지 않는다면, 즉 인스턴스를 참조할 필요가 없다면 정적 메서드로 변경하여도 동작한다.
- MDN과 같은 문서를 보면 다음과 같이 정적과 프로토타입의 프로퍼티/메서드를 구분하여 소개하고 있다. 따라서 표기법 만으로 구별할 수 있어야 한다.
<19.13 프로퍼티 존재 확인>
- in 연산자는 객체 내의 특정 프로퍼티가 존재하는지 여부를 확인한다.
/**
* key: 프로퍼티 키를 나타내는 문자열
* object: 객체로 평가되는 표현식
*/
key in object
- 단 확인 대상 객체의 프로퍼티뿐만 아니라 그 객체가 속한 프로토타입 체인 상에 존재하는 모든 프로토타입의 프로퍼티도 확인하니 주의가 필요하다.
- 예제)
const person = {
name: 'Lee',
address: 'Seoul'
};
// person 객체에 name 프로퍼티가 존재한다.
console.log('name' in person); // true
// person 객체에 address 프로퍼티가 존재한다.
console.log('address' in person); // true
// person 객체에 age 프로퍼티가 존재하지 않는다.
console.log('age' in person); // false
console.log('toString' in person); // true
- in 연산자 대신 ES6에서 도입된 Reflect.has 메서드를 사용할 수도 있다.
const person = { name: 'Lee' };
console.log(Reflect.has(person, 'name')); // true
console.log(Reflect.has(person, 'toString')); // true
- Object.prototype.hasOwnProperty 메서드를 사용해도 객체에 특정 프로퍼티가 존재하는지 확인할 수 있다.
- 메서드의 이름에서도 확인할 수 있듯이 객체 고유의 프로퍼티 키가 아닌 상속받은 프로토타입의 프로퍼티 키인 경우 false를 반환한다.
console.log(person.hasOwnProperty('name')); // true
console.log(person.hasOwnProperty('age')); // false
console.log(person.hasOwnProperty('toString')); // false
<19.14 프로퍼티 열거>
- 객체의 모든 프로퍼티를 순회하여 열거(enumeration)하려면 for...in 문을 사용한다.
for (변수선언문 in 객체) { ... }
const person = {
name: 'Lee',
address: 'Seoul'
};
// in 연산자는 객체가 상속받은 모든 프로토타입의 프로퍼티를 확인한다.
console.log('toString' in person); // true
// for...in 문도 객체가 상속받은 모든 프로토타입의 프로퍼티를 열거한다.
// 하지만 toString과 같은 Object.prototype의 프로퍼티가 열거되지 않는다.
for (const key in person) {
console.log(key + ': ' + person[key]);
}
// name: Lee
// address: Seoul
- 객체를 순회하며 key 변수에 프로퍼티 키를 할당한다.
- for...in 문은 in 연산자 처럼 상속받은 프로토타입의 프로퍼티까지 열거한다. 하지만 위의 예제에서는 toString과 같은 Object.prototype의 프로퍼티가 열거되지 않는다.
- 그 이유는 Object.prototype.string 프로퍼티의 프로퍼티 어트리뷰트 [[Enumerable]]의 값이 false이기 때문이다.
- 프로퍼티 어트리뷰트 [[Enumerable]]은 프로퍼티의 열거 가능 여부를 나타내며 불리언 값을 갖는다.
- for...in 문은 객체의 프로토타입 체인 상에 존재하는 모든 프로토타입의 프로퍼티 중에서 프로퍼티 어트리뷰트 [[Enumerable]]의 값이 true인 프로퍼티를 순회하며 열거한다.
- 예제)
const person = {
name: 'Lee',
address: 'Seoul',
__proto__: { age: 20 }
};
for (const key in person) {
console.log(key + ': ' + person[key]);
}
// name: Lee
// address: Seoul
// age: 20
- for...in 문은 프로퍼티가 심벌인 프로퍼티는 열거하지 않는다.
const sym = Symbol();
const obj = {
a: 1,
[sym]: 10
};
for (const key in obj) {
console.log(key + ': ' + obj[key]);
}
// a: 1
- 상속받은 프로퍼티가 아닌 객체 자신의 프로퍼티만 열거하려면 Object.prototype.hasOwnProperty 메서드를 사용하여 객체 자신의 프로퍼티인지 확인해야 한다.
- for...in 문은 프로퍼티를 열거할 때 순서를 보장하지 않으므로 주의해야 된다.
- 하지만 대부분의 모던 브라우저는 순서를 보장하고 숫자인 문자열의 프로퍼티 키에 대해서는 정렬을 실시한다.
const obj = {
2: 2,
3: 3,
1: 1,
b: 'b',
a: 'a'
};
for (const key in obj) {
if (!obj.hasOwnProperty(key)) continue;
console.log(key + ': ' + obj[key]);
}
/*
1: 1
2: 2
3: 3
b: b
a: a
*/
- 배열에는 for...in 문을 사용하지 말고 일반적인 for 문이나 for...of 문 또는 Array.prototype.forEach 메서드를 사용하기를 권장한다.
- 객체 자신의 고유 프로퍼티만 열거하기 위해서는 for...in 문을 사용하는 것보다 Object.keys/valuse/entries 메서드를 사용하는 것을 권장한다.
- Object.keys 메서드는 객체 자신의 열거 가능한 프로퍼티 키를 배열로 반환한다.
- 예제)
const person = {
name: 'Lee',
address: 'Seoul',
__proto__: { age: 20 }
};
console.log(Object.keys(person)); // ["name", "address"]
- ES8에 도입된 Object.values 메서드는 객체 자신의 열거 가능한 프로퍼티 값을 배열로 반환하고, ES8에 도입된 Object.entries 메서드는 객체 자신의 열거 가능한 프로퍼티 키와 값의 쌍의 배열을 배열에 담아 반환한다.
- 예제)
console.log(Object.values(person)); // ["Lee", "Seoul"]
console.log(Object.entries(person)); // [["name", "Lee"], ["address", "Seoul"]]
Object.entries(person).forEach(([key, value]) => console.log(key, value));
/*
name Lee
address Seoul
*/
* [43장 Ajax] > [p.816 ~ p.829]
<43.1 Ajax란?>
- Ajax는 자바스크립트를 사용해 브라우저가 서버에게 비동기 방식으로 데이터를 요청하고, 서버가 응답한 데이터를 수신하여 웹페이지를 동적으로 갱신하는 프로그래밍 방식이다.
- Ajax는 XMLHttpRequest 객체를 기반으로 동작하며, http 비동기 통신을 위한 메서드와 프로퍼티를 제공한다.
- 전통적인 웹페이지의 생명주기
- 전통적인 방식의 단점
- 차이가 없어 변경할 필요가 없는 부분까지 완전한 HTML을 서버로부터 매번 다시 전송받아 매번 불필요한 데이터 통신이 발생한다.
- 변경할 필요가 없는 부분까지 처음부터 다시 렌더링하기 때문에 화면 전환이 일어나면 화면이 순간적으로 깜박이는 현상이 발생한다.
- 클라이언트와 서버와의 통신이 동기 방식으로 동작하기 때문에 서버로부터 응답이 있을 떄까지 다음 처리는 블로킹된다.
- Ajax의 등장으로 서버로부터 웹페이지의 변경에 필요한 데이터만 비동기방식으로 전송받아, 변경하고 싶은 부분만 한정적으로 렌더링이 가능해졌다.
- Ajax 덕분에 브라우저에서도 애플리케이션과 유사한 빠른 퍼포먼스와 부드러운 화면전환이 가능해진 것이다.
- Ajax 방식
- Ajax 방식의 장점
- 변경할 부분만의 데이터를 서버로 전송받기 때문에 불필요한 데이터 통신이 발생하지 않는다.
- 변경할 부분은 다시 렌더링하지 않으므로 순간적으로 깜박이는 현상이 발생하지 않는다.
- 클라이언트와 서버와의 통신이 비동기적으로 동작하기 때문에 서버에게 요청을 보낸 후 블로킹이 발생하지 않는다.
<43.2 JSON>
- JSON (JavaScript object Notation)은 클라이언트와 서버 간의 HTTP 통신을 위한 텍스트 데이터 포맷, 자바스크립트에 종속되지 않는 언어 독립형 데이터 포맷이기 때문에 대부분의 프로그래밍 언어에서 사용할 수 있다.
- 객체 리터럴과 마찬가지로 키와 값으로 구성된 순수한 텍스트이다.
- JSON의 키는 항상 큰따옴표로 묶어야한다.
- JSON.stringify 메서드는 객체를 JSON 포맷의 문자열로 변환한다. 클라이언트가 서버로 객체를 전송하려면 객체를 문자열화 해야하는데, 이를 직렬화라고 부른다.
- 예제)
const obj = {
name: 'Lee',
age: 20,
alive: true,
hobby: ['traveling', 'tennis']
};
// 객체를 JSON 포맷의 문자열로 변환한다.
const json = JSON.stringify(obj);
console.log(typeof json, json);
// string {"name":"Lee","age":20,"alive":true,"hobby":["traveling","tennis"]}
// 객체를 JSON 포맷의 문자열로 변환하면서 들여쓰기 한다.
const prettyJson = JSON.stringify(obj, null, 2);
console.log(typeof prettyJson, prettyJson);
/*
string {
"name": "Lee",
"age": 20,
"alive": true,
"hobby": [
"traveling",
"tennis"
]
}
*/
// replacer 함수. 값의 타입이 Number이면 필터링되어 반환되지 않는다.
function filter(key, value) {
// undefined: 반환하지 않음
return typeof value === 'number' ? undefined : value;
}
// JSON.stringify 메서드에 두 번째 인수로 replacer 함수를 전달한다.
const strFilteredObject = JSON.stringify(obj, filter, 2);
console.log(typeof strFilteredObject, strFilteredObject);
/*
string {
"name": "Lee",
"alive": true,
"hobby": [
"traveling",
"tennis"
]
}
*/
- JSON.stringify 메서드는 객체뿐만 아니라 배열도 JSON 포맷의 문자열로 변환한다.
- JSON.parse 메서드는 JSON 형태의 문자열을 객체로 변환한다.
- 서버로부터 클라이언트에 들어온 문자열을 사용하기 위해 JSON을 객체로 변환하는 것을 역직렬화라고 한다.
- 예제)
const obj = {
name: 'Lee',
age: 20,
alive: true,
hobby: ['traveling', 'tennis']
};
// 객체를 JSON 포맷의 문자열로 변환한다.
const json = JSON.stringify(obj);
// JSON 포맷의 문자열을 객체로 변환한다.
const parsed = JSON.parse(json);
console.log(typeof parsed, parsed);
// object {name: "Lee", age: 20, alive: true, hobby: ["traveling", "tennis"]}
- 배열이 JSON 포맷의 문자열로 변환되어 있는 경우 JSON.parse는 문자열을 배열 객체로 변환하고 배열의 요소가 객체일 경우 배열의 요소도 원래 상태대로 객체로 변환한다.
<43.3 XMLHttpRequest>
- 브라우저는 알다시피 주소창이나 HTML의 form나 a 태그를 통해 Http 요청 전송 기능을 기본적으로 제공한다.
- 자바스크립트를 활용하여 Http 요청을 전송하려면 XMLHttpRequest 객체를 활용한다.
- Web API인 XMLHttpRequest 객체는 Http 요청 전송과 Http 응답 수신을 위한 다양한 메서드와 프로퍼티를 제공한다.
- XMLHttpRequest 객체 생성
- 객체는 생성자 함수를 호출하여 생성한다.
- XMLHttpRequest 객체는 브라우저에서 제공하는 Web APi이므로 브라우저 환경에서만 정상적으로 실행된다.
// XMLHttpRequest 객체 생성
const xhr = new XMLHttpRequest();
- XMLHttpRequest 객체의 프로퍼티와 메서드
- 프로토타입 프로퍼티
- 이벤트 핸들러 프로퍼티
- 메서드
- 정적 프로퍼티
- Http 요청 전송
- open 메서드로 Http 요청을 초기화한다.
- setRequestHeader 메서드로 특정 Http 요청의 헤더 값을 설정한다.
- send 메서드로 Http 요청을 전송한다.
- XMLHttpRequest.prototype.open
- open 메서드는 서버에 전송할 요청을 초기화한다.
- XMLHttpRequest.prototype.send
- send 메서드는 open 메서드로 초기화된 요청을 서버에 전송한다.
- send 메서드에서 페이로드가 객체인 경우 반드시 JSON.stringify메서드를 사용하여 직렬화한 다음 전달해야 한다.
- Http 요청 메서드가 GET인 경우 send 메서드에 페이로드로 전달한 인수는 무시되고 몸체엔 null로 설정된다.
- XMLHttpRequest.prototype.setRequestHeader
- setRequestHeader 메서드는 특정 요청의 헤더값을 설정한다.
- 반드시 open 메서드가 호출된 이후에 호출해야만 한다.
- Http 응답처리
- 서버가 전송한 응답을 처리하려면 XMLHttpRequest 객체가 발생시키는 이벤트를 캐치해야 한다.
- 이벤트 핸들러 프로퍼티 중에서 Http 요청의 현재 상태를 나타내는 readyState 프로퍼티 값이 변경된 경우엔 readystatechange 이벤트를 캐치하여 처리할 수 있다.
- XMLHttpRequest 객체는 브라우저에서 제공하는 Web API 이므로 반드시 브라우저에서 실행해야 한다.
- readystatechange 예제)
// XMLHttpRequest 객체 생성
const xhr = new XMLHttpRequest();
// HTTP 요청 초기화
// https://jsonplaceholder.typicode.com은 Fake REST API를 제공하는 서비스다.
xhr.open('GET', 'https://jsonplaceholder.typicode.com/todos/1');
// HTTP 요청 전송
xhr.send();
// readystatechange 이벤트는 HTTP 요청의 현재 상태를 나타내는 readyState 프로퍼티가
// 변경될 때마다 발생한다.
xhr.onreadystatechange = () => {
// readyState 프로퍼티는 HTTP 요청의 현재 상태를 나타낸다.
// readyState 프로퍼티 값이 4(XMLHttpRequest.DONE)가 아니면 서버 응답이 완료되지 상태다.
// 만약 서버 응답이 아직 완료되지 않았다면 아무런 처리를 하지 않는다.
if (xhr.readyState !== XMLHttpRequest.DONE) return;
// status 프로퍼티는 응답 상태 코드를 나타낸다.
// status 프로퍼티 값이 200이면 정상적으로 응답된 상태이고
// status 프로퍼티 값이 200이 아니면 에러가 발생한 상태다.
// 정상적으로 응답된 상태라면 response 프로퍼티에 서버의 응답 결과가 담겨 있다.
if (xhr.status === 200) {
console.log(JSON.parse(xhr.response));
// {userId: 1, id: 1, title: "delectus aut autem", completed: false}
} else {
console.error('Error', xhr.status, xhr.statusText);
}
};
- send로 http 요청을 보내면 서버는 응답을 반환하지만 언제 응답이 클라이언트에 도달할지 모르기 때문에 readystatechange 이벤트를 통해 http 요청의 현재 상태를 확인 해야한다.
- 따라서, readyState 프로퍼티의 값이 변경될 때마다 실행된다.
- 서버의 응답이 완료 되면 Http 요청에 대한 응답 상태 (Http 상태 코드)를 나타내는 xhr.status가 200일 때 정상처리를 한다.
- 그 후 응답으로 받아온 것을 JSON.parse를 통해 사용 가능하게 만든다.
- load 예제)
// XMLHttpRequest 객체 생성
const xhr = new XMLHttpRequest();
// HTTP 요청 초기화
// https://jsonplaceholder.typicode.com은 Fake REST API를 제공하는 서비스다.
xhr.open('GET', 'https://jsonplaceholder.typicode.com/todos/1');
// HTTP 요청 전송
xhr.send();
// load 이벤트는 HTTP 요청이 성공적으로 완료된 경우 발생한다.
xhr.onload = () => {
// status 프로퍼티는 응답 상태 코드를 나타낸다.
// status 프로퍼티 값이 200이면 정상적으로 응답된 상태이고
// status 프로퍼티 값이 200이 아니면 에러가 발생한 상태다.
// 정상적으로 응답된 상태라면 response 프로퍼티에 서버의 응답 결과가 담겨 있다.
if (xhr.status === 200) {
console.log(JSON.parse(xhr.response));
// {userId: 1, id: 1, title: "delectus aut autem", completed: false}
} else {
console.error('Error', xhr.status, xhr.statusText);
}
};
- readystateChange이벤트가 아니라 load 이벤트를 캐치해도 된다.
- load는 Http 요청이 성공적으로 완료 되었을 때만 발생한다.
- 따라서 readyState가 XMLHttpRequest.DONE인지 확인할 필요가 없다.
'모던 자바스크립트 Deep Dive' 카테고리의 다른 글
Chapter 34 & 41 & 48 (0) | 2024.01.29 |
---|---|
Chapter 42 & 45 & 46 (0) | 2024.01.22 |
Chapter 22 & 24 & 26 (0) | 2024.01.17 |