[P2_S2] 모던 자바스크립트

보리·2024년 3월 23일
0

codeit-sprint

목록 보기
10/22

(1) 모던 자바스크립트 이해하기

✨ECMAScript

📘공식 문서

ECMA-262 - Ecma International

📘진행 현황

ECMA스크립트

📘브라우저 지원 현황

📘버전의 정식 표기

  • ES6부터는 연호를 사용해서 ES2015, ES2016이라고도 부른다.
  • 개발자들 사이에서는 짧고 빠르게 소통하기 위해서 ES6, ES7이라는 용어를 사용하지만, 실제로 ECMA International에서 버전을 발표할 때 표기하는 정식 명칭은 연호를 사용해서 ECMAScript 2015라고 표기한다.

📘JavaScript vs ECMAScript

JavaScript

  • 프로그래밍 언어
  • JavaScript는 ECMAScript를 준수해서 만들어낸 '결과물'
  • ECMAScript를 기반으로 하지만 ECMAScript에 정의된 내용뿐만 아니라, 다른 부가적인 기능도 있다.

ECMAScript

  • 프로그래밍 언어의 표준
  • ECMAScript는 JavaScript가 갖추어야 할 내용을 정리해둔 '설명서'
  • JavaScript 뿐만 아니라 모든 스크립트 언어(scripting languages)가 지켜야 하는 표준

(2) 자바스크립트의 동작원리

📘자바스크립트의 데이터 타입

  1. number
  2. string
  3. boolean
  4. undefined
  5. null
  6. object
  7. symbol
  8. bigint

📘자바스크립트 데이터 타입 특징

✔️Symbol

심볼은 코드 내에서 유일한 값을 가진 변수 이름을 만들 때 사용한다.

const user = Symbol();
const user = Symbol('this is a user');

괄호 안에 심볼에 대한 설명을 붙일 수도 있다. 이렇게 Symbol 값을 담게 된 user라는 이름의 변수는 다른 어떤 값과 비교해도 true가 될 수 없는 고유한 변수가 된다.

const user = Symbol('this is a user');

user === 'this is user'; // false
user === 'user'; // false
user === 'Symbol'; // false
user === true; // false
user === false; // false
user === 123; // false
user === 0; // false
user === null; // false
user === undefined; // false
...

똑같은 설명을 붙인 심볼을 만들더라도 두 값을 비교하면 false가 반환된다.

const symbolA = Symbol('this is Symbol');
const symbolB = Symbol('this is Symbol');

console.log(symbolA === symbolB); // false

✔️BigInt

  • 자바스크립트에서 아주 큰 정수(Integer)를 표현하기 위해 등장한 데이터 타입
    • 자바스크립트에서 안전한 최대 정수는 2**53 - 1
    • 안전한 최소 정수는 -(2**53 - 1)
    • 안전한 정수 표현이라는 의미는 자바스크립트에서 이 숫자 범위를 초과하는 정숫값을 사용하려고 하면 연산에 미세한 오류가 발생한다는 뜻.
console.log(9007199254740991 + 1 === 9007199254740991 + 2); // true
console.log(9007199254740991 + 2); /// 9007199254740992
console.log(9007199254740993); /// 9007199254740992

이 숫자 범위는 JavaScript가 IEEE 754에 기술된 배정밀도 부동소수점 형식 숫자체계를 사용하기 때문이다. (자바스크립트의 숫자형(number type) 값에는 9000조 정도의 정수 표현의 한계가 존재한다.)

  • 암호 관련 작업이나 계산기 관련 작업을 할 때, 아주 큰 숫자를 다루거나 혹은 굉장히 정확한 연산이 필요한 상황에서 BigInt라는 데이터 타입의 값을 사용하면 된다.
  • BigInt 타입의 값은 일반 정수 마지막에 알파벳 n을 붙이거나 BigInt라는 함수를 사용하면 된다.
console.log(9007199254740993n); // 9007199254740993n
console.log(BigInt('9007199254740993')); // 9007199254740993n
  • 이렇게 BigInt 타입을 사용하면 2**53 - 1 보다 큰 정숫값도 안전하게 표현할 수가 있다.
  • 참고로 BigInt의 생성자에 문자열로 값을 넘겨 준 이유는 큰 정수를 그대로 사용하면 안전한 최대 정수로 처리하기 때문.
  • BigInt 타입은 말 그대로 큰 정수를 표현하기 위한 데이터 타입이기 때문에 소수 표현에는 사용할 수가 없다.
1.5n; // SyntaxError

그래서 소수 형태의 결과가 리턴되는 연산은 소수점 아랫부분은 버려지고 정수 형태로 리턴된다.

10n / 6n; // 1n
5n / 2n; // 2n

BigInt 타입끼리만 연산할 수 있고, 서로 다른 타입끼리의 연산은 명시적으로 타입 변환을 해야 한다.


3n * 2; // TypeError
3n * 2n; // 6n
Number(3n) * 2; // 6

✔️typeof 연산자

typeof 'Codeit'; // string
typeof Symbol(); // symbol
typeof {}; // object
typeof []; // object
typeof true; // boolean
typeof(false); // boolean
typeof(123); // number
typeof(NaN); // number
typeof(456n); // bigint
typeof(undefined); // undefined

⚠️ typeof 연산자의 결과가 모든 타입과 1:1로 매칭되지 않는다!

❓null이 object라고?

typeof null을 하면 문자열 null이 리턴되는 게 아니라 문자열 object가 리턴

typeof null; // object

→ 자바스크립트가 처음 구현될 때의 특별한 문법 설계 때문이다. 나중에 ECMAScript에서 수정이 제안되었었지만, 이미 개발된 많은 프로젝트에 버그가 생기는 우려로 인해 반영되지 않고 있다.

→ null은 원래 null이라는 자체적인 데이터 유형을 가지고 있어야 하지만, 초기버전의 자바스크립트에서는 object로 분류되었다.

→ JavaScript를 처음 구현할 때, JavaScript 값은 타입 태그와 값으로 표시되었다. 객체의 타입 태그는 0이었다. null은 Null pointer(대부분의 플랫폼에서 0x00)로 표시되었다. 그 결과 null은 타입 태그로 0을 가지며, 따라서 typeof는 object를 반환한다.

typeof - JavaScript | MDN

❓function?

함수에 typeof 연산자를 사용하면 function이라는 값을 리턴한다.

function sayHi() {
  console.log('Hi!?');
}

typeof sayHi; // function

📘Truthy 값과 Falsy 값

  • if, for, while 등 불린 타입의 값이 요구되는 맥락에서는 조건식이나 불린 타입의 값 뿐만 아니라 다른 타입의 값도 불린 값처럼 평가될 수 있다.
  • false 처럼 평가되는 값을 falsy 값, true 처럼 평가되는 값을 truthy값이라고 부른다.
  • falsy값에는 false, null, undefined, 0, NaN, ''(빈 문자열)이 있고, falsy값을 제외한 값들은 모두 truthy값이 된다.
