막학기에 졸프 하나만 듣고 등교를 안하게 되어서 자꾸 게을러지게 되는것 같아서 부트캠프에 들어가게 되었다. 프론트엔드 + sql + 백엔드 과정으로 이어지고 팀 프로젝트를 총 2번해서 많이 배울 수 있을 것 같다고 생각했다.

처음 커리큘럼은 JS로 공부를 하긴 해봤지만,아직 많이 부족한 점을 깨닫게 되었다.
이번에 새롭게 알게 되었거나 헷갈리는 부분들을 정리하고 회고하려고 한다.

문자열

String Interning

  • V8 엔진은 메모리를 절약하기 위해 동일한 문자열 리터럴이 생성되면, 매번 새로운 메모리를 쓰지 않고 기존에 만들어진 문자열의 주소를 재사용
    > %DebugPrint(h)
    DebugPrint: 0xb172ecef8d9: [String] in OldSpace: #Hello
    0xa12be380691: [Map] in ReadOnlySpace
     - map: 0x0a12be380841 <MetaMap (0x0a12be380099 <null>)>
     - type: INTERNALIZED_ONE_BYTE_STRING_TYPE
     - instance size: variable
     - elements kind: HOLEY_ELEMENTS
     - enum length: invalid
     - stable_map
     - non-extensible
     - back pointer: 0x0a12be380069 <undefined>
     - prototype_validity cell: 0
     - instance descriptors (own) #0: 0x0a12be380c91 <DescriptorArray[0]>
     - prototype: 0x0a12be380099 <null>
     - constructor: 0x0a12be380099 <null>
     - dependent code: 0x0a12be380c51 <Other heap object (WEAK_ARRAY_LIST_TYPE)>
     - construction counter: 0
    
    'Hello'
    > 
    > %DebugPrint(h2)
    DebugPrint: 0x35d9d63f629: [String] in OldSpace: #hi
    0xa12be380691: [Map] in ReadOnlySpace
     - map: 0x0a12be380841 <MetaMap (0x0a12be380099 <null>)>
     - type: INTERNALIZED_ONE_BYTE_STRING_TYPE
     - instance size: variable
     - elements kind: HOLEY_ELEMENTS
     - enum length: invalid
     - stable_map
     - non-extensible
     - back pointer: 0x0a12be380069 <undefined>
     - prototype_validity cell: 0
     - instance descriptors (own) #0: 0x0a12be380c91 <DescriptorArray[0]>
     - prototype: 0x0a12be380099 <null>
     - constructor: 0x0a12be380099 <null>
     - dependent code: 0x0a12be380c51 <Other heap object (WEAK_ARRAY_LIST_TYPE)>
     - construction counter: 0
    
    'hi'
    > %DebugPrint(h9)
    DebugPrint: 0xb172ecef8d9: [String] in OldSpace: #Hello
    0xa12be380691: [Map] in ReadOnlySpace
     - map: 0x0a12be380841 <MetaMap (0x0a12be380099 <null>)>
     - type: INTERNALIZED_ONE_BYTE_STRING_TYPE
     - instance size: variable
     - elements kind: HOLEY_ELEMENTS
     - enum length: invalid
     - stable_map
     - non-extensible
     - back pointer: 0x0a12be380069 <undefined>
     - prototype_validity cell: 0
     - instance descriptors (own) #0: 0x0a12be380c91 <DescriptorArray[0]>
     - prototype: 0x0a12be380099 <null>
     - constructor: 0x0a12be380099 <null>
     - dependent code: 0x0a12be380c51 <Other heap object (WEAK_ARRAY_LIST_TYPE)>
     - construction counter: 0
    
    'Hello'
    ⇒ h1와 h9가 변수 이름은 다르지만 값(”Hello”)로 같으므로 주소가 0xb172ecef8d9 로 동일함 ⇒ hash를 돌리면 모두 값이 같기 때문에 한번만 만들어서 메모리 절약
    • hash
      • 메모리 주소와 연관되어 있음
      • 탐색 시간 줄일 수 있음
  • 문자열의 불변성
    • 문자열은 원시 타입이므로 불변

    • h9를 “hi” 로 값을 바꾸면 기존 0xb172ecef8d9 의 내용이 바뀌는 것이 아니라, 새로운 메모리 주소에 hi를 만들고 그곳을 가리키게 됨

      ⇒ 배열처럼 .push()같은 메서드로 원본을 직접 수정하는 것이 불가능

