프론트 공부/JavaScript와 모던 JS

모던 자바스크립트 기본 문법 정리

홍구리당당 2024. 1. 17. 20:29
  • 코드잇 인강을 들으면서 개인적으로 정리한 글입니다.

모던 자바스크립트 기본 문법 정리.

0. 목차

  1. 모던자바스크립트란
  2. 데이터 타입
  3. AND, OR 연산
  4. null 병합 연산자
  5. 변수와 스코프
  6. 함수
  7. 문장식과 표현식
  8. spread
  9. 옵셔널 체이닝
  10. 구조분해
  11. 모듈

1. 정리

1. 모던 자바스크립트?

js 를 공부하다보면 ECMAScript 에크마 스크립트라는 용어를 보게 된다.

ECMAScript: 자바스크립트 표준 명세서, 즉 js 언어의 표준이라 이해하면 됨. js가 점점 발전되고 여러 기능이 추가되면서, ECMA라는 국제 표준화 기구가 표준 룰을 관리하게 되는데 그 때 이 룰을 ECMA-262 라는 이름의 문서로 관리함. 이 문서의 내용이 바로 ECMAScript 인 것이다!!

시간에 따라 표준 룰도 바뀌고 js의 버전이 바뀌면서, 이 ECMAScript도 여러 버전들이 생겨왔다. ECMAScript version 1, version 2 ... 이걸 줄여서 ES1, ES2 이렇게 부르다가 2015년에 6번째 버전인 ES6가 등장했다!!

지금은 1년마다 ES 표준을 출시하기로 결정돼서 공식 명칭은 ES2015, ES2016 같은 거지만, 일단 js에 가장 큰 영향을 준 건 ES 버전 6인지라, 보통 ES6을 말한다. 얼마나 ES6가 대단한지 ES6 이후의 버전들을 묶어서 ES6+라고 하기도 한다.

하여간 js는 이렇게 빠르게 발전하는 언어지만, 웹브라우저가 그 속도를 따라가지 못하고 js의 일부 문법을 지원하지 못할 수도 있다. 그래서 현 시점에서 사용하기 적합한 범위 내에서 최신 버전의 표준을 준수하는 js를, Modern Javascript 라고 부르고, 이를 사용하게 됨!!

즉 모던 자바스크립트는 js에서 벗어난 새로운 언어인 게 아니라, ESMAscript 버전을 준수하면서 동시에 웹 브라우저들이 지원하는 범위 내에서 기능을 제공하는 js 언어 버전인 것임.

그리고 js는 프로그래밍 언어, ECMAScript는 언어가 아니라 js의 표준 룰. 그래서 JS는 ECMAScript를 기반으로 하지만 에크마엔 없는, 부가적인 기능도 제공한다. 예를 들어 js에서 제공하는 DOM control 문법들은 ECMAScript 표준 문법이 아니라, WebIDL에 표준화된 기술.

참고로 이 사이트는 웹브라우저들이 js 버전을 어디까지 지원하는지 말해주는 표이다!!
https://compat-table.github.io/compat-table/es6/

2. 데이터 타입

js는 굉장히 유연한 언어이고, 따로 데이터 타입을 정의해주지 않는다. (그래서 처음에 js 배울 때 굉장히 애먹은... ;;) 그치만 js에는 엄연히 데이터 타입 개념이 존재하고, 이를 잘 이해하고 있어야 한다.

  • 데이터 타입 종류
  • 기본형: primitive Type: Number | String | Boolean | Null | Undefined | Symbol | BigInt
  • 참조형: Reference Type: Object

다른 언어들과 데이터 타입이 비슷하긴 한데, ES2015에 Symbol, ES2020에 BigInt 형이 추가되었다!

Symbol 형:

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

const user = Symbol("this is a only-user!!");
const user2 = Symbol("this is a only-user!!");

console.log(user === "this is a only-user!!"); // false
console.log(user === 'user'); // false
console.log(user === user2); // false
BigInt 형:

(이건 내가 java 공부할 때 봤던 건데... 여기서도 추가됐네.)

엄청 큰 정수를 표현할 때 쓰는 데이터 타입이다. 웬만해서 쓸 일은 없을텐데, 가끔 백준 문제를 풀 때 필요할 거 같긴 하다. (자바로 문제 풀 때도 BigInt 형을 썼던 기억이 있다. ㅡ.ㅡ;)