// falsy
Boolean(false);
Boolean(null);
Boolean(undefined);
Boolean(0);
Boolean(NaN);
Boolean('');

// truthy
Boolean(true);
Boolean('codeit');
Boolean(123);
Boolean(-123);
Boolean({});
Boolean([]);

✨AND & OR

📘AND 와 OR 연산자의 연산 우선순위

function checkAnswer(value) {
  if (value < 10 && value > 0 && value !== 3) {
    return '정답입니다!';
  }

  return '틀렸습니다!';
}

console.log(checkAnswer(4)); // 정답입니다!

⚠️위에 있는 코드처럼 AND 연산자나 OR 연산자 중 하나만 계속해서 사용할 때는 문제 없지만, 만약 AND 연산자와 OR 연산자를 섞어서 사용할 때는 연산의 우선순위가 존재한다.
AND 연산자의 우선순위가 더 높다.

console.log(true || false && false); // true
console.log((true || false) && false); // false

console.log('Codeit' || NaN && false); // Codeit
console.log(('Codeit' || NaN) && false); // false

console.log(true || (false && false)); // true
console.log((true || false) && false); // false

console.log('Codeit' || (NaN && false)); // Codeit
console.log(('Codeit' || NaN) && false); // false

❓null 병합 연산자 ??

물음표 두 개(??)를 사용해서 null 혹은 undefined 값을 가려내는 연산자

const example1 = null ?? 'I';
const example2 = undefined ?? 'love';
const example3 = 'Codeit' ?? 'JavaScript';

console.log(example1, example2, example3); // ?

연산자 왼편의 값이 null 이나 undefined라면 연산자 오른편의 값이 리턴

연산자 왼편의 값이 null 이나 undefined가 아니라면 연산자 왼편의 값이 리턴

→ I love Codeit 출력

⚠️OR 연산자(||)와 비교

const title1 = null || 'codeit';
const title2 = null ?? 'codeit';

console.log(title1); // codeit
console.log(title2); // codeit

null 병합 연산자(??)는 왼편의 값이 null이나 undefined인지 확인하고 OR 연산자(||)는 왼편의 값이 falsy인지를 확인하기 때문에 null이나 undefined가 아닌 falsy 값을 활용할 때 결과가 서로 다르다.

const title1 = false || 'codeit';
const title2 = false ?? 'codeit';

console.log(title1); // codeit
console.log(title2); // false

const width1 = 0 || 150;
const width2 = 0 ?? 150;

console.log(width1); // 150
console.log(width2); // 0

✨자바스크립트의 다양한 변수 선언 방식

자바스크립트가 처음 등장할 때부터 사용되던 var

var의 부족함을 채우기 위해 ES2015에서 새롭게 등장한 let과 const가 있다.

var 변수 특징

  1. 변수 이름 중복선언 가능,
  2. 변수 선언 전에 사용 가능(호이스팅),
  3. 함수 스코프

let과 const 변수 특징

  1. 변수 이름 중복선언 불가 (SyntaxError 발생)
  2. 변수 선언 전에 사용 불가 (ReferenceError 발생)
  3. 블록 스코프

const 키워드는 let 키워드와 다르게 값을 재할당할 수 없다는 특징도 있다.

📘함수 스코프(function scope)와 블록 스코프(block scope)

var 키워드로 선언한 변수는 함수 스코프

let과 const 키워드로 선언한 변수는 블록 스코프

함수 스코프란 말 그대로 함수를 기준으로 스코프를 구분한다. 함수 안에서 선언한 변수는 함수 안에서만 유효하다.

function sayHi() {
  var userName = 'codeit';
  console.log(`Hi ${userName}!`);
}

console.log(userName); // ReferenceError

⚠️하지만 함수를 제외한 for, if, while 등과 같은 문법 안에서 선언한 변수는 그 문법 밖에서도 계속 유효했었기 때문에 때로는 중복선언등의 문제가 생겨나기도 했다.
이런 문제를 해결하기 위해 let과 const 키워드와 함께 블록 스코프가 등장했다.

for (var i = 0; i < 5; i++) {
  console.log(i);
}

console.log(i); // 5

블록 스코프는 중괄호로 감싸진 코드 블록에 따라 유효 범위를 구분한다.

아래 코드에서

함수와 다른 문법들 뿐만 아니라, 그냥 중괄호로 감싸진 코드 블록으로도 유효 범위가 구분된다.

function sayHi() {
  const userName = 'codeit';
  console.log(`Hi ${userName}!`);
}

for (let i = 0; i < 5; i++) {
  console.log(i);
}

{
  let language = 'JavaScript';
}

console.log(userName); // ReferenceError
console.log(i); // ReferenceError
console.log(language); // ReferenceError

(3) 함수 다루기

✨함수 선언

function 키워드를 통해 함수를 선언하는 방식

// 함수 선언
function sayHi() {
  console.log('Hi!');
}

✨함수 표현식

자바스크립트에서 함수는 값으로 취급될 수도 있기 때문에 변수에 할당해서 함수를 선언할 수도 있다.

// 함수 표현식
const sayHi = function () {
  console.log('Hi!');
};

✨다양한 함수의 형태

// 변수에 할당해서 활용
const printJS = function () {
  console.log('JavaScript');
};

// 객체의 메소드로 활용
const codeit = {
  printTitle: function () {
    console.log('Codeit');
  }
}

// 콜백 함수로 활용
myBtn.addEventListener('click', function () {
  console.log('button is clicked!');
});

// 고차 함수로 활용
function myFunction() {
  return function () {
    console.log('Hi!?');
  };
};

✨파라미터의 기본값

자바스립트에서 함수의 파라미터는 기본값을 가질 수가 있다.

기본값이 있는 파라미터는 함수를 호출할 때 아규먼트를 전달하지 않으면, 함수 내부의 동작은 이 파라미터의 기본값을 가지고 동작하게 된다.

function sayHi(name = 'Codeit') {
  console.log(`Hi! ${name}`);
}

sayHi('JavaScript'); // Hi! JavaScript
sayHi(); // Hi! Codeit

✨arguments 객체

자바스크립트 함수 안에는 arguments라는 독특한 객체가 존재한다.
arguments 객체는 함수를 호출할 때 전달한 아규먼트들을 배열의 형태로 모아둔 유사 배열 객체다.

function printArguments() {
  // arguments 객체의 요소들을 하나씩 출력
  for (const arg of arguments) {
    console.log(arg);
  }
}

printArguments('Young', 'Mark', 'Koby');

✨Rest Parameter

파라미터 앞에 마침표 세 개를 붙여주면, 여러 개로 전달되는 아규먼트들을 배열로 다룰 수 있다.
그리고 arguments객체는 유사 배열이기 때문에 배열의 메소드를 활용할 수 없는 반면, rest parameter는 배열이기 때문에 배열의 메소드를 자유롭게 사용할 수 있다.

