자바스크립트에서 클래스의 속성들은 기본적으로 Public이기 때문에 클래스 외부에서 접근할 수 있다.
하지만 ES2019부터 #prefix 를 추가해 클래스 내부에서 Private하게 메소드와 필드를 선언할 수 있게 되었다. 실제 현업에서도 많이 사용되는 방식이라고 하니, 반드시 알고 있어야 한다.
(보안을 위해서도 필요하다고 한다.)
Private한 메소드와 필드는 클래스 내부에서만 불러올 수 있게 된다.
예시)
class ClassWithPrivateMethodAndField{
field = 'public field'
#privateField = 'private field'
publicMethod() {
return 'call private method: '+ this.#privateMethod();
}
#privateMethod() {
return this.#privateField
}
}
let cls = new ClassWithPrivateMethodAndField()
console.log(cls.field)
console.log(cls.publicMethod())
public field
call private method: private field
실제로 다음을 보면 외부에서 접근시 다음과 같은 에러를 반환합니다.
class ClassWithPrivateMethodAndField{
field = 'public field'
#privateField = 'private field'
publicMethod() {
return 'call private method: '+ this.#privateMethod();
}
#privateMethod() {
return this.#privateField
}
}
let cls = new ClassWithPrivateMethodAndField()
console.log(cls.privateField)
console.log(cls.privateMethod())
undefined
TypeError: cls.privateMethod is not a function
또 다른 예시
class Factory {
#privateField = 'foo'
publicField = 'bar'
#privateMethod() {
return 'baz'
}
publicMethod() {
return this.#privateField;
}
}
const obj = new Factory()
obj.#privateField // Uncaught SyntaxError: Private field '#privateMehtod' must be declared in an enclosing class --> 프라이빗이 잘 작동한 것을 확인할 수 있다!
obj.publicField // 'bar'
obj.#privateMethod() // Uncaught SyntaxError: Private field '#privateMehtod' must be declared in an enclosing class --> 프라이빗이 잘 작동한 것을 확인할 수 있다!
obj.publicMethod() // 'foo'
객체지향 프로그래밍은 현실에 존재하는 객체를 소프트웨어에 표현하기 위해 상태와 행위를 추상화(abstraction)하여 모델링하는 프로그래밍 패러다임을 말한다.
다시 말해, 우리가 주변의 사물과 우리의 행위를 프로그래밍에 접목하려는 것을 말한다.
Java나 C++ 같은 클래스 기반의 언어에서는 class를 이용하여 속성(attribute)과 행위(method)를 정의한다.
new를 사용하여 인스턴스화하는 과정을 통해 객체를 생성한다.
class Person {
private String job;
public Person(String job) {
this.job = job;
}
public void setJob(job) {
this.job = job;
}
public String getJob() {
return this.job;
}
public static void main(String args[]) {
Person bkjang = new Person("developer");
String job = bkjang.getJob();
System.out.println(job); //developer
}
}
자바스크립트는 클래스라는 개념이 없다.(ES6에서 class가 생기긴 했지만 이는 사실 함수이며 프로토타입 기반의 syntactic sugar라고 할 수 있다.)
자바스크립트는 대표적인 프로토타입 기반의 객체지향 언어이다.
클래스라는 개념이 없지만 자바스크립트에는 3가지의 객체 생성 방법이 있다.
객체 리터럴
Object() 생성자 함수
생성자 함수
// 객체 리터럴
var obj = {
name : 'BKJang',
job : 'Developer'
}
// Object 생성자 함수
var obj = new Object();
obj.name = 'BKJang';
obj.job = 'Developer';
// 생성자 함수
function Person(name, job) {
this.name = name;
this.job = job;
}
var obj = new Person('BKJang', 'Developer');
자바스크립트는 생성자 함수를 이용해 객체를 생성할 수 있다.
function Developer(lang) {
this.lang = lang;
//메서드 정의
this.setLang = function(lang) {
this.lang = lang;
};
this.getLang = function() {
return this.lang;
};
}
var frontEnd = new Developer('Javascript');
var backEnd = new Developer('Java');
console.log(frontEnd.getLang()); //Javascript
console.log(backEnd.getLang()); //Java
backEnd.setLang('Node.js');
console.log(backEnd.getLang()); //Node.js
위 예제에서 볼 수 있듯이 생성자 함수내에 프로퍼티와 메서드를 정의하고 new연산자를 이용해 객체를 생성한다.
console.log(frontEnd); //{lang: "Javascript", setLang: ƒ, getLang: ƒ}
console.log(backEnd); //{lang: "Node.js", setLang: ƒ, getLang: ƒ}
하지만 위에서 볼 수 있듯이 frontend와 backend 객체는 각각 setLang()과 getLang() 메서드를 가지고 있다.
위의 방식으로 구현하면 객체가 많아질수록 불필요하게 같은 메서드를 모두 각각 가지고 있게 되며 이는 메모리의 낭비로 이어질 수 있다.
이를 막기 위해 자바스크립트에서는 프로토타입을 이용할 수 있다.
자바스크립트에서 모든 객체는 프로토타입이라는 내부 링크를 갖고 있고 프로토타입을 통해 객체를 연결한다. 이를 프로토타입 체인이라고 한다.
function Developer(lang) {
this.lang = lang;
//메서드 정의
Developer.prototype.setLang = function(lang) {
this.lang = lang;
};
Developer.prototype.getLang = function() {
return this.lang;
};
}
var frontEnd = new Developer('Javascript');
var backEnd = new Developer('Java');
console.log(frontEnd.getLang()); //Javascript
console.log(backEnd.getLang()); //Java
backEnd.setLang('Node.js');
console.log(backEnd.getLang()); //Node.js
위의 코드를 보면 생성자 함수 내부에서 Developer() 함수의 프로토타입 프로퍼티가 가리키는 Developer.prototype 객체에 setLang()과 getLang()을 정의한다.
이후 frontEnd와 backEnd객체에서는 프로토타입 체인을 통해 [[Prototype]] 프로퍼티가 가리키는 즉, 부모 객체인 Developer.prototype객체에 정의된 메서드들을 사용할 수 있다.
console.log(frontEnd); //Developer {lang: "Javascript"}
console.log(backEnd); //Developer {lang: "Node.js"}
console.log(frontEnd.__proto__ === backEnd.__proto__); //true
console.log(frontEnd.__proto__ === Developer.prototype); //true
console.log(Developer.prototype); //{setLang: ƒ, getLang: ƒ, constructor: ƒ}
위 코드의 출력 값들을 보면 frontEnd.proto와 backEnd.proto는 모두 Developer.prototype 객체를 가리킨다.
또한 getLang()과 setLang() 메서드는 frontEnd와 backEnd 각각의 객체가 아닌 Developer.prototype 객체에만 정의되어 있는 것을 볼 수 있다.
이를 통해 첫 번째 예제에서 문제가 됐던 메모리 낭비 부분을 없앨 수 있다.
▶ prototype property 내부에 할당되지 않고, Array 생성자 함수 객체에 직접 할당되어 있는 property
▶ 이들을 static method, static properties 라 칭한다
▶ 이들은 Array 생성자 함수를 new 연산자 없이 함수로써 호출할 때에만 의미가 있다.
▶ 보통 해당 Class의 소속인 instance들의 개별적인 동작이 아니라,
소속 여부의 확인, 소속 부여 등의 공동체적인 판단을 필요로 하는 경우에 static 메서드를 활용하곤 한다.
▶ prototype property 내부에 정의된 메서드들을 일컬어 prototype methods라고 한다.
▶ 일반적으로는 prototype를 생략하고 methods라고 부르는 경우가 많다.
static한 값들은 왼쪽에, prototype methods는 우측에 두었다.
이 둘은 instance에서 직접 접근 가능한가❓❕
prototype methods는 instance의 proto로 연결 되어 있고 proto는 생략 가능하기 때문에 instance에서 direct로 접근 가능하다.
반면 생성자 함수 내부에 있는 property나 methods들은 direct로 접근 할 방법이 없다.
물론, prototype의 constructor를 통해 우회하는 법은 가능하지만 instance를 this를 하기 위해서는 별도의 처리가
필요하며 그런 처리에 의해서도 정상적인 동작을 기대하기는 어렵다.
생성자 함수 Person에서 prototype으로 선언한 getName(), getAge() 메서드는 prototype methods 이기 때문에 gomu.getName(), gomu.getAge()가 정상적으로 작동한다.
반면, 생성자 함수 Person에서 static method로 선언한 getInformations() 메서드는 instance로 접근할 경우 Error가 발생한다.
prototype chaining은 대각선으로만 검색하기 때문에 static 메서드에서 제대로 된 결과를 얻기 위해서는 instance가 아니라 생성자 함수에서 직접 접근해야 한다
Class는 어떤 공통된 속성이나 기능을 정의하기 위한 추상적인 개념이며, 이 Class에 속한 객체를 instance라고 한다.
Class에는 instance에서는 직접 접근할 수 없는 Class 자체에서만 접근 가능한 static 멤버와 instance에서 직접 활용 할 수 있는 prototype 메서드가 있다.