4. Node 배우기전에 (ES2015+ 간단 학습)

ES2015+

1. ES6 – 역사적인 변화

  • 2015년 자바스크립트 문법에 매우 큰 변화가 있었다.
  • 2015년을 기점으로 매년 분법 변경 사항이 발표되고 있다.
  • 최신 버전은 ES2023.

인터넷 익스플로러와 같은 구형 브라우저에서 최신 문법을 사용할 수 없지만, 요즘에는 바벨(babel)처럼 구형 브라우저에
맞게 문법을 변환해주는 도구가 널리 쓰이므로 큰 문제가 되지 않는다.

2. const, let

보통 자바스크립트를 배울 때는 var로 변수를 선언하는 방법부터 배운다.
하지만 var은 이제 constlet이 대체 한다.

const와 let이 공통적으로 갖는 특징인 블록 스코프(범위)

// 정상 출력되는 코드
if(true) {
	var x = 3;
}
console.log(x); //3

// 에러가 발생하는 코드
if(true) {
	const y = 3;
}
console.log(y);

x는 정상적으로 출련된다. 하지만 y는 에러가 발생하는데 var을 const로 바꿧을 뿐인데 차이가 생겼다. var은 함수 스코프를 가지므로 if문의 블록과 관계없이 접근할 수 있다.
하지만 const와 let은 블록 스코프를 가지므로 블록 밖에서는 변수에 접근할 수 없다.

블록의 범위

  • if
  • while
  • for
  • function

등등에서 볼 수 있는 중괄호 { 와 } 사이 이다.
함수 스코프 대신 블록 스코프를 사용함으로써 호이스팅 같은 문제도 해결되고 코드 관리도 수월해졌다.

const, let과 var의 차이

  • const는 한번 값을 할당하면 다른 값을 할당할 수 없다.
    • 다른 값을 할당하려고 하면 에러가 발생한다.
    • 초기화할 때 값을 할당하지 않으면 에러가 발생한다.
    • const로 선언한 변수를 상수라고 부른다.
const a = 0;
a = 1; // Type Error : Assignment to constant variable

let b = 0;
b = 1; // 1

💡 const와 let 중에서 어느 것을 써야 할까? 자바스크립트를 사용할 때 한 번 초기화했던 변수에 다른 값을 할당하는 경우는 의외로 적다. 따라서 변수 선언 시에는 기본적으로 const를 사용하고, 다른 값을 할당해야 하는 상황이 생겼을 때 let을 사용하면 된다.

3. 템플릿 문자열