function printArguments(...args) {
  // args 객체의 요소들을 하나씩 출력
  for (const arg of args) {
    console.log(arg);
  }
}

printArguments('Young', 'Mark', 'Koby');
function printRankingList(first, second, ...others) {
  console.log('코드잇 레이스 최종 결과');
  console.log(`우승: ${first}`);
  console.log(`준우승: ${second}`);
  for (const arg of others) {
    console.log(`참가자: ${arg}`);
  }
}

printRankingList('Tommy', 'Jerry', 'Suri', 'Sunny', 'Jack');

⚠️이름 그대로 앞에 정의된 이름 그대로 앞에 정의된 파라미터에 argument를 먼저 할당하고 나머지 argument를 배열로 묶는 역할을 하기 때문에 일반 파라미터와 함께 사용할 때는 반드시 가장 마지막에 작성해야 한다!!

✨Arrow Function

arrow function은 익명 함수를 좀 더 간결하게 표현할 수 있도록 ES2015에서 새롭게 등장한 함수 선언 방식.
표현식으로 함수를 정의할 때, 콜백 함수로 전달할 때 활용

// 화살표 함수 정의
const getTwice = (number) => {
  return number * 2;
};

// 콜백 함수로 활용
myBtn.addEventListener('click', () => {
  console.log('button is clicked!');
});
// 1. 함수의 파라미터가 하나 뿐일 때
const getTwice = (number) => {
  return number * 2;
};

// 파라미터를 감싸는 소괄호 생략 가능
const getTwice = number => {
  return number * 2;
};

// 2. 함수 동작 부분이 return문만 있을 때
const sum = (a, b) => {
  return a + b;
};

// return문과 중괄호 생략 가능
const sum = (a, b) => a + b;
  • arguments 객체가 없다.
  • this가 가리키는 값이 일반 함수와 다르다.

✨this

웹 브라우저에서 this가 사용될 때는 전역 객체, Window 객체를 가지게 된다. 하지만 객체의 메소드를 정의하기 위한 함수 안에선 메소드를 호출한 객체를 가리키게 된다.

const user = {
  firstName: 'Tess',
  lastName: 'Jang',
  getFullName: function () {
    return `${this.firstName} ${this.lastName}`;
  },
};

// getFullName 안에서의 this는 getFullName을 호출한 user
console.log(user.getFullName()); 

✨이름이 있는 함수 표현식

📘Named Function Expression (기명 함수 표현식)

  • 함수 표현식으로 함수를 만들 때는 선언하는 함수에 이름을 붙여줄 수 있다.
  • 이름이 있는 함수 표현식, 즉 기명 함수 표현식이라고 부른다.
    함수 표현식으로 함수가 할당된 변수에는 자동으로 name이라는 프로퍼티를 가지게 된다.
const sayHi = function () {
  console.log('Hi');
};

console.log(sayHi.name); // sayHi

이름이 없는 함수를 변수에 할당할 때는 변수의 name 프로퍼티는 변수 이름 그 자체를 문자열로 가지게 된다.

하지만 함수에 이름을 붙여주게 되면, name 속성은 함수 이름을 문자열로 갖게 된다.

const sayHi = function printHiInConsole() {
  console.log('Hi');
};

console.log(sayHi.name); // printHiInConsole

이 함수 이름은 함수 내부에서 함수 자체를 가리킬 때 사용할 수 있고 함수를 외부에서 함수를 호출할 때 사용할 수는 없다.

const sayHi = function printHiInConsole() {
  console.log('Hi');
};

printHiInConsole(); // ReferenceError

기명 함수 표현식은 일반적으로 함수 내부에서 함수 자체를 가리킬 때 사용된다.

let countdown = function(n) {
  console.log(n);

  if (n === 0) {
    console.log('End!');
  } else {
    countdown(n - 1);
  }
};

countdown(5);

자기 자신을 부르는 함수를 재귀 함수(Recursive function)
그런데 만약 이 함수를 복사하려고 다른 변수에 똑같이 담았다가, countdown 변수에 담긴 값이 변하게 되면 문제가 발생한다.

let countdown = function(n) {
  console.log(n);
  if (n === 0) {
    console.log('End!');
  } else {
    countdown(n - 1);
  }
};

let myFunction = countdown;

countdown = null;

myFunction(5); // TypeError

마지막 줄에서 myFunction 함수를 호출했을 때, 함수가 실행되긴 하지만, 6번줄 동작을 수행할 때 호출하려는 countdown 함수가 이미 12번에서 null 값으로 변경되었기 때문에 함수가 아니라는 TypeError가 발생한다.

이런 상황을 방지하기 위해서 함수 내부에서 함수 자신을 사용하려고 하면 함수표현식에서는 반드시 기명 함수 표현식을 사용하는 것이 좋다.

let countdown = function printCountdown(n) { // 기명함수
  console.log(n);
  if (n === 0) {
    console.log('End!');
  } else {
    printCountdown(n - 1);
  }
};

let myFunction = countdown;

countdown = null;

myFunction(5); // 정상적으로 동작

함수 표현식을 작성할 때, 함수에 이름을 지정할 수 있다는 점과 특히 이렇게 함수 내에서 함수를 가리켜야 할 때는 꼭 함수 이름을 작성해주는 것이 안전하다!!

function sayHi() {
  console.log('Hi!');
}

sayHi();

일반적으로는 이렇게 함수를 먼저 선언한 다음,선언된 함수 이름 뒤에 소괄호를 붙여서 함수를 실행한다.
그런데 때로는 함수가 선언된 순간에 바로 실행을 할 수도 있다.

📘즉시 실행 함수

(function () {
  console.log('Hi!');
})();
  • 함수선언 부분을 소괄호로 감싼 다음에 바로 뒤에 함수를 실행하는 소괄호를 한 번 더 붙여주는 방식.
  • 이렇게 하면 함수가 선언된 순간 바로 실행된다.

→ 함수 선언과 동시에 즉시 실행되는 함수: 즉시 실행 함수 (표현)
영어로는 Immediately Invoked Function Expression, 줄여서 IIFE라고 부름.

(function (x, y) {
  console.log(x + y);
})(3, 5);
  • 즉시 실행 함수도 일반 함수처럼 파라미터를 작성하고, 함수를 호출할 때 아규먼트를 전달할 수 있다.
  • 즉시 실행 함수는 함수에 이름을 지어주더라도 외부에서 재사용할 수 없다.
(function sayHi() {
  console.log('Hi!');
})();

sayHi(); // ReferenceError

그래서 일반적으로는 이름이 없는 익명 함수를 사용한다.

(function countdown(n) {
  console.log(n);
  if (n === 0) {
    console.log('End!');
  } else {
    countdown(n - 1);
  }
})(5);

📘즉시 실행 함수의 활용

즉시 실행 함수는 선언과 동시에 실행이 이뤄지기 때문에 일반적으로 프로그램 초기화 기능에 많이 활용된다.

