본문 바로가기
전공 수업/웹 서버 프로그래밍(Node.js)

[4주 차] - 변수 키워드 const, let, var 주요 정리, Node.js를 위한 JavaScript 기본 문법 (2)

by TwoJun 2023. 3. 29.
728x90
반응형

    과목명 : 웹 서버 프로그래밍(Web Server-side programming with Node.js)

수업일자 : 2023년 03월 23일 (목)

Node.js & Express.js

 

 

 

 

 

1. 자바스크립트의 변수 키워드 : const, let, var

1-1. 개요

- ES5 이전 var 키워드가 가지는 불완전한 요소(var가 갖는 변수 스코프, 중복 선언, 호이스팅의 위험성)을 해결하기 위해 ES2015(ES6) 이후 const, let 키워드가 추가되었습니다.

 

<const, let, var에 대해 간단히 정리한 표>

변수 키워드 스코프 중복 선언 여부 호이스팅
const Block scope 불가능 호이스팅 가능, TDZ에 존재하기 때문에 선언 이전 변수에 대해 미리 접근할 수 없다.
let
var Function scope 가능 호이스팅 가능, 선언 이전 변수에 대해 Undefined로 초기화되었기 때문에 변수에 접근 가능하다.

* TDZ(Temporal Dead Zone) - 직역하면 "일시적인 사각지대"라는 뜻을 가지는 용어로써, 간단히 말하면 변수가 선언되고 초기화가 이루어지기 전까지의 구간을 의미하며 TDZ에서는 변수 선언 이전이나 초기화 전의 변수를 사용하는 것을 허용하지 않는 범위입니다.

 

 

 

 

1-2. 변수의 스코프(Scope of variable)

- 자바스크립트에서의 스코프는 코드가 변수에 접근할 수 있는 범위입니다.

전역 스코프(Global scope) 지역 스코프(Local scope)
어떤 영역에서든 해당 변수에 접근 가능 한정적인 영역에서만 해당 변수에 접근 가능

- Function scope, Block scope

 

 

- 자바스크립트 스코프는 크게 3가지 타입이 존재합니다.

(1) Function scope (var) 

- Function scope의 경우, 특정 함수 내부에서 선언된 변수는 해당 함수 내부에서만 접근 가능합니다.

 

(2) Block scope (let, const)

- Block scope의 경우, {}와 같은 특정 블록 내부에서 선언된 변수는 해당 블록 내부에서만 접근 가능합니다.

 

(3) Global scope

- Global scope의 경우, 변수가 어느 함수에도 속하지 않은 최상위 함수 외부에 선언된 변수로써 어떤 영역에서든 해당 변수에 접근 가능합니다.

 

 

 

case 1) : var 키워드는 Function scope이다.

- Function scope 외부의 영역에서 변수에 접근하면 Reference Error가 발생합니다.

 

- Function scope 내부 영역에서 접근하면 정상적으로 참조 가능합니다.

 

- var Function scope,  중복 선언이 가능한 변수 키워드이므로 if문의 Block scope에서 var 키워드로 선언한 변수 x의 값이 최종적으로 출력됩니다.

 

-  var는 Function scope를 가지므로 for문의 Block scope는 영향을 받지 않아 그대로 값이 출력되고, for문 이후부터 function scope이기 때문에 i의 최종 값인 2가 출력됩니다.

 

 

 

case 2) : let 키워드는 Block scope이다.

- Block scope 내부에서 변수에 접근할 시 정상적으로 참조 가능합니다.

 

- Block scope 외부에서 변수에 접근할 시 Reference Error가 발생합니다.

 

- Block scope를 벗어난 영역에 let 변수가 있다면 해당 변수가 참조됩니다.

 

- Block scope 내부에서 접근한 변수, Function scope 내부에서 접근한 변수에 따라 다른 값이 출력될 수 있습니다.

 

- let은 Block scope이므로 아래 for문 내부에서만 console.log()가 실행됩니다.

 

