개인노트 nomad ES6의 정석 #14 ES2021 & ES2022

2023. 3. 8. 16:50개인노트-인강

ECMA2021:

  • Logical Assignment Operators
    • 1. Logical OR Assignment (||=)
    • 2. Logical AND Assignment (&&=)
    • 3. Logical NULLISH Assignment (??=)
  • Numeric Separators
  • Promise.any
  • String.prototype.replaceAll

ECMA2022:

  • .at()
  • Object.hasOwn(obj, propKey)
  • error.cause
  • Classes:
    • 1. Class field declarations
    • 2. Private Methods & fields
    • 3. Static class fields & private static methods

 

# 14.1 Logical OR Assignment

Logical Assignment Operators를 사용하면 우리들의 코드량을 줄여줍니다. 일종의 shortcut 역할을 해줍니다.

const name = prompt("what is your name");

console.log(`Hello ${name}`);

위 코드를 실행해서 입력창에 값을 입력하면 콘솔에 출력될것입니다.

하지만 만약에 user가 cancel을 누르거나 아무것도 입력하지 않으면 null이 출력됩니다.

이 null 대신에 다른 값을 출력하고 싶다면?

let name = prompt("what is your name");
if(!name) {
    name = "anonymous";
}

console.log(`Hello ${name}`);

위처럼 작성해 줄 수도 있겠지만,

대신에 logical or operator를 사용하여 표현할 수도 있습니다.

let name = prompt("what is your name");
// if(!name) {
//     name = "anonymous";
// }
name ||= "anonymous";

console.log(`Hello ${name}`);

logical or operator는 변수가 falsy일 때 오른쪽의 값을 변수에 값을 넣을 수 있게 해줍니다.

변수에 텍스트, Array, Object가 오면 일어나지 않습니다.

* falsy : undefined, false, 빈 문자열, 0, null, NaN, -0

 

# 14.2 Logical AND Assignment

Logical AND Assignment는 OR 와 완전히 반대입니다.

변수에 value가 truthy라면 변수를 수정합니다. (덮어씁니다.)

 

예시 코드

const user = {
    username: 'nico',
    password: 123
};

console.log(user);
// { username: 'nico', password: 123 }

위 코드에서 password의 value가 존재할 때,

콘솔에 공개적으로 password가 보이지 않게 보호하고 싶다면?

const user = {
    username: 'nico',
    password: 123
};
if(user.password) {
    user.password = "[secret]";
}

console.log(user);
// { username: 'nico', password: '[secret]' }

Logical AND Assignment (&&=) 를 사용해서 바꾸면..

const user = {
    username: 'nico',
    password: 123
};
// if(user.password) {
//     user.password = "[secret]";
// }
user.password &&= "[secret]";

console.log(user);
// { username: 'nico', password: '[secret]' }

 

추가 예시

const user = {
    username: 'nico',
    password: 123
};

user.password &&= "[secret]";

user.name &&= "nico"; // user 객체에 name이 없기 때문에 동작하지 않습니다.
console.log(user);
// { username: 'nico', password: '[secret]' }

user.name ||= "nico"; // user 객체에 name이 없기 때문에 동작합니다.
console.log(user);
// { username: 'nico', password: '[secret]', name: 'nico' }

 

이처럼 적절한 상황에서 사용한다면 if나 else보다 훨씬 보기 좋습니다.

 

# 14.3 Logical NULLISH Assignment

Logical NULLISH Assignment (??=) 은 Logical OR Assignment (||=) 하고 굉장히 비슷합니다.

 

const user = {
    username: "nico",
    password: 123,
    isAdmin: ""
};

user.isAdmin ||= true;
user.isAdmin ??= true;
// 변수가 null이거나 undefined 일 때 true를 넣습니다.
// isAdmin이 undefined 일 경우 isAdmin은 true
// isAdmin이 null 일 경우 isAdmin은 true
// isAdmin이 "" 일 경우 isAdmin은 ""
// isAdmin이 0 일 경우 isAdmin은 0
// isAdmin이 false 일 경우 isAdmin은 false

console.log(user);

isAdmin이 falsy한 값(undefined, null, 0, "", false, NaN, -0) 이라면 ||=에 의하여 isAdmin에는 true가 출력될 것입니다.

 

||= 를 ??=로 바꾸면 ??=는 변수가 falsy인지 확인하는 것이 아니라. 오직 null이거나 undefined인 경우만 확인합니다.

즉, 오직 null이거나 undefined인 경우만 값을 할당합니다.

 

