동작을 나타내는 메소드는 자신이 속한 객체의 상태, 프로퍼티를 참조하고 변경할 수 있어야 한다.
→ 이때 메소드가 자신이 속한 객체의 프로퍼티를 참조하려면 먼저 자신이 속한 객체를 가리키는 식별자를 참조할 수 있어야 한다.
위 문장을 이해하기 위해 모던 자바스크립트 딥 다이브의 예제를 봐보자.
const circle = {
radius: 5, // 이건 프로퍼티다. 객체 고유의 상태 데이터를 의미한다.
getDiameter() { // 이건 메소드다. 상태 데이터를 참조하고 조작하는 동작을 의미한다.
return 2 * circle.radius;
// 이 메서드가 자신이 속한 객체의 프로퍼티나 다른 메소드를 참조하려면
// 자신이 속한 객체인 circle을 참조할 수 있어야 한다.
}
};
❓ 위에 name은 왜 작동하지 못할까?
타입스크립트는 describe 메소드 내부 또는 클래스 외부에 전역 변수로서 존재하는 변수 이름을 찾을 것이다.
윈도우 객체의 브라우저에는 전역 name 변수가 존재한다. name을 찾고 찾다가 우주로 나간 전역 name 변수까지 찾고 있는 것이다.
클래스 내부의 클래스 속성이나 메소드를 참조하려면 this 키워드
를 사용해야 한다.
this?
일반적으로 생성된 클래스의 구체적인 인스턴스를 참조한다.
말이 어렵다. 쉬운 말을 찾아보자.
this는 자신이 속한 객체 또는 자신이 생성할 인스턴스를 가리키는 자기 참조 변수다. this를 통해 자신이 속한 객체 또는 자신이 생성할 인스턴스의 프로퍼티나 메소드를 참조할 수 있다.
<모던 자바스크립트 딥 다이브>
this는 기본적으로 window이지만, 객체 메소드, bind, call, apply, new 일 때 this가 바뀐다. 이벤트 리스너나 기타 라이브러리처럼 this를 내부적으로 바꿀 수 있어 항상 확인해야 한다.
<제로초 블로그>
위의 말을 보고 딥 다이브의 다음 줄을 읽으니 살짝 명확해지고 있다.
this가 가리키는 값, 즉 this 바인딩은 함수 호출 방식에 의해 동적으로 결정된다.
<모던 자바스크립트 딥 다이브>
this는 일반적으로 메소드를 호출한 객체가 저장되어 있는 속성입니다.
<너나들이 개발 이야기>
어? 이 말 이해된다. 이거였네
다시 코드로 돌아가자
this는 점 표기로 인스턴스의 모든 속성과 메소드에 접근할 수 있다.
이렇게 고치면 Deparment: Accounting
으로 제대로 출력되는 것을 볼 수 있다.
Class 포스트 쓰면서 봤던 이 문제!!!
describe 매개변수에 this가 이 클래스를 참조한다는 것을 타입으로 알려줌으로써 프로퍼티 참조를 가능케 한다.
그리고 accountingCopy를 선언할 때 속성(프로퍼티)을 명확히 입력해주어 this가 가르키는 것을 올바르게 찾도록 해준다.
(아마 이 해석이 맞지 않을까..?)
this가 동적 호출이라는 점을 기억하고 호출 방식이 4가지로 나뉜다는 것만 외우자.
✔️ 일반 함수 호출
전역 함수, 중첩 함수, 콜백 함수 모두 일반 함수로 호출되었다면 전역 객체가 바인딩된다.
메소드 내부의 중첩 함수나 콜백 함수의 this 바인딩을 메소드의 this 바인딩과 일치를 시켜야할때면 this 바인딩을 변수에 할당하여 참조시키기(딥 다이브 349p), Function.prototype.apply/call/bind 메소드 사용하기, 화살표 함수 사용하기의 방법이 있다.
✔️ 메소드 호출
맞아요 나 아이패드 없어요
주의할 점은 메소드 내부의 this는 메소드를 소유한 객체가 아닌 메소드를 호출한 객체에 바인딩된다는 것이다.
✔️ 생성자 함수 호출
미래에? 컴퓨터가 개발자가 뭘 입력할지도 알게 된단 것인가?
어림없다. 코드 흐름 상 미래를 얘기하는 것이다. 다음 코드를 봐보자.
function Circle(radius) {
// 생성자 함수 내부의 this는 생성자 함수가 생성할 인스턴스를 가리킨다.
this.radius = radius;
this.getDiameter = function () {
return 2 * this.radius;
};
}
const circle1 = new Circle(5); // 반지름이 5인 Circle 객체 생성
const circle2 = new Circle(10); // 반지름이 10인 Circle 객체 생성
console.log(circle1.getDiameter()); // 10
console.log(circle2.getDiameter()); // 20
생성자 함수는 이름 그대로 객체(인스턴스)를 생성하는 함수다.
일반 함수와 동일한 방법으로 생성자 함수를 정의하고 new 연산자와 함께 호출하면 해당 함수는 생성자 함수로 동작한다.
→ new 연산자가 없다면? 바로 일반 함수가 되는 것이다~
✔️ Function.prototype.apply/call/bind 메소드에 의한 간접 호출
apply, call, bind 메소드는 Function.prototype의 메소드이다.
→ 이 메소드들은 모든 함수가 상속받아 사용할 수 있다.
apply와 call 메소드의 본질적인 기능은 함수를 호출하는 것이다.
이 메소드들은 함수를 호출하면서 첫 번째 인수로 전달한 특정 객체를 호출한 함수의 this에 바인딩한다.
apply와 call 메소드는 호출할 함수에 인수를 전달하는 방식만 다를 뿐 동작은 동일하다.
bind 메소드는 함수를 호출하지 않고, 첫 번째 인수로 전달한 값으로 this 바인딩이 교체된 함수를 새롭게 생성해 반환한다.
bind 메소드는 메소드의 this 메소드 내부의 중첩 함수/콜백 함수의 this가 불일치하는 문제를 해결하기 위해 많이 사용된다.
함수 호출 방식 | this 바인딩 |
---|---|
일반 함수 호출 | 전역 객체 |
메소드 호출 | 메소드를 호출한 객체 |
생성자 함수 호출 | 생성자 함수가 미래에 생성할 인스턴스 |
Function.prototype.apply/call/bind 메소드에 의한 간접 호출 | Function.prototype.apply/call/bind 메소드에 첫번째 인수로 전달한 객체 |
⭐ 한 줄 요약 : 전역 키워드 this는 window 객체를 의미하고, 이벤트 발생 시 this는 이벤트가 발생한 DOM을 의미한다. 그러나 메소드 내에서 this는 클래스 객체를 의미한다.
OOP (객체 지향 프로그래밍) 언어에서 getter와 setter를 사용되는 이유는 캡슐화 (encapsulation)를 위해서다.
❓ 캡슐화
▪️ 오브젝트 내부의 성질을 오브젝트 외부에서 직접 관여하지 못하게 하는 것이고, 오브젝트가 제공하는 루트를 통해서만 관여할 수 있게끔 하는 것이다.
더 쉬운 의미를 찾아보았다.
▪️ 일반적으로 연관 있는 변수와 함수를 클래스로 묶는 작업
▪️ 객체가 기능을 어떻게 구현 했는지 외부에 감추는 것
→ 구현에 사용된 데이터의 상세 내용을 외부에 감춤
여기서 정보은닉이라는 개념이 등장한다.
캡슐화는 왜 필요할까?
캡슐화의 장점은 외부에 영향 없이 객체 내부 구현이 변경 가능하기 때문이다.
클래스 내부 메소드의 구현만 변경하면 되기 때문에 외부 소스코드의 연쇄적인 변경을 막는다.
이를 통해 프로그래밍 할 때 객체의 무결성을 보장할 수 있다.
위와 같은 이유로 객체들의 데이터를 외부에서 직접적으로 접근하는 것을 막는데, 데이터를 private 접근 제어자로 막고 각 데이터는 getter/setter로 접근한다.
객체의 무결성을 보장하기 위해 게터와 세터를 사용한다는데 getter setter를 사용하는 이유 이 포스트를 보니 세터는 무결성과는 조금 먼 이야기인가 싶다.
대략 흐름은 필드를 private로 만들어 외부의 접근을 제한한 후, setter를 사용해 전달받은 값을 가공하여 필드에 넣어준다. 필드 값을 가져올 때도, getter를 사용해 필드의 값을 숨긴 채 내부에서 가공된 값을 꺼낼 수 있게 한다.
근데 사실 여기까진 자바스크립트의 내용이 아니라 자바의 내용이다. (뭔가 잘못 찾아본거 같음)
자바스크립트는 다르게 동작한다 하면 댓글로 좀 알려주세요...
✔️ 그래서 Getter와 Setter가 뭘까?
Getter?
값을 가지고 올 때 함수나 메소드를 실행하는 속성
get 키워드를 입력하고 원하는 이름을 지어 게터를 생성할 수 있다.
게터는 일반적으로 괄호 쌍과 중괄호 쌍을 입력해야하며, 코드 블록 내에 return을 입력해야 한다.
→ 게터 메소드 내에 무언가를 반드시 반환하도록 작성해야 한다.
private 제어자를 사용한 인스턴스를 점 표기법으로 가져오지 못할 때 게터를 사용해 속성처럼 사용하여 로직을 실행할 수 있게 된다.
class AccountingDepartment extends DepartmentData {
private lastReport: string;
get mostRecentReport(){
if (this.lastReport){
return this.lastReport;
}
throw new Error('No report found.');
}
}
console.log(Accounting.mostRecentReport);
접근할 때는 메소드로써 실행하지 않고, 일반 속성처럼 접근한다.
Setter?
속성 값을 설정
set 키워드를 사용한 후 설정할 속성과 관련된 원하는 이름을 입력한다. (게터와 같은 이름을 사용할 수 있다.)
getter와 만드는 방법은 동일하다.
class AccountingDepartment extends DepartmentData {
private lastReport: string;
set mostRecentReport(value: string){
if (!value){
throw new Error('Please pass in a valid value.');
}
this.addReport(value);
}
addReport(text: string){
this.reports.push(text);
this.lastReport = text;
}
}
사용자가 전달할 값인 인수를 취할 수 있도록 지정해주어야 한다.
→ set mostRecentReport(value: string){
세터에 해당하는 값을 전달할 수 있는 메소드에 this을 통해 접근이 가능하다.
그 후 메소드 뒤 괄호에 값을 전달해주면 된다.
→ this.addReport(value);
게터와 세터는 로직을 캡슐화하고 속성을 읽거나 설정하려 할 때 실행되어야 하는 추가적인 로직을 추가하는 데 유용하다. 또한 개발자가 더 복잡한 로직을 추가할 수 있도록 해준다.
이렇게 간단하게 설명할 수 있는데...
private 접근 제어자를 constructor() 앞에 붙이면 new 키워드를 통해 인스턴스를 생성하지 못하도록 제한할 수 있다. 대신 공개된 static 메소드 getInstance()를 통해 오직 한 번만 인스턴스를 생성할 수 있는데 이를 싱글턴 패턴이라고 한다.
TypeScript Guidebook
싱글턴 패턴은 소프트웨어 디자인 패턴의 한 종류이다.
→ 클래스가 단 하나의 인스턴스만 갖도록 보장한다는 내용을 담고있다.
메모리의 효율을 위해 반복되는 인스턴스를 한 번만 선언하고 필요한 곳에 가져다 쓸 수 있게 한다.
싱글턴 패턴은 잘못 적용시 SOLID 원칙에 위배될 수 있기 때문에 분명한 경우에만 적용을 시켜주는 것이 좋다.
싱글턴 패턴은 정적 메소드나 속성을 사용할 수 없거나 사용하지 않고자 하는 동시에 클래스를 기반으로 여러 객체를 만들 수는 없지만 항상 클래스를 기반으로 정확히 하나의 객체만 가질 수 있도록 하고자 하는 경우에 유용하다. (네?)
class AccountingDepartment extends DepartmentData {
private static instance: AccountingDepartment;
private constructor(id: string, private reports: string[]){
super(id, 'Accounting');
this.lastReport = reports[0];
}
static getInstance(){
if (AccountingDepartment.instance){
return this.instance;
}
this.instance = new AccountingDepartment('d2', []);
return this.instance;
}
}
// const Accounting = new AccountingDepartment('d2', []); 이렇게 사용 X
생성자 앞에 private을 붙이면 클래스 밖에서 new 키워드를 사용할 수 없게된다.
이로써 클래스 내에서만 접근을 가능케 한다.
→ 그렇다면 내부로 접근을 못하는 걸까?
놉! 클래스 자체에서 정적 메소드를 호출하면 된다.
이걸 어떻게 정리해야할지 모르겠다!
자료들을 찾아보면 싱글턴 패턴 과연 좋은가? 라는 질문들이 많았지만,,, 알아두면 좋으니까,,,