- let은 Block scope를 가지므로 for문 내부의 console.log()에 대해선 값이 출력되나, block scope를 벗어나는 순간 이후의 console.log()는 실행되지 않고 Reference Error가 발생하게 됩니다.

 

 

 

 

1-3. 변수의 중복 선언(Redeclaration of variable)

(1) 중복 선언 가능 키워드 : var

(2) 중복 선언 불가 키워드 : const, let

 

case 1) : var 키워드의 경우 변수의 중복 선언이 가능합니다.

 

case 2) : let 키워드의 경우 변수의 중복 선언이 불가능합니다.

 

 

case 3) : const 키워드의 경우 Block scope를 가지며 중복 선언이 불가능합니다.

- 또한 const의 주요 특성으로 한 번 초기화된 값은 다른 값으로 재할당할 수 없습니다.

 

 

case 4) : const 키워드의 경우 선언과 동시에 초기화(Initialization)까지 진행해 주어야 합니다.

- 상수는 할당 연산자를 통해 새로운 값을 추가로 할당할 수 없기 때문입니다.

 

 

 

 

1-4. 변수의 호이스팅(Hoisting of variable)

- 자바스크립트에서 변수의 호이스팅(Hoisting)은, 프로그램이 실행되기 이전에 변수의 선언 및 초기화 부분을 프로그램의 최상단으로 위치시키는 것을 의미하며 자바스크립트 엔진에게 사용할 변수들의 존재에 대해 미리 알려주는 것으로 생각할 수 있습니다.

 

 

case 1) : var 키워드로 선언된 변수는 호이스팅될 때 자동으로 undefined이라는 값으로 초기화됩니다.

 

 

case 2) : const, let 키워드로 선언된 변수는 호이스팅되지만 TDZ 영역에 위치하게 되어 let 키워드 변수에 대해 선언 이전에 접근하는 경우 해당 변수에 참조할 수 없습니다. (var 키워드와 달리 변수에 대한 초기화 자체가 일어나지 않음.)

 

 

 

 

1-5. 변수의 Global scope

- 전역 스코프란, 변수가 어떠한 함수에도 속하지 않은 최상위 함수 외부에 선언된 변수를 의미합니다.

 

 

(1) Window 객체 

- 브라우저에 대한 정보를 담고 있는 브라우저의 최상위 객체로써, 작성한 코드와 라이브러리들이 공유하는 전역 객체

 

(2) var 키워드로 전역 변수를 생성하면 Window 객체에 등록됩니다.

 

(3) let 키워드로 전역 변수를 생성하면 Window 객체에 등록되지 않습니다.

var 키워드로 선언한 전역변수 aVar만 최상위 객체 Window에 등록된 것을 확인할 수 있다.

 

 

 

 

1-6. 상수 키워드(const)의 주요 특성

(1) const 타입을 갖는 상수 타입의 객체가 존재할 때, 객체의 속성을 변경할 수 있습니다.

- 단, 새로운 객체의 속성을 할당하는 것은 불가능합니다.

 

 

- 위에서 언급된 것처럼, 기존에 생성된 const 객체에 새로운 객체를 할당해 주는 것이 아닌 속성 자체를 변경해 주는 것은 가능합니다.

 

 

(2) 만약, 객체의 속성까지도 수정되는 것을 제한하고 싶다면(객체의 불변성 유지) Object.freeze() 메소드를 사용해야 합니다.

 

 

 

 

 

 

2. 템플릿 리터럴(Template Literal) 또는 템플릿 문자열

(1) ES6 이전에는, 변수명과 문자열을 함께 표현할 때 가독성이 좋지 않았습니다.

 

(2) 이 문제를 극복하고자 ES6 이후부터는 자바스크립트에서 템플릿 문자열을 도입하여, `(Backtick), $(Dollar sign) 기호를 사용해 문자열과 변수를 혼합하여 표현할 때 가독성이 향상되었습니다.

 

(3) 변수는 $(Dollar sign)으로 감싸줄 수 있고, 전체적인 리터럴은 `(Backtick) 기호 내부에서 표현합니다.

 

 

 

 

 

 

