자바스크립트 완벽 가이드 4장 정리
포스트
취소

자바스크립트 완벽 가이드 4장 정리

해당 포스트는 자바스크립트 완벽 가이드라는 교재로 스터디를 하면서 4장을 정리한 포스트입니다.
주관적으로 해석한 내용이 들어가 있어서 잘못된 내용이 포함될 수 있습니다.
또한 교재의 모든 내용을 정리하지 않고 주관적인 판단에 의해 필요한 내용만 작성했습니다.

📌 용어 정리

1. 표현식

어떤 값으로 평가되는 구절입니다.

리터럴, 변수 등이 있습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
let digit = 0;
const arr = [1, 2, 3];
const person = { name: "john" };
const func = v => v;

// 아래부터는 모두 표현식입니다. 즉, 값으로 평가됩니다.
1;
"1";
true;

1 + 1;

digit;
arr[2];
person.name;
func(1);

📌 연산자 개요

교재에 나오는 모든 연산자에 대해 작성하지 않고 익숙하지 않은 연산자, side effect가 있는 연산자, 주관적인 기준으로 사용법을 알 필요가 있다고 판단한 연산자에 대해서만 살펴보겠습니다.

1. 전위/후위 증가/감소 연산자 ( Side Effect 발생 )

++, --로 피연산자의 앞쪽에 사용하면 전위, 뒤쪽에 사용하면 후위 연산자로 사용됩니다.
전위 연산자로 사용되는 경우 값으로 평가되기 전 연산을 하고 후위 연산자로 사용되는 경우 값으로 평가되고 나서 연산합니다.
간단한 내용이라 예시는 생략하겠습니다.

2. delete ( Side Effect 발생 )

객체의 프로퍼티를 제거하는 연산자입니다.

delete의 특이한 점은 제거하는 즉시 할당 해제를 시키지 않는다는 점입니다.
제가 처음 생각했을 때는 제거하고 가비지 컬렉터에 의해서 바로 할당 해제를 시킬 거로 생각했습니다
하지만 바로는 아니고 이후에 참조가 가능한지 판단 후 제거한다고 합니다. ( MDN - delete 연산자 )

그래서 제 생각에는 만약 프로퍼티를 제거하고 참조하는 곳이 없다면 null로 초기화하고 delete를 하는게 좋을 것 같습니다.
null로 바꾸면 참조 대상이 없으니 바로 가비지 컬렉터의 대상이 되고, delete하면 프로퍼티로도 제거되기 때문에 깔끔한 것 같습니다.
물론 코드가 한줄 더 생긴다는 문제 때문에 판단이 된다면 상황에 맞게 사용하면 좋을 것 같습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
const person = {
  name: "john",
};

person.name = null;
delete person.name;

// 바로 할당 해제되면 안되는 예시
const person = {
  vision: {
    left: 1.2,
    right: 0.8,
  },
}
// destructuring
const { vision } = person;
delete person.vision;

console.log(person.vision); // undefined
console.log(vision); // { left: 1.2, right: 0.8 }

3. void

다른 연산자는 존재는 알았지만, 사용법을 모르거나 사용하지 않거나였는데 이 연산자는 존재 자체를 처음 봤습니다.
TypeScript에서 함수의 반환 값이 없음을 의미하는 데 사용하는 것으로만 봐서 TypeScript 전용인 줄 알았습니다.

이 연산자는 특이하게 모든 값을 undefined로 바꿔버립니다.
실질적인 사용 예시는 교재에서도 없었고 제가 생각했을 때도 딱히 없는 것 같습니다.

1
2
3
4
console.log(void []); // undefined
console.log(void {}); // undefined
console.log(void 1); // undefined
console.log(void true); // undefined

4. 옵셔널 체이닝 연산자

프로퍼티로 가질 수 없는 값에 .를 사용하면 TypeError가 발생합니다.

특정 변수가 값이 존재하는지 확인하는 구문 ? 존재한다면 프로퍼티에 접근 : 그게 아니라면 null과 같은 형식으로 코드를 구성하면 매우 귀찮아집니다.
변수가 확실하게 값을 가지고 있다고 판단할 수 없는 경우 사용하면 좋습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
let person;
// console.log(person.name); // TypeError
console.log(person?.name); // undefined

let func;
// func(); // TypeError
func?.(); // undefined

// 단, 위에서는 "null", "undefined"인지만 체크 ( 즉, 함수인지 체크하는 것은 아님 )
func = "func";
// func?.(); // TypeError
typeof func === "function" && func();

