ASAC 07기 : 24.12.26 자바스크립트 기본, 심화 문법 및 엔진 동작 원리

2SEONGA·2025년 1월 8일
0

ASAC

목록 보기
7/13
post-thumbnail

함수형 프로그래밍 패러다임과 순수 함수성

  • 일급 함수(First-Class Functions)
    • 함수는 변수에 할당 가능하며, 파라미터로 전달하거나 반환값으로도 사용 가능
  • 참조 투명성(Referential Transparency)
    • 함수가 외부 상태에 의존하지 않으며, 동일한 입력값에 대해 항상 동일한 출력값 반환
  • 부수 효과 없음(No Side Effects)
    • 함수가 외부 상태를 변경하거나 영향을 미치지 않으며, 예를 들어 멀티스레드 환경에서 함수가 서로의 상태 미변경

일급 함수 = 함수 변수 + 함수 파라미터 + 함수 반환

  • 함수 변수 할당 = 함수 표현식 (Expression)

  • 함수 파라미터

  • 함수 반환

순수 함수 = Thread-Safe

정직하게 일정 값(파라미터)을 넣으면, 일정한 값(반환값)이 나오며, 프로그램의 어느것도 건들지 않음

  • Thread-Safe : 그래서 멀티스레드를 활용한 개발 시 순수함수 특성은 매우 중요하며
    • 병렬적으로 수행되는 모든 함수들은 서로에게 영향을 주어선 안되고, 외부의 값을 변경해서도 안됨
    • 하나의 작업을 여러개로 나누어 수행한 뒤 합쳤을때, 하나의 작업을 한번에 수행했을때와 같은 결과여야함
  • 함수형 프로그래밍 : 함수형 프로그래밍에서 우리가 사용하는 모든 함수 단위들은 순수함수여야함

순수 함수의 장점 : 순수 함수를 사용해야하는 이유

순수 함수의 장점

  1. Testability 테스트 가능 : 일정한 값(파라미터)에 일정한 값(반환값)이 나온다면, 유닛테스트 용이
  2. Debugging 디버깅 가능 : 문제가 생기는 함수만 보면 문제가 해결
  3. Memoization 메모이제이션 가능 : 캐싱 가능

비순수 함수의 단점

  1. Side Effect 부수효과 발생 : 난 분명 이 함수를 고쳤는데, 왜 갑자기 전혀 상관없는 옆 함수가 터져?
  2. Unpredictable Output 예상치 못한 결과 : 같은 파라미터값인데 왜 다른값이 나오지?
  3. Difficult to Reason 디버깅 및 프로그램 흐름 분석이 어려워짐 : 함수호출 없었는데 왜 객체가 바뀌어있지?

참조 투명성 사례

  1. Inconsistent Results 비일관 결과 : 같은 파라미터에 같은 값 → 같은 파라미터에 다른 값
    • 외부 상태에 의존

사이드 이펙트(옆구리) 사례

Difficult to Reason 디버깅 및 프로그램 흐름 분석이 어려워짐 : 암살자에 의해 어디에선가 객체가 변동

외부 상태를 변경 : 해당 객체가 무엇에 의해 변경되고있는지 추적 난해

1. 어떤 개발자가 자신 의도대로 외부 객체를 변경 시 어떤 누군가는 의도치않게 변경된 객체 사용 = 어리둥절

2. 파라미터를 통해 바꿀 객체를 전달하니 1번 보단 낫지만, 반환값 없이 파라미터 객체를 변경하는 것은 비명시적

3. 2번에서의 명시적이지 않은 문제는 해결했지만, ① 파라미터 객체가 바뀐 문제 ② 바꾼 객체가 파라미터 객체와 동일

사이드 이펙트(옆구리) 앞 사례 해결 방법 : Immutability 불변성

  • immutable 불변 객체 사용 : 파라미터(인풋)은 외부 상태이기에 변경이 없어야함
    • 이 말은 즉, 무조건 새로운 값이 반환되야한다는 의미
    • 메모리가 부족하지 않을까라고 생각할 수 있지만, 파라미터로 가진 객체를 다른 사람이 사용할 때
      해당 객체가 어떤 방식으로든 바뀔 것이라고 가정하게 두어서는 안됨
    • 극단적으로는 JS에서 사용하는 모든 객체는 Object.freeze() + const 사용하는게 좋음
const member = Object.freeze({ name: 'Seonga', social: '010407-0000000' })
console.log(member) // { name: 'Seonga', social: '010407-0000000' }
member.social = '961028-1111111'
console.log(member) // { name: 'Seonga', social: '010407-0000000' } <- 변하지 않음
  • 심화 : React 에서 Immutability 불변성은 매우 중요
    • React 가 리렌더를 위한 상태 변경의 기준을 “객체 주소의 변경”으로 인지하기 때문

  • object 는 객체를 가리키는 주소값을 가짐 : 주소를 복사하는가? 값을 복사하는가?

2.3.1. 얕은 복사(Shallow Copy) vs 깊은 복사(Deep Copy)

  • Object.assign얕은 복사(Shallow Copy)에 해당
    • 얕은 복사1 계층만 복사 : 아래 예시를 보면 Earth → Moon 을 바꿨으나 모두에서 변경

  • 깊은 복사모든 계층의 복사
    : 몇가지 도전 - 객체 내 객체, 배열은 어떻게 복사할 것인가?
    1. 어려운 방법 : typeof 를 활용한 재귀 함수 직접 개발
    2. 쉬운 방법 : Stringify 하여 Object → String 으로 만들었다가 다시 역으로 String → Object 변환

자바스크립트 변수 선언과 엔진 구동방식에 따른 TDZ 와 Lexical Scope

0. 자바스크립트 버전 : ECMAScript (ES)

자바스크립트에도 여타 프로그래밍 언어와 같이 버전이 존재

  • 자바 버전 : Java 11, Java 17 등 지원하는 문법들이 점차 추가되고 발전
  • 자바스크립트 버전 : 자바스크립트도 자바나 다른 여느 언어와 같이 ECMAScript 으로 매년 새 표준이 등장
    - 자바와 달리 프로그램 실행을 위한 엔진 버전이 유저들이 쓰는 웹 브라우저마다 달라 Transpiler 필요

1. 자바스크립트 변수 선언 방법 : var → let, const

변수 선언은 자바스크립트 버전인 ECMAScript 6 (2015) 이전에는 var 하나의 방법으로만 선언
ECMAScript 6 버전 이후로 변수 선언letconst 로 선언할 수 있다.

  • let : 가변 변수 선언 시 - 예) `let heroLevel = 1` 레벨이 올라감에 따라 변경되는 레벨값
  • const : 불변 변수 선언 시 - 예) `const TOTAL_MONTHS = 12` 고정된 12 라는 값
재선언재할당스코프
varO : 재선언 시 덮어쓰기O함수
letX : 방어O : 가변 변수블록
constX : 방어X : 불변 변수블록
  • var : 재선언 가능 + 재할당 가능 + 함수레벨 스코프
    • 호이스팅 발생 with Initialization
  • let : 재선언 불가 + 재할당 가능 + 블록레벨 스코프
    • 호이스팅 발생 without Initialization (Reference Error)
  • const : 재선언 불가 + 재할당 불가 + 블록레벨 스코프
    • 호이스팅 발생 without Initialization (Reference Error)

(1) 스코프 : 변수에 대한 접근 영역

함수 스코프와 블록 스코프는 어떤것이며, 어떤 점에서 다른가

  • 함수 스코프 : 함수가 생성 시 스코프 영역(심화 : 실행 컨텍스트)이 생겨난다

  • 블록 스코프 : 블록이 생성 시 스코프 영역이 생겨난다

(2) 번외 : Literal 리터럴 이란 무엇인가? 값 표현 (값 그 자체)

  • 0숫자 리터럴
  • ‘hello’문자열 리터럴
  • function() {}함수 리터럴
  • {}객체 리터럴

2. 자바스크립트 엔진의 수행 방식 = 함수 실행 원리

자바스크립트 엔진은 자바스크립트 파일을 실행한다고 생각하지만 사실 함수를 실행하고, 파일은 main 함수인격

따라서 자바스크립트 엔진의 자바스크립트 파일 구동 방식자바스크립트 함수 구동 방식과 동
이에 자바스크립트 엔진의 자바스크립트 함수 구동 방식에 대해 아래 간단히 2개의 Phase 로 나누어 진행

  1. Creation(Pre-parsing) Phase : 실행에 앞서 어떤 변수와 함수가 정의되어있는지 먼저 분석
  2. Execution Phase : 그리고나서야 변수에 값을 할당하고, 함수는 실행

(1) 자바스크립트 엔진 구성 : Stack + Heap

  1. 실행 : 싱글 스레드
  2. 메모리 : Stack + Heap 두 개만 기억
    • (Call) Stack - 함수 실행 순서대로 적재 및 수행
      • 간단하게, 함수 실행 및 순서는 Stack
    • Heap - 선언 및 할당된 변수 및 함수 저장
      • 간단하게, 여러 변수 및 함수 담는 바구니는 Heap

(2) 엔진 내 함수 동작원리 : Stack(실행 컨텍스트) + Heap(렉시컬 환경)

자바스크립트 엔진은 Stack + Heap 두개만 기억 = 자바스크립트 함수 동작 원리 이해도 마찬가지로 두개만 기억

  • 함수 실행을 위한 메모리 영역 = 실행 컨텍스트 + 렉시컬 환경
    • Stack : 실행 컨텍스트 = 함수 실행 환경
    • Heap : 렉시컬 환경 (Lexical Environment) = 변수/함수 저장소
      • 심화 : 원래는 아래 2개로 구성인데, 쉬운 설명 및 이해를 위해 렉시컬 환경 만 교육
        • 렉시컬 환경 (Lexical Environment) : let, const 변수 + 함수 : Block-level
          • let, const 는 Hoisting 발생 - 정확히는 발생이나 ReferenceError 에러
        • 변수 환경 (Variable Environment) : var 변수 : Function-level

1. 실행 컨텍스트

프로그램에 메모리와 CPU 자원을 할당한 뒤 구동하면 프로세스

  • 모든 함수의 구동은 그 함수 구동을 위한 메모리 영역을 확보하는 것에서부터 시작
    • 이때 자바스크립트에서 함수 구동을 위한 메모리 영역이 실행 컨텍스트
  • 앞으로 언급하는 모든 실행 컨텍스트는 언급이 없어도 렉시컬 환경 / 변수 환경을 갖고있는 것으로 가정
  • JS 엔진 수행을 위한 최상위 실행 컨텍스트 : Global Scope 혹은 Global Execution Context
    • 타 프로그램에서도 모든 함수들이 수행되는 가장 메인 함수인 `main()` 함수같은 개념이라 생각
      • 그래서 모든 프로그램의 시작은 main() 메서드 혹은 함수의 Stack 에서 처음 시작
    • Global ScopeGlobal Object 라는 전역객체를 갖고, 그곳에서 정의되는 함수들은 Global 메서드
      • 웹 서버(Node.js)에서의 전역객체는 = global
      • 웹 브라우저(예, 크롬)에서의 전역객체는 = window

2. Creation(Pre-parsing) Phase : 함수(파일) 분석하며 메모리 내 변수 함수 적재