||=는 falsy 일 때 동작하고,

&&=는 truthy 일 때 동작하고,

??=는 null 또는 undefined 일 때 동작합니다.

 

# 14.4 Numeric Separators

단위 수를 알아보기 쉽게 _ 를 사용하여 구분하여 표현할 수 있습니다.

가끔 진짜 큰 숫자를 쓸 때 유용합니다.

const allTheMoney = 11_000_000_000_000_000;
// _ 를 사용해서 숫자를 나누면 훨씬 쉽게 셀 수 있습니다.

console.log(allTheMoney);
// 출력
// 11000000000000000
// 숫자를 읽기 쉽게 바꿔줄뿐 값을 변경시키지는 않습니다.

작성자 입장에서 편리한 기능입니다. 실제로 값에 영향을 주지는 않습니다.

float에서도 동작합니다.(소수점)

작성자가 원하는 곳 아무대나 넣어서 사용할 수 있습니다. (예 : 1_1_0_0_0_1.345)

단, 연속으로 _ 를 2개 이상 사용하면 안됩니다.

 

# 14.5 Promise.any

Promise.any는 매우 흥미롭습니다. 그 전에

먼저, Promise.all 을 먼저 알아봅니다.

Promise.all([p1, p2, p3]).then();

위 코드에서 Promise.all은 p1, p2, p3 가 모두 다 끝날 때까지 기다립니다.

p1, p2, p3가 끝난 뒤에야 .then으로 가서 Promise.all이 끝납니다.

 

Promise.any는 다릅니다.

Promise.any([p1, p2, p3]).then();

Promise.any는 p1, p2, p3 중 어느 하나가 끝나기를 기다립니다.

이것들 중 어느 것이라도 끝이 나면 다음으로 넘어갑니다. 예를 들어 p2가 먼저 끝났다면, p1과 p3를 기다리지 않고 바로 then으로 넘어갑니다.

만약, 이들 중 아무것도 끝나지 않았다면 aggregate error가 발생합니다.

 

const p1 = new Promise((resolve, reject) => {
    setTimeout(reject, 1000, "quick");
});

const p2 = new Promise((resolve, reject) => {
    setTimeout(reject, 5000, "quick");
});

Promise.any([p1, p2]).then(console.log);
// then은 실행되지 않습니다. 그 대신 에러가 발생합니다.
// Problems : All Promises were rejected

이번에는 catch로 error를 잡아봅니다.

const p1 = new Promise((resolve, reject) => {
    setTimeout(reject, 1000, "quick");
});

const p2 = new Promise((resolve, reject) => {
    setTimeout(reject, 5000, "quick");
});

Promise.any([p1, p2]).then(console.log).catch(console.log);
// 2개가 다 실행되어 reject가 된 후, catch가 실행됩니다.
// AggregateError : All promises were rejected

위 에러 메세지는 전체 promise 포함하여 하나의 에러 메세지로 나타냈지만,

 

error 메세지를 직접 출력해보면 각각의 Promise 별로 에러상태를 배열로 구분하여 출력해줍니다.

Promise.any([p1, p2])
.then(console.log)
.catch(e => {
    console.log(e.errors);
});
// ["quick", "slow"]

 

all은 모두가 끝나길 기다리고,

any는 하나만 끝나길 기다리고 나머지는 신경쓰지 않습니다.

 

# 14.6 replaceAll

replaceAll은 string 안에 있는 모든 표적을 대체할 수 있습니다.

replaceAll은 원래 값을 변화시키지 않고 새로운 string을 return합니다.

 

첫번째 argument는 바꾸고 싶은 것, 두번째 argument는 어떤 걸로 바꿀지 넣어주면 됩니다. 

const name = "Nicolaso";

const newName = name.replaceAll("o", "❤");

console.log(name, newName);
// Nicolaso Nic❤las❤

 

# 14.7 at()

const arr = ["a", "b", "c", "d"];

console.log(arr.at(2)); // c
console.log(arr[2]); // c
// 여기까지는 기존의 arr[2] 나 arr.at(2) 는 차이가 없습니다.

기존의 arr[2] 나 arr.at(2) 는 차이가 없습니다.

만약 배열에서 0부터 시작하는 인덱스의 항목을 찾고 싶다면, 이 둘은 다른점이 없습니다.

 

하지만, 끝에서부터 item을 찾고 싶다면 at 메서드가 빛을 발합니다.

at() 은 끝에서부터도 item을 찾을 수 있게 해줍니다.

