Node.js 강의 2장 [개정 3판] Node.js 교과서 - 기본부터 프로젝트 실습까지

남경민·2023년 3월 27일

Node.js

목록 보기
2/3

📕 2장. 알아두어야 할 자바스크립트

  • 이벤트 루프
function oneMore(){
	console.log('one more');
}
function run(){
	console.log('run run');
	setTimeout(()=>{
		console.log('wow');
	},0);
	new Promise((resolve)=>{
		resolve('hi');
	})
		.then(console.log);
	oneMore();
}

setTimeout(run,5000);

1) 호출스택 → anonymous
2) 호출스택 → anonymous, setTimeout(run,5000)

3) 호출스택 → anonymous , 백그라운드 → 타이머(run,5초)

4) 백그라운드 → 타이머(run,5초)

  • 5초 센 다음에 run함수를 테스크 큐로 보냄

5) 테스크 큐 → run (이제 호출 스택이 비었으니 이동, 비어있어야 이동가능!)

6) 호출스택 → run, 콘솔 → run run

7) 호출스택 → run, setTimeout(익명,0) , 콘솔 → run run

  • Timeout은 바로 실행X, 무조건 백그라운드 가야함

8) 호출스택 → run, 백그라운드 → 타이머(익명,0), 콘솔 → run run

9) 호출스택 → run, new Promise , resolve(’hi’) , 백그라운드 → 타이머(익명,0), 콘솔 → run run

10) 호출스택 → run , 백그라운드 → 타이머(익명,0) , then console.log(hi)

  • then 만나는 순간 비동기로 가서 백그라운드로 감

11) 호출스택 → run, oneMore, console.log , 백그라운드 → 타이머(익명,0), then console.log(hi) , 콘솔 → run run

12) 백그라운드 → 타이머(익명,0), then console.log(hi) , 콘솔 → run run , one more

  • 백그라운드 안에서는 먼저 끝나는 쪽이 먼저 태스크 큐로 감

13) 태스크 큐 → 익명 , console.log(hi)

  • (중요!) 우선순위 then > 타이머 ⇒ then이 먼저 호출 스택으로 감

14) 호출 스택 → console.log(hi) , 테스크 큐 → 익명

