모던 자바스크립트 (4)

깨진알·2023년 12월 13일

JavaScript

목록 보기
14/21

자바스크립트의 문법과 표현

1. 문장과 표현식

  • 문장 : 어떤 동작이 일어나도록 작성된 최소한의 코드 덩어리를 가리킨다.

  • 표현식 : 결과적으로 하나의 값이 되는 모든 코드를 가리킨다.

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

// 할당 연산자는 값을 할당하는 동작도 하지만, 할당한 값을 그대로 가지는 표현식이다.
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가 발생하게 된다. 조건문이나 반복문은 값으로 평가되지 않고 오로지 문장으로만 평가되기 때문이다.

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


2. 조건을 다루는 표현식

// 조건 연산자 (Conditional operator)
// 조건 ? truthy 할 때 표현식 : falsy 할 때 표현식
// 삼항 연산자 (Ternary operator)
const CUT_OFF = 80;

function passChecker(score) {
  return score > CUT_OFF ? '합격' : '불합격';
};

console.log(passChecker(75)); // 불합격

3. Spread 구문

// Spread 구문 (Spread Syntax)
const numbers = [1, 2, 3];

console.log(...numbers); // 1 2 3

// ------------------------- //
const webPublishing = ['HTML', 'CSS'];
const interactiveWeb = [...webPublishing];
// 바로 추가 => const interactiveWeb = [...webPublishing, 'JavaScript'];

interactiveWeb.push('JavaScript');

console.log(webPublishing); // ["HTML", "CSS"]
console.log(interactiveWeb); // ["HTML", "CSS", "JavaScript"]

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

const arr3 = [...arr1, ...arr2];
console.log(arr3); // (6) [1, 2, 3, 4, 5, 6]

const arr4 = arr1.concat(arr2);
console.log(arr4); // (6) [1, 2, 3, 4, 5, 6]

// ------------------------- //
const members = ['A', 'B', 'C'];

const newObject = { ...members };

console.log(newObject); // {0: "A", 1: "B", 2: "C"}

3-1. 객체 Spread하기

const cracked = { 
  name: 'cracked', 
};

const crackedClone = { 
  ...cracked, // spread 문법!
};

console.log(cracked); // {name: "cracked"}
console.log(crackedClone); // {name: "cracked"}

중괄호 안에서 객체를 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"}

다른 객체가 가진 프로퍼티에 다른 프로퍼티를 추가해서 새로운 객체를 만들 때 활용할 수도 있다.

  • 주의사항
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하면 새로운 배열을 만들거나 함수의 아규먼트로 쓸 수 있었지만, 객체로는 새로운 배열을 만들거나 함수의 아규먼트로 사용할 수는 없다. 그렇기 때문에 Spread할 때는 반드시 객체를 표현하는 중괄호 안에서 활용해야 한다는 점도 기억해 두면 좋다.


4. 모던한 프로퍼티 표기법

// 모던한 프로퍼티 표기법
const title = 'Cracked';
const birth = 2023;
const job = '개발자';

// 프로퍼티 네임과 변수 이름이 같다면 하나만 작성해도 된다.
const user = {
  title,
  birth,
  job
};

// ----------------------------- //
function getFUllName() {
  return `${this.firstName} ${this.lastName}`;
};

const user = {
  firstName: 'Cracked',
  lastName: 'Egg',
  getFullName, // 한번만 작성 가능
};

const adming = {
  fristName: 'Hi',
  lastName: 'Hello',
  getFullName, // 한번만 작성 가능
};

console.log(user.getFullName());
console.log(admin.getFullName());

// ----------------------------- //
const user = {
  firstName: 'Cracked',
  lastName: 'Egg',
  getFullName() { // :와 function 키워드 생략 가능
    return `${this.firstName} ${this.lastName}`;
  };
};

console.log(user.getFullName());

// ----------------------------- //
// 계산된 속성명 (computed property name)
const user = {
  [표현식]:;
};

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

const Hello = {
  ['topic' + 'Name']: 'Modern JavaScript,
  [propertyName]: 2023,
  [getJob()]: '개발자',
};

console.log(Hello); // {topicName: "Modern JavaScript", birth: 2023, job: "개발자"}

4-1. 옵셔널 체이닝 (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);
}