원시 타입 vs 객체 타입

메모리 공간인 stack과 heap을 어떻게 사용하는지의 차이

구분원시 타입 (Primitive)객체 타입 (Object/Reference)
종류String, Number, Boolean, null, undefined, SymbolArray, Function, Object
저장 위치Call Stack에 값 자체가 저장됨 (혹은 상수는 별도 풀)Heap에 실제 데이터 저장, Stack에는 그 주소(참조값) 저장
특징불변 (Immutable). 값이 바뀌면 주소가 바뀜.가변 (Mutable). 참조 주소는 그대로 두고 힙의 데이터만 수정 가능.

왜 굳이 스택과 힙을 나누고 참조할까?

  1. 데이터 크기의 차이 (고정 vs 기반)
    • Stack: 함수 실행 컨텍스트 등 정해진 크기의 데이터를 빠르게 쌓고 치움
    • Heap: 배열이나 객체처럼 크기가 얼마나 커질지 모르는 데이터를 저장하는 거대한 창고
  2. 접근 속도와 효율성
    • 객체 데이터를 스택에 억지로 넣거나, 힙을 연결리스트처럼 prev, next로만 관리한다면
      • 데이터를 찾기 위해 하나하나 건너가야하므로 탐색 속도가 매우 느려짐 O(n)
    • Stack에는 데이터가 힙의 어디에 있는지(메모리주소)만 저장 → 크기 일정
    • Reference만 알면 힙의 해당 위치로 즉시 이동 가능 O(1)

Realm

  • 자바스크립트 코드가 실행되는 하나의 독립적인 세계
  • 연결된 코드가 전역 객체와 같은 ECMAScript 리소스에 접근할 수 있는 영역
  • 미리 만들어진 것
    • ex) 식당에서 공유하는 샐러드바
    • Built-in properties: Infinity, NaN, undefined, null
    • Built-in functions: eval, isInifite, isNaN, parseInt, parseFloat
  • 신뢰할 수 없는 외부 코드를 실행해야 할때, 별도의 Realm에서 실행하면 전역 객체나 프로토타입이 오염되는 것을 막을 수 있음 ⇒ 샌드박싱
  • Shadow Realm
    • 격리를 위해 설계된 특정 유형의 Realm
    • 부모 Realm과 동일한 실행 컨텍스트 내에서 밀접하게 통합되도록 설계
    • 전역 객체 및 내장 객체의 엄격한 분리

클로저

  • 나를 return해주는 함수의 Env Record에 가까이 있다는 뜻
  • 참조하고 있어서 못 없어지는 것
  • 클로저를 알아야 깔끔한 리액트 Hook 작성 가능
    • 클로저를 모르면 메모리 누수, 잘못된 상태 참조 문제 발생 가능
  • 사용하는 이유
    • 상태를 안전하게 은닉
    • 비순수 함수 → 순수 함수
      • 외부 변수를 참조해서 사용하게 되면 사이드이펙트 발생 가능

currying

  • 화살표 함수인데, 함수를 return하는 함수
  • () ⇒ () ⇒ {}
  • ex) 버튼 컴포넌트: (theme) ⇒ (onClick) ⇒ {}
  • 공통적인 인자를 사용하는 부분을 별도로 분리하여 인자를 줄이고 재사용 가능
    • 기존 코드
      function multiple(a: number, b: number) {
        return a * b;
      }
      
      const CONSTANT = 2;
      
      const test1 = multiple(CONSTANT, 1); // 2
      const test2 = multiple(CONSTANT, 2); // 4
      const test3 = multiple(CONSTANT, 3); // 6
      const test4 = multiple(CONSTANT, 4); // 8
      const test5 = multiple(CONSTANT, 5); // 10
    • currying 적용
      function multiple(a: number) {
        return function (b: number) {
          return a * b;
        };
      }
      
      const CONSTANT = 2;
      const multipleWithConstant = multiple(CONSTANT);
      
      const test1 = multipleWithConstant(1); // 2
      const test2 = multipleWithConstant(2); // 4
      const test3 = multipleWithConstant(3); // 6
      const test4 = multipleWithConstant(4); // 8
      const test5 = multipleWithConstant(5); // 10
  • 클로저와의 관계
    • JS에서는 커링이 내부적으로 구현되어있지 않음 ⇒ 클로저를 활용해서 커링 활용 가능
      function multiple(a: number) {
        return function (b: number) {
          return a * b;
        };
      }
      function outer(a) {
        const base = a;
      
        return function inner(b) {
          return base + b;
        };
      }