: 함수(파일) 분석하며 메모리(실행 컨텍스트, 렉시컬 환경) 내 변수, 함수 적재

  • 변수 선언 & 함수 선언 = Declaration
    • 변수 선언 + Hoisting 호이스팅 : 변수 할당 전에 변수를 선언 → 어디에 선언하든 선선언
      • var 는 선언과 동시에 초기화
      • const 와 let 도 어김없이 변수 호이스팅
        • 잘못된 오해 : const 와 let 는 호이스팅 X → 여전히 호이스팅 O

        • var 의 예시 : 변수 호이스팅되나, 초기화가 되기때문에 할당 및 사용 시 에러 미발생

          console.log(hoisting) // **변수 호이스팅 (선언 X, 초기화 O)** = undefined
          var hoisting = 'hello'
          hoisting = 'world'    // **변수 호이스팅 (선언 X, 초기화 O)** = 'world'
          var hoisting = 'hello'
        • let 의 예시 : 변수 호이스팅되나, 초기화가 안되기때문에 할당 및 사용 시 에러 발생

          console.log(hoisting) // **변수 호이스팅** **(선언 X, 초기화 X)** = ReferenceError: hoisting is not defined
          let hoisting = 'hello'
          hoisting = 'world'    // **변수 호이스팅** **(선언 X, 초기화 X)** = ReferenceError: Cannot access 'hoisting' before initialization
          let hoisting = 'hello'
        • function 도 어김없이 함수 호이스팅

      • 함수 선언 + Lexical Scope 박제 : 함수에서 변수 참조 시 함수가 선언된 시점(Lexing)의 변수를 참조
        • 짜장면 500원 시절에 짜장면 값 줄게 함수 선언 : 박제
        • 짜장면 5000원 시절에 해당 함수 실행 시 500원 받는것 : Lexical Scope
          • Lexical(Static) Scope : 함수 선언 시점에 짜장면 값을 받음
          • Dynamic Scope : 함수 실행 시점에 짜장면 값을 받음

3. Execution Phase : 함수 실행을 위한 메모리 영역 확보 및 실행

: 함수 실행을 위해 먼저 메모리(실행 컨텍스트, 렉시컬 환경) 확보 후 실행

  • 변수 할당 & 함수 실행
    • 변수 할당 + Scope Chain : 변수 할당 시 앞선 함수 호출지엔 선언이 없을까? 호출지 찾아나서기
      • Scope Chain 시 어디에도 없으면 Global (웹 브라우저에서는 Window) 내 선언 후 할당
      • let 은 선언과 동시에 초기화되지 않아, 호이스팅이 발생해도 할당 전에 초기화가 안되어있어 오류
    • 함수 실행 : 함수 선언지로부터 연결된 메모리 영역을 확보 후 그 안에서 함수 실행

  • 여기서 말하는 초기화 라는 것은 undefined 라는 빈 값을 변수에 할당해주는 것 (예비 빈 값을 넣어주는것)

  • 자바스크립트도 컴파일을 한다는 사실

    • 구글 V8 엔진에서 함수가 실행될 때 → Ignition 인터프리터 + TurboFan 최적화 컴파일을 통해 실행

4. TDZ (Temporal Dead Zone) : let, const 의 ReferenceError 이해

let, const 도 결국 var 와 동일하게 변수 호이스팅이 발생하는데, 왜 ReferenceError 가 발생하지?

**ReferenceError**: Cannot access ? before **initialization**
  • var 와 let 의 차이
  • 위 그림에서 let 의 (1) Compile Phase, (2) Execution Phase 예시를 다시 살펴보자
    • let 은 (1) Compile Phase 끝나자마자 선언이 되어있다 = 변수 호이스팅 발생
    • let 은 (2) Execution Phase 진입하여 실행 시 초기화가 안되어있는데, 할당을 시도해서 에러 발생
      • var 와 let 은 초기화 시점이 달라서 똑같이 변수 호이스팅이 발생해도 아닌 것처럼 보이는 것
        • var 는 (1) Compile Phase 에서 선언 and 초기화 를 수행
        • let 은 (2) Execution Phase 에서 초기화 or 할당 을 수행

(3) 예시로 기술 면접문제 이해하기 : Lexical Scope + Scope Chain

  • Lexical(Static) Scope : 함수 호출 시점에서의 스코프가 아닌 함수 선언 시점에서의 스코프 = 박제
    • 함수의 2개의 고향 → 연어
      • 내가 호출된 곳 : Dynamic Scope - 함수가 호출되는 곳은 어디서든 다를수있으니 Dynamic
      • 내가 선언된 곳 : Lexical(Static) Scope - 함수가 선언된 곳은 단, 한곳밖에 없으니 Static
    • 뒤에서 배울 Closure 가 이 박제(Lexical Scope) 의 개념을 잘 활용한 것
  • (Lexical) Scope Chain : (2) Execution 시 변수 선언이 안됐을 때, 함수 선언부 이전 실행 컨텍스트서 검색
    • 변수 사용 / 할당 시 선언된 변수가 없다면, 함수가 선언된 곳(고향, 부모)로부터 선언된 변수를 찾음
var name = 'zero';
function log() {
  console.log(name);
}

function wrapper() {
  name = 'nero'; // 이때 어떤 결과가 나올지 생각해보고, var name = 'nero'; 일땐 어떨까?
  log();
}
wrapper();

  • Lexical(Static) Scope 면접 문제 풀이 : name = “nero” 사례

  • Lexical(Static) Scope 면접 문제 풀이 : name = “zero” 사례

  • Lexical) Scope Chain 시 Global Execution Context 까지 다 찾았음에도 없다면 가장 마지막에 생성

    • 변수 할당 시 Scope Chain : 가장 마지막인 Global Object 전역객체 자리에 Property 선언 및 할당
    • 변수 사용 시 Scope Chain : ReferenceError: X is not defined 발생 = 선언하지 않음
function doublewrapper() {
  name = 'zero';
  function log() {
    console.log(name);
  }
  
  function wrapper() {
    name = 'nero'; 
    log();
  }
  wrapper();
}
doublewrapper();

  • 만약 (Lexical) Scope Chain 시 Global Execution Context 까지 다 찾아보았음에도 없을 시, 그 자리에 var 선언 및 할당

자바스크립트 함수 작성 방법과 메서드 내 this 에 대한 암시적/명시적 바인딩

1. 함수 선언문(Declaration) = 함수 선언(정의)

  • 함수 호이스팅 : function () {} = 함수 선선언
hoisting() // **함수 호이스팅** = function is hoisted

function hoisting() {
	console.log('function is hoisted')
}
  • 변수 호이스팅 : undefined / ReferenceError = 변수 선선언 + 초기화 (var 의 경우)
    • var 의 예시

      console.log(hoisting) // **변수 호이스팅 (초기화 O)** = undefined
      var hoisting = 'hello'
      hoisting = 'world'    // **변수 호이스팅 (초기화 O)** = 'world'
      var hoisting = 'hello'
    • let 의 예시

      console.log(hoisting) // **변수 호이스팅** **(초기화 X)** = ReferenceError: hoisting is not defined
      let hoisting = 'hello'
      hoisting = 'world'    // **변수 호이스팅** **(초기화 X)** = ReferenceError: Cannot access 'hoisting' before initialization
      let hoisting = 'hello'

(1) 함수와 메서드의 차이

  • 함수 : 함수 그 자체

function func() {
	console.log('함수')
}
func() // '함수'
  • 메서드 : 클래스 내 함수 = 필드 접근 가능
const object = {
	field: '+필드',
	method: function() { console.log('메서드' + this.field) }
}
object.method() // '메서드+필드'

2. 함수 표현식(Expression) = 식별자(변수) + 연산자(=) + 리터럴(함수)

함수 선언문 : 함수 호이스팅 발생함수 선선언 (정의)

함수 표현식 : 변수 호이스팅 발생변수 선선언 + 할당

hoisting() // **변수 호이스팅** = ReferenceError: Cannot access 'hoisting' before initialization

let hoisting = function () {
	console.log('variable is hoisted')
}
let named = function name() {
	console.log('기명함수')
}

let unnamed = function () {
	console.log('익명함수')
}

3. 화살표 함수 : 표현만 간략해진게 아니라 this 또한 없어짐

함수 표현식은 2가지 방식으로 표현 : (traditional) function() {} 그리고 (alternative) () ⇒ {}

  • 화살표 함수 () ⇒ {} 는 기존 function() {} 가 가진 아래 특성들을 모두 갖지 않는다.
    1. 함수 자체의 this, arguments, super, new.target 바인딩을 갖지않음, 이 중 가장 핵심은 this

      • 일반 함수 표현식에는 자체적으로 this, arguments, new.target 이 있음을 확인 가능
      function traditional() {
        console.log(this)
        console.log(arguments)
        console.log(new.target)
      }
      
      new traditional()
      • 일반 함수 표현식 : 어디서 호출되었는지에 따라 this 에 바인딩될 (객체) 동적 바인딩 (Dynamic)
      var param = 'global param'
      
      let printParam = function() {
      	console.log(this.param) // this = object
      	/* function() {} 일반 함수 표현법은
      	   **동적 바인딩**이라 해당 함수가 **호출**된 **객체**의 this 를 참조
      		 - **호출**된 **객체** : object 객체에서 **호출**됨 */
      }
      let object = {
      	param: 'object param',
      	func: printParam
      }
      
      object.func() // object param
      • 화살표 함수 표현식 : Scope Chain 을 통해 this 에 바인딩될 (함수) 정적 바인딩 (Lexical)
      var param = 'global param'
      
      let printParam = () => {
      	console.log(this.param) // this = window(global)
      	/* () => {} 화살표 함수 표현법은
      	   정적 바인딩이라 해당 함수가 선언된 함수의 this 를 참조 (앞서 배운 Lexical Scope 와 동일)
      	   - 선언된 함수 : global object(function, main() 함수 개념)에서 호출됨 */
      }
      let object = {
      	param: 'object param',
      	func: printParam
      }
      
      object.func() // global param
      • var param 이 아닌 let param 으로 바꾸었을때 undefined 가 출력
        • let 전역 변수는 보이지 않는 개념적인 블록 내에 존재하게 되기 때문
        • var 전역 변수는 전역 객체의 프로퍼티가 되지만
        • let 전역 변수는 전역 객체의 프로퍼티가 아님
  1. 메서드가 될 수 있는 function() {} 와 달리 () ⇒ {} 는 메서드가 될 수 없다 = 필드 접근 불가
const object = {
	field: '+필드',
	not_method: () => { console.log('넌 내가 아직도 메서드로 보이니', this.field) }
}
object.not_method() // 출력 결과 : '넌 내가 아직도 메서드로 보이니 undefined'
  1. 일반 함수 표현식은 재선언이 가능하지만, 화살표 함수는 변수 선언이 필수적으로 필요하여 방지 가능
  • 주의 : 변수 선언 나름인게 var 의 경우엔 재선언 가능 → let 과 const 쓸 것을 강하게 권장
  • 질문 : 아래의 A, B, C 예시 각각에서 helloworld() 를 가장 위에서 호출한다면?
function helloworld() {
  console.log('h')
}

function helloworld() {
  console.log('w')
}

helloworld()
var helloworld = () => {
  console.log('h')
}

var helloworld = () => {
  console.log('w')
}

