본문 바로가기
ASAC

[ASAC 06] 자바스크립트 객체, 스크립트, Callback과 Promise

by suhsein 2024. 10. 17.
728x90

바인딩

암시적 바인딩 (Implicit Binding)

객체 내부에 메서드를 가지는 경우. this = 객체

const implicit = {
  name: 'Aaron',
  method: function() { console.log(this.name) },
  method_shorten() { console.log(this.name) }, // 콜론, function 생략 가능. 축약형
}

implicit.method() // Aaron
implicit.method_shorten() // Aaron

명시적 바인딩 (Explicit Binding)

this를 사용하는 함수에 명시적으로 객체를 할당해주면 메서드가 된다.
call, bind, apply 사용하여 명시적 바인딩이 가능하다. 그러나 일시적으로만 바인딩된다.

const explicit = {
  name: 'Aaron',
}
function method() { console.log(this.name) }
method.call(explicit) // Aaron        엮기 + 실행
method.bind(explicit)() // Aaron     엮고, 실행은 따로 해주어야 함.
method.apply(explicit) // Aaron        엮기 + 실행

자바스크립트 객체

자바스크립트 객체는 new 키워드를 쓰지 않고, {} 내부에 메서드 + 필드를 정의한 json 형식으로 객체를 만들 수 있다.

  • 필드는 명시적 정의 혹은 암시적 정의
  • 명시적 정의 -> {} 내부에서 필드명을 써주기
  • 암시적 정의 -> 생성자 안에서 this 접근자를 사용해 값 할당
    • private의 경우 암시적 정의 해서는 안된다. 오류 발생
  • private 필드, 메서드
    • 필드명 혹은 메서드명 앞에 #을 붙인다.
class User {
  #first_name;
  #last_name;

  constructor(first_name, last_name) {
    this.#first_name = first_name;
    this.#last_name = last_name;
  }

  information() {
    console.log(`${this.#fullname} - 사람 이름 입니다.`)
  }

  get #fullname() {
    return `${this.#first_name} ${this.#last_name}`;
  }

  set fullname(value) {
    [this.#first_name, this.#last_name] = value.split(" ")
  }
}

const user = new User('Aaron', 'Ryu');
user.fullname = 'Aaron Ryu'
Object.getOwnPropertySymbols(user)
console.log(user.first_name) // undefined
console.log(user.last_name) // undefined
console.log(user.fullname) // undefined
console.log(user.information()) // 'Aaron Ryu - 사람 이름 입니다.'
  • 자바스크립트의 getter와 setter는 get, set 키워드를 사용해서 만들 수 있다. 또한 getter, setter 사용 시 메서드를 호출하는 것이 아닌, 필드를 호출하는 것처럼 보인다.
  • 자바스크립트에서는 동일한 이름을 가지는 private, public 필드를 만들 수 있다. 둘은 # 키워드를 사용해 구분되며, 서로 다른 필드로 취급되기 때문에 주의해야 한다.
  • 위 코드에서 user.first_name이라는 필드에 접근한 것처럼 보인다. 그러나 이는 private 필드에 접근한 것이 아닌, 동명의 public 필드에 대해서 접근하려는 시도이므로 존재하지 않는 필드에 접근한 것이다. 그래서 undefined가 출력되었다.

스크립트

  • 스크립트는 쪼개서 여러 개 넣을 수 있다.
    • 단, 위에서 넣은 것부터 순차적으로 가져오게 된다.
    • 의존성 문제 때문에 맨 위에는 라이브러리 관련된 script 넣어야 한다.
  • script 태그
    1. 성능 문제
    2. 의존성 관리 어려움 -> 개발자가 신경써서 순서를 맞춰서 넣어줘야 한다.
    3. 전역 스코프 문제 -> 전역 스코프로 변수 이름 충돌이나 오염 발생
    4. 코드 재사용의 어려움
  • 라이브러리 스크립트의 성능 문제
    1. 다운로드의 문제
    2. 실행의 문제
      1. DOM 로드 지연
        • 다운 받은 js 파일 모두 로드 및 실행을 하는 데 오랜 시간이 소요됨
      2. DOM 접근 불가
        • 다운 받은 js 실행 시 스크립트 아래 위치한 DOM 요소에 접근 불가능
      3. 스크립트 로드 시점 DOM 로드에 의존
        • 스크립트가 html 아래에 존재하는 경우, DOM 완성이 될 때까지 스크립트 늦게 실행
    • 성능 개선 방법론
      • 비동기로 script의 라이브러리를 다운로드

스크립트 성능 개선 방법론

  1. Defer : 실행 지연 스크립트
    • DOM 요소가 모두 파싱 완료될 때까지 로딩된 스크립트를 실행하지 않는다.
    • 보통 큰 스크립트부터 작은 스크립트 순차적으로 실행함. (실행 순서 보장)
    • 사용 예시
      • DOM 요소 접근이 필요한 경우
      • 스크립트 파일끼리 의존성을 가지는 경우 (순서 보장이 필요한 경우)
  2. Async : 실행 비동기 스크립트
    • 스크립트 로딩이 끝나면, DOM 요소의 파싱 작업을 멈추고 바로 스크립트를 실행한다.
    • 실행 순서 비보장. Load-First Order (먼저 로드된 스크립트부터 실행)
    • 사용 예시
      • 방문자 수 카운터, 광고 관련 스크립트
      • 독립적인 역할을 하는 서드파티 스크립트에 적합

