Chapter link: https://www.theodinproject.com/lessons/node-path-javascript-classes
Translated from: https://javascript.info/class
class MyClass {
// class methods
constructor() { ... }
method1() { ... }
method2() { ... }
method3() { ... }
...
}
객체 생성은 new MyClass()
를 사용한다.
constructor()
메소드는 new
에 의해 자동으로 호출되고 객체를 초기화 한다.
class User {
constructor(name) {
this.name = name;
}
sayHi() {
alert(this.name);
}
}
// Usage:
let user = new User("John");
user.sayHi();
new User("John")
이 호출될 때:
1. 새 객체가 생성된다.
2. constructor
가 발동하고 인수를 this.name
에 할당한다.
... 이후 우리는 user.sayHi()
와 같은 객체의 메소드를 발동할 수 있게 된다.
자바스크립트에서 class
는 일종의 함수처럼 기능한다.
class User {
constructor(name) { this.name = name; }
sayHi() { alert(this.name); }
}
// proof: User is a function
alert(typeof User); // function
class User {...}
는 실제로 어떻게 기능하는가?
User
라는 함수를 만든다. 함수의 코드는 constructor
메소드에서 가져온다. (만약 해당 메소드를 작성하지 않았다면 비어있다고 가정한다)sayHi
와 같은 클래스 메소드를 User.prototype
에 저장한다.new User
객체가 만들어지고 나면, 우리가 호출하는 메소드는 prototype에 저장된 것을 꺼내오는 것이다. 따라서 객체는 클래스 메소드에 접근할 수 있다.
class User {
constructor(name) { this.name = name; }
sayHi() { alert(this.name); }
}
// class is a function
alert(typeof User); // function
// ...or, more precisely, the constructor method
alert(User === User.prototype.constructor); // true
// The methods are in User.prototype, e.g:
alert(User.prototype.sayHi); // the code of the sayHi method
// there are exactly two methods in the prototype
alert(Object.getOwnPropertyNames(User.prototype)); // constructor, sayHi
// rewriting class User in pure functions
// 1. Create constructor function
function User(name) {
this.name = name;
}
// a function prototype has "constructor" property by default,
// so we don't need to create it
// 2. Add the method to prototype
User.prototype.sayHi = function() {
alert(this.name);
};
// Usage:
let user = new User("John");
user.sayHi();
자바스크립트에서는 class
를 사용하지 않고도 같은 결과를 불러오는 코드를 얼마든지 작성할 수 있다. 이런 특성 때문에 때때로 사람들은 자바스크립트의 클래스는 새로운 기능이 없는, 그저 가독성을 위해 만들어진 기능이라고 말한다.
그럼에도 불구하고 class
에는 중요한 특성이 내포되어 있다.
class
에 의해 만들어진 함수는 [[IsClassConstructor]]: true
라는 내부적 속성을 가진다.new
를 통해서만 호출되어야 한다.class User {
constructor() {}
}
alert(typeof User); // function
User(); // Error: Class constructor User cannot be invoked without 'new'
alert(User); // class User { ... }
클래스 메소드는 열거가 불가능하다. 클래스는 "prototype"
의 모든 메소드의 enumerable
플래그에 false
를 부여한다.
따라서 우리가 객체에 for...in
을 사용할 때 클래스 메소드를 불러오지 않을 수 있다.
클래스는 항상 strict
모드에서 쓰여진다.
함수와 마찬가지로 클래스도 다른 표현식의 안에서 정의되거나, 여기저기로 전달되거나, 리턴되거나 할당될 수 있다.
let User = class {
sayHi() {
alert("Hello");
}
};
기명 함수 표현식(Named FUnction Expressions)처럼 클래스 표현식도 이름을 가질 수 있다.
클래스를 기명 함수 표현식을 통해 작성하면, 클래스의 이름은 클래스 내부에서만 보인다.
// "Named Class Expression"
// (no such term in the spec, but that's similar to Named Function Expression)
let User = class MyClass {
sayHi() {
alert(MyClass); // MyClass name is visible only inside the class
}
};
new User().sayHi(); // works, shows MyClass definition
alert(MyClass); // error, MyClass name isn't visible outside of the class
아래와 같이 좀 더 역동적으로 수요에 그때그때 대응하는 클래스를 만들수도 있다.
function makeClass(phrase) {
// declare a class and return it
return class {
sayHi() {
alert(phrase);
}
};
}
// Create a new class
let User = makeClass("Hello");
new User().sayHi(); // Hello
다른 literal object들과 같이, 클래스도 getters/setters나 computed properties를 쓸 수 있다.
class User {
constructor(name) {
// invokes the setter
this.name = name;
}
get name() {
return this._name;
}
set name(value) {
if (value.length < 4) {
alert("Name is too short.");
return;
}
this._name = value;
}
}
let user = new User("John");
alert(user.name); // John
user = new User(""); // Name is too short.
이러한 클래스 선언은 getters와 setters를 User.prototype
에 저장함으로써 기능한다.
class User {
['say' + 'Hi']() {
alert("Hello");
}
}
new User().sayHi();
❗️ Class fields는 최근에 추가된 기능으로 구버전의 브라우저에서 작동하지 않을 수 있다.
"Class fields"는 메소드 뿐 아니라 다른 속성들을 클래스에 추가해줄 수 있는 구문이다.
예를 들어 다음과 같이 class User
에 name
이라는 속성을 부여할 수 있다.
class User {
name = "John";
sayHi() {
alert(`Hello, ${this.name}!`);
}
}
new User().sayHi(); // Hello, John!
"="를 써서 선언하기만 하면 된다.
Class fields의 중요한 점은 바로 해당 속성이 User.prototype
이 아닌 각각의 객체에만 세팅이 된다는 것이다.
class User {
name = "John";
}
let user = new User();
alert(user.name); // John
alert(User.prototype.name); // undefined
다음과 같이 좀더 복잡한 표현식이나 함수 호출을 할당할 수도 있다.
class User {
name = prompt("Name, please?", "John");
}
let user = new User();
alert(user.name); // John
class Button {
constructor(value) {
this.value = value;
}
click() {
alert(this.value);
}
}
let button = new Button("hello");
setTimeout(button.click, 1000); // undefined
위 코드는 도중에 this
를 잃어버리는 문제점을 내포하고 있다.
이를 바로잡기 위해 Function binding 에서 언급되었던 두가지 해결방안을 도입할 수 있다.
setTimeout(() => button.click(), 1000)
Class fields는 이에 더해 좀 더 우아한 방식을 제공한다.
class Button {
constructor(value) {
this.value = value;
}
click = () => {
alert(this.value);
}
}
let button = new Button("hello");
setTimeout(button.click, 1000); // hello
Class field click = () => {...}
는 각각의 객체를 기반으로 만들어진다. 따라서 각각의 Button
객체는 해당 객체와 연결된this
를 가진 서로 다른 함수를 갖고 있게 되는 것이다. 이 경우 this
의 값은 언제나 뜻대로 움직이게 된다.
이 기능은 특히 브라우저 환경에서 이벤트 리스너를 사용할 때 유용하다.
기본적인 클래스 구문은 다음과 같다.
class MyClass {
prop = value; // property
constructor(...) { // constructor
// ...
}
method(...) {} // method
get something(...) {} // getter method
set something(...) {} // setter method
[Symbol.iterator]() {} // method with computed name (symbol here)
// ...
}
종합적으로 MyClass
는 메소드, getters, setters가 MyClass.prototype
에 씌여진, constructor
로써 제공되는 함수라고 정의할 수 있다.