helloworld()
let helloworld = () => {
  console.log('h')
}

let helloworld = () => {
  console.log('w')
}

// SyntaxError: Identifier 'helloworld' has already been declared.
  • (객체) 동적 바인딩 (동적 스코프) 과 (함수) 정적 바인딩 (정적 스코프) 의 차이
    • Lexical(Static) Scope 정적 스코프 : 변수를 호출하는 함수가 어떤 함수에서 선언
      • (함수) 정적 바인딩 = 해당 함수가 선언함수
    • Dynamic Scope 동적 스코프 : this 를 호출되는 함수가 어떤 객체에서 호출
      • (객체) 동적 바인딩 = 해당 함수가 호출객체
      • 주의 : JS정적 스코프? → JS 일반 함수 내 this 는 예외적으로 (객체) 동적 바인딩

(1) 면접 준비 : “함수 내 this” 와 “메소드 내 this” 의 차이

(1) 일반 함수 표현식 내, (2) 화살표 함수 표현식 내

→ (1) 일반 함수 표현식 의 경우에 함수로도 사용가능하지만, 메서드로도 사용가능하기에 (1.A)(1.B) 로 나뉜다.

  • (1) 일반 함수 표현식this : (객체) 동적 바인딩 = 동적 스코프 (Dynamic Scope)
    • (1.A) 함수 : 어렵게 생각할필요 없이, 무지성 window(global)
    • (1.B) 메서드 : 객체화 혹은 초기화가 되었는가 여부로 판단, 해당 객체 내 스코프
  • (2) 화살표 함수 표현식this : (함수) 정적 바인딩 = 정적 스코프 (Static, Lexical Scope)

  • (1.A) 함수 : 어렵게 생각할필요 없이, 무지성 window(global)
function Constructor() {                 // 호출지 : Constructor 객체
  this.field = 0;                        // (위와 동일) Constructor 객체 내 this.field = 0
  function inner() {                     // 호출지 : 전역객체 (객체화 혹은 초기화 되지 않음)
    console.log(this.field);             // (위와 동일) this.field = undefined (전역객체에 var 선언)
  }
  inner();
}
var object = new Constructor();
  • 여담 : 자바스크립트에서 함수는 객체 (typeof 는 다르지만) → 그래서 초기화 가능

= 기술면접을 위해 별의 별 짓으로 다 꼬아놓아도, 초기화되지 않았으면 → 무지성 window(global)

function Constructor() {                 // 호출지 : Constructor 객체
  this.field = 0;                        // (위와 동일) Constructor 객체 내 this.field = 0
  setInterval(function inner() {         // 호출지 : 전역객체 (객체화 혹은 초기화 되지 않음)
    console.log(this.field);             // (위와 동일) this.field = undefined (전역객체에 var 선언)
	}, 1000);
}
var object = new Constructor();

  • 객체화 혹은 초기화가 안됐으면 그냥 밖으로 나가면 된다. 그리고 Global Object(전역객체)를 만나서 없으니 var 새로 생성

  • this.field = 0;new Constructor() 객체화때문에 객체 내 갇힘. inner() 함수는 객체화 되지 않아 밖으로 나감

  • 추가 예시를 통한 이해

function Constructor1() {               // 호출지 : Constructor1 객체
    this.field = 0;                     // (위와 동일) Constructor1 객체 내 this.field = 0
    console.log(this.field)             // (위와 동일) Constructor1 객체 내 this.field = 0
    function doublewrapper() {          // 호출지 : 전역객체 (객체화 혹은 초기화 되지 않음)
        this.field = 1;                 // (위와 동일) 전역객체 내 this.field = 1
        console.log(this.field);        // (위와 동일) 전역객체 내 this.field = 1

        function Constructor2(){        // 호출지 : Constructor2 객체
//		      field = 4;                  // - 추가 케이스 1: 주석을 걷어내면 어디에 값이 설정될까?
//		      this.field = 4;             // - 추가 케이스 2: 주석을 걷어내면 어디에 값이 설정될까?
            console.log(this.field);    // (위와 동일) Constructor2 객체 내 this.field = undefined
        }
        new Constructor2();
    }
    doublewrapper();
}
new Constructor1();
console.log(field);                     // 전역객체 내 field = 1
  • 추가 : var field = 100;field = 100; 차이 ← 아래 코드의 동작 방식
    • var field = 100; : Global Variable in Global Scope = Global Execution Context
    • field = 100; : Global Property of Global Object (Scope Chain 에도 변수 선언이 없는 경우)
  • 쉬운 이해를 위해 Global Object 다음 Global Scope 가 있고, 그 위에서 JS 파일이 실행된다 생각
// var field = 100;                   // Global Variable in Global Scope = Global Execution Context
// field = 100;                       // Global Property of Global Object (implicit assignment)

function Constructor1() {             // 호출지 : Constructor1 객체
  this.field = 0;                     // (위와 동일) Constructor1 객체 내 this.field = 0
  console.log(this.field)             // (위와 동일) Constructor1 객체 내 this.field = 0
  function doublewrapper() {          // 호출지 : 전역객체 (객체화 혹은 초기화 되지 않음)
      this.field = 1;                 // (위와 동일) 전역객체 내 this.field = 1
      console.log(this.field);        // (위와 동일) 전역객체 내 this.field = 1
  }
  doublewrapper();
}
new Constructor1();
console.log(field);                   // 전역객체 내 field = 1
  • (1.B) 메서드 ← (1) 일반 함수 표현식 내 this : (객체) 동적 바인딩 = 동적 스코프 (Dynamic Scope)
    • 객체를 찾을 필요가 없이, 메서드 정의 자체로 인해 메서드가 속한 객체가 this
const Object = {
  field: 0,
  method() {
    console.log(this.field);
  }
}
Object.method();
  • (2) 화살표 함수 표현식this : (함수) 정적 바인딩 = 정적 스코프 (Static, Lexical Scope)
    • ~~함수가 어떤 객체에서 호출되었는지 기준 : (객체) 동적 바인딩 X~~
    • 함수가 어떤 함수에서 선언되었는지 기준 : (함수) 정적 바인딩 O
      • 아래 코드에서 화살표 함수는 함수 Construcor 내 선언되었기에 해당 함수의 this 와 동일하고
      • 함수 Construcor 는 객체화 되었기에 해당 함수의 this 는 객체를 가르키게 되므로 = 0 출력
    • 아래 코드에서 마지막 console.log('Outer : ' + field); 출력값은
      1. var object = **new Constructor**(); 일때 : ReferenceError: field is not defined
      2. var object = **Constructor**(); 일때 : Outer : 0 출력
        • Constructor 함수 내 this.field = 0; 통해 Global Object 전역객체에 Property 추가
function Constructor() {
  this.field = 0;
  setInterval(() => {
    console.log('Callback : ' + this.field);
	}, 1000);
}
var object = new Constructor();
console.log('Outer : ' + field); // new Constructor(); 일때와 Constructor(); 일때가 다르다

  • 왼쪽 : 일반 함수 / 오른쪽 : 화살표 함수 | 우리가 앞서 배웠던 Lexical Scope 를 그대로 적용 → Constructor 함수를 바라봄

  • 종합 문제

const object = {
  nickname: 'Aaron',
  method: function() { console.log(this.nickname) },
  method_shorten() { console.log(this.nickname) },
  arrow_function: () => console.log(this.nickname),
}

object.method();
object.method_shorten();
object.arrow_function();
const object = {
//  arrow_function: () => console.log(this.nickname), // Global Property in Global Object
    arrow_function: () => console.log(nickname),      // Global Variable in Global Scope = Global Execution Context
}

object.arrow_function(); // ReferenceError: nickname is not defined
// this.nickname -> undefined 로 표기 = Global Object 전역객체에 없는 프로퍼티기 때문
//      nickname -> ReferenceError: nickname is not defined = 선언되어있지 않은 변수이기에

(2) Explicit Binding 명시적 바인딩 (call, apply, bind)

방금 배운 내용 2가지만 제대로 복습한다면, 명시적 바인딩은 좀 더 쉽게 이해

  • (1.A) 함수 ← (1) 일반 함수 표현식 내 this : (객체) 동적 바인딩 = 동적 스코프 (Dynamic Scope)
    • 호출 시 속해있는 객체가 따로 존재하지 않는다면 무지성 window(global) 전역객체
  • 메서드가 될 수 있는 function() {} 와 달리 () ⇒ {} 는 메서드가 될 수 없다 = 필드 접근 불가

  • Implicit Binding 암시적 바인딩은 일반 함수 표현식이 객체 내 메서드로 정의되었을 때 this = 객체
  • Explicit Binding 명시적 바인딩이란 해당 함수가 어떤 객체에서 호출되었는지 기준을 직접 설정해주는 것
    • 방법은 call, apply, bind 을 통해 이뤄짐

  • Explicit Binding 명시적 바인딩this 를 사용하는 함수에 명시적으로 객체를 할당해주면 메서드가 됨

  • Explicit Binding 명시적 바인딩에 따른 (1.B) 메서드와 (2) 화살표 함수 간 this 차이

function createObject() {
	// this.foo = 21;
  console.log('Inside `createObject`:', this.foo)
  return {
    foo: 42,
    bar: function() { console.log('Inside `bar`:', this.foo) },
	};
}
createObject.call({ foo: 21 }).bar()
function createObject() {
	// this.foo = 21;
  console.log('Inside `createObject`:', this.foo)
  return {
    foo: 42,
    bar: () => console.log('Inside `bar`:', this.foo),
  };
}
createObject.call({ foo: 21 }).bar()

자바스크립트 객체 정의 및 사용

1. 객체 : 프로퍼티 집합 = 필드 + 메서드

  • HTML (DOM) 객체 명칭 : 요소(Element = 객체) = 속성(Attribute = 프로퍼티) + Content
    • 요소 : <head></head>
    • 속성 : <div **class=”example”**>

2. 메서드 표현법 : Default & Shorten

  1. 메서드 표현법 : Default 기본 버전
var person = {
    name: "Seonga",
    sayName: function() {
        console.log(this.name);
    }
};
class Person {
    name = "Seonga";
    sayName = function() {
        console.log(this.name);
    }
};
  1. 메서드 표현법 : Shorten 간략 버전
var person = {
    name: "Seonga",
    sayName() {
        console.log(this.name);
    }
};

3. 자바스크립트 객체 생성 방법

JSON (JavaScript Object) 보면 알 수 있듯 Java 와 같이 객체 사용에 꼭 클래스가 필요한건 X

  • 일반적으로 JS 코딩 시 웬만해선 클래스 없이 메서드 + 필드 만으로 모든 것들을 제작 가능

(1) 객체 리터럴 : 예, JSON (JavaScript Object Notation)

일반적으로 메서드를 포함하지 않은 JSON 으로 데이터 전송 시 유용하게 사용 - Java 의 DTO 와 유사

{
		name: "Seonga",
		age: 25,
}

(2) 클래스 (ES6+ 에서 사용 가능)

SOLID 나 디자인 패턴같은 객체지향적 요인으로 코드 재사용성을 증대시키기 위해선 클래스를 사용

  • 클래스를 왜 사용하는가? Encapsulation : 단순히 감춤이 아닌 독립된 시스템 구축
    • 객체지향 프로그래밍을 얼마나 잘하는가? = 얼마나 클래스(독립된 시스템 구축)를 잘 만드는가?
      • “독립” 의 영역을 어떻게 잘 “구분”하는가?