(function init() {
  // 프로그램이 실행 될 때 기본적으로 동작할 코드들..
})();

재사용이 필요 없는, 일회성 동작을 구성할 때 활용하는 경우도 있다.

const firstName = 'Young';
const lastName = 'Kang';

const greetingMessage = (function () {
  const fullName = `${firstName} ${lastName} `;

  return `Hi! My name is ${fullName}`;
})();

함수의 리턴값을 바로 변수에 할당하고 싶을 때 활용할 수 있다.

⚠️고차함수

function getFunction() {
  return function () {
    console.log('Codeit');
  }
}

const printCodeit = getFunction;
printCodeit();
  • getFunction은 새로운 함수를 리턴하는 고차 함수다.
  • printCodeit이라는 변수에 getFunction을 그대로 할당했다. 그러면 결국 printCodeitgetFunction과 그냥 똑같이 동작한다.

그래서 printCodeit의 호출 결과는 콘솔에 Codeit이라는 문자열을 출력하는게 아니라 콘솔에 'Codeit' 이라는 문자열을 출력하는 함수를 리턴하게 된다.

✨문장 (statements)

우리가 작성하는 모든 자바스크립트 코드는 모두 문장과 표현식으로 구성되어 있다.
자바스크립트에서 문장은 어떤 동작이 일어나도록 작성된 최소한의 코드 덩어리를 가리킨다.

let x;
x = 3;

if (x < 5) {
  console.log('x는 5보다 작다');
} else {
  console.log('x는 5와 같거나 크다');
}

for (let i = 0; i < 5; i++) {
  console.log(i);
}

선언문, 할당문, 조건문, 반복문 ..

→ 끝에 문이라고 붙은 이유가 모두 동작을 수행하는 문장이기 때문.

✨표현식 (expressions)

표현식은 결과적으로 하나의 값이 되는 모든 코드

5 // 5

'string' // string

어떤 하나의 값을 그대로 작성하는 것도 표현식이지만,

5 + 7 // 12

'I' + ' Love ' + 'Codeit' // I Love Codeit

true && null // null

이렇게 연산자를 이용한 연산식도 결국은 하나의 값이 되고,


const title = 'JavaScript';
const codeit = {
  name: 'Codeit'
};
const numbers = [1, 2, 3];

typeof codeit // object
title // JavaScript
codeit.name // Codeit
numbers[3] // undefined

선언된 변수를 호출하거나, 객체의 프로퍼티에 접근하는 것도 결국에는 하나의 값으로 평가된다.

길이와는 상관없이 결과적으로 하나의 값이 되는 코드를 모두 표현식이라고 할 수가 있다.

📘표현식이면서 문장, 문장이면서 표현식

표현식은 보통 문장의 일부로 쓰이지만, 그 자체로 문장일 수도 있다.

ex) 할당식, 함수 호출

// 할당 연산자는 값을 할당하는 동작도 하지만, 할당한 값을 그대로 가지는 표현식이다.
title = 'JavaScript'; // JavaScript

// 함수 호출은 함수를 실행하는 동작도 하지만, 실행한 함수의 리턴 값을 가지는 표현식이다.
sayHi(); // sayHi 함수의 리턴 값

// console.log 메소드는 콘솔에 아규먼트를 출력하는 동작도 하지만, undefined 값을 가지는 표현식이다.
console.log('hi'); // undefined

할당연산자 자체가 할당한 값을 그대로 리턴하는 특징이 있어 연산 자체로 값이 되는 표현식이다. 그런데 할당식은 왼쪽에 있는 피연산자에 오른쪽 피연산자 값을 할당하는 동작을 하기 때문에, 문장이 되기도 한다.
그리고 함수 호출도 함수를 호출한 자리가 결국에는 하나의 리턴하는 값을 가지기 때문에 표현식이라고 할 수도 있지만 함수 내부에 정의한 코드를 실행하는 동작이기 때문에 문장이 되기도 한다.

📘표현식인 문장 vs 표현식이 아닌 문장

결과적으로 문장은 다시 표현식인 문장과, 표현식이 아닌 문장으로 나눌 수 있다.
이 둘을 구분하는 가장 간단한 방법은 우리가 구분하고자 하는 문장을 변수에 할당하거나, 어떤 함수의 아규먼트로 전달하는 것이다.

let x;
x = 3;

console.log(if (x < 5) {
  console.log('x는 5보다 작다');
} else {
  console.log('x는 5보다 크다');
});

const someloop = for (let i = 0; i < 5; i++) {
  console.log(i);
};

console.log 메소드의 아규먼트로 if문을 전달하거나 someloop라는 변수에 for 반복문을 할당하게 되면, Error가 발생한다.
조건문이나 반복문은 값으로 평가되지 않고 오로지 문장으로만 평가되기 때문이다.

자바스크립트에서 특별한 경우를 제외하면 일반적으로 표현식인 문장은 세미콜론으로, 표현식이 아닌 문장은 문장 자체의 코드 블록(중괄호)로 그 문장의 범위가 구분된다.

(3 + 4) * 2;
console.log('Hi!');

while(true) {
  x++;
}

(4) 자바스크립트 문법과 표현

✨조건부 연산자 (Conditional operator)

삼항 연산자 (Ternary operator)라고도 불리는 이 연산자는 자바스크립트에서 세 개의 피연산자를 가지는 유일한 연산자다.

if문과 같은 원리로 조건에 따라 값을 결정할 때 활용된다.

const cutOff = 80;

const passChecker = (score) => score > cutOff ? '합격입니다!' : '불합격입니다!';

console.log(passChecker(75));
  • 간단한 조건식의 경우에는 if문 보다 훨씬 더 간결하게 표현할 수 있다.
  • 내부에 변수나 함수를 선언한다거나 반복문 같은 표현식이 아닌 문장은 작성할 수 없다는 한계가 있기 때문에 if문을 완벽하게 대체할 수는 없다.

✨객체 Spread하기

여러 개의 값을 묶어놓은 배열이나 객체와 같은 값은 바로 앞에 마침표 세 개를 붙여서 펼칠 수가 있다.

const webPublishing = ['HTML', 'CSS'];
const interactiveWeb = [...webPublishing, 'JavaScript'];

console.log(webPublishing);
console.log(interactiveWeb);

const arr1 = [1, 2, 3];
const arr2 = [4, 5, 6];

const arr3 = [...arr1, ...arr2];
console.log(arr3);
  • Spread 구문은 배열이나 객체를 복사하거나 혹은 복사해서 새로운 요소들을 추가할 때 유용하다.
  • 배열은 객체로 펼칠 수 있지만 객체는 배열로 펼칠 수 없다!!
const members = ['태호', '종훈', '우재'];
const newObject = { ...members };

console.log(newObject); // {0: "태호", 1: "종훈", 2: "우재"}

const topic = {
  name: '모던 자바스크립트',
  language: 'JavaScript',
}
const newArray = [...topic]; // TypeError!
const codeit = {
  name: 'codeit',
};