참고로 정수만 가능하고, 소수점이 double이나 float처럼 소수점이 붙어있으면 못 쓴다. js가 지멋대로 소수점 아래 부분은 버리고 정수 형태 숫자를 리턴한다.

BigInt 형끼리만 사칙연산이 가능하고, 만약 BigInt와 Number 형 숫자를 계산하려면 BigInt형을 Number로 타입 변환 해줘야 한다.

// 숫자 뒤에 n을 붙이거나, BigInt 형 새 인스턴스를 만들어 사용하기.
consolse.log(1111111111111111111111n);
consolse.log(BigInt(11111111111111111111));

3n * 2; // TypeError
3n * 2n; // 6n
Number(3n) * 2; // 6
타입을 확인할 때엔 typeof 연산자 쓰기.
console.log(typeof "aaa"); // string;
console.log(typeof("aaa")); // string;
형변환할 때 false로 평가되는 값, true로 평가되는 값.

if (조건) ~ 문에서, 조건이 굳이 true, false로 판가름되는 식이 아니더라도 js가 자동으로 형변환해준다.

const a = "a";
if (a) {... } // string을 true로 형변환함.

이렇게 js가 형변환을 할 때, false로 평가되는 값들이 있고 true로 평가되는 값들이 있다.

  • false로 평가되는 값: falsy 값이라고 불림.

    • boolean 형의 false
    • null 형의 null
    • undefined 형의 undefined
    • Number 형의 NaN, 0
    • string형의 ""
  • true로 평가되는 값: false 값 이외. truthy 값이라 불림.

    • 참고로 {}, [] 같이 빈 배열, 빈 객체도 true로 평가됨.

3. AND 와 OR 연산자

js에서는 AND와 OR 연산자가 무조건 boolean 형 값을 내놓지 않고, 좌변과 우변 둘 중 하나를 선택하는 방식으로 동작한다. 이 개념 덕분에 리액트에서 조건부렌더링이 쉬워진다...

1. 좌변 AND 우변
2. 좌변 OR 우변

AND 연산자에선
- 좌변이 truthy일 경우 바로 우변을 출력.
- 좌변이 falsy일 경우 우변은 안 보고 바로 좌변을 출력.
OR 연산자에선
- 좌변이 truthy일 경우 우변은 안 보고 바로 좌변을 출력.
- 좌면이 falsy일 경우 바로 우변을 출력.

예시.

console.log(true && false) // 좌변이 truthy니까 우변 false 출력.
console.log(false && true) // 좌변이 falsy니까 우변 보지도 않고 바로 좌변 false 출력.
console.log("This is string1" && "This is string2") // 좌변이 truthy 값이니 우변 "This is string2" 출력됨. true가 출력되는 게 아님!
console.log(null && "This is string3") // 좌변이 falsy 값이니 그냥 그대로 null 출력됨.

console.log(true || false) // 좌변이 truthy니까 우변 보지도 않고 바로 좌변 true 출력.
console.log(false || false) // 좌변이 falsy니까 우변의 false를 바로 출력.
console.log("This is string1" || null ) // 좌변이 truthy 값이니 우변 보지도 않고 바로 "This is string1" 출력.
console.log("" || undefined ) // 좌변이 falsy 값이니 바로 우변 undefined가 출력됨.

참고로 우선순위는 AND가 OR보다 높다!!! 그래서 AND, OR 여러 개를 섞어쓸 때엔 괄호를 써서 계산 순서를 명시해주는 게 좋다.

4. null 병합 연산자 ??

AND, OR 말고 또 유용한 연산자가 있는데 바로 null 병합 연산자이다. ES2020에서 새로 추가되었다!! 영어로는 'nullish coalescing'이라 하는 모양.

AND, OR이 falsy와 truthy 값을 가려낸다면, null 병합 연산자는 OR 처럼 연산하되 null이나 undefined만 가려낸다.

console.log("a" ?? "b"); // 좌변이 undefined, null이 아니니 바로 좌변을 출력. "a"
console.log(false ?? "a"); // 좌변이 undefined, null이 아니니 바로좌변을 출력. false 
console.log(undefined ?? "b"); // 좌변이 undefined니 바로 우변을 출력. "b"
console.log(null ?? false); // 좌변이 null이니 바로 우변을 출력. false