15) 최종 콘솔 : run run , one more, hi , wow

  • 2.1.1 const, let
    • const : 블록밖에서 사용X, 블록: if,while,for,function 등의 중괄호({와} 사이), const 는 다른 값 할당불가(상수)

      const a=3;
      a='5'; //에러
      
      const b={name : 'zerocho'};
      b.name ='nerocho'; //이건 가능
      
      const c; //에러
  • 2.1.2 템플릿 문자열
    var won=1000;
    va result='이 과자는' + won + '원입니다.;
    //이 과자는1000원입니다. (띄어쓰기 안댐 -> 직접 해야함)
    
    const result=`이 과자는 ${won}원입니다`; //최신기능
    
    function a() {}
    a(); //예전 함수호출
    a``; //최신버전 함수호출
  • 2.1.3 객체 리터럴
    • 예전버전

      var sayNode=function(){
      	console.log('Node');
      };
      var es='ES';
      var oldObject={
      	sayJS:function(){
      		console.log('JS');
      	},
      	sayNode:sayNode,
      ];
      oldObject[es+6]='fantastic';
      oldObject.sayNode(); //Node
      oldObject.sayJS(); //JS
      console.log(oldObject.ES6); //fantastic
    • 최신버전

      const newObject={
      	sayJS(){ //:function 삭제(객체의 메서드에 함수를 연결할 때, 콜론과 function 붙이지 않아도댐)
      		console.log('JS');
      	},
      	sayNode, //:sayNode 삭제(속성명과 변수명 동일한 경우 한번만 써도 가능!)
      	[es+6]:'fantastic', //바로 넣음(객체의 속성명을 동적으로 변경 가능, 객체 리터럴 안에 동적 속성 선언 가능!)
      };
      newObject.sayNode(); //Node
      newObject.sayJS(); //JS
      console.log(newObject.ES6); //fantastic
  • 2.1.4 화살표 함수
    • function 과 ⇒ 의 차이점

      • funtion은 this가 각자 다른 함수 스코프의 this 가짐, 전체 부모 relationship1의 this를 가져오려면 that으로 변수를 사용해야함
      • 화살표는 this가 따로 없기 때문에 relationship2의 this 그대로 가져옴
    • 결론 : 기본적으로 화살표 함수를 쓰되, this를 사용해야 하는 경우에는 화살표 함수와 함수 선언문(function) 둘 중 하나 고르기!!

      var relationship1 = {
        name: 'zero',
        friends: ['nero', 'hero', 'xero'],
        logFriends: function () {
          var that = this; // relationship1을 가리키는 this를 that에 저장
          this.friends.forEach(function (friend) {
            console.log(that.name, friend); //this로 변경할 경우, undefined nero, undefined hero, undefined xero
          });
        },
      };
      relationship1.logFriends(); //zero nero, zero hero, zero xero
      
      const relationship2 = {
        name: 'zero',
        friends: ['nero', 'hero', 'xero'],
        logFriends() {
          this.friends.forEach(friend => {
            console.log(this.name, friend);
          });
        },
      };
      relationship2.logFriends(); //zero nero, zero hero, zero xero
  • 2.1.5 구조 분해 할당
    • 객체와 배열로부터 속성이나 요소 쉽게 꺼낼 수 있음

    • 단, this는 함수를 호출할 때 어떻게 호출되었냐에 따라 결정되기 때문에 this가 있으면 구조 분해 할당을 안하는 것이 좋음!!, 구조분해 할당하면 함수의 this가 달라짐
      - 달라진 this를 원래대로 바꿔주려면 bind함수를 따로 사용해야함

      var candyMachine = {
        status: {
          name: 'node',
          count: 5,
        },
        getCandy: function () {
          this.status.count--;
          return this.status.count;
        },
      };
      var getCandy = candyMachine.getCandy;
      var count = candyMachine.status.count;
      
      //위의 코드 이렇게 변경 가능
      const candyMachine = {
        status: {
          name: 'node',
          count: 5,
        },
        getCandy() {
          this.status.count--;
          return this.status.count;
        },
      };
      const { getCandy, status: { count } } = candyMachine;
      var array = [‘nodejs’, {}, 10, true];
      var node = array[0];
      var obj = array[1];
      var bool = array[3];
      
      //구조 분해 할당(배열)
      const array = [‘nodejs’, {}, 10, true];
      const [node, obj, , bool] = array;
  • 2.1.6 클래스
    • 다른 언어처럼 클래스 기반 동작X, 프로토타입 기반으로 동작하지만 보기 좋게 클래스로 바꾼것

    • 프로토타입으로만 작성한 것(예전 버전)

      var Human = function(type) {
        this.type = type || 'human';
      };
      
      Human.isHuman = function(human) {
        return human instanceof Human;
      }
      
      Human.prototype.breathe = function() {
        alert('h-a-a-a-m');
      };
      
      var Zero = function(type, firstName, lastName) {
        Human.apply(this, arguments);
        this.firstName = firstName;
        this.lastName = lastName;
      };
      
      Zero.prototype = Object.create(Human.prototype);
      Zero.prototype.constructor = Zero; // 상속하는 부분
      Zero.prototype.sayName = function() {
        alert(this.firstName + ' ' + this.lastName);
      };
      var oldZero = new Zero('human', 'Zero', 'Cho');
      Human.isHuman(oldZero); // true
    • 클래스로 바꾼것(최신 버전) → 프로토타입 기반으로 동작!!!

      class Human {
        constructor(type = 'human') { //생성자 함수
          this.type = type;
        }
      
        static isHuman(human) { //Human.isHuman같은 클래스 함수 static 전환
          return human instanceof Human;
        }
      
        breathe() {
          alert('h-a-a-a-m');
        }
      }
      
      class Zero extends Human { //extends: 상속
        constructor(type, firstName, lastName) {
          super(type); //super : Human
          this.firstName = firstName;
          this.lastName = lastName;
        }
      
        sayName() {
          super.breathe();
          alert(`${this.firstName} ${this.lastName}`);
        }
      }
      
      const newZero = new Zero('human', 'Zero', 'Cho');
      Human.isHuman(newZero); // true
  • 2.1.7 Promise
    • 자바스크립트와 노드는 주로 비동기를 접함 → 이벤트 리스너 사용에서 콜백말고 프로미스기반으로 재구성, 중요!!

    • 실행은 바로 하되 결과값은 나중에 받는 객체, 결과값은 실행이 완료된 후 then이나 catch메서드를 통해 받음!
      - 밑의 코드에서 new Promise는 바로 실행되지만, 결과값은 then을 붙였을 때 받음

      const condition = true; // true이면 resolve, false이면 reject
      const promise = new Promise((resolve, reject) => {  //1. 프로미스 생성
        if (condition) {
          resolve('성공');
        } else {
          reject('실패');
        }
      });
      // 다른 코드가 들어갈 수 있음
      promise
        .then((message) => {
          console.log(message); // 성공(resolve)한 경우 실행
        })
        .catch((error) => {
          console.error(error); // 실패(reject)한 경우 실행
        })
        .finally(() => { // 끝나고 무조건 실행
          console.log('무조건');
      });
      function findAndSaveUser(Users) {
        Users.findOne({}, (err, user) => { // 첫 번째 콜백
          if (err) {
            return console.error(err);
          }
          user.name = 'zero';
          user.save((err) => { // 두 번째 콜백
            if (err) {
              return console.error(err);
            }
            Users.findOne({ gender: 'm' }, (err, user) => { // 세 번째 콜백
              // 생략
            });
          });
        });
      }
      
      //위의 콜백 코드를 프로미스로 변경!
      function findAndSaveUser(Users) {
        Users.findOne({})
          .then((user) => {
            user.name = 'zero';
            return user.save();
          })
          .then((user) => {
            return Users.findOne({ gender: 'm' });
          })
          .then((user) => {
            // 생략
          })
          .catch(err => {
            console.error(err);
          });
      }
  • 2.1.8 async/await
    • 프로미스를 사용한 코드를 한 번 더 깔끔하게 줄여줌