if문을 활용할 수도 있지만, 일반적으로는 간결하게 AND 연산자를 활용해서 이 문제를 해결한다. 이마저도 객체의 이름이나 프로퍼티의 이름이 길어질수록 가독성이 나빠지는 문제가 있다. 이런 상황에 훨씬 더 코드를 간결하게 사용할 수 있는 문법치 바로 옵셔널 체이닝(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); // 함께 지내는 고양이가 없습니다.

5. 구조 분해 (Destructuring) I

// 구조 분해 (Destructuring)
const rank = ['A', 'B', 'C', 'D'];
const part = ['a', 'b' 'c', 'd', 'e'];

// 배열의 요소들이 순서대로 할당된다.
const [macbook, ipad, airpods, coupon] = rank;
const [num1, num2, num3, ...num4] = part; // 남은 나머지 요소를 마지막 변수에 배열로 할당한다. (마지막 변수에만 사용해야 한다.)

console.log(macbook); // A
console.log(ipad); // B
console.log(airpods); // C
console.log(coupon); // D

// --------------------------- //
let macbook = 'A';
let ipad = 'B';

[macbook, ipad] = [ipad, macbook];

배열을 할당할 떄 오른쪽의 값이 배열이 아니거나 아무것도 할당하지 않으면 오류가 발생한다. 또한 선언된 변수의 개수와 배열의 길이가 같을 필요는 없다. 할당된 배열의 길이가 길더라도 순서대로 할당된다. 만약 배열의 길이가 더 짧을 경우 남는 변수에는 undefined값이 할당된다. (프로퍼티에 기본값을 할당할 수 있다.)

6. 구조 분해 (Destructuring) II

const macbook = {
  title: '맥북 프로',
  price: 3690000,
  memory: '16GB',
  storage: '1TB SSD',
  display: '16형 Retina',
};

// const title = macbook.title;
// const price = macbook.price;
const { title, price } = macbook;

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

const { title: product, ...rest } = macbook; // 새로운 이름의 변수 선언

console.log(product); // 맥북 프로

배열과 마찬가지로 객체에 존재하지 않는 프로퍼티 값을 입력하면 undefined 값이 출력된다. 마찬가지로 기본값을 지정한는 것도 가능하다. 또한 마침표 세 개(...)를 통해 남은 프로퍼티를 하나의 변수에 객체로 만들 수도 있다.

변수 이름에 'serial-num'처럼 따움표를 사용해야 하는 변수이름들은 serialNum처럼 새로운 변수 이름을 지정해 주어야 한다.

7. 함수와 Destructuring

// 배열
function printWinners([macbook, ipad, airpods, ...coupon]) {
  console.log(`${macbook}`);
  console.log(`${ipad}`);
  console.log(`${airpods}`);
  
  for ( let user of coupon ) {
    console.log(`${user}`);
  };
};

const ranks = ['A', 'B', 'C', 'D', 'E'];
printWinners(ranks);

// 객체
const macbook = {
  title: '맥북 프로',
  price: 3690000,
  memory: '16GB',
  storage: '1TB SSD',
  display: '16형 Retina',
};

function printSummary({ title, color, price }) {
  console.log(`${title}`);
  console.log(`${color}`);
  console.log(`${price}`);
};

printSummary(macbook);

// DOM 이벤트
const btn = document.querySelector('#btn');

btn.addEventListener('click', ({ target }) => {
  target.classList.toggle('checked');
});

// 중첩 객체 구조 분해 (Nested Object Destructuring)
btn.addEventListener('click', ({ target: { classList } }) => {
  classList.toggle('checked');
});

중첩 객체 구조 분해는 가독성을 떨어뜨릴수 있기 때문에 권장되지는 않는다.


8. 에러와 에러 객체

// 에러와 에러 객체(name 프로퍼티와 message 프로퍼티)
// RefernceError : 존재하지 않는 변수나 함수를 호출할 때
// TypeError : 잘못된 방식으로 자료형을 다룰 때
// SyntaxError : 문법에 맞지 않는 코드를 작성할 때

// 의도적으로 에러 객체를 만드는 법
const error = new TypeError('타입 에러가 발생했습니다.');

throw error; // 이후 코드는 동작하지 않는다.

9. try catch문