새로운 문자열이 생겼다. 이 문자열은 큰따옴표나, 작음따옴표로 감싸는 기존 문자열과 달리 백틱(`)으로 감싼다. 특이한 점은 문자열 안에 변수를 넣을 수 있다는 것이다.

// 기존 ES 문법
var num1 = 1;
var num2 = 2;
var result = 3;
var string1 = num1 + ' 더하기 ' + num2 + '는 \\'' + result + '\\'';
console.log(string1) // 1 더하기 2는 '3'

문자열 string1은 띄어쓰기와 변수, 더하기 기호 때문에 가독성이 좋지 않다.
또한, 작은 따옴표를 이스케이프하느라 코드가 지저분하다.

// 바뀐 문법
const num3 = 1;
const num4 = 2;
const result = 3;
const string2 = `${num3} 더하기 ${num4}' ${result2}'`
console.log(string1) // 1 더하기 2는 '3'

${변수} 형식으로 변수를 더하기 기호 없이 문자 열에 넣을 수 있다.
기존 따옴표 대신 백틱을 사용하므로 큰따옴표나 작은따옴표와 함께 사용할 수도 있습니다.

4. 객체 리터럴

객체 리터럴에 편리한 기능들이 추가되었다.

// oldObject 객체에 동적으로 속성을 추가하는 예제
// ES5 이전
var sayNode = function() {
	console.log('Node');
}
var es = 'ES';
var oldObject = {
	sayJS: function() {
		console.log('JS');
	},
	sayNode: sayNode,
};
oldObject[es+6] = 'Fatastic';
oldObject.sayNode(); // Node
oldObject.sayJs(); // JS
console.log(oldObject.ES6) // Fantastic

// ES6 이후
const newObject = {
	sayJS() {
		console.log('JS');
	},
	sayNode,
	[es + 6]: 'Fantastic'
}
newObject.sayNode(); // Node
newObejct.sayJS(); // JS
console.log(newObject.ES6) // Fantastic
  • sayJS 같은 객체의 메서드에 함수를 연결할 때 더는 콜론(:)과 function을 붙이지 않아도 된다.
  • sayNode: sayNode처럼 속성명과 변수명이 동일한 경우에는 한 번만 써도 되게 바뀌었다. 이때 코드의 중복을 피할 수 있어 편리하다.

객체의 속성명은 동적으로 생성할 수 있다. 예전 문법에서는 ES6 속성명을 만들려면 객체 리터럴 바깥에서 생성해야 했지만, ES2015 문법에서는 객체 리터럴 안에 동적 속성을 선언해도 된다.

5. Arrow Function (화살표 함수)

화살표 함수라는 새로운 함수가 추가되었으며, 기존 function() {}도 그대로 사용할 수 있다.

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

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

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

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

function not1(x) {
	return !x;
}

const not2 = x => !x;
  • add1, add2, add3, add4 모두 같은 기능을 하는 함수 이다. 마찬가지로 not1, not2도 같은 기능을 한다.
  • 화살표 함수는 function ⇒ 기호로 사용한다.
  • 변수에 대입하면 나중에 재사용할 수 있다.
  • 내부에 return문 밖에 없는 경우, return문을 줄일 수 있다. 중괄호 대신 add3과 add4처럼 return할 식을 바로 적으면 된다. 또는 add4처럼 보기 좋게 소괄호로 감쌀 수도 있다. not2처럼 매개변수가 한 개이면 매개변수를 소괄호로 묶어주지 않아도 된다.
  • return문을 줄이는 문법은 자주 사용하므로 눈여겨 보자.

기존 Function과 다른점은 무엇일까? this 바인드 방식

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);
       });
}, };
relationship1.logFriends();
   

const relationship2 = {
     name: 'zero',
     friends: ['nero', 'hero', 'xero'],
     logFriends() {
       this.friends.forEach(friend => {
         console.log(this.name, friend);
}); },
   };
   relationship2.logFriends();
  • relationship1.logFriends() 안의 forEach문에서는 function 선언문을 사용했는데 각자 다른 함수 스코프의 this를 가지므로 that이라는 변수를 사용해서 relationship1에 간접적으로 접근하고 있다.
  • relationship2.logFriends() 안의 forEach문에서는 화살표 함수를 사용했다. 따라서 바깥 스코프인 logFriends()의 this를 그대로 사용할 수 있다. 상위 스코프의 this를 그대로 물려받은것이다.

기본적으로 화살표 함수를 쓰되, this를 사용해야 하는 경우에는 화살표 함수와 함수 선언문(function) 둘 중 하나를 고르면 된다.

6. 구조 분해 할당

구조 분해 할당을 사용하면 객체와 배열로부터 속성이나 요소를 쉽게 꺼낼 수 있다.

구조분해 할당 이전

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;
  • 해당 문번은 유효한 문법으로 candyMachine 객체 안의 속성을 찾아서 변수와 매칭한다.
  • count 처럼 여러 뎁스안에 있는 속성도 찾을 수 있다. 결과적으로 getCandy와 count 변수가 초기화 된것이다.
  • 구조 분해 할당을 사용하면 함수의 this가 달라질 수 있다. 달라진 this를 원래대로 바꿔주려면 bind 함수를 따로 사용해야한다.

배열 구조분해 할당

// 할당 이전
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;
  • 어색하거나 좀 잘못된거 같지만 나름대로 규칙이 있다.
    • node, obj와 bool의 위치를 보면 배열의 첫 번째 요소, obj는 두 번째 요소, bool은 네번 째 요소라는 것을 알 수 있다. obj와 bool의 사이의 요소인 10에는 변수명을 지어주지 않았으므로 10은 무시하도록 처리된다.

7. Class의 추가

  • 다른 언어 처럼 클래스 기반으로 동작하는 것이 아니라 여전히 프로토타입 기반으로 동작한다. 프로토타입 기반 문법을 보기 좋게 클래스로 바꾼 것으로 이해하면 된다.

프로토타입 상속 예제 ( 클래스 이전 문법

// 클래스 이전 문법
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
  • Human 생성자 함수가 있고, 그 함수를 Zero 생성자 함수가 상속한다. Zero 생성자 함수를 보면 상속 받기 위한 코드가 상당히 난해한걸 볼 수 있다.
  • Human.apply와 Object.create 부분이 상속받는 부분이다.

Class 기반 코드로 변경

class Human {
     constructor(type = 'human') {
       this.type = type;
     }

      static isHuman(human) {
         return human instanceof Human;
      }

      breathe() {
	alert('h-a-a-a-m');
      } 
}

class Zero extends Human {
    constructor(type, firstName, lastName) {
      super(type);
      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
  • 전반적으로 코드가 class 안으로 그룹화 되어있다.
  • 생성자 함수는 constructor로 사용한다.
  • Human.isHuman 같은 클래스 함수는 static 키워드로 전환되었다.
  • 프로토 타입 함수들도 모두 class 블록 안에 포함돼서 어떤 함수가 어떤 클래스 소속인지 확인하기 쉽다.
  • 상속도 간단해져서 extends 키워드로 쉽게 상속할 수 있다.

하지만 클래스 문법으로 바뀌었더라도 본질은 프로토타입 기반으로 작동한다는걸 잊으며 안된다.

8. Promise 기반

자바스크립트와 노드에서는 주로 비동기를 접한다. 특히 이벤스 리스너를 사용할 때 콜백 함수를 자주 사용한다. ES2015부터는 자바스크립트와 노드의 API들이 콜백 대신 프로미스(Promise) 기반으로 재구성되며, 악명 높은 콜백 지옥(CallBack Hell) 현상을 극복했다라고 평가 받고 있다.

Promise는 반드시 알아둬야할 객체뿐만 아니라 다른 자료를 참고해서라도 꼭 숙지해야 한다.

프로미스의 규칙

  • 프로미스 객체 생성
const condition = true; // true이면 resolve, false이면 reject 
const promise = new Promise((resolve, reject) => {
	if (condition) { 
		resolve('성공');
	}else{ 
		reject('실패');
	} 
});
// 다른 코드가 들어갈  있음
promise
  .then((message) => {
		console.log(message); // 성공(resolve) 경우 실행 
	})
	.catch((error) => {
		console.error(error); // 실패(reject) 경우 실행
	})
	.finally(() => { // 끝나고 무조건 실행
		console.log('무조건'); 
	});

new Promise로 프로미스를 생성할 수 있으며, 안에 resolve와 reject를 매개변수로 갖은 콜백 함수를 넣는다. 이렇게 생성하면 promise 변수에 then과 catch 메서드를 붙일 수 있다. Promise 내부에서 resolve가 호출되면 then이 실행되고, reject가 호출되면 catch가 실행된다. finally 부분은 성공/실패 여부와 상관없이 실행된다.

  • resolve와 reject에 넣어준 인수는 각각 then과 catch의 매개변수에서 받을 수 있다.
  • then은 message가 성공, catch는 error가 실패로 처리된다.

그래서 프로미스가 뭔데?

프로미스를 쉽게 설명하면, 실행은 바로 하되 결과로 나온 값은 나중에 받는 객체다. 결과값은 실행이 완료된 후 then이나 catch 메서드를 통해 받는다. 예제에 new Promise와 promise.then 사이에 다른 코드가 들어갈 수도 있다. 그 결과 new Promise는 바로 실행되지만, 결과값은 then을 붙였을 때 받게 된다.

  • then이나 catch에서 다시 다른 then이나 catch를 붙일 수 있는데, 이전 then의 return 값을 다음 then의 매개변수로 넘긴다. return한 경우 프로미스가 수행된 후 다음 then이나 catch가 호출된다.
promise
                .then((message) => {
            	    return new Promise((resolve, reject) => {
            	      resolve(message);
            	    });
            	})
		.then((message2) => {
		   console.log(message2);
		   return new Promise((resolve, reject) => {
			     resolve(message2);
		   });
		})
		.then((message3) => {
			  console.log(message3);
		})
		.catch((error) => {
		       console.error(error);
		});
  • 처음 then에서 message를 resolve하면 다음 then에서 message2로 받을 수 있다. 여기서 다시 message2를 resolve한 것을 다음 then에서 message3로 받았다.
  • 단, then에서 new Promise를 return해야 다음 then에서 받을 수 있다는 것을 기억해야한다.
    • 이것을 활용해서 콜백을 프로미스로 바꿀 수 있다.

콜백을 쓰는 패턴중 하나의 방법 ( 프로미스로 변경해보기

// 3 콜백으로 프로미스로 바꾸기 
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);
    }); 
}
  • 코드의 깊이가 세 단계 이상 깊어지지 않는다.
  • then 메서드들이 순차적으로 실행된다.
  • 콜백에서 매번 따로 처리해야 했던 에러도 마지막 catch에서 한 번에 처리할 수 있다.

모든 콜백 함수를 위와 같이 바꿀 수 없다. 메서드가 프로미스 방식을 지원해야 한다.

Promise 한번에 실행하기

const promise1 = Promise.resolve('성공1'); 
const promise2 = Promise.resolve('성공2'); 

Promise.all([promise1, promise2])
	.then((result) => {
		console.log(result); // ['성공1', '성공2'];
        })
        .catch((error) => {
                console.error(error);
        });
  • Promise.all에 넣으면 모두 resolve가 될 때까지 즉, 실행이 완료될 때 까지 기다렸다가 then으로 넘어간다.
  • 결과값은 배열로 각각 들어가 있다.
  • 하나라도 reject가 되면 catch로 넘어간다.

Promise reject 체크 하기

const promise1 = Promise.resolve('성공1');
const promise2 = Promise.reject('실패2');
const promise3 = Promise.resolve('성공3'); 

Promise.allSettled([promise1, promise2, promise3])
  .then((result) => {
    console.log(result);
/*
	{ status: 'fulfilled', value: '성공1' },
	{ status: 'rejected', reason: '실패2' },
	{ status: 'fulfilled', value: '성공3' }
*/
	})
	.catch((error) => {
	  console.error(error);
	});
}
  • Promise.allSettled를 사용하면 결과값이 좀 더 자세해져서 어떤 프로미스가 reject되었는지 status를 통해 알 수 있습니다.
  • 실패 이유는 reason에 들어있다. 따라서 Promise.all 대신 Promise.allSettled를 사용하는 것을 좀 더 권장한다.

Node 16 버전부터는 reject된 Promise에 catch를 달지 않으면

unhandledPromiseRejection 에러가 발생한다. 에러가 발생하면 다음 코드가 실행되지 않으니 반드시 프로미스에 catch 메서드를 붙이는걸 권장한다.

try { 
	Promise.reject('에러');
} catch (e) {
  console.error(e); 
// UnhandledPromiseRejection: This error originated either
// by throwing inside...
}
Promise.reject('에러').catch(() => {
	// catch 메서드를 붙이면 에러가 발생하지 않음
})

9. async/await

노드7.6 버전부터 지원되는 기능으로, ES2017에 추가되었다. 노드처럼 비동기 위주로 프로그래밍을 해야 할 때 도움이 많이 된다. 프로미스가 콜백 지옥을 해결했지만, 여전히 코드는 장황하다. then과 catch가 계속 반복되기 때문이다. async/await 문법은 프로미스를 사용한 코드를 한 번 더 깔끔하게 줄여준다.

async/await 사용 전

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) => { //  번째 콜백
			// 생략
	      }); 
	  });
        }); 
}

async/await 사용 후

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

함수 선언부를 일반 함수 대신 async function으로 교체한 후, 프로미스 앞에 await을 붙였다. 함수는 해당 프로미스가 resolve될 때까지 기다린 뒤 다음 로직으로 넘어간다.

  • await User.findOne({})이 resolve될 떄까지 기다린 다음 user 변수를 초기화 시킨다.

async/await Reject 추가

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

Arrow Function에서 async/await 사용하기

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

for문과 async/await 같이 사용해 순차적으로 실행하기

for문과 함께 쓰는 것은 노드 10 버전부터 지원하는 ES2018 문법이다.

const promise1 = Promise.resolve('성공1'); 
const promise2 = Promise.resolve('성공2'); 
(async () => {
    for await (promise of [promise1, promise2]) {
       console.log(promise);
		} 
})();
  • for await of 문을 사용해서 프로미스 배열을 순회하는 모습이다. async 함수의 반환값은 항상 Promise로 감싸진다. 따라서 실행 후 then을 붙이거나 또 다른 async 함수 안에서 await을 붙여서 처리할 수 있다.
async function findAndSaveUser(Users) { 
	// 생략
}

findAndSaveUser().then(() => { /* 생략 */ }); 

// 또는

async function other() {
  const result = await findAndSaveUser();
}
  • 중첩되는 콜백 함수가 있다면 프로미스를 거쳐 async/await 문법으로 바꾸도록 하자. 그리고 적극 활용하는 것이 좋다.

10. Map/Set

  • ES2015에는 새로운 자료구조들이 추가되었다. 그중 자주 쓰이는 것은 Map과 Set인데, Map은 객체와 유사하고 Set은 배열과 유사하다.

Map 사용해보기

const m = new 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
  • Map은 속성들 간의 순서를 보장하고 반복문을 사용할 수 있다. 속성 명으로 문자열이 아닌 값도 사용할 수 있고, size 메서드를 통해 속성의 수를 쉽게 알 수 있다는 점에서 일반 객체와 다르다.

Set 사용해보기

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() 전부 제거합니다
  • Set은 중복을 허용하지 않는다는 것이 가장 큰 특징이다. 따라서 배열 자료구조를 사용하고 싶으나 중복은 허용하고 싶지 않을때 Set을 대신 사용하면 된다.
  • 기존 배열에서 중복을 제거할 때도 사용한다.

Set으로 기존 배열 중복 제거하기

const arr=[1,3,2,7,2,6,3,5];
const s = new Set(arr);
const result = Array.from(s);
console.log(result); // 1, 3, 2, 7, , 5
  • new Set(배열) 하는 순간 배열의 중복딘 요소들이 제거된다.
  • Set을 배열로 되돌리려면 Array.from(Set)을 하면 된다.

11. Null 병합/Optional chaining) 연산자

ES2020에서 추가된

  • ??(널 병합 연산자)
    • 널 병합 연산자는 주로 || 연산자 대용으로 사용되며, false 값 (0, ‘’, false, NaN, null, undefined)중 null과 undifined만 따로 구분한다.
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이나 undefined의 속성을 조회하는 경우 에러가 발생하는 것을 막는다.
const a={}
a.b; // a가 객체이므로 문제없음

const c = null;
try {
  c.d;
} catch (e) {
	console.error(e); // TypeError: Cannot read properties of null (reading 'd')
}
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]; // 문제없음
  • 위 코드처럼 일반적은 속성뿐만 아니라 함수 호출이나 배열 요소 접근에 대해서도 에러가 발생하는 것을 방지할 수 있다.
  • c?.d와 c?.f(), c?.[0]의 값은 undefined가 된다는 것을 알아둬야 한다.
  • 옵셔널 체이닝 연산자는 자바스크립트 프로그래밍을 할 때 발생하는 TypeError : Cannot read properties of undefined null 에러의 발생 빈도륵 획기적으로 낮출 수 있기 때문에 자주 사용한다.
LIST