let arr;
// arr[0]; // TypeError
arr?.[0]; // undefined

5. null 병합 연산자

피연산자가 null or undefined인지 판단해주는 연산자입니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
console.log(null ?? "a"); // "a"
console.log(undefined ?? "a"); // "a"

// 위쪽의 두 코드만 보면 굳이 "??"를 써야할 필요가 없다고 생각할 수 있습니다.
// 아래의 두 코드로도 같은 결과를 낼 수 있기 때문입니다.

console.log(null || "a"); // "a"
console.log(undefined || "a"); // "a"

// 하지만 가끔 유용하게 사용되는 경우가 falsy인 값을 true로 평가하고 싶은 경우 입니다.

// 만약 digit가 숫자인 경우 그대로 두고 그게 아니라면 100을 넣는 코드를 구현한다고 가정합니다.
// 실질적인 예시는 아니지만 떠오르는 예시가 없어 해당 상황으로 설명하겠습니다.
let digit;

// 아래의 경우 0이라는 숫자를 그대로 사용하고 싶지만 0은 falsy인 값이기 때문에 false로 판단돼서 숫자임에도 불구하고 100으로 교체됩니다.
digit = 0;
digit = digit || 100;
console.log(digit); // 100

// 하지만 ??를 사용하는 경우 null과 undefined만 체크하기 때문에 숫자 0을 그대로 유지할 수 있습니다.
digit = 0;
digit = digit ?? 100;
console.log(digit); // 0

6. in 연산자와 배열

in 연산자는 객체에 해당 프로퍼티를 갖는지 판단하는데 사용하는 연산자입니다.

이 연산자를 배열에 사용하면 특이한 결과가 나타납니다.
아래의 특이한 결과를 이해하기 위해서는 JavaScript의 배열에 대해 조금 자세히 알아야 할 필요가 있습니다.
JavaScript의 배열은 일단 객체이고, 배열의 동작을 흉내 낸 희소 배열입니다.
즉, 연속적인 크기로 메모리에 적재되지 않고 객체처럼 프로퍼티를 이용해서 접근해야 합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
const fruits = ["apple", "banana", "melon"];

console.log("0" in fruits); // true
console.log("1" in fruits); // true
console.log("2" in fruits); // true
console.log("3" in fruits); // false

// 위 결과를 토대로 배열이 아래처럼 생겼다고 예측할 수 있습니다. ( 확실하지 않음 )
const fruits = {
    "0": "apple",
    "1": "banana",
    "2": "melon",
    "length": 3, // 프로퍼티의 개수를 감시해서 그에 맞게 증감 ( 감시하는 방법은 몰라서 직접적으로 작성 )

    // ... 어떤 방식으로 반복되는지 선언 ( iterator protocol )
    *[Symbol.iterator]() {
        for (const key in this) {
            yield this[key];
        }
    },
};

// 반복에서 제외
Object.defineProperty(fruits, "length", { enumerable: false });

for (let i = 0; i < fruits.length; i++) {
    console.log(fruits[i]); // "apple", "banana", "melon"
}

console.log("========== 구분 ==========");

for (const iterator of fruits) {
    console.log(iterator); // "apple", "banana", "melon"
}

console.log("========== 구분 ==========");

console.log(...fruits); // "apple" "banana" "melon"

📌 단축 평가 ( short circuit )

단축 평가란 연산을 끝까지 하지 않고 일부만의 연산으로 결과를 내는 것을 의미합니다.

예시가 이해하기 쉽기 때문에 바로 예시를 보겠습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// 논리 OR 연산자 ( "||" ) ( 맨 처음 true인 놈을 반환 )
// OR은 하나만 true여도 평가를 끝내도 되기 때문에 true가 나온 시점 이후의 평가를 하지 않습니다.
console.log(1 || 2 || 3); // 1

// 만약 이후의 평가에 Side Effect가 발생한다면 문제(?)가 생길 수 있습니다.
let digit = 0;
// 아래의 예시는 1에서 평가가 끝나기 때문에 "++digit"구문까지 가지 않습니다.
console.log(1 || 2 || ++digit); // 1
// 따라서 digit가 증가되지 않습니다.
console.log(digit); // 0

// 논리 AND 연산자 ( "&&" ) ( 맨 처음 false인 놈을 반환, 없다면 제일 마지막놈 반환  )
console.log(1 && 2 && 3); // 3
console.log(0 && 1 && 2); // 0