const codeitClone = {
  ...codeit, // spread 문법!
};

console.log(codeit); // {name: "codeit"}
console.log(codeitClone); // {name: "codeit"}

중괄호 안에서 객체를 spread 하게되면, 해당 객체의 프로퍼티들이 펼쳐지면서 객체를 복사할 수 있다.

const latte = {
  esspresso: '30ml',
  milk: '150ml'
};

const cafeMocha = {
  ...latte,
  chocolate: '20ml',
}

console.log(latte); // {esspresso: "30ml", milk: "150ml"}
console.log(cafeMocha); // {esspresso: "30ml", milk: "150ml", chocolate: "20ml"}

⚠️주의 사항

배열을 Spread 하면 새로운 배열을 만들거나 함수의 아규먼트로 쓸 수 있었지만, 객체로는 새로운 배열을 만들거나 함수의 아규먼트사용할 수는 없다.

const latte = {
  esspresso: '30ml',
  milk: '150ml'
};

const cafeMocha = {
  ...latte,
  chocolate: '20ml',
}

[...latte]; // Error

(function (...args) {
  for (const arg of args) {
    console.log(arg);
  }
})(...cafeMocha); // Error

객체를 spread할 때는 객체를 표현하는 중괄호 안에서 활용해야 한다!!!

✨옵셔널 체이닝 (Optional Chaining)

일반적으로 객체의 프로퍼티는 점 표기법을 통해서 접근한다.

function printCatName(user) {
  console.log(user.cat.name);
}

const user1 = {
  name: 'Captain',
  cat: {
    name: 'Crew',
    breed: 'British Shorthair',
  }
}

printCatName(user1); // Crew

객체를 활용해서 데이터를 표현하다 보면 이렇게 중첩된 객체를 작성하게 될 일이 빈번하고, 함수에서도 이런 중첩 객체의 프로퍼티를 활용할 일이 많다.
함수 printCatNameuser 파라미터에 중첩된 cat객체의 name 프로퍼티를 콘솔에 출력해주는 함수다.

⚠️중첩 객체를 다룰 때 한가지 조심해야 될 부분이 있다.

const user2 = {
  name: 'Young',
}

console.log(user2.cat); // undefined
printCatName(user2); // TypeError: Cannot read property 'name' of undefined

cat 프로퍼티를 가지고 있지 않은 user2cat 프로퍼티가 undefined이기 때문에 user2.cat.name에 접근하려는 순간 에러가 발생한다.

그래서 printCatName과 같이 중첩된 객체의 프로퍼티를 다룰 때는 user.cat.name에 접근하기 전에 user.catnull 혹은 undefined가 아니라는 것을 검증하고 접근해야 에러를 방지할 수 있다.

function printCatName(user) {
  console.log(user.cat && user.cat.name);
}

하지만, 객체의 이름이나 프로퍼티의 이름이 길어질수록 가독성이 나빠지는 문제가 생겼다. 이런 상황에 훨씬 더 코드를 간결하게 사용할 수 있는 문법이 바로 옵셔널 체이닝(Optional Chaining)이다.

function printCatName(user) {
  console.log(user.cat?.name);
}

물음표와 마침표를 붙여 사용하는 부분이 바로 옵셔널 체이닝 연산자(?.) 이다.
만약 옵셔널 체이닝 연산자 왼편의 프로퍼티 값이 undefined 또는 null이 아니라면 그다음 프로퍼티 값을 리턴하고 그렇지 않은 경우에는 undefined를 반환하는 문법이다.

옵셔널 체이닝 연산자의 동작 원리를 삼항 연산자를 통해 구체적으로 표현하면 다음과 같이 작성할 수 있다.

function printCatName(user) {
  console.log((user.cat === null || user.cat === undefined) ? undefined : user.cat.name);
}
function printCatName(user) {
  console.log(user.cat?.name ?? '함께 지내는 고양이가 없습니다.');
}

const user2 = {
  name: 'Young',
}

printCatName(user2); // 함께 지내는 고양이가 없습니다.

✨모던한 프로퍼티 표기법

ES2015 이후부터는 자바스크립트에서 변수나 함수룰 활용해서 프로퍼티를 만들 때 프로퍼티 네임과 변수나 함수 이름이 같다면 축약해서 사용할 수 있다.

function sayHi() {
  console.log('Hi!');
}

const title = 'codeit';
const birth = 2017;
const job = '프로그래밍 강사';

const user = {
  title,
  birth,
  job,
  sayHi,
};

console.log(user); 
// {title: "codeit", birth: 2017, job: "프로그래밍 강사", sayHi: ƒ}

메소드를 작성할 때도 function 키워드를 생략할 수가 있다.

const user = {
  firstName: 'Tess',
  lastName: 'Jang',
  getFullName() {
    return `${this.firstName} ${this.lastName}`;
  },
};

console.log(user.getFullName()); // Tess Jang

대괄호를 활용하면 다양한 표현식으로 프로퍼티 네임을 작성할 수 있다.

const propertyName = 'birth';
const getJob = () => 'job';

const codeit = {
  ['topic' + 'name']: 'Modern JavaScript',
  [propertyName]: 2017,
  [getJob()]: '프로그래밍 강사',
};

console.log(codeit);

✨구조 분해 Destructuring

배열과 객체와 같이 내부에 여러 값을 담고 있는 데이터 타입을 다룰 때 Destructuring 문법을 활용하면, 배열의 요소나 객체의 프로퍼티 값들을 개별적인 변수에 따로 따로 할당해서 다룰 수 있다.

// Array Destructuring
const members = ['코딩하는효준', '글쓰는유나', '편집하는민환'];
const [macbook, ipad, coupon] = members;

console.log(macbook); // 코딩하는효준
console.log(ipad); // 글쓰는유나
console.log(coupon); // 편집하는민환

// Object Destructuring
const macbookPro = {
  title: '맥북 프로 16형',
  price: 3690000,
};

const { title, price } = macbookPro;

console.log(title); // 맥북 프로 16형
console.log(price); // 3690000

함수에서 default parater, rest parameter를 다루듯이 Destructuring 문법을 활용할 때도 기본값과 rest 문법을 활용할 수 있다.

// Array Destructuring
const members = ['코딩하는효준', '글쓰는유나', undefined, '편집하는민환', '촬영하는재하'];
const [macbook, ipad, airpod = '녹음하는규식', ...coupon] = members;

console.log(macbook); // 코딩하는효준
console.log(ipad); // 글쓰는유나
console.log(airpod); // 녹음하는규식
console.log(coupon); // (2) ["편집하는민환", "촬영하는재하"]

// Object Destructuring
const macbookPro = {
  title: '맥북 프로 16형',
  price: 3690000,
  memory: '16 GB 2667 MHz DDR4',
  storage: '1TB SSD 저장 장치',
};

const { title, price, color = 'silver', ...rest } = macbookPro;

