16. Class and OOP
class 자체의 타입은 function입니다. 클래스로 생성된 instance의 경우 object 타입입니다.
class className {
valueName; // constructor에서 정의할 경우 생략 가능
#valueName; // private한 필드 정의, ES7
static valueName; // static한 필드로 클래스에 귀속된 필드. 인스턴스 없이 클래스가 직접 사용할 수 있음
constructor(value) {
this.valueName = value;
}
funcName() {
// 기능 및 동작
}
static funcName() {
// 기능 및 동작
}
// getter
// 함수의 형태로 정의했지만, 호출 시 변수처럼 호출
// ex) class.funcName;
get funcName() {
return this.valueName;
}
// setter
// 함수의 형태이지만, 사용할 때 값을 할당하는 것처럼 사용하면 됩니다.
// ex) class.setValueName = value;
set setValueName(value) {
this.valueName = value;
}
}
+) static을 활용한 factory constructor
상속
class ParentClass {
// 필드와 메서드
val1;
val2;
constructor(val1, val2) {
this.val1 = val1;
this.val2 = val2;
}
func() {
return this.val1;
}
}
class ChildClass extends ParentClass {
// 추가 필드와 메서드
valueName;
constructor(val1, val2, value) {
// 부모의 constructor overriding
super(val1, val2);
this.valueName = value;
}
func2() {
return this.valueName;
}
// 부모 메서드 오버라이딩(overriding)
// 부모의 필드(속성)를 불러오는 경우 super 키워드가 아닌 this 키워드를 사용
func() {
// return this.val1 + this.valueName;
// or
return super.func() + this.valueName;
}
}
17. 객체 추가 정보
객체 선언 방법
// 1. object 사용 {}
let obj = {
name: ''
}
// 2. class 인스턴스화
class Obj {
name;
constructor(name) {
this.name = name;
}
}
// 3. function 사용 => 생성자 함수
function Obj(name) {
this.name = name;
}
프로퍼티 속성(property attribute)
1. 데이터 프로퍼티: 키-값으로 형성된 실질적인 값을 가진 프로퍼티
2. 액세스 프로퍼티: 자체적으로 가진 값은 없지만 다른 값을 가져오거나 설정할 때 호출되는 함수로 구성된 프로퍼티. ex) getter, setter
const obj = {
name: 'nulzi',
year: 1999,
get age() {
return new Date().getFullYear() - this.year;
},
set age(age) {
this.year = new Date().getFullYear() - age;
}
}
console.log(Object.getOwnPropertyDescriptor(obj, 'name'));
// { value: 'nulzi', writable: true, enumerable: true, configurable: true }
console.log(Object.getOwnPropertyDescriptors()); // 복수 형태로 사용 시 속성 전체 출력
/* {
name: { value: 'nulzi', writable: true, enumerable: true, configurable: true },
year: { value: 1999, writable: true, enumerable: true, configurable: true },
age: { get: [Function: get age], set: [Function: set age], enumerable: true, configurable: true },
} */
위 코드의 결과로 나온 값이 프로퍼티 속성입니다.
1. value: 실제 프로퍼티의 값
2. writable: 값의 수정 여부.
3. enumerable: 열거 여부. 예를 들어 for...in 반복문을 사용 가능한지에 대한 여부.
4. configurable: 프로퍼티 속성의 재정의 가능 여부. false인 경우 프로퍼티의 삭제나 속성 변경이 금지. 단, writable이 true인 경우, 값의 변경과 writable을 true > false로 변경하는 것은 가능.
프로퍼티 속성을 변경해서 프로퍼티를 추가하고 싶은 경우에는 Object의 defineProperty 함수를 사용하면 됩니다.
const obj = {};
Object.defineProperty(obj, 'key', {
value: 'value',
writable: true, // default: false
enumertabl: true, // default: false
configurable: true, // default: false
});
// 다시 한 번 수정하는 것 또한 가능하다.
Object.defineProperty(obj, 'key', {
writable: false,
});
불변 객체(Immutable Object)
1. Extensible: Object.isExtensible(obj) 함수를 통해서 extensible 속성값을 확인할 수 있습니다. 기본 값은 true이고 false로 바꾸기 위해서 Object.preventExtensions(obj) 함수를 사용합니다. 해당 속성의 값이 false인 경우 해당 객체는 더 이상 새로운 프로퍼티를 추가할 수 없습니다. 하지만 삭제는 가능합니다.
2. Seal: Object.isSealed(obj) 함수를 통해 seal 속성값을 확인할 수 있습니다. 기본 값은 false이고 Object.seal(obj) 함수를 사용해 true로 바꿀 수 있습니다. 속성의 값이 true가 되면 extensible과 같이 새로운 프로퍼티를 추가할 수 없습니다. 차이점은 삭제도 불가능합니다. 또한 앞에서 배운 property attribute의 configurable의 값이 false로 바뀐다는 점입니다.
3. Freeze: Object.isFrozen(obj) 함수를 통해 freeze 속성값을 확인할 수 있습니다. 기본 값은 false이고 Object.freeze(obj) 함수를 통해 true로 값을 바꿀 수 있습니다. seal과 동일한 기능을 수행하는데 차이점은 configurable 뿐만 아니라 writable의 값도 false로 변경된다는 것입니다.
!! 주의 사항 !!
세 가지 속성 모두 객체 리터럴에서 에러가 발생하지 않습니다. 에러는 Object.defineProperty() 함수를 통해 값을 수정하려고 하면 에러가 발생합니다. 추가적으로 nested 객체 즉, 객체 안에 있는 객체에는 세 가지 속성 모두 적용이 되지 않는다는 점을 알아두어야 합니다.
생성자 함수: this 키워드를 이용해 class 키워드가 나오기 전 OOP를 적용하기 위해서 사용한 방법으로 항상 new 키워드와 함께 사용해야 하며 return을 하지 않아야 한다. new 키워드를 사용했다는 가정하에 {}를 반환하는 경우 {}만 반환이 되고, 내부 변수의 값들은 쓸모가 없어진다. 원시값을 반환하는 경우에는 원하던 객체가 반환된다. 추가적으로 new 키워드를 사용하지 않고 함수를 실행시킨다면 window, global과 같은 this가 가리키던 상위 스코프의 변수가 생성되거나 값이 변경될 수 있다.
tip! 함수 내부에서 new.target의 값을 확인해서 new 키워드를 사용했는지 확인할 수 있다.
function Func() {
if(!new.target) return new Func();
// ...
}
prototype
모든 객체에 존재하는 프로퍼티인 __proto__는 클래스에서 부모 클래스와 비슷합니다.
function Func(val) {
this.value = val;
// Overriding
this.sayHi = function() {
return 'Hi W';
}
}
console.log(Func.prototype.constructor === Func); // true
console.log(Func.prototype.constructor.prototype === Func.prototype); // true
const func = new Func('');
console.log(func.__proto__ === Func.prototype); // true
console.log(Func.__proto__ === Function.prototype); // true
console.log(Function.prototype.__proto__ === Object.prototype); // true
console.log(Func.prototype.__proto__ === Object.prototype); // true
// static
Func.staticFunc = function() {
// ...
};
Func.prototype.sayHi = function() {
return 'Hi';
}
prototype chain
객체.hasOwnProperty(): 실제 객체가 가지고 있는 property인가를 확인하는 함수. 부모 즉, __proto__에서 받아온 함수나 변수인지 아닌지 확인하는 함수.
property shadowing: class의 override
Object.getPrototypeOf(): 해당 객체의 __proto__ 값을 가져온다.
Object.setPrototypeOf(): 해당 객체의 __proto__ 값을 원하는 prototype을 가리키게 변경한다.
함수의 prototype 변환
scope
변수와 함수는 항상 가장 가까운 상위 스코프 내부에 있는 값을 찾는다.
scope chain
lexical scope: 선언된 위치가 상위 스코프를 정한다. <=> dynamic scope: 실행된 위치가 상위 스코프를 정한다.
block scope: let, const 키워드를 사용했을 때 적용되는 스코프
function scope: 함수 내부 스코프
// block scope
var i = 999;
for(var i=0; i<10; i++) {
console.log(i); // 0 1 2 3 4 5 6 7 8 9
}
console.log(`global ${i}`); // global 10
i = 999;
for(let i=0; i<10; i++) {
console.log(i); // 0 1 2 3 4 5 6 7 8 9
}
console.log(`global ${i}`); // global 999
this
this 키워드 바인딩이 객체가 생성되는 시점에 결정된다. 그렇기에 js에서 this는 원하는 것을 가리키지 않는 상황이 올 수 있다.
1. 일반 함수 호출 시 this는 최상위 객체
2. 메서드로 호출할 때 해당 객체를 가리킨다
3. new 키워드를 사용해서 객체를 생성할 때 해당 객체를 가리킨다
원하는 대로 this를 binding 하기
1. apply()
2. call()
3. bind()
execution context
js 코드와 코드가 실행될 때 필요한 정보를 담고 있는 특수한 환경. call stack == execution context stack.
- Global Context
- Function Context
1. Creation Phase
2. Execution Phase
tip! html 내부의 script태그로 js를 작성하고 vscode의 extension인 live server를 사용하고, 개발자도구의 debug 기능을 활용하면 call stack을 확인할 수 있다. 또한 hoisting도 scope탭에서 확인해 볼 수 있다.
closure
클로저는 어떤 함수와 해당 함수가 선언된 렉시컬 환경의 조합이다.
function Func() {
// 1. 계산이 오래걸릴 경우
let num = 5;
function innerFunc() {
return num;
}
return innerFunc;
}
const runner = Func();
console.log(runner()); // 5
사용 이유
1. 데이터 캐싱 - 실행 시간이 오래 걸리는 경우, 반복적으로 특정 값을 변경시켜야 하는 경우
2. 정보 은닉
18. 비동기 프로그래밍
single thread
tip! new Date()로 생성된 인스턴스는 getTime()이라는 함수를 실행시킬 수 있다. 해당 함수는 1970년 1월 1일부터 지금 코드가 실행되는 순간까지의 시간을 밀리초로 반환한다.
동기 프로그래밍으로 시간 보내기
const base = new Date();
const baseTime = base.getTime();
const focusTime = baseTime + 2 * 1000; // 예를 들어 2초 후로 설정
while(baseTime < focusTime) {
// 동작
}
event loop
callback
Promise
// resolve는 성공했을 때 실행되는 함수
// reject는 실패(에러)했을 때 실행되는 함수
const promise = new Promise((resolve, reject) => {
resolve('result');
});
// then은 resolve의 결과를 catch는 reject의 결과를 넘겨받습니다.
// finally는 then이든 catch든 무조건 실행됩니다.
promise.then((result) => {
console.log(result);
}).catch((result) => {
console.log(result);
}).finally(() => {
console.log('default');
});
// promise chain을 통해 순서대로 실행시킬 수 있다.
const getPromise = (sec) => new Promise((resolve, reject) => {
setTimeout(() => {
resolve('success')
},sec*1000);
});
getPromise(1).then((res)=>{
console.log('first');
console.log(res);
// Promise를 반환해 계속 이어 나갈 수 있다.
return getPromise(2);
}).then((res) => {
console.log('second');
console.log(res);
});
// all을 사용해 가장 오래 걸리는 함수를 기준으로 then이 실행됩니다.
Promise.all([getPromise(1), getPromise(2), getPromise(1)]).then((res) => {
console.log(res);
});
Async & Await
const getPromise = (sec) => new Promise((resolve, reject) => {
setTimeout(() => {
resolve('success');
},sec*1000);
});
// await는 promise를 반환하는 함수에만 사용할 수 있고 동기 코드처럼 작성 가능
// async 키워드가 사용된 함수에서만 await 키워드 사용 가능
async function runner() {
// 만약 reject의 결과를 받기 위해서는 try..catch문을 활용한다.
try {
const result1 = await getPromise(1);
console.log(result1);
const result2 = await getPromise(2);
console.log(result2);
const result3 = await getPromise(1);
console.log(result3);
} catch(e) {
console.log(e);
} finally {
console.log('finally');
}
}
runner();
console.log('no blocking');
참고
인프런 9시간 JS 정복, 코드 팩토리, 2024.12.11
'개발 > JS' 카테고리의 다른 글
| [JS 꿀팁] 배열 요소 확인에는 어떤 걸 사용해야 좋을까? (0) | 2025.04.04 |
|---|---|
| [JS 꿀팁] label (0) | 2025.04.03 |
| [JS] 한 번에 보는 JS 정리 1 (0) | 2024.12.11 |
| [주의] JS String.prototype.trim() (1) | 2024.11.22 |
| [JS] 변수는 메모리에 어떻게 저장될까?(기본형, 참조형) (2) | 2024.11.14 |