자바스크립트 완벽 가이드 7판 스터디
책의 목차를 따라가며 관련 내용을 학습하고 공유하는 스터디입니다.
1. JavaScript Class
2. 생성자 함수를 클래스로 변경해보기
3. 클래스의 메서드: constructor, prototype method, static method
4. 클래스의 프로퍼티: instance property
5. 클래스 필드: private, static
6. 클래스의 getter, setter
7. 상속(확장): extends, super, overriding
8. 예제 연습
// 대문자 사용
function Range(from, to) {
if (!new.target) console.error('Range가 생성자로 호출되지 않음');
//인스턴스 프로퍼티 : 상속되지않는 인스턴스 고유의 값
this.from = from;
this.to = to;
}
// 프로토타입 설정
// Range를 생성자로 호출 시 자동으로 Range.prototype을 새 Range 객체의 프로토타입으로 사용
Range.prototype = {
// 역참조. 새로 생성한 객체로 덮어썼기 때문에 constructor를 작성 필요
// constructor는 생성자 함수 자체를 참조함
constructor: Range,
// 프로토타입 메서드
includes: function (x) {
return this.from <= x && x <= this.to;
},
[Symbol.iterator]: function* () {
for (let x = Math.ceil(this.from); x <= this.to; x++) {
yield x;
}
},
// 화살표 함수로 메서드를 작성하는 것은 무의미
//
toString: () => {
console.log(this); // Window, 호출한 객체가 아니라 자신이 정의된 컨텍스트에서 this를 상속받는다.
return `(${this.from} ... ${this.to})`;
},
};
// new 사용
const oneToTen = new Range(1, 10);
const tenTotwenty = new Range(10, 20);
// Range 생성자함수
console.dir(Range);
// Range 생성자함수를 사용해 만든 인스턴스
console.dir(oneToTen);
console.dir(tenTotwenty);
console.log(oneToTen instanceof Range); // true
console.log(tenTotwenty instanceof Range); // true
console.log(Range.prototype.isPrototypeOf(oneToTen)); // true
console.log(Range.prototype.isPrototypeOf(tenTotwenty)); // true
console.log(oneToTen.prototype === tenTotwenty.prototype); // true
// class 선언의 바디는 묵시적으로 스트릭트 모드로 동작
class Range {
// 생성자 함수 정의
// 새 변수 Range에 constructor 함수의 값을 할당
constructor(from, to) {
this.from = from;
this.to = to;
}
// function 키워드 생략
// 콤마로 구분 x
// key:value로 정의하지 않음
includes(x) {
return this.from <= x && x <= this.to;
}
*[Symbol.iterator]() {
for (let x = Math.ceil(this.from); x <= this.to; x++) {
yield x;
}
}
toString() {
console.log(this);
return `(${this.from} ... ${this.to})`;
}
}
// new 사용
const oneToTen = new Range(1, 10);
const tenTotwenty = new Range(10, 20);
// Class Range
console.dir(Range);
// Range 클래스로 만든 인스턴스
console.dir(oneToTen);
console.dir(tenTotwenty);
console.log(oneToTen instanceof Range); // true
console.log(tenTotwenty instanceof Range); // true
console.log(Range.prototype.isPrototypeOf(oneToTen)); // true
console.log(Range.prototype.isPrototypeOf(tenTotwenty)); // true
console.log(oneToTen.prototype === tenTotwenty.prototype); // true
constructor
constructor
의 인자로 받거나 내부에서 값을 할당한다.class Car {
// 이 시점에 constructor가 객체(인스턴스)를 생성하고,
// this가 그 객체(인스턴스)를 참조한다.
// 그렇기 때문에 constructor가 제일 상단에 있어야 한다.
constructor(name, manufacturer) {
this.name = name;
this.manufacturer = manufacturer; // 인자로 받음
this.id = 1; // 내부에서 할당
}
display() {
console.log(`${this.name} of ${this.manufacturer}`);
}
}
constructor
가 정의된고 빈 객체를 생성한다.class Car {
// constructor() {}
constructor() {}
// 작성하지 않아도 이렇게 임의의 빈 constructor가 정의된다.
// 빈 객체를 생성하고 this는 그 빈 객체를 참조한다.
go() {
this; // 생성된 인스턴스 객체
console.log(`gogogo`);
}
}
class Car {
constructor(name, manufacturer) {
this.name = name;
this.manufacturer = manufacturer;
return 'car';
}
display() {
console.log(`${this.name} of ${this.manufacturer}`);
}
}
class Truck {
constructor(name, manufacturer) {
this.name = name;
this.manufacturer = manufacturer;
return { length: 100 };
}
display() {
console.log(`${this.name} of ${this.manufacturer}`);
}
}
const myRaptor = new Truck('Raptor', 'Ford');
console.log(myRaptor); // {length: 100}
constructor
메서드와 프로토타입의 constructor
다르다.prototype method
prototype method
로 추가된다.prototype method
가 아닌 instance method
가 되니 주의한다.class Range {
constructor(from, to) {
this.from = from;
this.to = to;
}
includes(x) {
return this.from <= x && x <= this.to;
}
*[Symbol.iterator]() {
for (let x = Math.ceil(this.from); x <= this.to; x++) {
yield x;
}
}
toString() {
console.log(this);
return `(${this.from} ... ${this.to})`;
}
}
const oneToTen = new Range(1, 10);
const tenToTwenty = new Range(1, 10);
console.dir(oneToTen);
console.dir(tenToTwenty);
console.log(oneToTen instanceof Range);
console.log(tenToTwenty instanceof Range);
console.log(Range.prototype.isPrototypeOf(oneToTen));
console.log(Range.prototype.isPrototypeOf(tenToTwenty));
console.log(oneToTen.prototype === tenToTwenty.prototype);
static method
static
을 붙이면 정적 메서드가 된다.class Range {
constructor(from, to) {
this.from = from;
this.to = to;
}
static maker() {
return 'sangbeomheo';
}
includes(x) {
return this.from <= x && x <= this.to;
}
toString() {
console.log(this);
return `(${this.from} ... ${this.to})`;
}
}
const oneToTen = new Range(1, 10);
console.log(Range.maker()); // 'sangbeomheo'
console.log(oneToTen.maker()); // TypeError: oneToTen.whose is not a function
Math.max();
Math.min();
Object.keys();
Array.from();
Array.isArray();
등등;
this[propertyName]
으로 접근한다.class Range {
constructor(from, to) {
// 인스턴스 프로퍼티 초기화
// 만들어진 빈 객체를 this가 가리키고, 그 this에 프로퍼티를 추가한다.
this.from = from;
this.to = to;
}
toString() {
console.log(this);
return `(${this.from} ... ${this.to})`;
}
}
constructor
를 반드시 사용해야 한다.this
없이 선언할 수 있다.constructor
를 사용하고,class Person {
name = 'Sangbeom'; // 클래스 필드 선언. 앞에 아무것도 없이 선언하면 public이다.
constructor(age) {
this.age = 19;
}
}
new Person(); // Person {age: 19, name: "Sangbeom"}
private
#
을 붙여 사용. 외부에서 접근이 불가능하다.static
static
을 붙여 사용private
으로 캡슐화가 가능하다.class Car {
constructor(name, manufacturer) {
this.name = name;
this.manufacturer = manufacturer;
}
static #count = 1; // static, private
// static method
// 이름과 제조사를 랜덤으로 받는 새로운 인스턴스 객체를 리턴하는 정적메소드
static makeRandomCar() {
const [name, manufacturer] = [
`RandomName${this.#count}`, // 클래스 내부에서 접근 가능
`RandomManufacturer${this.#count}`,
];
this.#count++;
return new Car(name, manufacturer);
}
display() {
console.log(`${this.name} of ${this.manufacturer}`);
}
}
const myModel3 = new Car('Model3', 'Tesla');
const myRapter = new Car('Rapter', 'Ford');
const random1 = Car.makeRandomCar();
const random2 = Car.makeRandomCar();
console.log(random1); // Car {name: 'RandomName1', manufacturer: 'RandomManufacturer1'}
console.log(random2); // Car {name: 'RandomName2', manufacturer: 'RandomManufacturer2'}
console.log(Car.#count); // SyntaxError: Private field '#count' must be declared in an enclosing class
console.log(random2.display()); // RandomName2 of RandomManufacturer2
console.log(random2.makeRandomCar()); // Uncaught TypeError: random2.makeRandomCar is not a function
class Student {
#firstName;
#lastName;
constructor(firstName, lastName) {
this.#firstName = firstName;
this.#lastName = lastName;
}
get fullName() {
return `${this.#lastName} ${this.#firstName}`;
}
set fullName(value) {
console.log('set', value);
}
}
const student = new Student('상범', '허');
student.#firstName = '종승'; // Uncaught SyntaxError: Private field '#firstName' must be declared in an enclosing class
console.log(student.#firstName); // // Uncaught SyntaxError: Private field '#firstName' must be declared in an enclosing class
console.log(student.fullName); // 허 상범
student.fullName = '박상은'; // set 박상은
console.log(student.fullName); // 허 상범
수퍼클래스
& 서브클래스(상속을 통해 확장된 클래스)
extends
class Animal {
constructor(name, weight) {
this.name = name;
this.weight = weight;
}
static testStatic() {
console.log('static');
}
eat() {
console.log(`${this.name}이 먹는다.`);
}
sleep() {
console.log(`${this.name}이 잔다.`);
}
}
class Dog extends Animal {} // 확장
const dog = new Dog('a', 300);
console.dir(dog);
dog.sleep(); // a이 잔다.
dog.eat(); // a이 먹는다.
Dog.testStatic(); // 'static'
super
를 호출하면 수퍼클래스의 constructor를 호출한다.super
를 호출하면 수퍼클래스의 메서드를 호출할 수 있다.class Animal {
constructor(name, weight) {
this.name = name;
this.weight = weight;
}
static testStatic() {
console.log('static');
}
eat() {
console.log(`${this.name}이 먹는다.`);
}
sleep() {
console.log(`${this.name}이 잔다.`);
}
}
class Cat extends Animal {
constructor(name, weight, space) {
// 수퍼클래스의 constructor를 호출해서 인스턴스 객체를 생성한다.
// 그렇기 때문에 수퍼클래스의 프로퍼티를 그대로 갖는다.
// constructor를 생략하지 않는 경우 무조건 super를 호출해야 한다.
super(name, weight);
this.space = space;
}
eat() {
// method overriding
// 수퍼클래스의 프로토타입 메서르르 가리킨다.
super.eat();
console.log('고양이는 많이 먹는다');
}
}
const cat = new Cat('b', 100, '러시안블루');
console.log(cat);
cat.eat(); // b이 먹는다. 고양이는 많이 먹는다
cat.sleep(); // b이 잔다.
Cat.testStatic(); // static (정적 메소드도 상속)
class Classifier {
static classify(number) {
return (
`\n${number} : ` +
(Classifier.isPerfect(number) ? 'perfect, ' : '') +
(Classifier.isAbundant(number) ? 'abundant, ' : '') +
(Classifier.isDeficient(number) ? 'deficient, ' : '') +
(Classifier.isPrime(number) ? 'prime ' : '') +
(Classifier.isSquared(number) ? 'squard ' : '')
);
}
static isPerfect(number) {
return this.sum(this.getFactors(number)) - number == number;
}
static isAbundant(number) {
return this.sum(this.getFactors(number)) - number > number;
}
static isDeficient(number) {
return this.sum(this.getFactors(number)) - number < number;
}
static isPrime(number) {
return this.getFactors(number).length === 2;
}
static isSquared(number) {
return this.getFactors(number).some(factor => Math.pow(factor, 2) === number);
}
static sum(factors) {
return factors.reduce((total, curr) => total + curr, 0);
}
static getFactors(number) {
return [...Array(number)]
.map((v, i) => i + 1)
.filter(potentialFactor => number % potentialFactor == 0);
}
}
Object.freeze(Classifier); // 메소드를 변경하지 못하게 프리즈
console.log(Classifier.classify(17)); // 17 : deficient, prime
console.log(Classifier.classify(16)); // 16 : deficient, squard