만약 false || "a" 였다면 "a"가 출력되었겠지만, false ?? "a" 에선 false가 출력된다.

생각보다 겁나 유용하다. 본인은 리액트 조건부 렌더링에서 많이 썼으니 기억해두자.

5. 변수와 스코프

옛날 js 글들을 보면 var 키워드로 변수를 선언했었다. (사실 나도 대학교에서 그렇게 배웠다...)

그런데 ES2015 이후엔, letconst를 더 자주 쓰게 되었다.

var키워드를 사용하면 hoisting이라든가 scope라든가 조금 문제가 있어 요즘엔 let, const가 권장된다. 참고로 var 키워드를 사용할 때의 문제는 기술면접 단골 문제라 하니 나중에 잘 공부해두자.

하여간 우리가 기억해둘 것은, hoisting 문제를 해결하기 위해, scope를 코드 블록으로 한정하기 위해 let과 const 키워드를 쓰게 되었단 것!!

6. 함수

함수 만드는 법
  1. function declaration, 함수 선언은 가장 일반적인 방법. fucntion 키워드를 통해 선언하는 방식.

  2. 함수를 변수에 할당하는 방법은 function expression, 함수 표현식이라 한다. 어떤 자리에 콜백함수 형식으로 함수를 할당하는 등, 함수를 변수처럼 활용하는 방식들을 모두 function expression이라 함!!

기명 함수 표현식

named function expression이라고도 하고, 함수표현식을 쓸 때 함수에 이름을 붙이는 방식임.

arrow function을 쓰든 function 키워드를 쓰든 보통 함수 표현식으로 변수에 함수를 할당할 때엔 이름을 안 적어도 자동으로 name 프로퍼티를 가짐.

const printHi = function () {
    console.log("Hi!");
} // printHi는 그냥 변수 이름이고, 함수의 이름은 아님!! 지금은 무기명 함수라서 함수의 name 프로퍼티에 자동으로 sayHi 값이 들어가게 됨.

그런데 우리가 이름을 직접 붙여줄 수도 있긴 하다.

const printHi = function printHiFunction() {
    console.log("Hi!");
} // printHi는 그냥 변수 이름, 이 함수의 name 프로퍼티에는 printHiFunction 이라는 값이 들어가게 된다.

물론 우리가 함수의 name을 설정했다 해서 함수를 외부에서 호출할 때 쓸 수 있는 건 아니다. 이 name 프로퍼티는 함수의 내부에서 함수 본인을 가리킬 때 쓴다.

const printHi = function printHiFunction() {
      console.log(typeof printHiFunction);
    console.log("Hi!");
} 

printHi(); // 정상 작동
printHiFunction(); // reference error 발생!

보통 이렇게 이름을 적어주는 건 재귀함수를 만들 때 정도이다. 내부에서 자기 자신을 또 호출해야 하니까!

즉시 실행 함수

보통 함수는 선언할 때와 호출할 때가 나뉘어있다!!

function 키워드로 함수를 만든다해서 함수가 그 자리에서 바로 실행되는 건 아니다.
printHi(); 이렇게 내가 원하는 자리에 함수를 호출해야, 그제야 함수가 실행된다.

반면 IIFE 즉시 실행 함수는 선언과 동시에 그자리에서 실행된다.

재사용성이 없는 함수라 보통은 이름 없이 익명으로 사용하지만, 가끔 재귀함수 식으로 구현할 때나 특이 케이스에선 기명 함수로 표현할 수도 있긴 있다.

어쨌거나 js에서 함수는 값으로도 활용 가능한 독특한 객체이다.

그래서 필요하다면 객체의 property로 함수를 넘겨줄 수도 있긴 한데, (메소드 선언 방식) 실제로 잘 안쓴다고 알고 있다... 그냥 알아두면 좋을듯?

고차함수라는 응용 개념도 있긴 있지만... react에서 hoc을 사용할 때에나 접했던 개념이고, 많이 본 적은 없다. 그치만 알아두면 요긴할 것같으니 나중에 따로 공부해보겠다.

이렇게 함수를 값처럼 자유롭게 쓸 수 있는 걸 일급 함수, first class function이라 하고, js는 일급 함수를 가진 프로그래밍 언어라고 기억하면 좋을 것이다!!

parameter

다른 언어들과 통용되는 개념이니 간단하게 정리하자.