console.log(arr.at(-1)); // d
console.log(arr[-1]); // undefined

console.log(arr.at(-3)); // b

at()은 음수 인덱스를 사용해서 끝에서부터 찾을 수 있게 해줍니다.

 

Javascript에서는 일반적인 방법으로는 끝에서부터 찾을 수 없습니다. at()이 이것을 가능하게 해줍니다.

 

# 14.8 Object hasOwn

Object.hasOwn은 object가 property를 가지고 있는지 확인합니다.

 

Object.hasOwn 이전에 기존에 사용한 Object.hasOwnProperty에 대해 먼저 확인해 봅니다.

const user = {
    name: "nico",
    isAdmin: "hi"
};

console.log(user.hasOwnProperty("isAdmin")); // true

기존의 hasOwnProperty는 조사하려는 객체에서 hasOwnProperty메서드를 호출하여 인자로 조사하려는 속성이름을 넘겼지만, 

Object.hasOwn 은 Object.hasOwn(객체, "확인하려는 속성 이름"); 으로 나타냅니다.

Object.hasOwn(obj, propKey)

const user = {
    name: "nico",
    isAdmin: "hi"
};

console.log(user.hasOwnProperty("isAdmin")); // true
console.log(Object.hasOwn(user, "isAdmin")); // true

 

공식문서 중..

Object.hasOwn() is intended as a replacement for Object.prototype.hasOwnProperty().
Object.hasOwn()은 Object.prototype.hasOwnProperty()를 대체하기 위한 것입니다.

 

가능하다면 hasOwnProperty() 보다 Object.hasOwn() 을 사용하는 것을 추천합니다.

이유 공식문서 확인 : https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/hasOwn

 

## object에 property가 존재하는지 확인하는 작은 트릭

console.log("isAdmin" in user); // true

 

# 14.9 Error cause

개발자의 DX를 위해 만들어졌습니다. (DX : 개발자 경험)

많은 라이브러리나 오픈소스에서 이걸 사용하고 있을 거라고 생각됩니다.

 

error.cause를 사용하지 않고 기존의 에러 처리를 보면,

try {
    2+2;
    throw new Error("DB Connection Failed.");
} catch(e) {
    console.log(e.message);
}

// 출력
// DB Connection Failed.

 

error.cause는 에러에 메세지를 추가할 수 있을 뿐만 아니라, 또 다른 정보를 추가하는 기능도 있습니다.

try {
    2+2;
    throw new Error("DB Connection Failed.", {
        cause: "Password is incorrect"
    });
} catch(e) {
    console.log(e.message, e.cause);
}

// 출력
// DB Connection Failed. Password is incorrect

위처럼 에러에 대한 추가적인 정보를 제공할 수 있습니다.

 

그리고 cause가 꼭 문자열일 필요는 없습니다. 객체로 넘겨서 더 많은 정보를 남길 수 있습니다.

try {
    2+2;
    throw new Error("DB Connection Failed.", {
        cause: {
            error: "Password is incorrect.",
            value: 1234,
            message: ["too short", "only number not ok."]
        }
    });
} catch(e) {
    console.log(e.cause);
}

// 출력
// { error: "Password is incorrect.", value: 1234, message: Array(2) }

 

# 14.10 Class Field Declarations

JavaScript는 진짜 객체 지향 프로그래밍 언어들이 지원하는 모든 특징을 가지고 있지는 않습니다.

하지만 ECMA2022로 오면서 점점 가까워지고 있어서, 이전에 사용하지 못했던 객체 지향 프로그래밍 언어들이 가진 특징들을 더 많이 쓸 수 있게 됐습니다.

 

이번에는 Class에서의 필드 선언에 대한 것부터 알아보겠습니다.

먼저 예시코드로 Counter class를 만들어보겠습니다.

class Counter {
    constructor() {
        this.count = 0;
    }
    plus() {
        this.count++;
    }
}

위에서처럼 변수를 초기화 하려면 constructor를 작성해야 했습니다.

Class Field Declarations를 사용하면 constructor를 작성하지 않고도 바로 필드에서 초기화 할 수 있습니다.

class Counter {
    count = 0;
//    constructor() {
//        this.count = 0;
//    }
    plus() {
        this.count++;
    }
}

코드는 더 깔끔해졌고, 결과는 똑같이 나옵니다.

 

# 14.11 Private Methods and Fields

Private 메소드와 필드에 대해 알아보겠습니다.