console.log(title); // 맥북 프로 16형
console.log(price); // 3690000
console.log(color); // silver
console.log(rest); // {memory: "16 GB 2667 MHz DDR4", storage: "1TB SSD 저장 장치"}

✨에러와 에러 객체

자바스크립트에서 에러가 발생하면 그 순간 프로그램 자체가 멈춰버리고 이후의 코드가 동작하지 않는다.

그리고 에러가 발생하면 에러에 대한 정보를 namemessage라는 프로퍼티로 담고 있는 에러 객체가 만들어진다.
대표적인 에러 객체) SyntaxError, ReferenceError, TypeError

에러 객체는 직접 만들 수도 있다.
new 키워드와 에러 객체 이름을 딴 함수를 통해 에러 객체를 만들 수 있고, throw 키워드로 에러를 발생시킬 수 있다.

throw new TypeError('타입 에러가 발생했습니다.');

✨try...catch문

try {
  // 실행할 코드
} catch (error) {
  // 에러 발생 시 동작할 코드
}
  • try문 안에서 실행할 코드를 작성하고, try문에서 에러가 발생한 경우에 실행할 코드를 catch 문 안에 작성한다.
  • 이 때 try문에서 발생한 에러 객체가 catch문의 첫 번째 파라미터로 전달된다.
  • 만약, try문에서 에러가 발생하지 않을 경우 catch문의 코드는 동작하지 않는다.
  • try...catch문에서 에러의 유무와 상관없이 항상 동작해야할 코드가 필요하다면 finally문을 쓰면 된다.
try {
  // 실행할 코드
} catch (error) {
  // 에러가 발상했을 때 실행할 코드
} finally {
  // 항상 실행할 코드
}

✨finally문

try {
  // 실행할 코드
} catch (err) {
  // 에러가 발생했을 때 실행할 코드
} finally {
  // 항상 실행할 코드
}

try문에서 에러가 발생하지 않는다면 try문의 코드가 모두 실행된 다음에,
try문에서 에러가 발생한다면 catch문의 코드가 모두 실행된 다음 실행할 코드를 finally문에 작성하면 된다.

try문에서 어떤 코드를 실행할 때 에러 여부와 상관 없이 항상 실행할 코드를 작성한다.

function printMembers(...members) {
  for (const member of members) {
    console.log(member);
  }
}

try {
  printMembers('영훈', '윤수', '동욱');
} catch (err) {
  alert('에러가 발생했습니다!');
  console.error(err);
} finally {
  const end = new Date();
  const msg = `코드 실행을 완료한 시각은 ${end.toLocaleString()}입니다.`;
  console.log(msg);
}

❓finally문에서의 에러 처리는?

finally문에서 에러가 발생할 경우에는 다시 그 위에 있는 catch문으로 넘어가지 않는다.

만약 finally문에서도 에러 처리가 필요한 경우에는 try...catch문을 중첩해서 활용해야 한다.

try {
  try {
    // 실행할 코드
  } catch (err) {
    // 에러가 발생했을 때 실행할 코드
  } finally {
    // 항상 실행할 코드
  }
} catch (err) {
  // finally문에서 에러가 발생했을 때 실행할 코드
}

📕에러 객체 유형

Error - JavaScript | MDN

(5) 자바스크립트의 유용한 내부 기능

✨forEach 메소드

  • 배열의 요소를 하나씩 살펴보면서 반복 작업을 하는 메소드.
  • 첫 번째 아규먼트로 콜백 함수
  • 콜백 함수의 파라미터에는 각각 배열의 요소, index, 메소드를 호출한 배열이 전달된다. (index와 array는 생략가능)
const numbers = [1, 2, 3];

numbers.forEach((element, index, array) => {
  console.log(element); // 순서대로 콘솔에 1, 2, 3이 한 줄씩 출력됨.
});

✨map 메소드

  • forEach와 비슷하게 배열의 요소를 하나씩 살펴보면서 반복 작업을 하는 메소드.
  • 단, 첫 번째 아규먼트로 전달하는 콜백 함수가 매번 리턴하는 값들을 모아서 새로운 배열을 만들어 리턴하는 특징이 있다.
const numbers = [1, 2, 3];
const twiceNumbers = numbers.map((element, index, array) => {
  return element * 2;
});

console.log(twiceNumbers); // (3) [2, 4, 6]

✨filter 메소드

  • filter 메소드는 배열의 요소를 하나씩 살펴보면서 콜백함수가 리턴하는 조건과 일치하는 요소만 모아서 새로운 배열을 리턴하는 메소드.
const devices = [
  {name: 'GalaxyNote', brand: 'Samsung'},
  {name: 'MacbookPro', brand: 'Apple'},
  {name: 'Gram', brand: 'LG'},
  {name: 'SurfacePro', brand: 'Microsoft'},
  {name: 'ZenBook', brand: 'Asus'},
  {name: 'MacbookAir', brand: 'Apple'},
];

const apples = devices.filter((element, index, array) => {
  return element.brand === 'Apple';
});

console.log(apples); 
// (2) [{name: "MacbookPro", brand: "Apple"}, {name: "MacbookAir", brand: "Apple"}]

✨find 메소드

  • find 메소드는 filter 메소드와 비슷하게 동작하지만, 배열의 요소들을 반복하는 중에 콜백함수가 리턴하는 조건과 일치하는 가장 첫번째 요소를 리턴하고 반복을 종료하는 메소드.
const devices = [
  {name: 'GalaxyNote', brand: 'Samsung'},
  {name: 'MacbookPro', brand: 'Apple'},
  {name: 'Gram', brand: 'LG'},
  {name: 'SurfacePro', brand: 'Microsoft'},
  {name: 'ZenBook', brand: 'Asus'},
  {name: 'MacbookAir', brand: 'Apple'},
];

const myLaptop = devices.find((element, index, array) => {
  console.log(index); // 콘솔에는 0, 1, 2까지만 출력됨.
  return element.name === 'Gram';
});

console.log(myLaptop); // {name: "Gram", brand: "LG"}

✨some 메소드

  • some 메소드는 배열 안에 콜백함수가 리턴하는 조건을 만족하는 요소가 1개 이상 있는지를 확인하는 메소드.
  • 배열을 반복하면서 모든 요소가 콜백함수가 리턴하는 조건을 만족하지 않는다면 false를 리턴하고, 배열을 반복하면서 콜백함수가 리턴하는 조건을 만족하는 요소가 등장한다면 바로 true를 리턴하고 반복을 종료.
const numbers = [1, 3, 5, 7, 9];

// some: 조건을 만족하는 요소가 1개 이상 있는지
const someReturn = numbers.some((element, index, array) => {
  console.log(index); // 콘솔에는 0, 1, 2, 3까지만 출력됨.
  return element > 5;
});

console.log(someReturn); // true;