// try catch문
try {
  // 코드
} catch (error) { // 변수명은 마음대로 해도 되지만 대부분 'error', 'err', 'e'로 지정한다.
  // 에러가 발생했을 때 동작할 코드
  // 에러가 발생한 문장 이후의 코드들은 동작하지 않는다.
}

9-1. try catch문 활용하기

// try catch문 활용하기
// 예외 처리 (Exception Handling)
function printMembers(members) {
  try {
    for (const member of members) {
      console.log(member);
    }
  } catch (err) {
    console.error(err); // 조금 더 익숙한 error문 출력 가능
    alert(`${err.name}가 발생했습니다. 콘솔창을 확인해 주세요.`);
  }
};

const teamA = ['A', 'B', 'C', 'D'];
printMembers(teamA); // 출력 O

const cracked = { name: 'cracked' };
printMembers(cracked); // Error

const teamB = ['a', 'b', 'c', 'd'];
printMembers(teamB); // 출력 O (이전에 오류가 발생해도 출력이 된다.)

try문 안에서 만든 변수는 catch문 또는 외부에서 사용이 불가능하다.

9-2. finally문

try...catch는 사실 finally라는 코드 블록을 하나 더 가질 수 있다. finally문은 try...catch문이 끝난 다음에 최종적으로 실행될 코드를 다룰 때 활용된다.

(1) finally문

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

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

try문에서 에러가 발생하지 않는다면 try문의 코드가 모두 실행된 다음에, try문에서 에러가 발생한다면 catch문의 코드가 모두 실행된 다음 실행할 코드를 finally문에 작성하면 된다. 즉, try문에서 어떤 코드를 실행할 때 에러 여부와 상관 없이 항상 실행할 코드를 작성하는 것이다.

(2) finally문에서의 에러 처리

finally문에서 에러가 발생한 경우에는 다시 그 위에 있는 catch문으로 넘어가지 않는다. 만약 finally문에서도 에러 처리가 필요한 경우에는 try...catch문을 중첩해서 활용하는 방법이 있다.

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

10. 종합 정리

(1) 조건부 연산자 (Conditional operator)

const cutOff = 80;

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

console.log(passChecker(75));

삼항 연산자 (Ternary operator)라고도 불리는 이 연산자는 자바스크립트에서 세 개의 피연산자를 가지는 유일한 연산자이다. 간단한 조건식의 경우에는 if문보다 훨씬 더 간결하게 표현할 수 있는 장점이 있지만 내부에 변수나 함수를 선언한다거나 반복문 같은 표현식이 아닌 문장은 작성할 수 없다는 한계가 있기 때문에 if문을 완벽하게 대체할 수는 없다.

(2) 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!

(3) 모던한 프로퍼티 표기법

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

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

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

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

console.log(user); // {title: "cracked_egg", 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);

(4) 구조 분해 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 parameter, 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 저장 장치"}

(5) 에러와 에러 객체

자바스크립트에서 에러가 발생하면 그 순간 프로그램 자체가 멈춰버리고 이후의 코드가 동작하지 않게 된다. 그리고 에러가 발생하면 에러에 대한 정보를 namemessage라는 프로퍼티로 담고 있는 에러 객체가 만들어지게 되며, 대표적인 에러 객체는 SyntaxError, ReferenceError, TypeError이다.

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

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

(6) try...catch문

try...catch문은 자바스크립트에서 대표적인 에러 처리 방법이다.

try {
  // 실행할 코드
} catch (error) {
  // 에러 발생 시 동작할 코드
}

try문 안에서 실행할 코드를 작성하고, try문에서 에러가 발생한 경우에 실행할 코드를 catch문 안에 작성하면 된다. 이 때 try문에서 발생한 에러 객체가 catch문의 첫 번째 파라미터로 전달된다. 만약, try문에서 에러가 발생하지 않을 경우 catch문의 코드는 동작하지 않는다.

에러의 유무와 상관없이 항상 동작해야할 코드가 있다면 finally문을 활용할 수도 있다.

try {
  // 실행할 코드
} catch (error) {
  // 에러가 발상했을 때 실행할 코드
} finally {
  // 항상 실행할 코드
}
profile
프론트엔드 지식으로 가득찰 때까지

0개의 댓글