모던 자바스크립트 (2)

깨진알·2023년 12월 12일

JavaScript

목록 보기
12/21

자바스크립트의 동작 원리

1. 데이터 타입의 특징과 종류

  • 기본형 (Primitive Type)
    Number, String, Boolean, Null, Undefined, Symbol(ES2015, 유일한 값을 만들 때), BigInt(ES2020, 엄청 큰 숫자를 다룰 때)

  • 참조형 (Reference Type)
    Object

1-1. Symbol과 BigInt

(1) Symbol

심볼(symbol)은 기본형 데이터 타입(primitive data type) 중 하나이다. 심볼은 코드 내에서 유일한 값을 가진 변수 이름을 만들 때 사용한다.

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
...

Symbol 값을 담게 된 user라는 이름의 변수는 다른 어떤 값과 비교해도 true가 될 수 없는 고유한 변수가 된다.

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

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

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

(2) BigInt

BigInt는 자바스크립트에서 아주 큰 정수(integer)를 표현하기 위해 등장한 데이터 타입이다. 자바스크립트에서 안전한 최대 정수는 2**53 - 1, 안전한 최소 정수는 -(2**53 - 1)이다. 약 9000조에 해당하는 숫자이다.

console.log(9007199254740991 + 1 === 9007199254740991 + 2); // true
console.log(9007199254740991 + 2); /// 9007199254740992
console.log(9007199254740993); /// 9007199254740992

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

BigInt 타입의 값은 일반 정수 마지막에 알파벳 n을 붙이거나 BigInt라는 함수를 사용하면 된다.

console.log(9007199254740993n); // 9007199254740993n
console.log(BigInt(9007199254740993)); // 9007199254740993

하지만 BigInt 타입에는 몇 가지 주의사항이 있다. 일단 BigInt 타입은 말 그대로 큰 정수를 표현하기 위한 데이터 타입이기 때문에 소수 표현에는 사용할 수가 없다.

1.5n; // SyntaxError

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

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

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

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

1-2. typeof 연산자

우리가 사용하는 값이 어떤 데이터 타입을 가지고 있는지 확인하려면 typeof 연산자를 사용하면 된다. typeof 연산자는 키워드 다음에 공백(띄어쓰기)을 두고 값을 작성해도 되고, 함수를 사용하듯 괄호를 감싸서 사용할 수도 있다.

typeof 'Cracked_Egg'; // 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로 매칭되지 않는다는 점이다.

(1) null이 object?

typeof null; // object

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

(2) function?

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

typeof sayHi; // function

자바스크립트에서 함수는 객체로 취급한다. 그래서 간혹 object를 리턴할거라 예상하는 실수를 하기도 한다.


2. 불린인 듯 불린 아닌 불린같은 값

if, for, while 등 불린 탕비의 값이 요구되는 맥락에서는 조건식이나 불린 타입의 값 뿐만아니라 다른 타입의 값도 불린 값처럼 평가될 수 있다. 이때 false를 리턴하는 값을 falsy값, true를 리턴하는 값을 truthy값이라고 한다. (빈 배열[], 객체{}는 모두 truthy값이다.)


3. AND와 OR의 연산 방식

// AND 연산 방식
// 왼쪽값이 truthy값이면 오른쪽 값을, 왼쪽값이 falsy값이면 왼쪽 값을 린턴한다.
console.log(true && true); // true
console.log(true && false); // false
console.log(false && true); // false
console.log(false && false); // false
console.log('Cracked' && 'Egg'); // Egg

// OR 연산 방식
// 왼쪽값이 truthy값이면 왼쪽 값을, 왼쪽값이 falsy값이면 오른쪽 값을 린턴한다.
console.log(true || true); // true
console.log(true || false); // true
console.log(false || true); // true
console.log(false || false); // false
console.log('Cracked' && 'Egg'); // Cracked

console.log(null && undefined); // null
console.log(0 || true); // true
console.log('0' && NaN); // NaN
console.log({} || 123); // {}


function print(value) {
    const message = value || 'Cracked_Egg';

    console.log(message);
}

print(); // Cracked_Egg
print('JavaScript'); // JavaScript

3-1. AND와 OR의 연산 우선순위

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

  return '틀렸습니다!';
}

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

파라미터 value로 전달되는 값이 10보다 작으면서 0보다는 크고, 그러면서도 3은 아닐 때 '정답입니다!'라는 문자열을 콘솔에 출력하는 함수이다.

하지만 위에 있는 코드와 다르게 AND 연산자와 OR 연산자를 섞어서 사용할 때는 연산의 우선순위가 존재한다. AND와 OR 연산자 사이에서는 AND 연산자의 우선순위가 더 높다.

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

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

하지만 여러 사람과 함께 협업하는 상황에서 다양한 연산자들을 복합적으로 사용해야 한다면 소괄호를 활용해서 의도에 맞는 연산 우선순위를 명확하게 표기하는 것이 좋은 습관이라고 불 수 있다.

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

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

3-2. null 병합 연산자 ??

(1) null 병합 연산자 ??

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

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

console.log(example1, example2, example3); // I love Cracked_Egg

연산자 왼편의 값이 null이나 undefined라면 연산자 오른편의 값이 리턴되고, 연산자 왼편의 값이 null이나 undefined가 아니라면 연산자 왼편의 값이 리턴되는 원리로 동작한다.

(2) OR 연산자(||)와 비교

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

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

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

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

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


4. 변수와 스코프

// 변수와 스코프
// 호이스팅(Hoisting)
console.log(title); // undefined
var title = 'Cracked_Egg';
console.log(title); // Cracked_Egg

console.log(title); // Error
let title;

// ----------------------------------
// 중복 선언 허용
var title = 'Cracked_Egg';
console.log(title); // Cracked_Egg
var title = 'JavaScript';
console.log(title); // JavaScript

let title = 'Cracked_Egg';
console.log(title); // Cracked_Egg

let title = 'JavaScript';
console.log(title); // Error

// ----------------------------------
// 스코프
var x = 3; // Global Variable

function myFunc() {
    var y = 4;  // Local Variable
    console.log(`x in myFunc: ${x}`); // x in myFunc: 3
    console.log(`y in myFunc: ${y}`); // y in myFunc: 4
}

myFunc();
console.log(x); // 3
console.log(y); // Error

// ----------------------------------
var x = 3;

if (x < 4) {
    var y = 3;
}

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

console.log('x:', x); // x: 3
console.log('y:', y); // y: 3
console.log('i:', i); // i: 5

let x = 3;

if (x < 4) {
    let y = 3; // 지역 변수
}

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

console.log('x:', x); // x: 3
console.log('y:', y); // Error
console.log('i:', i);

// ----------------------------------
{
  let title = 'Cracked_Egg';
  console.log(title); // Cracked_Egg
}

console.log(title); // Error

// ----------------------------------
var x;  // 함수 스코프 (function scope)
let y;  // 블록 스코프 (block scope)
const z;  // 블록 스코프 (block scope)

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

블록 스코프는 중괄호로 감싸진 코드 블록에 따라 유효 범위를 구분하게 된다. 함수와 다른 문법들 뿐만아니라, 그냥 중괄호로 감싸진 코드 블록으로도 유효 범위가 구분된다. (RefernceError)

profile
프론트엔드 지식으로 가득찰 때까지

0개의 댓글