ESM vs CJS

  • ESM (ECMAScript Modules) : import / export
    • 모듈 스코프 지원. 전역이 아닌 필요한 모듈만 가져다 쓸 수 있도록 함
    • 브라우저에서 import와 export를 완벽하게 지원하지 않아 Webpack과 같은 모듈 번들러 함께 사용
    • 비동기 동작 지원. => 다운로드와 실행의 분리 => 보다 간편한 비동기 처리를 위한 Top-Level-Await 지원
    • import는 디스크 혹은 네트워크에서 데이터를 비동기적으로 먼저 읽어온다 (= 파싱)
      • 실행 전에 모두 파싱 => 높은 보안.
      • 파싱은 정적 분석에 사용된다.
      • 정적 분석
        • 실행 불필요
        • 문법 오류 및 잠재적 버그 발견
        • 모듈 의존성 분석
    • Tree shaking
      • 정적 분석을 통하여 사용되지 않는 모듈을 제외한 번들링. => 파일 크기를 줄이고 성능을 높임
    • named export : 이름을 붙여서 export
      • => 꼭 그 이름으로만 import 해야 한다.
    • default export : 명칭 변경 가능
      • => 한 파일에 하나만 export
  • CJS (Common JS) : required / module.exports
    • 웹 브라우저가 아닌 웹 서버에서 자바스크립트 모듈을 쓰려면 파일 단위 모듈화가 절실하다.
    • 변수 함수의 모듈화 필요
    • Node에서는 CJS가 지배적이다.
    • 비동기 동작 미지원. => 동기로 동작. 로드와 실행이 동시 => Top-Level-Await 미지원
    • 필요할 때마다 가져와서 쓰기 때문에 정적 분석 불가능
  • 일반적으로 CJS는 Node.js(서버)에서 사용, ESM은 브라우저에서 사용
    • CJS를 지원하는 것이 중요한 이유 : Next.js 활용한 SSR 사용 서비스를 위해 CJS 지원 필요
    • ESM을 지원하는 것이 중요한 이유 : Tree-shaking을 지원하는 ESM이 브라우저 성능에 중요

+) 리액트 사용 시 Webpack 아닌 Vite를 사용한다. Vite는 알아서 필요한 라이브러리들을 Tree-Shaking한다.

Callback

  • 함수를 파라미터로 넘겨서, 필요할 때 사용할 수 있도록 실행권을 해당 함수로 위임한다.
  • 콜백 그 자체로는 어떠한 비동기적인 함의도 없다.
    • 다만 비동기는 언제 끝날지 모르기 때문에 콜백이 필요함
    • 비동기에게는 콜백이 가장 필수적인 요소임
  • 콜백 지옥의 원인
    • 이전 콜백함수의 리턴값이 다음 콜백 함수의 파라미터로 사용되기 때문에.(실행에 필요하기 때문에)

Promise

Promise = Callback + Asynchronous
즉, 프로미스는 비동기를 포함한 콜백
프로미스를 Producer-Consumer Pattern on Asynchronous라고 하기도 한다.

  1. Executor (실행자) = Asynchronous (Callee) = Producer. 파라미터를 주입
  2. Executee (피실행자) = Callback = Consumer. 파라미터를 받아 수행됨.

Callee가 Callback에게 파라미터를 넘겨주기까지 딜레이 발생
=> 비동기 처리

Promise 상태

  • pending : 초기 상태 => 프로미스에 값이 담기지 않은 상태
  • fulfilled : 성공 상태 => then 내부에 정의된 resolve(성공 콜백)로 처리
  • rejected : 실패 상태 => catch 내부에 정의된 reject(실패 콜백)로 처리

Promise 처리 방식 (체이닝)

  1. then : 내부에 성공 콜백 정의
  2. catch : 내부에 실패 콜백 정의
  3. finally : 내부에 성공, 실패 상관 없는 콜백 정의
  • new Promise(pending).then(resolve).catch(reject)
  • then, catch, finally로 나누는 이유는 다음과 같다.
    1. 체이닝이 가능함
    2. 콜백을 다양하게 사용할 수 있다. (인터페이스를 사용하는 것처럼)
    3. Promise 체이닝에서 리턴값은 항상 프로미스로 감싸져있기 때문에 체이닝이 가능하다.
  • await
    • Promise가 처리될 때까지 기다린다.
new Promise((resolve, reject) => { // caller 함수 (asynchronous 함수). Promise 정의 하자마자 실행됨.
    const result = producing()
    if (result.success) resolve(result.body) // resolve를 반환하면, then이 콜백함수의 파라미터로 받아서 사용
    if (result.failed) reject(result.error) // reject를 반환하면, catch가 콜백함수의 파라미터로 받아서 사용
})
    .then((body) => { consuming(body) }) // then 내부에 성공 콜백 함수
    .catch((error) => { consuming(error) }) // catch 내부에 실패 콜백함수 

// Promise Chaining 무한으로 가능. 항상 Promise로 감싸져 있다. 
// Promise.resolve 혹은 Promise.reject를 반환. (만약 아직 실행되지 않았다면, pending 상태의 Promise)

Promise 비동기 함수의 구동 시점

  • 프로미스 안의 비동기(Asynchronous = Caller = Executor) 함수는, 프로미스 정의 시 구동된다.
    • 주의 : await 했을 때가 아님. await은 그저 값을 받으려고 기다리는 것 뿐임.

Promise 사용 시 주의사항

  • Promise Hell 방지를 위해서, then 내부에서 다음 콜백을 호출하지 않는다.
  • 현재 콜백함수의 실행 값(리턴값)을 다음 콜백함수의 파라미터로 넘겨주기 위해서는 Promise Chaining을 활용한다.
  • 그렇지 않은 경우 무조건 반환을 위해서는 return 대신 resolve를 사용해 반환한다.
  • 실패 시에는 reject를 사용해 반환한다.
728x90