nullish 병합 연산자(??)
??(물음표 두 개)는 논리 연산자로 ?? 연산자를 기준으로 좌항의 피연산자가 null이거나 undefined일 경우
우항의 피연산자를 반환한다. 좌항의 피연산자가 null 또는 undefined가 아닐 경우 좌항의 피연산자를 반환한다.
a ?? b;
a가 null도 아니고 undefined도 아니면 a, 만약 a가 null 또는 undefined라면 b를 반환한다.
nullish 병합 연산자(??) 없이 위 표현식을 작성한다면
let x = ( a !== null && a !== undefined ) ? a : b;
a가 null이 아니다 그리고 a가 undefined가 아니다.
a가 빈 값이 아니라면 a의 값을, a가 비어있다면 b의 값을 변수 x에 대입한다.
변수중 실제 값이 있는 변수의 값을 출력하는데,
만약 모든 변수에 값이 없다면 '익명의 사용자'가 출력되도록 한다.
let firstName = null;
let lastName = null;
let nickName = 'violet';
alert(firstName ?? lastName ?? nickName ?? '익명의 사용자');
// firstName이 null이라 우항의 lastName을 평가하고,
// lastName이 null이라 우항의 nickName을 평가하는데
// nickName이 'violet'이라는 값을 가지고 있어 논리 평가를 멈추고 nickName을 알림한다.
nullish 병합 연산자를 사용한 곳에 ||(or) 연산자를 사용해도 결과는 같은데
다른 점이 있다면 ||(or) 연산자는 첫 번째 truthy 값을 반환하고,
nullish 병합 연산자는 첫 번째 정의된 값을 반환한다.
height = height ?? 100;
height에 대입되어 있는 값이 null 또는 undefined라면 height이라는 변수에 100을 대입하겠다.
height !== null --- true → height이 참조타입 변수라면(참조변수라면) 빈 값이 아니다 &&
height !== undefined --- true → height이 원시타입 변수라면 빈 값이 아니다
height은 객체타입이든 원시타입이든 값을 가지고 있다. 그래서 height이 가지고 있는 값을 그대로 간다.
그런데 만약 빈 값이라면 숫자 타입 100을 변수 height에 대입한다. 변수 height만 선언하고 초기화하지 않은 경우
변수 height에는 숫자 100이 할당된다. 만약 height에 0이 대입되어 있고, height = height ?? 100; 여기에 사용된 nullish 병합 연산자를 ||(or) 연산자로 바꿀 경우 다른 결과가 도출된다. truethy 값을 반환하는 ||(or) 연산자의 경우 0이 falsy로 처리되기 때문에
변수 height에 0이 대입되는 것이다.
nullish 병합 연산자와 ||(or) 연산자는 동일한 기능을 수행하지만
높이처럼 0이 할당될 수 있는 변수를 사용해 기능을 개발할 땐 ||보다 ??가 적합하다고 한다.
조건문에서 0, NaN, undefined, null, false 등을 모두 falsy로 처리되어 '값이 없음'으로 판단된다.
그런데 nullish 병합 연산자인 ??는 null과 undefined만 값이 없음으로 처리된다. 그 차이가 있다.
***&&(and) 연산자, ||(or) 연산자, 조건문 등에서 falsy로 처리되는 값들: null, undefined, false, 0, -0, NaN, ''(빈 문자열) 등이 있고,
' '(공백 문자열)은 truethy(값이 있음)로 처리된다.
***헷갈리는 truethy로 처리되는 값들: {}(빈 객체), [](빈 배열), '0'(문자열 형식으로 숫자 0이 감싸져 있음), infinity 또는 -infinity,
12n → 12n은 자바스크립트에서 데이터 타입 bigint를 의미한다.
***BigInt는 Number타입이 안정적으로 표현할 수 있는 값 최대치인 2^53-1보다 큰 정수를 표현할 수 있는 내장 객체다.
즉 Number타입 변수가 가질 수 있는 최대 값은(가장 큰 값) 2^53-1이라는 의미다.
```
console.log(Math.pow(2, 53)-1);
console.log(2**53-1);
console.log(Number.MAX_SAFE_INTEGER);
```
2^53-1의 값은 9007199254740991 이라고 나온다.
그런데 Number타입이 안정적으로 표현할 수 있는 값인 2^53-1초과의 값도 콘솔로 찍으면 문제없이 잘 찍힌다.
콘솔에는 잘 찍히지만 이 값보다(2^53-1) 큰 값은 연산결과를 신뢰할 수 없어
2^53-1의 값보다 더 큰 값으로 연산하고자 할 땐 Number타입이 아닌 BigInt타입으로 연산해야 한다고 한다.
2^53 === 2^53+1 // true
// 그런데 숫자뒤에 BigInt타입인 걸 표시하는 n을 표기하면 false가 나온다.
9007199254740992n === 9007199254740993n // false
BigInt타입의 값은 BigInt타입 끼리 연산이 가능하다. 예를 들어 1+1n 연산을 진행한다면 에러가 난다.
에러메시지: Uncaught TypeError: Cannot mix BigInt and other types, use explicit conversions.
→ 'BigInt타입과 다른 타입들을 섞을 수 없다. 명시적 형변환을 사용해라' 라고 알려주고 있다.
그래서 숫자 뒤에 BigInt임을 표시하는 n을 붙여서(명시적 형변환) 1n + 1n 이렇게 연산하면 에러없이, 2n이라는 값을 반환한다.
BigInt 타입을 사용할 때는 연산을 하기 전에 숫자 뒤에 n을 붙여야 한다(명시적 형변환)
----------------------------------------------------------------------------------------------------------
자바스크립트의 값이 없음으로 처리되는 falsy값을 검색해보다 재밌는 글을 발견하게 되었다.
https://inpa.tistory.com/entry/%F0%9F%93%9A-null-undefined-NaN
falsy를(값이 없음을) 나타내는 0, undefined, null, NaN을 두루마지 휴지로 비유한 내용이다.
정수 0의 상태는 휴지걸이와 휴지걸이에 다 쓴 휴지심이 걸려있는 이미지로 표현했다.
휴지걸이를 통해 이 곳은 휴지를 매다는 공간, 즉 어떤 값이 존재할 수 있는 데이터 타입을 의미하는 것 같고,
휴지는 없지만 휴지심이 걸려있는 걸로 보아 휴지를 다 쓴 상태,
즉 '사용할 휴지는 없지만' 휴지가 걸려있었구나 라는 것을 알 수 있는 의미를 전달하는 것 같다.
null은 개발자가 값이 없음을 표현하기 위해 의도적으로 넣어놓는 값으로
휴지걸이는 존재하지만 휴지심 조차 없는 사진으로 null을 표현하고 있다.
stack이라는 메모리 영역에 변수는 선언되어 있고, 이 공간에는 객체타입 주소 값이 저장될 것인데
현재로서는 비어있는 값으로 표현하고 싶을 때 null을 사용한다.
객체의 속성 값이 아무것도 존재하지 않을 때 reference 변수에(참조변수) null 값을 대입한다.
null 값은 오로지 reference 변수에만 대입할 수 있다. 원시타입(primitive type) 변수에는 null 값을 대입할 수 없다.
undefined는 값이 아예 없음을 의미한다. 그래서 휴지걸이 자체도 존재하지 않는 이미지로 표현되어 있다.
변수를 선언하기만 하고, 프로그래머가 값을 할당하지 않으면 프로그램이 자동적으로 undefined를 변수에 할당한다.
그리고 객체가 갖고 있지 않은 속성에 접근한다면 undefined를 반환한다.
NaN은 Not a Number의 약어로 숫자가 아닌 값을 나타내지만 NaN의 타입은 Number다.
NaN은 숫자타입이면서 숫자가 아닌 값을 나타낸다. 이 값은 말도 안되는 값을 나타낸다고 한다.
----------------------------------------------------------------------------------------------------------
쿵쿵따 게임 순서도
1. 시작
2. 몇 명이 참여할지 사용자로부터 입력받는다
1) 참여자 수를 1명 이상 입력한 뒤 확인 버튼을 눌렀는가? ---예→ 참여자 수를 전역 변수에 담는다 → 참가자 순번을 정한다
2) 입력하지 않고(혹은 입력했지만 입력한 수가 0으로) 확인버튼을 눌렀는가?
3) 취소버튼을 클릭했는가? falsy로 처리되면서 falsy한 값을 반환한다
→ 1명 이상을 입력해야한다고 알림한다 → 절차를 1번으로
3. 참가자 순번을 정한다.
1) 제시어가 존재하지 않는가? ( 제시어가 없는가? )--- 예→ 입력한 값이 올바른가? ( 입력한 값이 3글자인가? ) --- 예→ 입력한 값이 제시어가 된다
2) ---아니오(제시어가 존재하는 경우)→입력한 값이 올바른가(3글자인가)---예→ 제시어의 끝 글자와 입력 값의 첫 글자가 일치하는가?)---예→입력한 값이 제시어가 된다
2-1) ---아니오(제시어가 존재하는 경우)→3글자인가---아니오→입력 값은 3글자여야 합니다 라고 알림한다
2-2) ---아니오(제시어가 존재하는 경우) ---3글자인가?---예→제시어의 끝 글자와 입력 값의 첫 글자가 일치하는가?---아니오→제시어의 끝 글자와 입력 값의 첫 글자가 일치하지 않는다고 알림한다
```
제시어를 담을 변수 선언
let word; // let word = '';
입력 값을 담을 변수 선언 및 빈 문자열로 초기화
let newWord = '';
if(!word) { // 제시어가 존재하지 않는다면(제시어가 없다면)
if(newWord.length === 3) { // 입력 값이 3글자라면
word = newWord;
} else {
// 제시어가 존재하지만 입력 값이 3글자가 아니라면
alert('입력 값은 3글자여야 합니다.');
}
} else { // 제시어가 존재한다면
if(newWord.length===3) {
// 제시어가 존재하고, 입력 값이 3글자이고,
if(word[word.length-1] === newWord[0]) {
// 제시어의 끝 글자와 입력 값의 첫 글자가 일치한다면
word = newWord;
} else {
// 제시어가 존재하고, 입력 값이 3글자인데
// 제시어의 끝 글자와 입력 값의 첫 글자가 일치하지 않는다면
alert('제시어의 끝 글자와 입력 값의 첫 글자가 일치하지 않습니다.');
}
} else {
// 제시어가 존재하고, 입력값이 3글자가 아니라면
alert('입력 값은 3글자여야 합니다.');
}
}
```
제시어가 존재하든 존재하지 않든 입력 값이 3글자인 건 무조건 검사해야함
위 코드처럼 진행한다면 newWord.length === 3 입력 값의 길이를 중복으로 검사하는 상황이 발생
입력 값의 길이를 먼저 검사한후 제시어의 존재여부를 판단해보면 중복된 코드를 줄일 수 있다.
```
let word; // 제시어를 담을 변수 선언
let newWord; // 입력 값을 담을 변수 선언
if (newWord.length === 3) {
if (!word) { // 제시어가 존재하지 않는다면
word = newWord;
$word.textContent = word;
$input.value = '';
let num = Number($order.textContent);
$order.textContent = num + 1 > total ? 1 : num + 1;
} else {
// 입력 값이 3글자이고, 제시어가 존재한다면
if (word[word.length - 1] === newWord[0]) {
// 제시어의 끝 글자와 입력 값의 첫 글자가 일치한다면
word = newWord;
$word.textContent = word;
$input.value = '';
let num = Number($order.textContent);
$order.textContent = num + 1 > total ? 1 : num + 1;
} else {
// 제시어의 끝 글자와 입력 값의 첫 글자가 일치하지 않는다면
alert(`제시어의 마지막 글자인 '${word[word.length-1]}'과\n입력 값의 첫 글자인 '${word[0]}'과 일치하지 않습니다.`);
}
}
} else {
//입력 값이 3글자가 아니라면
alert('입력 값은 3글자여야 합니다.');
}
```
------------------------------------------------------------------------------------------------------------------
총 참가자수를 입력받을 때
----유효한 값을 입력했는가? ---예→입력한 값을 총 참가자의 변수에 대입한다
- 참가자를 입력했는가? ---예→입력한 값을 숫자로 변환했을 때 숫자인가?---예→입력한 숫자가 1 이상인가? ---예→ 총 참가자의 수로 정한다
----유효한 값을 입력받지 못한 경우
1) 취소버튼을 누른 경우
2) 숫자가 아닌 값을 입력한 경우
3) 숫자를 입력했지만 1명 미만인 경우
*** +prompt()는 입력한 문자열 타입의 값을 숫자타입으로 변환한 뒤 반환한다. 확인버튼을 누르지 않고 취소를 누르면 null이 반환되고, null을 숫자타입으로 변환하면 0, 즉 +prompt()로 뜬 알람창에서 취소를 누르면 (null→)0이 반환된다.
*** isNaN()은 매개변수로 전달된 값이 NaN인지를 판단한다. NaN은 Not-A-Number로 숫자가 아님을 의미하며
NaN은 자기 자신과 같지 않다고 비교되는 유일한 값
→ NaN === NaN; // false
→ Number.NaN === NaN; // false
→ isNaN(NaN); // NaN가 NaN이니? true
isNaN()은 현재 값이 NaN 이거나 숫자로 변환했을 때 NaN이 되면 참을 반환하지만
Number.isNaN()은 현재 값이 NaN이어야만 참을 반환한다.
*** if( total <= 0 || isNaN(total)) → 총 참가자 수가 0이거나 0보다 작은 수이거나 total의 값이 숫자가 아닌 경우에 true를 반환하고,
*** continue는 루프의 남은 부분을 건너뛰고, 다음 반복을 시작하는 역할을 하는데, 그 사용위치에 따라 동작이 달라진다.
```
let total;
do {
total = +prompt('총 참가자수를 입력해주세요!');
if (isNaN(total)) {
alert('숫자를 입력해주세요!');
continue;
} else if (total <= 0) {
alert('총 참가자수는 1명 이상이어야 합니다.');
}
} while (isNaN(total) || total <= 0);
```
------------------------------------------------------------------------------------------------------------------
*** EventTarget 인터페이스는 객체에 의해 구현된다.
그 객체는 이벤트들을 수신할 수 있고(receive events) 그 이벤트에 대한 수신기를 갖을 수 있어야 한다(may have listeners for them) In other words, 이벤트의 대상이 될 수 있는 객체는(any target of events) 이 인터페이스(EventTarget)와 관련된 세 개의 메서드들을 구현한다. Element(요소), Document, Window가 가장 흔한 이벤트 대상이며, 이 외에도 XMLHttpRequest 등의 객체도 이벤트 대상이다. 이 이벤트 대상은 또한 onevent properties와 attributes들을 통해 event handlers를 설정하는 것을 지원한다.
EventTarget.addEventListener() → EventTarget에서 특정 이벤트 유형(타입)에 이벤트 핸들러를 등록한다.
*** EventTarget.addEventListener() 메서드는
EventTarget 인터페이스의 메서드로, addEventListener() 메서드는 지정한 유형의 이벤트가 해당 대상에서 발생할 때마다
호출할 함수를 설정한다. addEventListener() 가 전달받는 두 개의 매개변수는 '지정한 유형의 이벤트' 그리고 지정한 유형의 이벤트가 발생할 때 호출할 함수다. 여기서 해당 대상은 Element(요소), Document, Window 그리고 XMLHttpRequest와 같이 이벤트를 지원하는 모든 객체가 대상이 될 수 있다.
addEventListener() 메서드는 EventTarget 인터페이스에서 지정한 이벤트 유형을 수신하는
이벤트 수신기의 목록에 handleEvent() 함수를 구현한 객체 또는 함수를 추가함으로써 addEventListener() 메서드가 작동한다.
```
addEventListener(type, listener);
```
type은 수신할 이벤트 유형을 나타낸다.
listener는 지정한 이벤트를 수신할 객체다 → handleEvent() 메서드를 포함하는 객체 또는 javascript 함수여야 한다.
// html 문서에서 첫 번째로 만나는 input태그를(input요소를) $input 변수에 대입한다.
const $input = document.querySelector('input');
// input요소에 어떤 이벤트가 발생할 때 실행할 메서드를 설정하기 위해 addEventListener() 메서드를 사용하기
// input요소에 입력 이벤트가 발생할 때마다 onInput 메서드를 실행할 것을 선언하기
const onInput = (event) => {
// event객체를 받아서 event.target.value를 newWord라는 변수에 담는다.
newWord = event.target.value;
}
$input.addEventListener('input', onInput);
→ input요소에 input이라는 이벤트가 발생할 때마다 onInput이라는 리스너가 작동한다.
input요소에 입력(input)이 발생할 때마다 onInput 리스너 작동한다.
onInput은 event객체를 전달받아(input event 객체)
event.target → 이벤트(입력)가 발생한 대상: element
event.target.value → 요소가 갖는 값: value
을 알 수 있다.
--------------------------------------------------------------------------------------------------------------------------------------------------------------
쿵쿵따 게임 실습
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div><span id="order">1</span>번째 참가자</div>
<div>제시어: <span id="word"></span></div>
<input type="text" />
<button id="btn">입력</button>
<script>
const $order = document.querySelector('#order'); // 순번을 담고 있는 요소
const $word = document.querySelector('#word'); // 제시어를 담고 있는 요소
const $input = document.querySelector('input');
const $button = document.querySelector('#btn');
let total;
do {
total = +prompt('총 참가자수를 입력해주세요!');
if (isNaN(total)) {
alert('숫자를 입력해주세요!');
continue;
} else if (total <= 0) {
alert('총 참가자수는 1명 이상이어야 합니다.');
}
} while (isNaN(total) || total <= 0);
// console.log(` total: ${total}`);
const participantsNum = total;
let word; // 제시어를 담을 변수 선언
let newWord; // 입력 값을 담을 변수 선언
const onInput = (event) => {
// console.log(`event: ${event}\nevent.target: ${event.target}\nevent.target.value: ${event.target.value}`);
newWord = event.target.value;
// console.log('newWord: '+newWord);
}
console.log(newWord);
const onClick = () => {
if (newWord.length === 3) {
if (!word) { // 제시어가 존재하지 않는다면
word = newWord;
$word.textContent = word;
$input.value = '';
let num = Number($order.textContent);
$order.textContent = num + 1 > participantsNum ? 1 : num + 1;
} else {
// 입력 값이 3글자이고, 제시어가 존재한다면
if (word[word.length - 1] === newWord[0]) {
// 제시어의 끝 글자와 입력 값의 첫 글자가 일치한다면
word = newWord;
$word.textContent = word;
$input.value = '';
let num = Number($order.textContent);
$order.textContent = num + 1 > participantsNum ? 1 : num + 1;
} else {
// 제시어의 끝 글자와 입력 값의 첫 글자가 일치하지 않는다면
alert(`제시어의 마지막 글자인 '${word[word.length-1]}'과\n입력 값의 첫 글자인 '${word[0]}'과 일치하지 않습니다.`);
}
}
} else {
//입력 값이 3글자가 아니라면
alert('입력 값은 3글자여야 합니다.');
}
}
$button.addEventListener('click', onClick);
$input.addEventListener('input', onInput);
</script>
</body>
</html>
제시어가 비어있든 비어있지 않든 3글자임을 확인해야 하는 것을 너무 어렵게 생각했나보다아래와 같이 작성해도 된다는 것을 댓글을 통해 알게 되었다.
if(( !word || word[word.length-1] === newWord[0] ) && newWord.length === 3)
const onClick을 하기와 같이 간단히도 작성할 수 있다는 것을 배웠다.
const onClick = () => {
// 제시어가 비어있거나 입력한 단어가 올바르고 세 글자인가?
if ( (!word || (word[word.length - 1] === newWord[0] && newWord.length === 3)) && newWord.length === 3) {
// !word → 제시어가 비어 있고, 입력한 값이 3글자이면 이 블록 안으로 들어온다.
// 1) !word && newWord.length === 3
// 제시어가 비어있지 않고, → 제시어가 존재한다
// 제시어의 끝 글자와 입력 값의 첫 글자가 일치하고,
// 입력한 값이 3글자이면
word = newWord;
$word.textContent = word;
$input.value = '';
let num = Number($order.textContent);
$order.textContent = num + 1 > participantsNum ? 1 : num + 1;
} else {
alert('입력 값이 틀렸습니다.');
}
}
참고----------------------------------------------------------------------
[ZeroCho TV] 자바스크립트 강좌 3-10. 셀프체크 - 쿵쿵따 만들기
'공부기록용' 카테고리의 다른 글
[ KISA ] Next.js를 활용한 검색 최적화 대응 웹사이트 구축하기(1) (1) | 2024.11.12 |
---|---|
[ javascript ] 두서없는 공부 기록 (0) | 2024.11.11 |
[ javascript ] 순서도 정리 및 복습, 옵셔널 체이닝(?.) (0) | 2024.11.06 |
[ javascript ] 끝말잇기 게임 프로그래밍 (3) | 2024.11.05 |
[ javascript ] 24.10.30 클로저 기록 (1) | 2024.10.30 |