caching

  • memoized function 순수함수
    function memoized(fn) {
      const cache = [];
      return function (k) {
        return cache[k] ?? (cache[k] = fn(k));
      };
    }
    
    // 함수 정의를 밖에서 하면 호출할 수 있으므로 밖에서 정의하지 않는다
    // function facto(k) {
    //   return k;
    // }
    const memoizedFactorial = memoized(function (k) {
      if (k === 1) return 1;
      return k * memoizedFactorial(k - 1); // 클로저를 사용하기 -> 캐싱된 재귀
    });

객체 생성과 this

생성자 함수

  • new로 호출
  • constructor()
    • heap에 instnace

팩토리 함수

  • heap에 객체

메서드 분리 할당 시 this 소실

globalThis.name = 'Global Name';

const obj = {
  name: 'Obj Name',
  printName() {
    console.log(this.name);
  },
};

const printName = obj.printName;  // <f.o> address
// obj = null; 함수 주소만 들어가고 obj와 연결이 되지 않음
printName();
  • printNameobj.printName을 가리킴
  • this.name을 찾기 위해 상위 스코프로 이동
    • obj = {} 는 스코프가 아니라 객체 리터럴{}임
    • this ≠ obj
  • globalThis.name인 Global Name출력
  • obj.printName()
    • 바인딩이기 때문에 obj를 가리킴
    • this는 동적할당 → 호출할 때 생성
    • this === obj
const dog = {
  name: 'Maxx',
  showMyName() {
    console.log(`My name is ${this.name}.`);
  },
  whatsYourName() {
    setTimeout(this.showMyName, 1000);
  }
};

dog.whatsYourName();

비동기 환경(setTimeout)에서의 this 소실

setTimeout

  • host api
  • 브라우저의 실행환경과 다름
  • 타이머 객체를 return함
    • 타이머 객체 속에서 실행됨
    • 태스크큐에서 대기 후 실행
    • 콜백만 태스크큐로 가는것 같지만 timer 객체가 통으로 이동함
  • this.showMyName의 object에 대한 주소만 전달
    • this를 알 수 없음 → timer를 가르킴

고치기 위해서는

  • bound Function Object를 만들어서 bound의 정보를 넘기자!!
    • ~~this.~~showMyName.bind(this)
  • [es6 이전] 클로저를 사용함
    var self = this;
    setTimeout(function () {
    	self.showMyName();
    }, 1000);
  • [es6] 화살표 함수
    setTimeout(() => {
    	this.showMyName();
    }, 1000);

고차함수

Arity 불일치 문제

const arr = ['1', '2', '3'];
const result = arr.map(parseInt); 

console.log(result); // [1, NaN, NaN]
  • map이 넘겨주는 인자와 parseInt가 받는 인자의 개수가 다름
  • map이 콜백 함수에게 넘겨주는 것: element, index, array
  • parseInt가 받아서 쓰는 것: string, radix
반복 횟수map이 넘긴 값 (값, 인덱스)parseInt의 해석 (string, radix)결과
1('1', 0)10진법(10진법)으로 변환1
2('2', 1)21진법으로 변환NaN
3('3', 2)32진법으로 변환NaN

단항 함수

  • unary: 매개변수(arity)가 1개인 함수
  • binary: 매개변수가 2개인 함수
const unary = fn => arg => fn(arg)
arr.map(unary(parseInt));