✨every 메소드

  • every 메소드는 배열 안에 콜백 함수가 리턴하는 조건을 만족하지 않는 요소가 1개 이상 있는지를 확인하는 메소드.
  • 배열을 반복하면서 모든 요소가 콜백함수가 리턴하는 조건을 만족한다면 true를 리턴하고, 배열을 반복하면서 콜백함수가 리턴하는 조건을 만족하지 않는 요소가 등장한다면 바로 false를 리턴하고 반복을 종료.
const numbers = [1, 3, 5, 7, 9];

// every: 조건을 만족하지 않는 요소가 1개 이상 있는지
const everyReturn = numbers.every((element, index, array) => {
  console.log(index); // 콘솔에는 0까지만 출력됨.
  return element > 5;
});

console.log(everyReturn); // false;

✨reduce 메소드

  • reduce 메소드는 누적값을 계산할 때 활용하는 메소드.
  • 첫 번째 파라미터: 반복동작할 콜백함수
    • 매번 실행되는 콜백함수의 리턴값이 다음에 동작할 콜백함수의 첫번째 파라미터로 전달된다. 결과적으로 마지막 콜백함수가 리턴하는 값이 reduce 메소드의 최종 리턴값이 된다.
    • 이 때 reduce 메소드의 두 번째 파라미터로 전달한 초기값이 첫 번째로 실행될 콜백함수의 가장 첫 번째 파라미터로 전달된다.
const numbers = [1, 2, 3, 4];

// reduce
const sumAll = numbers.reduce((accumulator, element, index, array) => {
  return accumulator + element;
}, 0);

console.log(sumAll); // 10

✨sort 메소드

  • 배열에서 sort라는 메소드를 활용하면 배열을 정렬할 수 있다.
  • sort 메소드에 아무런 아규먼트도 전달하지 않을 때는 기본적으로 유니코드에 정의된 문자열 순서에 따라 정렬된다.
const letters = ['D', 'C', 'E', 'B', 'A'];
const numbers = [1, 10, 4, 21, 36000];

letters.sort();
numbers.sort();

console.log(letters); // (5) ["A", "B", "C", "D", "E"]
console.log(numbers); // (5) [1, 10, 21, 36000, 4]

⚠️ sort 메소드에 콜백함수를 아규먼트로 작성해서 오름, 내림차순으로 정렬할 수 있다.

const numbers = [1, 10, 4, 21, 36000];

// 오름차순 정렬
numbers.sort((a, b) => a - b);
console.log(numbers); // (5) [1, 4, 10, 21, 36000]

// 내림차순 정렬
numbers.sort((a, b) => b - a);
console.log(numbers); // (5) [36000, 21, 10, 4, 1]

sort 메소드는 메소드를 실행하는 원본 배열의 요소들을 정렬한다.
→ 한 번 정렬하고 나면 정렬하기 전의 순서로 다시 되돌릴 수 없다!!!!

✨reverse 메소드

  • reverse 메소드는 배열의 순서를 뒤집어 주는 메소드
  • reverse 메소드는 별도의 파라미터가 존재하지 않기 때문에 단순히 메소드를 호출해주기만 하면 배열의 순서가 뒤집힌다.
  • sort 메소드와 마찬가지로 원본 배열의 요소들을 뒤집어 버린다.
const letters = ['a', 'c', 'b'];
const numbers = [421, 721, 353];

letters.reverse();
numbers.reverse();

console.log(letters); // (3) ["b", "c", "a"]
console.log(numbers); // (3) [353, 721, 421]

✨Map과 Set

  • 객체는 property name을 통해 이름이 있는 여러 값들을 묶을 때 활용할 수 있고,
  • 배열은 index를 통해 순서가 있는 여러 값들을 묶을 때 유용하게 활용할 수 있다.

그런데 ES2015에서 객체와 비슷한 Map과 배열과 비슷한 Set이라는 데이터 구조가 등장했다.

📘Map

  • Map은 이름이 있는 데이터를 저장한다는 점에서 객체와 비슷하다.
  • 할당연산자를 통해 값을 추가하고 점 표기법이나 대괄호 표기법으로 접근하는 일반 객체와 다르게 Map은 메소드를 통해서 값을 추가하거나 접근할 수 있다.
  • new 키워드를 통해서 Map을 만들 수 있고 메소드를 통해 Map 안의 여러 값들을 다룰 수 있다.
    • map.set(key, value): key를 이용해 value를 추가하는 메소드.
    • map.get(key): key에 해당하는 값을 얻는 메소드. key가 존재하지 않으면 undefined를 반환.
    • map.has(key): key가 존재하면 true, 존재하지 않으면 false를 반환하는 메소드.
    • map.delete(key): key에 해당하는 값을 삭제하는 메소드.
    • map.clear(): Map 안의 모든 요소를 제거하는 메소드.
    • map.size: 요소의 개수를 반환하는 프로퍼티. (메소드가 아닌 점 주의! 배열의 length 프로퍼티와 같은 역할)
// Map 생성
const codeit = new Map();

// set 메소드
codeit.set('title', '문자열 key');
codeit.set(2017, '숫자형 key');
codeit.set(true, '불린형 key');

// get 메소드
console.log(codeit.get(2017)); // 숫자형 key
console.log(codeit.get(true)); // 불린형 key
console.log(codeit.get('title')); // 문자열 key

// has 메소드
console.log(codeit.has('title')); // true
console.log(codeit.has('name')); // false

// size 프로퍼티
console.log(codeit.size); // 3

// delete 메소드
codeit.delete(true);
console.log(codeit.get(true)); // undefined
console.log(codeit.size); // 2

// clear 메소드
codeit.clear();
console.log(codeit.get(2017)); // undefined
console.log(codeit.size); // 0

문자열과 심볼 값만 key(프로퍼티 네임)로 사용할 수 있는 일반 객체와는 다르게
Map 객체는 메소드를 통해 값을 다루기 때문에, 다양한 자료형을 key로 활용할 수 있다.

📘Set

  • Set은 여러 개의 값을 순서대로 저장한다는 점에서 배열과 비슷하다.
  • 배열의 메소드는 활용할 수 없고 Map과 비슷하게 Set만의 메소드를 통해서 값을 다룬다.
  • Map과 마찬가지로 new 키워드로 Set을 만들 수 있고 메소드를 통해 Set 안의 여러 값들을 다룰 수 있다.
    • set.add(value): 값을 추가하는 메소드. (메소드를 호출한 자리에는 추가된 값을 가진 Set 자신을 반환.)
    • set.has(value): Set 안에 값이 존재하면 true, 아니면 false를 반환하는 메소드.
    • set.delete(value): 값을 제거하는 메소드. (메소드를 호출한 자리에는 셋 내에 값이 있어서 제거에 성공하면 true, 아니면 false를 반환.)
    • set.clear(): Set 안의 모든 요소를 제거하는 메소드.
    • set.size: 요소의 개수를 반환하는 프로퍼티. (메소드가 아닌 점 주의! 배열의 length 프로퍼티와 같은 역할)
// Set 생성
const members = new Set();