3. 객체 리터럴 표현 방식

(1) 자바스크립트에서 객체 리터럴은 객체를 표현할 수 있는 가장 일반적이고 간단한 방법입니다. 컨텐츠를 그대로 대입할 수 있는 방법입니다.

 

 

 

3-1. ES5 당시의 객체 표현 방법 예제

 

 

 

3-2. ES6부터 훨씬 간결한 문법으로 객체 리터럴을 표현할 수 있다.

(1) 위와 동일한 코드이나, 코드가 이전보다 조금 더 간결해진 것을 확인할 수 있습니다.

 

(2) 객체의 메소드에 function 키워드를 생략할 수 있습니다.

 

(3) 특정 함수를 객체로 불러올 때 속성명을 쓰지 않고 함수 이름만 사용할 수 있습니다.

 

(4) 동적 속성명을 객체 속성명으로 사용할 수 있습니다.

 

 

 

 

 

 

4. 화살표 함수(Arrow Function)

4-1. 정의

- 화살표 함수(Arrow function)의 경우 function 키워드를 사용해 일반적으로 함수를 정의하는 방식에서 "=>" 기호를 이용하여 좀 더 간결하게 표시된 함수를 말하며, 자바스크립트에서 함수(Function)는 객체로 간주되기 때문에, 이에 따라 변수에 함수를 할당할 수 있습니다.

 

 

(1) 일반적인 자바스크립트의 함수

 

 

 

 

4-2. 화살표 함수(Arrow function) 표현 방법 - 기본적으로 익명함수(Anonymous function)의 형태로 작성

- 일반적인 함수 표현 방법

function numberAdd(x, y) { return x + y; }

 

- 위의 numberAdd() 함수를 화살표 함수로 표현하기

const numberAdd = (x, y) => { return x + y; }

 

(1) 전달인자가 1개인 경우 소괄호를 생략할 수 있습니다

- 단 전달인자가 2개 이상인 경우 소괄호는 생략할 수 없습니다.

const numberAdd2 = x => { return x + y; }

 

 

(2) 함수에 return문만 존재하는 경우, return을 생략할 수 있습니다. (소괄호까지 생략 가능)

- 함수의 구현부 코드가  한 줄이라면, 암묵적으로 처리된 값이 return됩니다.

- return이 생략된 함수의 몸체를 소괄호로 감싸줄 수 있습니다.

const numberAdd = (x, y) => x + y; 
const numberAdd = (x, y) => (x + y);

 

 

(3) 객체를 반환하는 경우 반드시 소괄호를 사용합니다.

const objectReturn = () => ({ a: 1};)

 

 

 

 

4-3. 화살표 함수 사용 전의 예시 

- 화살표 함수가 기존의 function 키워드를 사용한 함수 표현 방법을 대체하는 것은 아닙니다.

 

(1) logFriends() 메소드의 this 값에 주목해 보면, forEach() 함수에서 사용된 this와 logFriends() 메소드의 this는 전혀 다른 의미입니다.

 

(2) that이라는 중간에 변수를 하나 선언해 주어 해당 변수를 통해 logFriends의 this를 전달하고 있습니다.

 

 

 

 

4-4. 화살표 함수를 사용한 후의 예시

(1) forEach() 함수의 인자로 화살표 함수가 사용된 부분에 주목해 보면, forEach() 내부 화살표 함수의 this와 logFriends의 this가 같은 의미가 됩니다.

 

(2) 화살표 함수는 자신을 포함하는 함수의 this를 물려받으며 물려받고 싶지 않다면 일반적인 함수 표현 방법을 사용합니다.

 

 

 

 

 

 

5. 구조분해 할당(Destructuring Assignment) 또는 비구조화 할당

5-1. 정의

- 구조분해 할당(Destructuring assignment) 또는 비구조화 할당은 배열이나 객체의 속성을 해체하여 해당 값을 개별적인 변수에 담을 수 있도록 한 자바스크립트 표현식입니다.

 

 

 

 