class User {

  constructor(firstname, lastname) {
    this.firstname = firstname
    this.lastname = lastname
  }

  fullname() {
    alert(`${this.firstname} ${this.lastname}`)
  }
}

1. 클래스 내 Private 필드 설정과 Getter 와 Setter 메서드

  • 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('Seonga', 'Lee');
user.fullname = 'Seonga Lee'
Object.getOwnPropertySymbols(user)
console.log(user.firstname)
console.log(user.lastname)
console.log(user.fullname)
  • 아래처럼 코드를 바꾸면 왜 firstname 에 접근이 가능한것처럼 보이나?
    • user.first_name은 public 속성으로 새로 추가된 것이며, 이는 #first_name (private 필드)와는 별개의 것
    • Object.getOwnPropertyNames(user)에는 #first_name이 나타나지 않고, 대신 새로 생성된 first_name 반환
    • user.information()은 여전히 #first_name을 참조하기 때문에 값이 변경되지 않고 Seonga Lee를 반환
class User {
  #first_name;
  **last_name;**

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

  information() {
    return `${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('Seonga', 'Lee');
user.fullname = 'Seonga Lee'
**user.first_name = 'Jeonga'**
Object.getOwnPropertyNames(user); // [ 'last_name', 'first_name' ]
console.log(user.first_name);     // 'Jeonga'
console.log(user.last_name);      // 'Lee'
console.log(user.information());  // 'Seonga Lee - 사람 이름 입니다.'

자바스크립트 모듈 시스템 : ESM 의 import / export 표현법

0. 호랑이 담배피던 시절의 모듈 시스템 :

HTML 내 <script> 를 통해 필요한 라이브러리들을 받아 사용하였으니, 아래의 수 많은 문제가 발생

  1. 성능 문제 : 페이지 로딩 시 동시에 모든 스크립트를 한번에 로드 후 실행, 큰 규모 프로젝트에 로딩 속도 문제
    • 성능 최적화의 한계 : 모든 스크립트를 동기적으로 로드 시 브라우저는 페이지 렌더링을 일시 중지
      • 물론 <script> 로드 시 Async 혹은 Defer 전략 사용 가능
  2. 의존성 관리의 어려움 : 여러 개의 스크립트 간 의존성 관리(스크립트 간의 로드 순서 관리) 취약
  3. 전역 스코프 문제 : 모든 <script> 스크립트들은 기본적으로 전역 스코프로 변수 이름 충돌이나 오염 발생
  4. 코드 재사용의 어려움 : 모든 페이지에서 동일한 스크립트를 사용 시 코드의 재사용이 어려움
  • 이에 따라 모듈 시스템에 대한 요구 등장 : 과거에는 IIFE, 클로저, 스코프 개념들을 조합하여 모듈을 힘들게 흉내
    • ESM 과 CJS 모듈 시스템 등장

(1) 스크립트 로드 전략 : HTML 파싱 (DOM 생성 중) JS 파일 로드 시점 조율

웹 브라우저는 페이지 표기를 위해 HTML 파일을 위에서 아래로 순서대로 읽으며 DOM Tree 생성

  • 중간에 JS 가 포함된 <script> 를 만난다면 이 또한 DOM 요소이기에 완벽하게 로드 및 실행 필요
  • 하지만, 이렇게 HTML (DOM) 웹 페이지 로드 중 중간에 <script> 의 JS 파일을 다 실행한다면 아래 문제 발생
    1. DOM 로드 지연 : 다운받은 JS 파일을 모두 로드 및 실행하는데 오랜 시간 대기
    2. DOM 접근 불가 : 다운받은 JS 파일 실행 시 <script> 아래 위치한 DOM 접근 불가
  • 스크립트 로드 시점 DOM 로드에 의존 : 그렇다고 <script> 를 맨 아래로 내리면 스크립트 늦게 실행

→ 스크립트 로드 시점 조율한다면 따라 웹 브라우저 내 HTML (DOM) 웹 페이지 로드 속도 향상 가능


1. 연속성 = Blocking vs Non-Blocking

일반적으로 CPU 가 멈추는걸 의미

  • Blocking : 하나의 작업이 간간히 방해받으며 수행
    • 방해 : 주기적인 프로그램 실행 상태를 확인하는 등
  • Non-Blocking : 하나의 작업이 어떠한 방해도 받지않고 수행

2. 동시성 = Synchronous vs Asynchronous

일반적으로 병렬처리를 의미

  • 동기 Synchronous : 앞선 작업이 완료되어야 그 다음 작업을 수행
  • 비동기 Asynchronous : 앞선 작업이 완료되든말든 그 다음 작업을 수행
    - 작업이 완료되면, 완료되었다는 응답 : Callback


1. Defer : (실행) 지연 스크립트

  • 병렬 : HTML 파싱스크립트 호출(로딩) → HTML 파싱 끝난 후 실행
  • DOMContentLoaded = HTML 파싱 및 Defer 지연 스크립트 호출 및 실행 이 다 끝났을 때
  • 실행 순서 보장 : HTML 내 추가된 순서대로 실행
    • 만약, 큰 스크립트가 앞에, 작은 스크립트가 뒤에 있으면, 앞에 큰 스크립트가 실행될 때까지 대기
  • 활용 예시
    • DOM 의 모든 엘리먼트에 접근할 수 있고, 실행 순서도 보장하기 때문에 가장 범용적으로 사용
    • 스크립트 파일끼리의 의존성이 있는 경우에도 정답

2. Async : (실행) 비동기 스크립트

  • 병렬 : HTML 파싱스크립트 호출(로딩) + 바로 실행
    • 스크립트 호출 이 끝난다면 HTML 파싱 을 잠시 멈추고, 스크립트 실행
  • 실행 순서 비보장(먼저 오면 먼저 실행) : 다운로드 끝난 Async 스크립트 먼저 실행 Load-First Order
  • 활용 예시
    • 방문자 수 카운터광고 관련 스크립트처럼 각각 독립적인 역할을 하는 서드 파티 스크립트에 적합
      • 개발 중인 스크립트에 의존하지 않고, 그 반대도 마찬가지이기 때문

1. ESM (ECMAScript Modules) : import / export

앞선 호랑이 담배피던 시절의 전역 스코프가 아닌 모듈 스코프를 통해 필요할 때만 가져다 쓸 수 있게 제공

  • 브라우저에서 import 와 export 를 완벽하게 지원하지 않아 Webpack 같은 모듈 번들러 함께 사용
  • ESM 은 비동기 동작 지원(ES11) = (다운)로드와 실행이 분리 ⇒ Top-level Await 지원
    • import 는 디스크(or 네트워크) 에서 데이터 “비동기”로 읽어온 후 우선 “파싱”
    • import 한 스크립트를 맨 처음부터 실행하기 전에 import, export 구문 모두 파싱
  1. Named Export 예시 : 고정된 명칭
    • 한 파일에 여러개 Export
export const sum = (x, y) => x + y
export const minus = (x, y) => x - y

import **{** sum, minus **}** from './util.mjs'
console.log(sum(2, 4))
  1. Default Export 예시 : 명칭 변경 가능
    • 한 파일에 하나만 Export
export default (x, y) => x + y

import ssam from './util.mjs'
console.log(ssam(2, 4))
const sum = (x, y) => x + y
export default sum

import ssam from './util.mjs'
console.log(ssam(2, 4));

2. CJS (CommonJS) : require / module.exports

웹 브라우저(프론트엔드)에서가 아닌 웹 서버(백엔드)에서 자바스크립트 모듈 쓰려면 파일 단위 모듈화 절실
다양한 라이브러리 등장 및 웹 서버 자바스크립트 코드량이 많아지면서 변수, 함수의 모듈화 필요

  • Node.js 12 부터 새로운 모듈 시스템으로 ESM 을 채택하긴했지만, 여전히 Node 에선 CJS 가 지배적
  • CJS 는 동기로 동작(비동기 미지원) = (다운)로드와 실행이 동시 ⇒ Top-level Await 미지원
    • require() 는 Promise 를 반환하거나 Callback 을 미호출
    • require() 는 디스크(or 네트워크) 에서 데이터 “동기”로 읽어온 후 바로 “실행”

3. 모듈 시스템을 어떤 때 어떤 것을 사용해야하는가?

일반적으로 CJS 는 Node.js(서버) 에서 사용 ESM 은 브라우저에서 사용

여러분들이 웹 브라우저와 웹 서버 자바스크리트 런타임을 지원하는 자바스크립트 라이브러리를 만든다면

  • CJS 를 지원하는것이 중요한 이유 = Node.js 활용한 SSR 사용 서비스를 위해 CJS 지원 필요
  • ESM 을 지원하는것이 중요한 이유 = Tree-shaking 을 지원하는 ESM 이 브라우저 성능에 중요
    - CJS - 모듈 동기 로드 : 빌드타임에 정적 분석 불가 → 런타임에 모듈 관계 파악
    - ESM - 모듈 비동기 로드 : 정적 모듈 의존 강제 = 빌드타임에 Tree-shaking 가능

자바스크립트 내 비동기 처리 : Promise 에 대한 상세한 이해와 Async/Await

1. Callback : 함수 파라미터 + 실행권 이양

Callback ≠ Asynchronous

함수(콜백)를 파라미터로 넘겨(일급함수) 파라미터를 받은 함수에게 실행권을 넘기는 것

function callback(param1, param2) {
  console.log(param1 + " + " + param2);
}

function caller(callback) {
  callback(arguments[1], arguments[2]);
}

caller(callback, "hello", "world");
function callback(param1, param2) {
  console.log(param1 + " + " + param2);
}

function caller(callback) {
  setTimeout(
	  /* 무엇을 호출할래요? */() => callback(arguments[1], arguments[2]),
	  /* 몇초뒤 호출할래요? */2000
  );
}

caller(callback, "hello", "world");
  • Callback 자체로는 비동기 함수와 직접관계는 없으나 Callback 이 많이 사용되는 곳은 비동기일 뿐
    • “비동기 로직이 끝나거든 그 비동기 로직의 결과값을 통해 Callback 을 실행”
      • 비동기의 꽃 = Callback : 비동기 함수에게 실행권을 넘기기 위해 Callback 을 많이 사용할 뿐

2. Callback Hell

Callback 결과값이 순차적으로 필요할때 발생 = Callback 결과값이 서로 의존성으로 연결

  • Callback 의 결과가 그 다음 Callback 실행에 필요한 경우
function eggToChick(egg, handleChick) {
	  const chick = "🐥"
	  console.log("egg to chick : ", egg, "", chick)
	  handleChick(chick)
}

function chickToHen(chick, handleHen) {
	  const hen = "🐓"
	  console.log("chick to hen : ", chick, " → ", hen)
	  handleHen(hen)
}

function henToEgg(hen, handleEgg) {
	  const egg = "🥚"
	  console.log("hen to egg : ", hen, " → ", egg)
	  handleEgg(egg)
}

function eggToFried(egg) {
	  const fried = "🍳"
	  console.log("egg to fried : ", egg, " → ", fried)
}
const egg = "🐣"
eggToChick(
	  /* 1단계 달걀 */egg,
	  /* 2단계 콜백 */(chick) => {
		    chickToHen(
			      /* 2단계 삐약 */chick,
			      /* 3단계 콜백 */(hen) => {
				        henToEgg(
					          /* 3단계 꼬꼬 */hen,
					          /* 4단계 콜백 */(egg) => {
						            eggToFried(egg)
					          }
				        )
			      }
		    )
	  }
)

3. Promise

Callback + Asynchronous = Promise 는 두 개로 이해

  • Asynchronous 비동기 함수 (Executor) : Caller = Producer (파라미터를 주입)
  • Callback 비동기 처리 후 수행할 함수 (Executee) : Callee = Consumer (파라미터를 받아 수행)
    • Executor 에게 자신의 제어권 넘김

그래서 Promise 를 Producer-Consumer Pattern on Asynchronous 라고 표현하기도

function caller(callee) {
  var produced = producing()
  callee(produced)
}

caller(function callee(produced) {
  consuming(produced)
})

(1) Promise 의 상태와 그에 따른 콜백

  • Promise 는 비동기를 위해 탄생한 개념(Callback + Asynchronous)이기에 비동기에 대해 상태를 가짐
    • Promise 객체의 3가지 상태 : 시작 → 성공 / 실패 (종료)
      • 시작 Pending : 약속된 결과 값이 반환(다음 상태로 전이)되지 않은 상태, 초기 상태
      • 종료 Non-Pending
        • 성공 (이행) Fulfilled : 연산이 성공적으로 완료된 상태
        • 실패 (거부) Rejected : 연산이 실패한 상태

(2) Promise 코드 ← Callback 코드 (Pseudo 코드 설명)

  • Callback 함수
    • 성공 (이행) Fulfilled : 연산이 성공적으로 완료된 상태 ⇒ 성공 시 실행할 Callback = resolve()
    • 실패 (거부) Rejected : 연산이 실패한 상태 ⇒ 실패 시 실행할 Callback = reject()

  • 성공 / 실패 Callback 함수 2개 전달 (Pseudo 코드)
function caller(resolve, reject) {
  const produced = producing() // API 호출해줘, 이미지 가져와줘
  if (succeeded) resolve(produced)
  if (failed) reject(produced)
}

caller(
  function resolve(produced) { consuming(produced) },
  function reject(produced) { consuming(produced) }
)
  • 성공 / 실패 Callback 함수 2개 전달 (실제 동작 코드) - A안
function producing() {
  // return { success: true, value: '성공했습니다 :)' };
  return { success: false, value: '실패했습니다 :(' };
}

function caller(/* 성공 콜백 */resolve, /* 실패 콜백 */reject) {
  var produced = producing()
  if (produced.success) { resolve(produced.value) }
  if (!produced.success) { reject(produced.value) }
}

caller(
  /* 성공 콜백 */function resolve(/* 성공 결과 */value) { console.log('성공👍 ' + value) },
  /* 실패 콜백 */function reject(/* 실패 결과 */value) { console.log('실패👎 ' + value) }
)
  1. Promise 형태
  • Callback.then.catch 로 주입된다.
    • .then() 내부에 resolve() 성공 콜백 함수
    • .catch() 내부에 reject() 실패 콜백 함수

  • Promise 형태 (Pseudo 코드, 기본 함수 리터럴)
new Promise(
	function caller(resolve, reject) {
		const produced = producing()
		if (succeeded) resolve(produced)
		if (failed) reject(produced)
	}
)
	.then(function resolve(produced) { consuming(produced) })
	.catch(function reject(produced) { consuming(produced) })
  • Promise 형태에서 성공 / 실패 Callback 함수 2개 전달 (실제 동작 코드) - B안
function producing() {
  // return { success: true, value: '성공했습니다 :)' };
  return { success: false, value: '실패했습니다 :(' };
}

new Promise(
  function producer(/* 성공 콜백 */resolve, /* 실패 콜백 */reject) {
    var produced = producing()
    if (produced.success) { resolve(produced.value) }
    if (!produced.success) { reject(produced.value) }
  }
)
  .then(/* 성공 콜백 */function resolve(/* 성공 결과 */fulfilled) { console.log('성공👍 ' + fulfilled) })
  .catch(/* 실패 콜백 */function reject(/* 실패 결과 */rejected) { console.log('실패👎 ' + rejected) })

  1. Promise 실무에서 사용하는 형태 (실제 코드, 화살표 함수)
new Promise((resolve, reject) => {
	const **result** = producing()
	if (**result.success**) resolve(**result.body**)
	if (**result.failed**) reject(**result.error**)
})
	.then((**body**) => { consuming(**body**) })
	.catch((**error**) => { consuming(**error**) })
  • 실무에서 Promise 형태에서 성공 / 실패 Callback 함수 2개 전달 (실제 동작 코드) - C안
function producing() {
  // return { success: true, value: '성공했습니다 :)' };
  return { success: false, value: '실패했습니다 :(' };
}

new Promise((/* 성공 콜백 */resolve, /* 실패 콜백 */reject) => {
  var produced = producing()
  if (produced.success) { resolve(produced.value) }
  if (!produced.success) { reject(produced.value) }
})
  .then(/* 성공 콜백 */(/* 성공 결과 */fulfilled) => { console.log('성공👍 ' + fulfilled) })
  .catch(/* 실패 콜백 */(/* 실패 결과 */rejected) => { console.log('실패👎 ' + rejected) })

(3) Promise 상태 : Executee(콜백)과 Executor(비동기) 정의 및 상태

Pending 상태 후 → 성공 (이행) Fulfilled / 실패 (거부) Rejected 상태에 따라 다른 Callback 함수 호출

  • Promise 객체는 Executor 로 초기 인스턴스화되고 = Pending 상태의 객체
  • 성공 (이행) Fulfilled / 실패 (거부) Rejected 상태에 따라 최종 Promise 객체 반환
    • Promise.resolve 객체 = 성공 (이행) Fulfilled 상태의 객체
    • Promise.reject 객체 = 실패 (거부) Rejected 상태의 객체

  • Promise.resolve 성공 객체 / Promise.reject 실패 객체에 따라 호출(처리)되는 Callback 함수 다름
    • Promise.resolve 객체 = 성공 (이행) Fulfilled 상태의 객체
      • .then() 성공 Callback 함수인 resolve() 에서 처리
    • Promise.reject 객체 = 실패 (거부) Rejected 상태의 객체
      • .catch() 내 실패 Callback 함수인 reject() 에서 처리

1. 성공 (Fulfilled) 상태 = Promise.resolve 객체 반환 → .then 에서 처리

성공 (이행) Fulfilled : 연산이 성공적으로 완료된 상태 = Promise.resolve 객체 반환

- Promise**State : “fulfilled”** + Promise**Result : “성공”** (Promise 객체 내 Caller 에서 resolve 로 반환한 값)

  • PromiseState : “fulfilled” + PromiseResult : “성공” (Promise 객체 내 Caller 에서 resolve 로 반환한 값)

  • 아까 Promise 예시(C안)에서 성공 시 resolve 호출하면 Promise.resolve 객체 반환되는 것 확인
new Promise((/* 성공 콜백 */resolve, /* 실패 콜백 */reject) => {
  var produced = producing()
  if (produced.success) { resolve(produced.value) }
  if (!produced.success) { reject(produced.value) }
})

Promise.resolve('성공했습니다 :)')

  • 이 성공 상태의 Promise.resolve 객체.then 콜백 함수를 통해 처리 가능

  • Promise 객체 내 값이 밖으로 꺼내어진것을 로그로 확인 가능
new Promise((/* 성공 콜백 */resolve, /* 실패 콜백 */reject) => {
  var produced = producing()
  if (produced.success) { resolve(produced.value) }
  if (!produced.success) { reject(produced.value) }
})
  .then(/* 성공 콜백 */(/* 성공 결과 */fulfilled) => { console.log('성공👍 ' + fulfilled) })

Promise.resolve('성공했습니다 :)')
  .then(/* 성공 콜백 */(/* 성공 결과 */fulfilled) => { console.log('성공👍 ' + fulfilled) })

2. 실패 (Rejected) 상태 = Promise.**reject 객체 반환 → .catch 에서 처리**

  • 실패 (거부) Rejected : 연산이 실패한 상태 = Promise.reject 객체 반환



  • PromiseState : “rejected” + PromiseResult : “실패” (Promise 객체 내 Caller 에서 reject 로 반환한 값)

  • 아까 Promise 예시(C안)에서 성공 시 reject 호출하면 Promise.reject 객체 반환되는 것 확인
    • 실패 콜백 reject 을 호출했지만 실패 콜백이 .catch 로 받아내지 못하는 경우 예외 확인 가능
new Promise((/* 성공 콜백 */resolve, /* 실패 콜백 */reject) => {
  var produced = producing()
  if (produced.success) { resolve(produced.value) }
  if (!produced.success) { reject(produced.value) }
})

Promise.reject('실패했습니다 :(')


  • 이 성공 상태의 Promise.reject 객체.catch 콜백 함수를 통해 처리 가능

  • Promise 객체 내 값이 밖으로 꺼내어진것을 로그로 확인 가능
    • 참고 : 빈 Promise 객체가 Fulfilled 상태로 되어있는건 .catch 의 결과가 또 새로운 Promise 기 때문
new Promise((/* 성공 콜백 */resolve, /* 실패 콜백 */reject) => {
  var produced = producing()
  if (produced.success) { resolve(produced.value) }
  if (!produced.success) { reject(produced.value) }
})
  .catch(/* 실패 콜백 */(/* 실패 결과 */rejected) => { console.log('실패👎 ' + rejected) })

Promise.reject('실패했습니다 :(')
  .catch(/* 실패 콜백 */(/* 실패 결과 */rejected) => { console.log('실패👎 ' + rejected) })

3. Promise 상태 : 완료 (성공 Fulfilled / 실패 Rejected 상관없이)

.finally()

(4) 기술 면접 대비 : Promise 내 Caller (Executor) 실행 시점은 언제인가

  • new Promise 가 생성되는 즉시 Caller (Executor) 함수가 실행 = 즉시 실행

(5) Promise 상태 다시 복습

  • Promise 객체의 3가지 상태
    • 시작 Pending : 약속된 결과 값이 반환(다음 상태로 전이)되지 않은 상태, 초기 상태
    • 종료 Non-Pending.finally (Non-Pending 콜백)
      • 성공 (이행) Fulfilled : 연산이 성공적으로 완료된 상태 ⇒ .then (성공 콜백)
      • 실패 (거부) Rejected : 연산이 실패한 상태 ⇒ .catch (실패 콜백)
  • Promise 3가지 상태별 객체
    • `new Promise((resolve, reject) => {})`

  • PromiseState : “pending” + PromiseResult : undefined (Promise 객체 내 Caller 에서 아무것도 반환하지 않았을 때)
    • new Promise((resolve, reject) => { **resolve**('성공') }) = Promise.**resolve**('성공')


  • PromiseState : “fulfilled” + PromiseResult : “성공” (Promise 객체 내 Caller 에서 resolve 로 반환한 값)
    • new Promise((resolve, reject) => { **reject**('실패') }) = Promise.**reject**('실패')


  • PromiseState : “rejected” + PromiseResult : “실패” (Promise 객체 내 Caller 에서 reject 로 반환한 값)

(6) Promise Hell = Nested Promise

  • Promise 의 결과가 그 다음 Promise 실행에 필요한 파라미터인 경우
    • Promise 결과값을 순차적으로 연결할때 발생 (Callback Hell 과 동일)
    • Promise 결과값을 .then 으로 전달하는 성공 Callback 에서 받아 사용하지말 것
step1(value1)
		.then((value2) => {
				step2(value2)
						.then((value3) => {
								step3(value3)
										.then((value4) => {
												console.log(value4)
										})
						})
		})

(7) Promise Chain

  • 코드 : Promise Hell 해결 방법 1 = Promise Chain 으로 아래와 같이 변환 → 장점 : 한번에 한 값 사용
    • Promise 결과값을 그대로 반환하여, 다음의 .then 에서 이어받아 사용
step1(value1)
		.then((value2) => {
				return step2(value2)
		})
		.then((value3) => {
				return step3(value3)
		})
		.then((value4) => {
				console.log(value4)
		})

4. Async / Await

Callback 따로 정의하지 않고, Promise 호출한 (외부) 함수가 처리하는것 = Callback 의 역할을 대신

  • 코드 : Promise Hell 해결 방법 2 = Async / Await 으로 아래와 같이 변환 → 장점 : 모든 값 사용 가능
    • Promise 결과값을 그대로 반환하여, 밖에서 이어받아 사용
  • 성공 / 실패에 따른 콜백이 따로 존재하지 않기 때문에
    • 성공했을때 그대로 값을 쓰면되지만
    • 실패했을때는 Exception 처리를 위해 try-catch 가 필요
const value2 = await step1(value1)
const value3 = await step2(value2)
const value4 = await step3(value3)
console.log(value4)
  • Promise 는 Caller (Executor) 와 Callee (Callback) 가 하나의 Promise 객체로 뭉쳐진 것
    • Async/Await 은 이 둘을 분리한것 = Produce-Consumer 둘을 똑 뗀 것
      • Async 은 Caller 를 정의하는 곳에서 사용함 : `async function caller() {}`
      • Await 은 Callee 를 정의하는 곳에서 사용됨 : const result = **await** caller()
let promise = new Promise(
		function caller(resolve, reject) {
			const produced = producing()
			if (succeeded) resolve(produced)
			if (failed) reject(produced)
		}
	)
//		.then(function resolve(produced) { consuming(produced) })
//		.catch(function reject(produced) { consuming(produced) })
let fulfilled = promise    // Promise {<fulfilled>, '완료!'}
let result = await promise // '완료!'

(1) Async / Await 쉽게 이해하기

  • async = Promise 상자 포장 (해당 함수의 반환값을 Promise 객체로 포장하여 반환)
  • await = Promise 상자 개봉 (Promise 객체를 기다렸다가 상자를 열어 내부 값을 반환)

  • async 가 붙은 함수는 반드시 Promise 객체를 반환하고, Promise 가 아닌 것은 Promise 로 감싸 반환
    1. return 값을 반환한 경우 : 성공 = Promise.**resolve 객체 반환**
    2. throw new Error() Exception 을 던진 경우 : 실패 = Promise.**reject 객체 반환**

  1. return 값을 반환한 경우 : 성공 = Promise.**resolve 객체 반환**
async function return_promise() {
  return 1 // = Promise.resolve(1) 를 해도 동일한 결과 반환
}
return_promise()                   // Promise { <fulfilled>: 1 }
await return_promise()             // 1
return_promise().then(console.log) // 1

  1. throw new Error() Exception 을 던진 경우 : 실패 = Promise.**reject 객체 반환**
async function throw_promise() {
  throw new Error(2)
}
throw_promise()                                           // Promise { <rejected>: Error: 2 at throw_promise ... }
await throw_promise()                                     // Uncaught Error: 2 at throw_promise ...
throw_promise().catch(console.log)                        // Error: 2 at throw_promise ...
try { await throw_promise() } catch(e) { console.log(e) } // Error: 2 at throw_promise ...

  • await 키워드를 만나면 Promise 가 처리될 때까지 대기
async function returningpromise() {
  let promise = new Promise((resolve, reject) => {
    setTimeout(() => resolve('완료!'), 1000)
  })

  let result = await promise // 프라미스가 이행될 때까지 대기 (*)
  console.log(result)        // '완료!'
}

returningpromise()
async function returningpromise() {
  let promise = new Promise((resolve, reject) => {
    setTimeout(() => resolve('완료!'), 1000)
  });

  let result = promise // 프라미스를 기다리지않고, 프라미스 객체 그대로 반환
  console.log(result)  // Promise { <pending> }
}

returningpromise()

5. Promise & Async/Await 사용 시 주의할 점

(1) Promise 에서는 return 이 아니라 resolve 로 결과 반환

1. Promise : return 로 결과 반환하게 되면 아래와 같이 Pending (정상 사용 X)

function hello() {
  return new Promise((resolve, reject) => {
    return 'Seonga' // 정상 사용 X
  })
}

const user = hello()
console.log(user)

2. Promise : resolve 로 결과 반환해야 정상 처리 O

function hello() {
  return new Promise((resolve, reject) => {
    resolve('Seonga') // 정상 처리 O
  })
}

const user = hello()
console.log(user)
function hello() {
  return new Promise((resolve, reject) => {
    resolve('Seonga') // O
  })
}

const user = hello()
user.then(console.log)
function hello() {
  return new Promise((resolve, reject) => {
    resolve('Seonga') // O
  })
}

const user = await hello()
console.log(user)
  • Async 함수에서는 그냥 Return 으로 반환 가능
async function hello() {
  return 'Seonga' // O
}

const user = hello()
user.then(console.log)
async function hello() {
  return 'Seonga' // O
}

const user = await hello()
console.log(user)

(2) Promise 에러 처리 방법 : .catch (실패 콜백)

  • .catch 에서 처리하기 위해서는 Promise 내 Caller 에서 Reject 로 반환
function hello() {
  return new Promise((resolve, reject) => {
    reject('Seonga')
  })
}

const user = hello()
user.catch(console.log) // 'Seonga'
function hello() {
  return new Promise((resolve, reject) => {
    reject(new Error('Seonga'))
  })
}

const user = hello()
user.catch(console.log) // Error: 'Seonga'

(3) Async / Await 에러 처리 방법 : Try-Catch

  • Await 에서 에러 처리하려면 어쩔 수 없이 Try-Catch 를 사용
function hello() {
  return new Promise((resolve, reject) => {
    reject('Seonga')
  })
}

const user = await hello()
console.log(user) // Uncaught (in promise) Seonga
function hello() {
  return new Promise((resolve, reject) => {
    reject('Seonga')
  })
}

try {
	const user = await hello()
	console.log(user)
} catch(e) {
	console.log(e) // 'Seonga'
}
  • Reject 로 Error 객체를 반환했을 때
function hello() {
  return new Promise((resolve, reject) => {
    reject(new Error('Seonga'))
  })
}

const user = await hello()
console.log(user) // Uncaught (in promise) Error: Seonga
function hello() {
  return new Promise((resolve, reject) => {
    reject(new Error('Seonga'))
  })
}

try {
	const user = await hello()
	console.log(user)
} catch(e) {
	console.log(e) // Error: 'Seonga'
}

6. Callback / Promise 기반 비동기 처리

(1) AJAX : Callback + 빌트인 (웹 브라우저 자바스크립트)

  • 단점 : 브라우저 호환성이 낮고, CSRF 를 손수 설정해야하는 단점
  • 코드 예시 : 사용하기엔 너무 복잡한 원시적인 코드
    var xhr = new XMLHttpRequest();
    xhr.onreadystatechange = function() {
     if (xhr.readyState === xhr.DONE) {
      if (xhr.status === 200 || xhr.status === 201) {
       console.log(xhr.responseText);
      } else {
       console.error(xhr.responseText);
      }
     }
    };
    xhr.open('GET', 'http://localhost');
    xhr.send();

(2) jQuery AJAX : Callback + 서드파티

  • 크로스 브라우저 환경에서 일관된 자바스크립트 문법 제공을 위해 만들어진 라이브러리
  • 자바스크립트를 통한 DOM, CSS 조작을 정말 간편히 해주어 엄청 유명한 서드파티 라이브러리
  • 코드 예시 : AJAX 만을 위해 jQuery 를 쓰는건 라이브러리가 너무 크다 (배보다 배꼽이 더 큰 상황)
    $.ajax({
       type: 'POST',
       url: url,
       data: data,
       dataType: dataType,
       success: function (res) { console.log(res); },
       error: function (res) {console.log(res); }
    });

(3) Axios : Promise + 서드파티

  • 장점 : 객체 전송시 JSON 직렬화 + 객체 반환시 JSON 역직렬화 모두 자동 수행하여, 사용이 수월
  • 장점 : 객체 전송시 JSON 직렬화 + 객체 반환시 JSON 역직렬화 모두 자동 수행하여, 사용이 수월
  • 코드 예시 : Promise 객체를 반환하기에, 사용이 훨씬 편해졌다.
    axios({
        method: 'post',
        url: '/user/12345',
        data: {
            firstName: 'Fred',
            lastName: 'Flintstone'
        }
    })
    .then(function (response) {
        console.log(response);
    })
    .catch(function (error) {
        console.log(error);
    });

(4) Fetch API : Promise + 빌트인 = 비동기 통신을 위한 ES6 자바스크립트 내장 API

  • 장점 : 서드파티 라이브러리를 따로 설치하지않아도 되기에 매우 가벼움
  • 단점 : 라이브러리 호환성 확인이 필요, JSON 변환 필요 (직렬화, 역직렬화)
  • 코드 예시 : Promise 객체 변환할뿐만 아니라, 라이브러리가 없어서 Bundled JS 로드시 가벼움
    try {
      let response = await fetch(url);
      let data = await response.json();
      console.log(data);
    } catch(e) {
      console.log("Oops, error", e);
    }

실무에서 많이 활용되는 자바스크립트 ES6+ 문법

0. 자바스크립트 버전

  • 자바 버전 : Java 11, Java 17 등 지원하는 문법들이 점차 추가되고 발전
  • 자바스크립트 버전 : 자바스크립트도 자바나 다른 여느 언어와 같이 ECMAScript 으로 매년 새 표준이 등장
    • React 개발 시 사용하고자 하는 ES 버전 명시하는 방법 그렇지 않으면 ts(2583)과 같은 에러 발생
      • 타입스크립트 사용 시 : `jsconfig.json`
      • 자바스크립트 사용 시 : `tsconfig.json`

  • ES6+ 문법을 통칭해서 ESNext 라고 명명하고, tsconfig.json 파일의 lib 옵션에 esnext 로 표기
    • 자바스크립트에 클래스, 추상 클래스와 모듈의 개념도 ES6 에서 도입된 것
      • 자바는 Java 8 기준으로 함수형 프로그래밍이 도입
      • 자바스크립트는 ES6 기준으로 객체지향 프로그래밍이 도입된 셈

1. 객체 / 배열 관련

(1) 비구조화 Destructuring : 객체 {} 혹은 배열 []

  • 객체 : const objectconst { name, age } = object
    • 장점 1 : 반복해서 객체명을 매번 사용하지 않아도 되고 (object. 반복해서 사용하지 않음)
    • 장점 2 : 객체 프로퍼티 중에 딱 필요한 것만 골라서 사용할 수 있음 (아래 예시에서 favor 미사용)
// 1. 객체 비구조화
const object = { name: 'Seonga', age: 25, favor: 'Game' }
const { name, age } = object
console.log(object.name) // 👎
console.log(name)        // 👍
console.log(age)
  • 추가적으로 객체 비구조화의 경우에는 원하는 명칭으로 바로 변환해서 사용 가능

  • 배열 : const arrayconst [ first, second ] = array
// 2. 배열 비구조화
const array = [ 'Seonga', 'Jeonga', 'Cheonga' ]
const [ first, second ] = array
console.log(array[0])    // 👎
console.log(first)       // 👍
console.log(second)
  • 단점 : 딱 필요한 것이 뒤에 있는 요소라면 그것만 가져와서 사용할 수 없음 (순서를 지켜야하기 때문)
const array = [ 'Seonga', 'Jeonga', 'Cheonga' ]
const [ _, __, third ] = array
console.log(array[2])    // 👎
console.log(third)       // 👍

✨ 실제 실무에서 활용

ex ) React - React Component 의 Props 사용 시

<Component value={state} onChange={() => setState(state + 1)} />

function Component({ value, onChange }) {
	return <button onClick={(e) => onChange()}>{value}</button>
}
function Component(props) {
	return <button onClick={(e) => props.onChange()}>{props.value}</button>
}

✨ 실제 실무에서 활용

ex ) DTO(Data Transfer Object, 함수 간 데이터 전달 시 사용하는 객체) 사용 시

  • DTO (Data Transfer Object) : @Getter + @Setter 모두 갖기에 데이터 조작 가능
  • VO (Value Object) : @Getter 만 갖기에 데이터 조작 불가 = 값 읽기만 가능
function tooManyArguments(a, b, c, d, e) { ... }
function simplifyMany

(2) 객체 프로퍼티 초기화 단축 Property Initializer Shorthand

  • 객체 초기화 시 (객체를 생성할 때) 객체 프로퍼티에 할당할 변수명이 객체 프로퍼티명과 같을 때, 콜론을 생략하는 것
const carname = "애스턴마틴 DB11"
const round = 11
const normal = {  // 👎
  carname: carname,
  round: round,
}
const carname = "애스턴마틴 DB11"
const round = 11
const shorten = { // 👍
	carname,
	round,
}

function parse({ name, age, favor }) {
  return { // 👎
    name,
    favor,
  }
}
function parse({ name, age, favor }) {
  return { // 👍
    name: name,
    favor: favor,
  }
}

✨ 실제 실무에서 활용

ex ) Function 함수에서 객체 반환 시

const users = [
  { name: 'Seonga', age: 10, favor: 'Game' },
  { name: 'Jeonga', age: 20, favor: 'Soccer' },
]

const parsed = users.map(({ name, age, favor }) => {
  return { name, favor }
})

(3) Spread Syntax : 배열 및 객체 펼치기

  • 배열 펼치기 : 배열 복사 + 배열 연결 + 배열 요소(Element) 추가
const array = [1, 2, 3]
//    array = [1, 2, 3]
// ...array =  1, 2, 3  => 대괄호 [] 제거
const added = [...array, 4, 5]
console.log(added)
/* [ 1, 2, 3, 4, 5 ] */

✨ 실제 실무에서 활용

ex ) 여러 개의 배열을 하나로 합칠 때

const array_1 = [1, 2]
//    array_1 = [1, 2]
// ...array_1 =  1, 2
const array_2 = [3, 4]
//    array_2 = [3, 4]
// ...array_2 =  3, 4
const combine = [...array1, ...array2]
//    combine = [1, 2, 3, 4]
  • 객체 펼치기 : 객체 복사 + 객체 연결 + 객체 프로퍼티(Property) 추가
const object = { name: 'Seonga', age: 25 }
//    object = { name: 'Seonga', age: 25 }
// ...object =   name: 'Seonga', age: 25   => 중괄호 {} 제거
const modified = {...object, name: 'Jeonga'}
console.log(modified)
/* { name: 'Jeonga', age: 25 } */

✨ 실제 실무에서 활용

ex ) React - setState 통한 상태변경 시 = 불변성 보장을 위해

  • React 에서는 상태가 같으면 리렌더가 발생하지 않아 이전 상태와 다른 값을 setState 내 주입
    • setState(state) : state 이전 주소값과 새로 주입하는 객체의 주소값이 동일
    • setState({ ...state, age: state.age + 1 })) : 새 주소값
function Component() {
	const [state, setState] = useState({ name: 'Aaron', age: 10 })

	return (
		<>
			<div>{state.name}</div>
			<div>{state.age}</div>
			<button onClick={() => setState({ ...state, age: state.age + 1 })}></button>
		</>
	)
}

2. 배열 관련

(1) 객체 프로퍼티 / 메서드 혹은 배열 요소 Trailing Comma

이거는 사실 안썼을 때 얻을 실익은 없기 때문에, 사실상 안쓰면 바보인 문법

  • 객체 내 프로퍼티(Property) : 새로운 프로퍼티를 아래에 추가할 때, 직전 프로퍼티에 콤마를 안써줘도됨
const object = {
  name: 'Seonga',
  age: 25,
  favor: 'Game' // 👎
}
**const object = {
  name: 'Seonga',
  age: 25,
  favor: 'Game', // 👍
}**
  • 배열 내 요소(Element) : 새로운 요소를 아래에 추가할 때, 직전 요소에 콤마를 안써줘도됨
const array = [
  'Seonga',
  'Jeonga',
  'Cheonga' // 👎
]
const array = [
  'Seonga',
  'Jeonga',
  'Cheonga', // 👍
]

⛔ Trailing Comma 에 심취해 JSON 파일에서까지 사용하려고 하지말기

(2) 특정 요소 포함 여부 확인 : Array.prototype.includes()

const numbers = [ 1, 12, 34, 12, 3, 2, 1423, 1314, 345, 35, 123, 4567, 345 ]
numbers.includes(12)

3. 함수 관련

(1) Rest Parameter : 함수 파라미터(Parameter 매개변수 = 안) 펼치기

→ Spread Syntax 에서 배운 개념을 거꾸로 뒤집기

function foo(first, ...rest) { // 1, 2, 3, 4   = ...rest
  console.log(rest)            //  [ 2, 3, 4 ] = rest
  console.log(...rest)         //    2, 3, 4   = ...rest
}
foo(1, 2, 3, 4)                //    2, 3, 4   = ...rest

(2) 함수 기본 파라미터값 Default Parameters

function create({ name = 'Unnamed', age = 1, favor = undefined }) {
  return { name, age, favor }
}

const created_user_1 = create({ name: 'Seonga' })
const created_user_2 = create({ name: 'Jeonga', age: 25 })
const created_user_3 = create({                age: 25, favor: 'Game' })
console.log(created_user_1)
console.log(created_user_2)
console.log(created_user_3)

✨ 실제 실무에서 활용

ex ) React - Component 에서 받지않아도되는 Props 에 기본값 설정

function Component({ value = '', color = 'red', type = 'text' }) {
	return (
		<input type={type} value={value} style={{ color }} />
	)
}

<Component type='password' />
<Component color='blue' />
<Component value='initialied' />

4. 문자열 및 자료구조 관련

주의 : MapSet 의 경우 jsconfig.jsonlib 옵션에 ES2015 을 등록해주어야 사용 가능

(1) 문자열 포맷팅 String Literal : 백틱을 통한 문자열 직관적 결합

const user = { name: 'Seonga', age: 25, favor: 'Game' }
// 👎
console.log('제 이름은 ' + user.name + '이고, 나이는 ' + user.age + '이며, ' + user.favor + '를 좋아합니다.')
const user = { name: 'Seonga', age: 25, favor: 'Game' }
// 👍
console.log(`제 이름은 ${user.name}이고, 나이는 ${user.age}이며, ${user.favor}를 좋아합니다.`)

(2) Map : 유일 Key 및 Key-Value 집합

  • 왜 굳이 Object 객체를 쓰지 않고 Map 을 사용하는가?
    • 어쨌든 Object 는 자료구조가 아닌 객체의 목적
const initialized = new Map([
  [1, 'first'],
  [2, 'second'],
  [3, 'third'],
  ['Seonga', { phone: '010-000-0000', address: 'Earth' }],
  ['Jeonga', { phone: '010-111-1111', address: 'Mars' }],
  ['Cheonga', { phone: '010-222-2222', address: 'Moon' }],
]);
initialized.set("Seonga", { phone: '010-333-3333', address: 'Jupiter' });
initialized.has("Seonga"); // true
initialized.get("Jeonga"); // undefined
initialized.set("Jeonga", { phone: '010-444-4444', address: 'Venus' });
initialized.get("Seonga"); // { phone: '010-333-3333', address: 'Jupiter' });
initialized.delete("Seongwoo"); // false
initialized.delete("Seonga"); // true
console.log(initialized.size); // 6
initialized.clear()
initialized.forEach(function)
initialized.entries()
initialized.keys()
initialized.values()

(3) Set : 유일 Key 집합

배열과 다른점은 Set 은 중복 요소를 허용하지 않는다는 것

const initialized = new Set([ 1, 2, 3, 'Seonga', 'Jeonga' ]);
initialized.add(1); // Set(5) { 1, 2, 3, 'Seonga', 'Jeonga' }
initialized.add(1); // Set(5) { 1, 2, 3, 'Seonga', 'Jeonga' }
initialized.add(1); // Set(5) { 1, 2, 3, 'Seonga', 'Jeonga' } 아무리 추가해도 그대로
initialized.has(1); // true
initialized.has(5); // false
initialized.add({ phone: '010-222-2222', address: 'Moon' });
console.log(initialized.size); // 6
initialized.delete(5); // false
initialized.delete(1); // true
initialized.has(1); // false, 1 은 제거되었습니다.

5. 반복문 및 조건문 관련

(1) for .. offor .. in 의 차이 + forEach

  • for .. of : Element (요소) ← iterable
const array = [ 'first', 'second', 'third' ]
for (let element of array) {
  console.log(element)
  // 'first'
  // 'second'
  // 'third'
}
  • for .. in : Key (프로퍼티명) ← enumerable
const array = [ 'first', 'second', 'third' ]
for (let key in array) {
  console.log(key)
  // '0'
  // '1'
  // '2'
}
const object = { name: 'Aaron', age: 10, favor: 'Game' }
for (let key in object) {
  console.log(key)
  // 'name'
  // 'age'
  // 'Game'
}
  • .forEach : Element (요소) ← iterable 다른 방식 | index 가 있어서 순서 활용 가능
const array = [ 'first', 'second', 'third' ]
array.forEach((each, index) => {
  console.log(`${index + 1}번째 요소는 ${each}`)
  // '1번째 요소는 first'
  // '2번째 요소는 second'
  // '3번째 요소는 third'
})
for (let [ index, each ] of array.entries()) {
  console.log(`${index + 1}번째 요소는 ${each}`)
  // '1번째 요소는 first'
  // '2번째 요소는 second'
  // '3번째 요소는 third'
}
  • 참고 : 배열은 아래와 같은 객체와 유사 (유사 배열과 배열의 차이)
const 진짜배열 = [ 'first', 'second', 'third' ]
<const 유사배열 = {
  0: 'first',
  1: 'second',
  2: 'third',
  length: 3,
}
console.log(진짜배열[1])     // 'second'
console.log(유사배열[1])     // 'second'
console.log(진짜배열.length) // 3
console.log(유사배열.length) // 3
console.log(진짜배열.includes('first')) // true
console.log(유사배열.includes('first')) // TypeError: 유사배열.includes is not a function
// 바로 위 코드를 보면 알 수 있듯, 유사 배열인 이유는 배열이 갖는 메서드를 유사 배열은 갖지 않기 때문

const 전환배열 = Array.from(object)
Array.isArray(진짜배열) // true  <- 배열
Array.isArray(유사배열) // false <- 유사 배열 = 객체
Array.isArray(전환배열) // true  <- 배열 (유사 배열로부터 만든)

(2) Null Guarding (null or undefined, Null 로 통칭)

  • ?? : Default = 앞엣값이 Null 인 경우 표시할 값 (Nullish coalescing)
  • ?. : Null 이라면 넘겨버림, Null Cascading 이라고도 부름
  • ! + !. : Non-null Assertion - 절대 Null 이면 안된다는 것을 개발자가 보장하는 것

실습 : API 혹은 유저가 입력한 객체가 Null 혹은 Undefined 일때 내부 프로퍼티 접근 시 에러 방지

  • object**?.**property : Null Cascading = object 가 Null 인 경우 property 에게 전달
  • object**!.**property : Non-null Assertion = object 가 Null 될 수 없음을 개발자가 보증
    • 본인이 예언자도 아니고, 웬만히 확실한게 아니면 보장하지마시고 ?. 쓰고 Null 처리하시길
  • ?. 은 런타임에서 Null 방어가 되지만 !. 은 런타임에서 여전히 문제 발생 (컴파일에서만 해결)
let user = { name: 'Aaron', age: 10 }
user = undefined
console.log(user?.name)
let user = { name: 'Aaron', age: 10 }
user = undefined
console.log(user!.name)

5.3. 선택적 호출 : && (if 대체) + ? Ternary Operator (if-else 대체)

  • && 의 단점 : 앞의 구문이 false 혹은 undefined 일때 그대로 false 혹은 undefined 를 반환
    • 예시) className 안에 undefined 혹은 false 클래스명이 들어갈 수 있음

실습 : React 에서 정말로 많이 사용하는 ?? + && + ||

  • ?? : Default 기본값 정의 시 사용
  • && : 조건에 맞는 경우 노출할 값 정의 시 사용
    • 1 && **1** = **1** ← 앞이 true 이면, 뒤엣것을 표기하는 이걸 노리는 것
    • 1 && **0** = **0** ← 앞이 true 이면, 뒤엣것을 표기하는 이걸 노리는 것
    • `0 && 1 = 0`
    • `0 && 0 = 0`
  • || : 조건에 맞지 않는 경우 노출할 값 (에러 혹은 검증메세지) 정의 시 사용
    • `1 || 1 = 1`
    • `1 || 0 = 1`
    • 0 || **1** = **1** ← 앞이 false 이면, 뒤엣것을 표기하는 이걸 노리는 것
    • 0 || **0** = **0** ← 앞이 false 이면, 뒤엣것을 표기하는 이걸 노리는 것
undefined ?? '기본값'
true && '선택적 렌더'
false || '에러메세지'

(4) Value Comparison Operator == and ===

  • == 은 암시적 타입 변환을 해주어 비교를 해주는데, 편하기보다 예상치 못한 결과를 받기 쉬워 사용하지말 것
  • === 은 어떠한 타입 변환없이 값뿐만 아니라 타입도 동일한지를 비교해주기에, 이것만 사용할 것

(5) Type Conversion (명시적) vs Coercion (암묵적) 타입 변환

1 == '1'         // Type Coercion   암시적 타입 변환
1 == Number('1') // Type Conversion 명시적 타입 변환

자바스크립트 심화 문법 : IIFE, 클로저, 커링활용

1. IIFE 이피 : 즉시 사용 함수

IIFE = Immediately Invoked Function Expression : “함수 정의부 + 함수 호출부” 한줄에 처리하는 것

  • Snapshot (딱 한번만 호출) : 재사용성이 있는 함수는 아니지만, 지금 당장 필요할 때
  • Function (Module) Encapsulation : 함수 은닉 (Private 화)
(function(parameter) { ... })(argument)
  • ex ) 삼항 연산자를 통해서 큰 객체의 결과값에 따라 해당 객체를 반환할 지, 다른걸 반환할 지 convert 한 뒤 default 활용 여부를 판단할 지 convert 한 것을 바로 파라미터로 주 입하여 default 활용 여부를 판단하여 반환할 지

 main: ((converted) => (converted.length > 0 ? converted : forModify ? [INITIAL_SKILL] : []))(
         (resume?.skills ?? [])
           .filter((personalSkill) => personalSkill.main === true)
           .map((personalSkill) => ({
             id: personalSkill.id,
             // main: personalSkill.main,
             priority: personalSkill.priority,
             stale: personalSkill.stale,
             // Skill
             skillId: personalSkill.skill.id,
             category: personalSkill.skill.category !== null ? personalSkill.skill.category : undefined, // nullable
             name: personalSkill.skill.name,
             order: personalSkill.order,
           })),
       ), // 빈배열 들어올 경우 초기값 할당
  • ex ) Function (Module) Encapsulation 예이나, 이렇게 활용해본 적은 없음
    • 외부에서 호출되지 않길바라는 함수 혹은 모듈 정의 시

      ((param: number) => {
        const privateFunction = (param) => {
          return param * 3
        }
        console.log(privateFunction(param))
      })(3)
  • 실습 : 이력서 작성 플랫폼에서 .map 으로 DB 내 데이터를 컴포넌트에 맞게 표기기본 폼 노출
    • 이력서에는 유저가 입력해야할 폼들이 많다. 그 중 회사 정보는 DB 와 입력폼 사이 변경 필요

      const companies = [
        { name: 'A Company', start: 2021, years: 3 },
        { name: 'B Company', start: 2019, years: 2 },
      ]
      
      const converted = companies
        .map((company) => ({
          name: company.name,
          start: company.start,
          end: company.start + company.years,
        }))
      
      const forms = converted.length === 0
        ? [{ name: '', start: 2024, years: 0 }]
        : converted
    • 다음과 같은 로직을 아래와 같이 변경하면 변수를 여러개쓰지 않고 한번에 해결 가능

      const forms = ((converted) =>
        converted.length !== 0 ? converted : [{ name: "", start: 2024, years: 0 }])(
        companies.map((company) => ({
          name: company.name,
          start: company.start,
          end: company.start + company.years,
        }))
      );

2. Currying 커링 : 함수를 반환하는 함수

함수를 반환하는 함수 = 함수를 만드는 메타함수

  • 불필요한 반복 파라미터 사용 방지
  • 파라미터를 계층화 하기에 뛰어난 방법 = 가독성 및 재사용성 향상
function currying(parameter) {
	return () => { ... }
}
  • ex ) 3의 배수, Array Stringify 등
    enum EVENT_TYPE {
      ERROR,
      INFO,
    }
    
    const threadLogger = (threadNumber: string) => {
      return ({ desc, type }: { desc: string, type: EVENT_TYPE }) => {
        console.log(`[${threadNumber}] ${type} : ${desc}`)
      }
    }
    
    const thread1Logger = threadLogger('dsadmkasnf38r0')
    const thread2Logger = threadLogger('dch89eddedjlfd')
    
    thread1Logger({ type: EVENT_TYPE.ERROR, desc: 'Not Found' })
    thread1Logger({ type: EVENT_TYPE.ERROR, desc: 'Not Found' })
  • 실습 : 초등학교때 배웠던 구구단
    • Non-Currying : 커링 미사용 시

      function noncurrying(operand, multiple) {
        console.log(operand * multiple)
      }
      
      // 구구단 2단 시작!
      noncurrying(2, 1)
      noncurrying(2, 2)
      noncurrying(2, 3)
      
      // 구구단 3단 시작!
      noncurrying(3, 1)
      noncurrying(3, 2)
      noncurrying(3, 3)
    • Currying : 커링 사용 시

      function currying(operand) {
        return function multiply(multiple) {
          console.log(operand * multiple)
        }
      }
      
      const multiply2 = currying(2)
      const multiply3 = currying(3)
      
      // 구구단 2단 시작!
      multiply2(1)
      multiply2(2)
      multiply2(3)
      
      // 구구단 3단 시작!
      multiply3(1)
      multiply3(2)
      multiply3(3)

3. Closure 클로저 : Encapsulation 및 상태 관리

  • Enclose (여러번 호출 가능) : IIFE 와 달리 여러번 사용이 가능하여, 중앙 상태관리로 활용 가능
  • Variable Encapsulation : Lexical Scope 을 활용한 (함수 선언 시 주변 환경 박제) 변수 캡슐화
function closure(parameter) {
	const encapsulated = 1
	return () => { ... }
}
  • ex ) 데이터베이스 인스턴스 생성 및 상태 관리 (본 코드는 실제 동작하진 않음, 참조용으로)
    const visitorCombiner = (id: string, pw: string) => {
      const connectedDatabase = SQL.connection(id, pw)
      const visitor = 0 // State + Encapsulation
      return {
        increase: () => { visitor += 1 }
        send: () => { connectedDatabase.create(
          { id: id, visitor: visitor })
        }
      }
    }
    
    const createdVisitorCombiner = visitorCombiner('id', 'pw')
    
    /**
     * 접근 불가
     *  - createdVisitorCombiner.visitor
     *  - createdVisitorCombiner.connectedDatabase.save()
     *  - createdVisitorCombiner.connectedDatabase.delete()
     */
    
    ....onClick(() => {
      createdVisitorCombiner.increase()  
    })
    
    ....userSessionExit(() => {
      createdVisitorCombiner.send()
    })

(1) 주의 : 클로저로 생성된 함수는 nullify 하지 않은 경우 Memory Leak

// 일반적인 함수
function sayHello() {
	let hello = 'Hello';
	console.log(hello)
}

sayHello() // 'Hello'

함수 내부에 있는 지역 변수는 함수 내부에서만 참조할 수 있기 때문에

일반적인 함수는 실행이 끝나면 내부에 있는 지역변수를 사용 불가

*즉, 함수에 있던 지역변수는 메모리에서 삭제*

(어디서도 참조하고 있지 않은 지역변수는 이후 가비지 컬렉터에 의해 메모리에서 정리)

// 클로저
function makeCounter() {
	let count = 0;

	return function() {
		return ++count
	}
}

const increase = makeCounter()
increase() // 1
increase() // 2

하지만 클로저는 외부 함수의 실행이 끝나더라도 내부 함수가 외부 함수의 지역변수를 참조할 수 있기 때문에 내부 함수에서 사용이 가능하므로, 외부 함수에 있던 지역변수는 메모리에서 삭제 불가
(내부 함수에서 외부함수의 지역변수를 참조하고 있기 때문에 가비지 컬렉터에 의해 제거 불가)

profile
(와.. 정말 Chill하다..)

0개의 댓글