함수 외부에서 함수에게로 특정 값을 넘겨주기 위해 선언하는 것이 파라미터이고, 함수를 호출할 때 파라미터로 특정 값을 넣어 호출하게 되는데 이 특정 값을 아규먼트라고 한다.

즉,

function printName(name){ // 함수의 parameter은 name
    console.log(name);
}

printName("홍길동"); // argument로 "홍길동" 을 넘겨줌.

참고로 js는 아무 인자도 안 넘겨준 채 파라미터가 있는 함수를 넘겨준다면 아규먼트를 undefined로 간주한다.
즉, 위의 함수에서 printName(); 을 할 경우 에러 나지 않고 console.log(undefined); 가 된다는 것!!!

리액트 컴포넌트로 인자를 넘겨줄 때, 아무 인자도 없이 그냥 컴포넌트를 호출하면 에러가 안 나고 작동은 되던데 이런 이유 때문이었다...

function Nav({userInfo}){
    // ...
}

<Nav /> // 얘도 에러 안 남. userInfo 인자로 undefined를 받았다고 자동 입력돼서...
<Nav userInfo = {user...}/>
arguments와 rest parameter

함수 parameter을 다룰 때 쓰는 문법으론 arguments이 있었다.

그런데 최신 문법 rest parameter이 나오면서, argument는 점차 안 쓰게 되었다....

먼저 arguments는 함수 내부에서 인자를 다룰 때 쓰는 특별한 객체이다. 함수는 arguments라는 속성을 자동으로 갖게 되는데, 유사 배열 형태로 함수가 받은 인자들이 담겨있다. 유사 배열이라 forEach 등의 메소드는 사용할 수 없다!!!

function printArgs(a, b, c){
    console.log(arguments);
}

printArgs("aaa", "bbb", "ccc"); // ["aaa", "bbb", "ccc"] 가 출력됨. 주의!!! 배열 아님!! 

그런데 이 arguments는 일단 배열을 리턴하는 게 아니고, 함수에 전달된 모든 arguments 전체를 다루기 때문에 slice하기 불편한 문제가 있다. 그래서 ES2015 에 rest parameter 문법이 등장한 것!!

function printRests(...args){ // 이 args 라는 이름의 배열에 인자들이 담김.
    console.log(args);
      console.log(args.splice(0, 2));
}

function printRests(a, b, ...others){ // 이 others 라는 이름의 배열에 인자들이 담김.
    console.log(args);
      console.log(args.splice(0, 2));
}

printRests("aaa", "bbb", "ccc"); // ["aaa", "bbb", "ccc"]
arrow function

arrow function도 ES2015에 등장한 문법이다. 보통 다른 함수의 아규먼트로 선언할 때나, 함수 표현식으로 변수에 함수를 할당할 때 자주 쓰이는 무기명 함수 선언.

사실 arrow function은 생략 가능한 부분이 많아서 코드를 쓸 때 짧고 간편하게 쓸 수 있어 좋긴 하다.

그치만 this 문법이라든가... 하여간 일반적인 함수 선언식과는 조금 다른 부분이 있어 주의해야 하긴 한다. (this는 arrow function 내에선 가변적이지 못하고 외부에서 가장 마지막에 정해진 this 값으로 고정된다는 문제가 있는데, 이건 나중에 따로 공부해보자.)

참고로 arguments 문법도 arrow function에선 못 쓰긴 하지만 요즘엔 거의 rest parameter을 쓰니 이건 문제될 게 없다.

7. 문장과 표현식

문장 statement: 어떤 동작이 일어나도록 작성된 최소한의 코드 덩어리.

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

8. spread

위에서 본 rest parameter과 비슷함! ES2015에 등장한 문법이다. (2015년 개발자들에게 대체 뭔 일이 있던 것임?)

const numbers = [1, 2, 3];
console.log(,,,numbers); // 1 2 3 출력됨. 

객체 spread는 진짜 자주 쓰인다.

  1. 객체나 배열을 복사해서 새로운 객체를 만들 때.
    const oldArray = [1, 2, 3];
    const newArray = [...oldArray]; 
    const newArrayPlusFour = [...oldArray, 4]; // 뒤에 원소 추가도 가능! 
    const newArrayLinked = [...oldArray, ...newArray]; // [1, 2, 3, 1, 2, 3]