이는 마치 Java나 C#, TypeScript 에서 코딩하는 느낌을 줄 것입니다.

지금까지의 JavaScript에서는 Private Methods and Fields는 없었습니다.

 

Private 메소드와 필드를 쓰는 이유는 'Class를 보호하기 위해서' 입니다.

#를 앞에 붙여서 필드나 속성, 메소드를 private로 만들 수 있습니다.

 

## Private Fields

class Counter {
    count = 0;
    plus() {
        this.count++;
    }
}

const c = new Counter();
c.plus(); // 1증가
c.plus(); // 1증가
c.count = 100000; // 누군가 악의적으로 값을 바꾼다면?
console.log(c.count); // 100000

위 코드처럼 원하지않는 상황이 발생할 수도 있습니다. count 값을 보호해야 하는 상황입니다.

이 때, private하게 바꾸어 클래스 내부에서만 count 값을 수정할수 있고, 클래스 밖에서는 수정할 수 없게 합니다.

count를 private 필드로 만드려면 변수명 앞에 #을 붙입니다.

class Counter {
    #count = 0;
    plus() {
        this.#count++;
    }
}

const c = new Counter();
c.plus(); // 1증가
c.plus(); // 1증가
// c.#count = 100000; // error 발생
console.log(c.#count); // 2

 

## Private Methods

Private 메소드도 만들 수 있습니다.

 

class Counter {
    #count = 0;
    plus() {
        if(this.#count === 5) {
            this.#reset();
        } else {
            this.#count++;
        }
    }
    
    #reset() {
        this.#count = 0;
    }
    
    get count() {
        return this.#count;
    }
}

const c = new Counter();
c.plus(); // 1증가
c.plus(); // 1증가
c.plus(); // 1증가
c.plus(); // 1증가
c.plus(); // 1증가
// c.#reset(); // private 메소드 이므로 error 발생.
console.log(c.count); // 5

c.plus(); // 1증가
console.log(c.count); // 0

Private Fields와 마찬가지로 Private Methods도 오직 클래스안에서만 접근할 수 있습니다.

위 코드에서 reset 함수는 외부로 부터 보호됩니다.

클래스 안에서만 사용하게 되는 메소드는 private하게 만들어주고, 클래스 외부에서 값을 불러올 수 있게 public으로 getter 함수를 만들어줍니다.

 

참고로 메소드 앞에 get키워드를 사용하면 javascript에게 getter함수로 인지하게 할 수 있습니다.

 

get 키워드를 사용할 경우.

// ...
    #count = 0;
// ...
    get count() {
        return this.#count;
    }
// ...

console.log(c.count);

 

get 키워드를 사용하지 않을 경우.

// ...
    #count = 0;
// ...
    count() {
        return this.#count;
    }
// ...

console.log(c.count());

 

# 14.12 Static Fields and Methods

1. static 메소드와 필드.

2. private static 메소드와 필드.

 

## static 메소드와 필드

static메소드는 인스턴스에 고정되지 않은 메소드입니다.

class Counter {
    #count = 0;
    static description = "Count up to five.";
    static isMyChild(instance){
        return instance instanceof Counter;
    };
    
    plus() {
        if(this.#count === 5) {
            this.#reset();
        } else {
            this.#count++;
        }
    }
    
    #reset() {
        this.#count = 0;
    }
    
    get count() {
        return this.#count;
    }
}

const c = new Counter();
// c.description // 동작하지 않습니다.
console.log(c.description); // undefined

console.log(Counter.description); // Count up to five.

// c라는 인스턴스가 Counter클래스의 인스턴스인지 확인합니다.
console.log(Counter.isMyChild(c)); // true

static 메소드와 필드는 class 그 자체에 붙어있습니다. 클래스의 인스턴스로 접근할 수 없습니다.

 

## private static 메소드와 필드

위에서 만든 static 필드와 메소드에 #을 붙여 private하게 만들수 있습니다.

class Counter {
    #count = 0;
    static #description = "Count up to five.";
    static #isMyChild(instance){
        return instance instanceof Counter;
    };
    
    plus() {
        if(this.#count === 5) {
            this.#reset();
        } else {
            this.#count++;
        }
    }
    
    #reset() {
        this.#count = 0;
    }
    
    get count() {
        return this.#count;
    }
}

const c = new Counter();
console.log(c.description); // undefined
// console.log(Counter.#description); // error

// c라는 인스턴스가 Counter클래스의 인스턴스인지 확인합니다.
// console.log(Counter.isMyChild(c)); // error