5-2. 구조분해 할당을 하지 않은 candyMachine 객체

 

 

 

 

5-3. 구조분해 할당으로 표현한 candyMachine 객체

(1) const { 변수 } = 객체명;

- 객체의 속성을 해체하여 객체 내부의 속성을 변수 이름으로 사용할 수 있습니다.

- count처럼, 속성 내부의 또 다른 속성들도 변수 이름으로 사용할 수 있습니다.

 

 

 

 

5-4. 배열도 구조분해 할당이 가능 - 구조분해 할당 이전

 

 

 

 

5-5. 배열도 구조분해 할당이 가능 - 구조분해 할당 이후

- const [ 변수 ] = 배열명;

- 각 배열 인덱스와 변수가 순차적으로 대응되므로 할당하지 않을 속성에 대해선 배열 변수에 , ... , 공백 처리가 필요합니다.

 

 

 

 

 

 

6. Callback hell

6-1. 콜백 함수(Callback function)

- 콜백 함수는 다른 함수에 매개변수로 넘겨준 함수를 말하며 함수를 명시적으로 호출하는 것이 아닌, 특정 이벤트 발생 시 시스템에 의해 해당 콜백 함수가 호출됩니다.

 

- 이러한 콜백 함수는 특정 함수의 전달인자(Argument)로 넘겨지는 또 다른 함수를 의미하며 매개변수로 넘겨 받은 함수는 필요에 따라 즉시 동기적으로(Synchronously) 실행되거나 비동기적(Asynchronously)으로 실행될 수 있습니다.

 

 

 

 

6-2. 정의

syncOrAsyncStep1() 함수에서 데이터를 받아와 매개변수로 전달된 익명함수로 넘겨주고 이후 다음 syncOrAsyncStep2() 함수에서 다른 처리를 한 후 매개변수로 익명함수를 넘겨준다. 이 구조가 반복됨에 따라 코드가 피라미드 구조를 형성하게 된다.

 

- Callback hell은 자바스크립트를 이용한 비동기 프로그래밍 시 발생하는 문제로써, 매개변수로 넘겨지는 콜백 함수가 반복됨에 따라 코드의 들여쓰기 수준이 감당하기 어려워질 정도로 심해지는 현상을 의미합니다.

 

 

 

 

6-3 Callback이 자주 발생되는 상황

- 이벤트 처리, 서버에서 데이터를 받아와 엔드 포인트에 표현되기까지의 인코딩 과정, 사용자 인증 처리, 서버 통신과 같은 비동기 작업을 수행할 때 자주 발생합니다. 이로 인해 코드의 가독성이 떨어지고 이후 유지보수가 어려워지게 됩니다.

 

 

 

 

6-4. Callback 현상의 해결 방법

(1) 자바스크립트의 비동기 처리를 위한 문법 - Promise, Async/Await 

 

 

 

 

 

7. 동기와 비동기(Synchronous & Asynchronous) 방식

7-1. 동기(Synchronous)

- 직렬적인 작업 처리 방식으로 하나의 태스크가 종료될 때까지 기다렸다가 다음 태스크가 실행되는 순차적인 실행 구조를 가지게 됩니다.

 

- 쉽게 말해, 정해진 순서에 맞게 코드가 실행되는 것을 의미합니다.

 

- 태스크 A의 종료 시간과 태스크 B의 시작 시간이 일치합니다.

태스크 A의 종료 시간, 태스크 B의 시작 시간이 일치한다.

 

 

 

 

7-2. 비동기(Asynchronous)

- 병렬적인 작업 처리 방식으로 한 번에 여러 개의 태스크가 동시에 병렬적으로 실행되는 기법입니다.

 

- 쉽게 말해, 코드의 실행 여부를 예측할 수 없는 것을 의미합니다.

 

- 태스크 A의 종료 시간과 태스크 B의 시작 시간이 일치하지 않습니다.

태스크 A의 종료 시간, 태스크 B의 시작 시간이 일치하지 않는다.

 

 

 

 

 

 

8. 프로미스(Promise)

8-1. 프로미스의 정의