참고로 객체 spread와 배열 spread는 좀 다르다!!! ES2015에선 배열만 spread가 가능했고, 그러다 ES2018에 와서야 객체 spread 문법이 추가되었는데... (뭐임?)

하여간, 객체를 spread해서 새로운 객체를 만드는 식으로 가능하단 걸 알아두면 좋을 것이다.

나중에 리액트에서 useState로 객체를 관리할 때 꼭 알아야 할 개념이다.

9. 옵셔널 체이닝

나중에 리액트에서 정말 많이 쓰일 문법!!! ES2020에서 새로 등장한 문법이다.

보통 obj.property 로 한 객체의 프로퍼티에 접근하는데, 만약 obj가 null 혹은 undefined라면 에러가 뜬다.

이걸 막기 위해 옵셔널 체이닝을 쓴다!!

const name = obj?.name || "익명";

만약 obj 객체가 있다면 객체의 name 프로퍼티에서 값을 가져오고, obj 객체가 없거나 name 프로퍼티 값이 없다면 "익명" 이라는 string을 name 변수에 넣어라!

10. 구조분해

이 문법도 리액트에서 주구장창~ 쓰인다.

배열과 객체를 분해해서 각 변수마다 원소들을 넣어주는 방식이다.

// 배열 구조분해
const numbers = [1, 2, 3];
const [a, b, c] = numbers; 
console.log(a); // 1 출력.
console.log(b); // 2 출력.

// 객체 구조분해
const user = {
    name: "홍길동",
      age: 20,
      birthday: "1999-01-01",
}
const {name, birth, age} = user;
console.log(name) // 홍길동
console.log(age) // 20 >> ???
console.log(birth) // undefined >>??????

위에서 예시를 들었는데 맨 마지막을 보면 console.log(birth)에서 undefined가 뜬다. 이게 배열 구조분해와 객체 구조분해의 가장 큰 차이이다!!!

배열 구조분해를 할 때엔 변수 이름을 뭘로 정하든 상관 없이 배열 순서대로 원소들이 차곡차곡 들어간다.

const numbers = [1, 2, 3];
const [a, b, c] = numbers;
const [one, two, three] = numbers;

이렇게 변수 이름을 뭘 하든 상관 없다!!! 그냥 순서대로 1은 a, 2는 b, c에는 3 ... 이 들어간다.
만약 const [b, a, c] = numbers; 라 했다면, b에 1, a에 2, c에 3 값이 들어갔을 것이다.

반면 객체는 변수 이름이 중요하다!!! 이름이 객체의 프로퍼티 이름과 같아야 한다.

그래서

const user = {
    name: "홍길동",
      age: 20,
      birthday: "1999-01-01",
}
const {name, birth, age} = user;
console.log(name) // 홍길동
console.log(age) // 20 >> ???
console.log(birth) // undefined >>??????

위 코드에서 birth에 age 값이 들어가지도 않았고, birth에 birtthday 값이 들어가지도 않은 것이다.

참고로 배열 구조분해에선 rest 문법도 섞어쓸 수 있다.

const numbers = [1, 2, 3, 4, 5, 6];
const [a, b, ...rest] = numbers;

이러면 a는 1, b는 2, rest는 3, 4, 5, 6 이다.

11. 모듈

자바스크립트는 객체지향 언어이지만, class를 활용하는 언어는 아니다. (물론 나중에 class 문법이 추가되었다곤 하지만 객체로 모든 게 해결됨... class는 단지 타 언어에서 js로 넘어올 때 러닝커브를 줄이기 위해 들여온 문법이라고 알고 있다.)

대신 js에선 모듈이란 단어를 쓴다!

원래 js에선 모듈을 만들고 export import하는 방식이 다양했는데, ES2015년을 기준으로 표준 모듈화 방식이 생겼다.

  1. 먼저 html 파일의 body 태그 안에 <script type="module" src="App.js"></script> 를 써주자. js를 모듈화해서 사용한다는 뜻이다. type이 module이 아니라면, 모듈 스코프가 없어서 좋지 않음.

  2. 이제 사용할 js 함수나 변수 앞에 export 키워드를 붙이자.

  3. export 한 함수를 사용하기 위해선 import문을 작성하자.

참고로 import문에서 이름을 바꾸어 import할 수도 있다. as 키워드를 쓰면 됨!!

import {title as printerTitle, print} from "./printer.js";

const title = "this title";
print(title);
print(printerTitle);