function findAndSaveUser(Users) {
  Users.findOne({})
    .then((user) => {
      user.name = 'zero';
      return user.save();
    })
    .then((user) => {
      return Users.findOne({ gender: 'm' });
    })
    .then((user) => {
      // 생략
    })
    .catch(err => {
      console.error(err);
    });
}

//async/await로 변경한것!
async function findAndSaveUser(Users) {
  let user = await Users.findOne({}); //프로미스 앞에 await 붙임, 함수는 해당 프로미스가 resolve될 때까지 기다린 뒤 다음 로직으로 넘어감
  user.name = 'zero';
  user = await user.save();
  user = await Users.findOne({ gender: 'm' }); 
  // 생략
}

//try, catch로 에러 처리
async function findAndSaveUser(Users) {
  try {
    let user = await Users.findOne({});
    user.name = 'zero';
    user = await user.save();
    user = await Users.findOne({ gender: 'm' });
    // 생략
  } catch (error) {
    console.error(error);
  }
}

//화살표 사용!!
const findAndSaveUser = async (Users) => {
  try {
    let user = await Users.findOne({});
    user.name = 'zero';
    user = await user.save();
    user = await Users.findOne({ gender: 'm' });
    // 생략
  } catch (error) {
    console.error(error);
  }
};
  • 2.1.9 Map/Set
    • Map : 객체 리터럴과 비슷, 속성들 간의 순서를 보장하고 반복문 사용 가능, 속성명으로 문자열이 아닌 값도 사용 가능하고, size 메서드를 통해 속성의 수를 알 수 있음
    • Set: 배열 리터럴과 비슷, 중복 허용X