- 프로미스는 자바스크립트의 비동기 처리 시 사용되며 관련 내용이 실행은 되었지만 결과를 반환하지 않은 객체를 의미합니다.

 

 

- 생성자 메소드에서 프로미스에서 사용될 콜백 함수를 선언할 수 있고, 콜백 함수의 인자로는 아래와 같이 두 가지가 존재합니다.

- resolve : resolve(단순 반환값 or 작업 성공 시 처리되는 코드에 대한 반환값);

- reject : reject(단순 반환값 or 작업 실패 시 처리되는 코드에 대한 반환값);

프로미스의 처리 흐름 - 출처 : MDN 공식 Reference

 

 

 

 

8-2. 프로미스 객체의 3가지 상태(State)

(1) Pending - 대기 상태

- new Promise()로 새로운 프로미스 객체를 생성하면 프로미스는 Pending 상태가 됩니다.

 

- 생성자로 프로미스 객체를 생성하는 순간, 해당 객체에서 할당된 작업들이 시작됩니다.

new Promise();

 

 

(2) Fulfilled - 이행 상태

- 특정 작업이 성공했을 때, 콜백 함수의 인자로 resolve() 함수가 실행됩니다.

 

- 이행 상태(Fulfilled state)일 때 then() 구문을 이용하여 처리 결과 값을 받을 수 있습니다.

 

- 여기서 then() 함수를 바로 사용할 수 있는 이유는, getData() 함수의 경우 프로미스를 생성하고 프로미스 객체를 반환하기 때문에 프로미스의 수행 결과에 따른 이후 처리 진행을 위해 then(), catch() 구문으로 넘어가게 할 수 있기 때문입니다.

 

 

(3) Rejected - 실패 상태

- new Promise()로 프로미스 객체를 생성하고 전달인자로 resolve, reject를 전달할 수 있었으며 만약 작업이 실패하게 된다면 reject() 함수가 호출됩니다.

 

- 실패 상태(Rejected state)일 때 catch() 구문을 이용하여 처리 결과 값을 받을 수 있고 이후 실패 상태에 따른 예외 처리(Exception handling)를 설정할 수 있습니다.

 

 

 

 

8-3. 프로미스 예제 코드 - Ajax(Asynchronous JavaScript and XML) 통신 API를 이용해 특정 URL에서 상품 정보를 받아오기

function getData() {
  // 새로운 프로미스 객체 생성
  return new Promise((resolve, reject) => {
    $.get('특정 url 주소/products/1', (response) => {
      if (response) {
        resolve(response);
      }
      reject(new Error("Request is failed"));
    });
  });
}

// $.get()의 통신 호출 결과에 따라 'response' 또는 'Error'를 출력한다.
getData().then((data) => {
  console.log(data);
}).catch((err) => {
  console.error(err);
});

 

 

 

 

8-4. 프로미스 예제 코드 (2) 

 

 

 

 

 

 

9. 프로미스 체이닝(Promise chaining)

9-1. 정의와 특징, 예제 코드

- 프로미스 체이닝(Promise chaining)이란, 프로미스가 갖는 또 다른 특징으로 여러 개의 프로미스를 연결하여 사용하는 것을 의미합니다.

 

- 특정 작업이 성공해서 resolve() 함수가 호출되면 then() 로직으로 넘어가게 되고 이때  새로운 프로미스 객체를 반환할 수 있도록 코드를 짤 수 있게 되는데 이러한 성질을 이용해 여러 개의 프로미스를 연결해 사용합니다. 

 

- then 내부에서 반환한 값이 다음 then 구문으로 넘어갑니다.

 

- 만약 반환한 값이 프로미스 객체라면 resolve() 함수가 호출됩니다.

 

- 만약 rejected 상태라면 바로 catch() 구문으로 이동되어 이후의 예외 처리를 진행하게 됩니다.

 

 

 

 

9-2. Callback pattern(3중첩)을 프로미스로 변환하는 예제

(1) 프로미스 적용 이전