// add 메소드
members.add('영훈'); // Set(1) {"영훈"}
members.add('윤수'); // Set(2) {"영훈", "윤수"}
members.add('동욱'); // Set(3) {"영훈", "윤수", "동욱"}
members.add('태호'); // Set(4) {"영훈", "윤수", "동욱", "태호"}

// has 메소드
console.log(members.has('동욱')); // true
console.log(members.has('현승')); // false

// size 프로퍼티
console.log(members.size); // 4

// delete 메소드
members.delete('종훈'); // false
console.log(members.size); // 4
members.delete('태호'); // true
console.log(members.size); // 3

// clear 메소드
members.clear();
console.log(members.size); // 0

⚠️일반 객체는 프로퍼티 네임

Map은 get메소드

배열은 index를 통해서 개별 값에 접근할 수 있다.

!!! 한 가지 특이한 점은 Set에는 개별 값에 바로 접근하는 방법이 없다.

// Set 생성
const members = new Set();

// add 메소드
members.add('영훈'); // Set(1) {"영훈"}
members.add('윤수'); // Set(2) {"영훈", "윤수"}
members.add('동욱'); // Set(3) {"영훈", "윤수", "동욱"}
members.add('태호'); // Set(4) {"영훈", "윤수", "동욱", "태호"}

for (const member of members) {
  console.log(member); // 영훈, 윤수, 동욱, 태호가 순서대로 한 줄 씩 콘솔에 출력됨.
}

그래서 반복문을 통해서 전체요소를 한꺼번에 다룰 때 반복되는 그 순간에 개별적으로 접근할 수가 있다.
중복을 허용하지 않는 값들을 모을 때 유용하다.

// Set 생성
const members = new Set();

// add 메소드
members.add('영훈'); // Set(1) {"영훈"}
members.add('윤수'); // Set(2) {"영훈", "윤수"}
members.add('영훈'); // Set(2) {"영훈", "윤수"}
members.add('영훈'); // Set(2) {"영훈", "윤수"}
members.add('동욱'); // Set(3) {"영훈", "윤수", "동욱"}
members.add('동욱'); // Set(3) {"영훈", "윤수", "동욱"}
members.add('동욱'); // Set(3) {"영훈", "윤수", "동욱"}
members.add('태호'); // Set(4) {"영훈", "윤수", "동욱", "태호"}
members.add('동욱'); // Set(4) {"영훈", "윤수", "동욱", "태호"}
members.add('태호'); // Set(4) {"영훈", "윤수", "동욱", "태호"}
members.add('태호'); // Set(4) {"영훈", "윤수", "동욱", "태호"}

최초에 추가된 순서를 유지하면서, 나중에 중복된 값을 추가하려고 하면 그 값은 무시한다.

처음 Set을 생성할 때 아규먼트로 배열을 전달할 수도 있다.
이런 특징을 활용해서 배열 내에서 중복을 제거한 값들의 묶음을 만들 때 Set을 활용한다.

const numbers = [1, 3, 4, 3, 3, 3, 2, 1, 1, 1, 5, 5, 3, 2, 1, 4];
const uniqNumbers = new Set(numbers);

console.log(uniqNumbers); // Set(5) {1, 3, 4, 2, 5}

(6) 자바스크립트 모듈

✨모듈

모듈은 간단하게, 자바스크립트 파일 하나라고 할 수 있다.
복잡하고 많은 양의 코드를 기능에 따라 각각의 파일로 나눠 관리하면

  1. 코드를 좀 더 효율적으로 관리할 수 있고,
  2. 비슷한 기능이 필요할 때 다른 프로그램에서 재사용 할 수도 있다.

✨모듈 스코프

모듈 파일 안에서 선언한 변수는 외부에서 자유롭게 접근할 수 없도록 막아야 한다.
다시 말해 모듈은 파일 안에서 모듈 파일만의 독립적인 스코프를 가지고 있어야 한다.

HTML파일에서 자바스크립트 파일을 불러올 때 모듈 스코프를 갖게 하려면
script태그에 type속성을 module이라는 값으로 지정해야 한다.

<body>
  <script type="module" src="index.js"></script>
</body>

✨모듈 문법

자바스크립트의 모듈 문법은 기본적으로 exportimport 입니다.
모듈 스코프를 가진 파일에서 외부로 내보내고자 하는 변수나 함수를 export 키워드를 통해 내보내고,
모듈 파일에서 내보낸 변수나 함수들은 다른 파일에서 import 키워드를 통해 가져온다.

// printer.js
export const title = 'CodeitPrinter';

export function print(value) {
  console.log(value);
};
// index.js
import { title, print } from './printer.js';

print(title);

✨이름 바꿔 import 하기

  • import 키워드를 통해 모듈을 불러올 때 as 키워드를 활용하면 import하는 대상들의 이름을 변경할 수 있다.
  • 이름을 바꿔서 import 하면 여러 파일에서 불러오는 대상들의 이름이 중복되는 문제를 해결할 수도 있다.
import { title as printerTitle, print, printArr } from './printer.js';
import { title, data as members } from './members.js';

printer(title);
arrPrinter(members);

✨한꺼번에 import 하기

import할 때 와일드카드 문자(*)as를 활용하면 모듈 파일에서 export하는 모든 대상을 하나의 객체로 불러올 수 있다.

import * as printerJS from './printer.js';

console.log(printerJS.title); // CodeitPrinter
console.log(printerJS.print); // ƒ print(value) { console.log(value); }

✨한꺼번에 export 하기

변수나 함수 앞에 매번 export 키워드를 붙일 수도 있지만, 선언된 변수나 함수를 하나의 객체로 모아 한꺼번에 내보낼 수도 있다.
이때 as 키워드를 활용하면 이름을 변경해서 export할 수도 있다.

const title = 'CodeitPrinter';

function print(value) {
  console.log(value);
}

function printArr(arr) {
  arr.forEach((el, i) => {
    console.log(`${i + 1}. ${el}`);
  });
}

export { title as printerTitle, print, printArr };

✨default export

export를 할 때 default 키워드를 함께 사용하면 모듈 파일에서 기본적으로 export할 대상을 정할 수 있다.
일반적으로 모듈 파일에서 export 대상이 하나라면, 이 default 키워드를 함께 활용하는 것이 조금 더 간결한 코드를 구성하는데 도움이 된다.

const title = 'CodeitPrinter';

function print(value) {
  console.log(value);
}

export default print;

default exportimport할 때 기본적으로 다음과 같이 불러올 수 있지만,


import { default as printerJS } from './printer.js';

console.log(printerJS.title); // CodeitPrinter
console.log(printerJS.print); // ƒ print(value) { console.log(value); }

👇🏻축약형 문법으로 import 할 수도 있다.👇🏻


import printerJS from './printer.js';

console.log(printerJS.title); // CodeitPrinter
console.log(printerJS.print); // ƒ print(value) { console.log(value); }
profile
정신차려 이 각박한 세상속에서

0개의 댓글