const m = new Map(); //map 생성

m.set('a', 'b'); // set(키, 값)으로 Map에 속성 추가
m.set(3, 'c'); // 문자열이 아닌 값을 키로 사용 가능합니다
const d = {};
m.set(d, 'e'); // 객체도 됩니다

m.get(d); // get(키)로 속성값 조회
console.log(m.get(d)); // e

m.size; // size로 속성 개수 조회
console.log(m.size) // 3

for (const [k, v] of m) { // 반복문에 바로 넣어 사용 가능합니다
  console.log(k, v); // 'a', 'b', 3, 'c', {}, 'e'
} // 속성 간의 순서도 보장됩니다

m.forEach((v, k) => { // forEach도 사용 가능합니다
  console.log(k, v); // 결과는 위와 동일
});

m.has(d); // has(키)로 속성 존재 여부를 확인합니다
console.log(m.has(d)); // true

m.delete(d); // delete(키)로 속성을 삭제합니다
m.clear(); // clear()로 전부 제거합니다
console.log(m.size); // 0
const s = new Set();
s.add(false); // add(요소)로 Set에 추가합니다
s.add(1);
s.add('1');
s.add(1); // 중복이므로 무시됩니다
s.add(2);

console.log(s.size); // 중복이 제거되어 4

s.has(1); // has(요소)로 요소 존재 여부를 확인합니다
console.log(s.has(1)); // true

for (const a of s) {
  console.log(a); // false 1 '1' 2
}

s.forEach((a) => {
  console.log(a); // false 1 '1' 2
})

s.delete(2); // delete(요소)로 요소를 제거합니다
s.clear(); // clear()로 전부 제거합니다
  • 2.1.10 널 병합/옵셔널 체이닝
    • 널 병합(??) : 주로 || 연산자 대용으로 사용되며, falsy 값(0,’’,false,NaN,null,undefined)중 null과 undefined만 따로 구분

      const a = 0;
      const b = a || 3; // || 연산자는 falsy 값이면 뒤로 넘어감
      console.log(b); // 3
      
      const c = 0;
      const d = c ?? 3; // ?? 연산자는 null과 undefined일 때만 뒤로 넘어감
      console.log(d); // 0;
      
      const e = null;
      const f = e ?? 3;
      console.log(f); // 3;
      
      const g = undefined;
      const h = g ?? 3;
      console.log(h); // 3;
    • 옵셔널 체이닝: null이나 undefine의 속성을 조회하는 경우 에러가 발생하는 것을 막음(?.)

      const a = {}
      a.b; // a가 객체이므로 문제없음
      
      const c = null;
      try {
        c.d;
      } catch (e) {
        console.error(e); // TypeError: Cannot read properties of null (reading 'd')
      //위의 에러 주의!!! -> reading 'd'는 d가 null이 아니라 앞에 c 가 null임!!!!
      }
      c?.d; // 문제없음
      
      try {
        c.f();
      } catch (e) {
        console.error(e); // TypeError: Cannot read properties of null (reading 'f')
      }
      c?.f(); // 문제없음
      
      try {
        c[0];
      } catch (e) {
        console.error(e); // TypeError: Cannot read properties of null (reading '0')
      }
      c?.[0]; // 문제없음
profile
백엔드 개발을 좋아하고 공부하고 있습니다. 코드 작성 뿐만 아니라 쿼리 성능 고려, 클린 코드, 테스트 케이스 작성에 주력해 모든 에러 상황을 대비하는 개발자로 성장하고 싶습니다.

0개의 댓글