Promise 안티패턴

  • fetch는 Promise를 반환하기 때문에 굳이 new Promise로 감싸줄 필요가 없음
    const myFetch = () =>
      new Promise((resolve, reject) => // 잘못된 코드!!!!
        fetch('https://jsonplaceholder.typicode.com/users/1')
          .then(res => res.json())
          .then(resolve)
          .catch(reject)
      );
    const myFetch = async (url) =>
        const res = await fetch(url);
        return res.json();
        // return await res.json(); 
        // 잘못된 코드!! 어차피 async는 Promise를 반환! 2번 await을 할뿐...
    );
    
    const myFetch2 = async(url) =>
    	fetch(url).then(res => res.json());
  • 결과 반환
    • call stack에 otherFunction이 먼저 들어감…. 순서가 안맞음…..

      let result;
      promiseFn().then(res =>
        result = res; // 순서 늦음!!!
      );
      otherFunction(result);
      let result;
      promiseFn().then(otherFunction);
  • Promise 오류 처리
    // 단, next는 비동기, getRow는 동기 함수!
    // https://npmtrends.com/mysql-vs-mysql2 
    
    const getAllUsers = (sql) =>
      new Promise((resolve, reject) =>
        query.execute(sql, (err, rs) => {
          if (err) reject(err);
      
          const results = [];
          while (rs.next())
            results.push(rs.getRow());
    
          resolve(results);
        });
      );
    execute(sqlStr, cb) {
       conn.query(sqlStr)
        .then(res => cb(null, res))
        .catch(err => cb(err))
    }
    
    const getAllUsers = (sql) =>
      new Promise((resolve, reject) =>
        query.execute(sql, (err, rs) => {
          if (err) reject(err);
      
          const results = [];
          (async function() {
            do {
              const row = await rs.next(); // promise
              if (!row) break;
              results.push(row);
            } while(true);
           )();
    
        });
      );
    
  • 아무것도 하지 않는 핸들러 : 다음에서 불필요한 코드는?
    try {
      randTime(1)
        .then(res => res)
        .then(res => {
          console.log('res>>', res);
          throw new Error('XXX');
        })
        .catch(err => {
          console.log('error!!!', err);
          throw err; // 어디로?!
        });
    } catch (Err) {
      console.error('@@@@@@@Err>>', Err);
    }
      randTime(1)
        .then(res => res)
        .then(res => {
          console.log('res>>', res);
          throw new Error('XXX');
        })
        .catch(err => {
          console.log('error!!!', err);
          throw err;
        }).catch (Err => {
          console.error('@@@@@@@Err>>', Err);
        });

비동기 iterator

function iter(vals) {
  let i = -1;
  return {
    async next() {
      i += 1;
      // await 사용하지 않으면 undefined됨
      return { value: await randTime(vals[i]), done: i >= 3 };
    },
  };
}

(async function () {
	const it = iter([1, 2, 3]);
	console.time('iter');
	console.log('1=', await it.next());
	console.log('2=', await it.next());
	console.log('3=', await it.next());
	console.log('4=', await it.next());
	console.timeEnd('iter');
})();

비동기 generator

function* pAfterTime(sec) {
  return yield afterTime(sec);
}

async function* asyncAfterTime(sec) {
  return await afterTime(sec);
}

const pat = pAfterTime(1);
console.log('promise>>', pat.next());

const aat = asyncAfterTime(1);
console.log('async>>', await aat.next());

for-await-of

for await (const fao of arr.values()) {
  console.log('fao=', fao);
}

회고

원래 개발할 때 AI에 많이 의존하게 됐는데,
기왕 공부하기로 한거 과제할때 AI 사용하지말고 해보자는 마음으로 풀었다. 개념도 어려웠지만, 실제로 연습 문제를 풀 때는 더 어려웠다..

아직 많이 부족한게 느껴져서 현타가 오긴 했지만,
그만큼 더 열심히 반복해서 풀어보고 술술 풀 수 있을 때까지 연습해야겠다고 느꼈다.

profile
또이의 개발새발 개발일기

0개의 댓글