function findAndSaveUser(Users) {
  // 첫 번째 callback
  Users.findOne({}, (err, user) => {
    if (err) {
      return console.error(err);
    }
    user.name = 'zero';

    // 두 번째 callback
    user.save((err) => {
      if (err) {
        return console.error(err);
      }
    
      // 세 번째 콜백
      user.findOne({gender: 'm'}, (err, user) => {
        // 생략
      });
    });
  });
}

 

 

(2) 프로미스 적용 후

- 해당 코드에서는 findOne(), save() 메소드가 프로미스를 적용한다고 가정합니다.

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);
    });
}

 

 

 

 

9-3. Promise.resolve(), Promise.reject(), Promise.all(Array_name)

(1) Promise.resolve()

- 바로 이행 상태로 만들어서 resolve() 함수를 호출하는 프로미스입니다.

 

(2) Promise.reject()

- 바로 실패 상태로 만들어서 reject() 함수를 호출하는 프로미스입니다.

 

(3) Promise.all(배열명)

- 여러 개의 프로미스를 배열에 담아 실행하고, 하나의 프로미스라도 reject() 함수가 호출되면 catch 구문으로 이동하게 됩니다.

 

 

 

 

 

 

10. Async / Await

10-1. 정의와 특징

- Async / await의 경우 자바스크립트에서의 비동기 처리를 위해 ES7 이후 추가된 새로운 비동기 처리 방식입니다.

 

- 기존 비동기 처리 방식인 Promise의 단점을 보완하고 코드의 가독성을 향상시킨 문법입니다.

 

- Promise보다 async/await 방법이 더 좋기 때문에 해당 방식만을 사용해야 하는 것은 아니며 주어진 상황에 따라 Promise로 처리하는 것이 더 효율적일 때도 있으므로 상황에 맞는 방법을 선택해야 합니다.

 

- 변수명 = await 프로미스(프로미스를 반환하는 함수)인 경우 프로미스가 resolve() 함수에서 처리된 값이 해당 변수에 저장됩니다.

(await 값인 경우 해당 값이 변수에 저장)

 

 

 

 

10-2. 9-2에서 확인한 Callback 3중첩 패턴 코드에서 Async / await을 사용

async function findAndSaveUser(Users) {
  let user = await Users.findOne({});
  user.name = 'zero';
  user = await user.save();
  user = await Users.findOne({ gender: 'm'});
  // 이후 생략
}

 

 

 

 

10-3. Exception handling의 경우 try-catch 구문을 사용

- 프로미스의 수행 결과에 따라 resolve() 함수로 처리되어야 하는 부분들은 try 구문 내부에 포함시키고 reject() 함수를 호출하여 처리된 부분에 대해선 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);
  }
}

 

 

 

 

10-4. async / await 구문에서 Arrow function 사용

const findAndSaveUser = async(Users) => {
  try {
    user.name = 'zero';
    user = await user.save();
    user = await Users.findOne({ gender: 'm'});
  } catch(error) {
    console.error(error);
  }
};

 

 

 

 

 

 

 

11. for await of

(1) Node 10부터 지원하게 된 문법입니다.

 

 

(2) 문법 :  for await (변수 of 프로미스가 담긴 배열명)

 

- Promise.all을 대체한 문법입니다.

 

- 프로미스에서 resolve() 함수를 통해 처리된 값이 배열에 담기게 됩니다.

 

- await을 사용하기 때문에 async 키워드가 정의된 함수에서 사용해야 합니다.

 

 

 

 

11-1. 예시 코드

 

 

 

 

 

12. Reference

Node.js 교과서(Node.js Textbook) - 저자 : 조현영 

https://www.zerocho.com/book/1

 

ZeroCho Blog

ZeroCho의 Javascript와 Node.js 그리고 Web 이야기

www.zerocho.com

 

 

 

 

- 학부에서 수강했던 전공 수업 내용을 정리하는 포스팅입니다.

- 내용 중에서 오타 또는 잘못된 내용이 있을 시 지적해 주시기 바랍니다.

728x90
반응형

댓글