// 이 예시도 위와 같은 문제(?)가 발생합니다.
let digit = 0;
console.log(0 || 1 || ++digit); // 1
console.log(digit); // 0

// 옵셔널 체이닝 연산자 ( "?." )
// "fix()"가 순수함수가 아니라고 가정했을 때 중간에 null or undefined가 존재하면 "fix()"에 의한 Side Effect가 발생하지 않습니다.
person?.vision?.right?.fix();

// null 병합 연산자 ( "??" )
let digit = 0;
// 아래에서 "++digit" 실행
0 ?? ++digit;
// 아래에서 "++digit" 실행하지 않음
null ?? ++digit;

위의 예시들은 조금 인위적으로 만든 예시입니다.
결론은 단축 평가가 될 수 있는 가능성이 있는 연산자를 사용하는 경우 Side Effect를 발생하는 코드를 사용하지 않는 것이 좋다는 것입니다.

📌 연산자의 자동 형 변환

1
2
3
4
5
6
7
8
9
10
11
// "+"는 문자열 연결 연산자가 우선순위가 높기 때문에 문자열로 형변환해서 연산됩니다.
// 나머지는 피연산자들이 숫지인 경우밖에 없기 때문에 숫자로 형변환 됩니다.
console.log(2 + "2"); // "22"
console.log(2 - "2"); // 0
console.log(2 * "2"); // 4
console.log(2 / "2"); // 1

// 몰랐던 부분
console.log(1 + {}); // "1[Object Object]"
console.log(1 + null); // 1 + 0 -> 0
console.log(1 + {}); // 1 + NaN -> NaN

📌 연산자를 이용한 간단한 형 변환

제가 자주 사용하는 간단한 형태의 형변환 방법들입니다.
Number(), parseInt(), String(), Boolean() 등을 사용해도 되지만 가장 간결하게 변환할 수 있는 것 같아서 소개해봤습니다.

1
2
3
4
5
6
7
8
9
// 숫자로 변환
console.log(+"1.123"); // 1.123

// 문자로 변환
console.log(123 + ""); // "123"

// 불리언 변환
console.log(!!1); // true
console.log(!!0); // false

단, 위처럼 사용하기 이전에 각 변환법의 차이점은 어느 정도 알고 이후에 사용하는 게 좋다고 생각합니다. ( 예시 2, 예시 3 )
저도 완벽하게 알지는 못하지만, 어느 정도 차이는 있으니 필요한 상황에 검색 or 테스트해서 원하는 기능을 골라서 사용할 수 있는 정도로 이해하고 있습니다.

1
2
3
4
5
6
7
8
9
// 예시 2
console.log(parseInt("1.123")); // 1
console.log(Number("1.123")); // 1.123
console.log(+"1.123"); // 1.123

// 예시 2
console.log(parseInt("1.123문자추가")); // 1
console.log(Number("1.123문자추가")); // NaN
console.log(+"1.123문자추가"); // NaN

📌 프로퍼티 접근 표현식

프로퍼티 접근 표현식은 두 가지 접근 방법이 있습니다.

  1. .로 접근
  2. []로 접근

일반적으로 프로퍼티의 이름을 알고 있는 경우 .를 사용합니다.
하지만 프로퍼티 명에 변수에 넣을 수 없는 문자(아래 예시로는 -)가 들어가거나, Symbol인 경우, 변수를 값으로 평가한 값을 넣는 경우 사용합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
const uniqueValue = Symbol("unique");
const uniqueValueCopy = Symbol("unique");
const n = "name";

const obj = {
    "api-key": "keyboardcat",
    name: "john",
    [uniqueValue]: "Symbol의 uniqueValue로만 접근이 가능",
};

// console.log(obj[name]); // Error: name is not defined
console.log(obj[n]); // "john"
console.log(obj["name"]); // "john"
console.log(obj.uniqueValue); // undefined -> "uniqueValue"라는 프로퍼티를 찾는 문법
console.log(obj[uniqueValue]); // "Symbol의 uniqueValue로만 접근이 가능"
console.log(obj[uniqueValueCopy]); // undefined
// console.log(obj.api-key); // 불가능한 문법
console.log(obj["api-key"]); // "keyboardcat"

📮 레퍼런스

  1. MDN - delete 연산자
  2. 1-blue - Js-Array-희소배열
이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.

실행 컨텍스트 ( Feat. 호이스팅, 클로저, 스코프 체인 )

JS의 데이터 타입 ( 원시/참조 타입 )