자바스크립트를 사용한지 약 4개월의 시간이 지났습니다. 파이썬, C언어 등 solid한 언어들과는 달리 조금 더 flexible한 언어의 특성을 가진 특성상 판단에 있어서 오류에 대해 굉장히 관대한 만큼 내가 작성한 코드의 어느 부분에서 구렁이 담넘어 가듯 찾는 것이 쉽지는 않았습니다.
구조를 잘 알고 데이터를 다루는 것은 컴퓨터 공학에서 가장 기본적인 부분이라고 생각됩니다. 물론 프로그래머스 및 기타 알고리즘 문제를 풀 때마다 마주하는 method 의존성과 로직의 구체화는 항상 대립하는 와중이지만, 되도록이면 많은 풀이를 만들어 내기 위해 여러 방면으로 생각해보려 합니다. 기록하고 또 기록해야하는 순간은 항상 늘어나네요. 문제를 접하면서 디자인 패턴과 같은 정형화된 양식도 어느정도는 필요하다고 느끼는 순간이 왔고, 한 권의 책을 기반으로 이를 정리하고자 글을 작성합니다.
자바스크립트에서 참이냐 거짓이냐의 구분은 까다롭다. 다른 언어에서는 Boolean값은 논리 연상 결과를 의미하는데, 자바스크립트에서는 문자열이 true의 값을 가질 수도 있다.
타입 | 결과 |
---|---|
undefined | false |
null | false |
Boolean | 참 : true, 거짓 : false |
Number | +0, -0, NaN : false / 나머지 : true |
String | ""(빈 문자열) : false / 나머지 : true |
Object | true |
function testTruthy(value) {
return value ? console.log('truthy') : console.log('falsy');
}
testTruthy(true);
// 'truthy'
testTruthy(new Boolean(false));
// 'truthy'
testTruthy('');
// 'falsy'
testTruthy(new String(''));
// 'truthy'
testTruthy(NaN);
// 'falsy'
testTruthy(new Number(NaN));
// 'truthy'
// 인스턴스는 객체이기에 항상 truthy
let obj = {name: 'John'};
testTruthy(obj);
// 'truthy'
testTruthy(obj.age);
// 'falsy'
// age 프로퍼티는 존재하지 않기(undefined)에 falsy
const myObject = {
foo1: false,
foo2: true,
foo3: false,
foo4: true
};
오브젝트가 하나 있습니다. 각 프로퍼티들의 value값을 true/false를 체크하는 가장 좋은 방법을 생각해봅시다.
👉🏻 foo1과 foo3만 false인지 판단하기 위해서 if(!foo1 && !foo3)의 조건문을 쓰기만 하면 될까? 아니다
👉🏻 foo3의 값이 null인 경우, null은 falsy값이기 때문에 false로 취급되어 위에서 !foo3 = true가 되어버린다.
👉🏻 foo3 프로퍼티가 없는 경우, myObject.foo3 는 undefined가 되어 !myObject.foo3 -> true가 되어버린다.
👉🏻 그리고 프로퍼티가 무한정 늘어난다고 생각해본다면, 조건문으로 이를 식별하는 건 비효율적이다.
const hasFoo2andFoo4 = {
foo1: false,
foo2: true,
foo3: false,
foo4: true
}
const hasFoo3andFoo4 = {
foo1: false,
foo2: false,
foo3: true,
foo4: true
}
// ... 나머지 경우의 수 ...
// 그 후에
if (isEqual(myObject, hasFoo2andFoo4)) {
// 오브젝트가 Foo2와 Foo4만 가지고 있는지 알 수 있습니다.
}
가지고 있는 프로퍼티의 수가 작다면 이해하겠지만, 많아질 경우를 고려하면 2^n 만큼 오브젝트 모델을 작성해야 하는 말도 안되는 상황이 펼쳐집니다.
자바스크립트 내부의 모든 정수들(64bit/9,007,199,254,740,991까지의 수)은 2진법으로 표기될 수 있습니다.
(1).toStirng(2);
// 1
(2).toString(2);
// 10
(3).toString(2);
// 11
(4).toString(2);
// 100
// ...
(3877494).toString(2);
// 1110110010101001110110
비트연산은 바이너리 문자열들을 직접 다루고 비교할 수 있게 해줍니다!
1010 & 1001 // 1000 - 교집합 개념
1010 | 1001 // 1011 - 합집합 개념
바이너리 문자열 오른쪽에 0를 넣어주는 << 비트연산은 우리의 10진법 정수를 2진법의 규칙에 맞게 증가시킵니다.
let fooBar = 2;
fooBar.toString(2);
// 10 <- 2의 2진법 표기입니다.
// fooBar의 바이너리 값의 끝에 0을 삽입할 것입니다.
// 표기법은 다음과 같습니다.
foobar = fooBar << 1;
fooBar.toString(2);
// 100
// ... 이제 4가 됐습니다.
console.log(fooBar);
// 4
정수 단위에서 계산하는 것 대신, 작성한 비트 연산자는 각 정수를 표현한 비트 위에서 작동하게 합니다. 우리에게 숫자를 직접 다루고 비교할 수 있게 만들어줍니다.
예시에선 true/false를 나타내는 속성들인 숫자의 0과 1에 따라서 단일 4비트 숫자 내부, 0000-1111 사이에 우리는 4개의 가능한 속성을 저장할 수 있습니다.
각각의 비트는 true일 때 (1)로, false일 때 (0)으로 표기될 수 있겠죠. 이러한 규칙을 이용해서 1111은 4개의 모든 속성이 true인 것이라는 것을 쉽게 상상할 수 있겠죠? 1000은 오직 네번째 값만 true인 것을 의미합니다.
// 체크할 오브젝트를 정의해봅시다. API 결과나, 유저와 상호작용할 때나, form 형식에서 불러질 수 있습니다.
const myObject = {
foo1: false,
foo2: true,
foo3: false,
foo4: true
}
// 코드를 더욱 이해하기 쉽게 만들 수 있는 상수를 정의합시다.
// 이 방법이 직관적으로 이해하기 가장 좋은 방법이라고 생각했습니다.
const HAS_FOO1 = 1; // 0001
const HAS_FOO2 = 1 << 1; // 0010
const HAS_FOO3 = 1 << 2; // 0100
const HAS_FOO4 = 1 << 3; // 1000
// 비트연산 숫자를 만드세요. 아마 case에 따라 다르게 만들어지겠죠.
// 오브젝트 키를 수동으로 체크하고 if문을 사용하여 한 번에 하나씩 속성을 추가합니다.
let myBitNumber = 0;
// 합집합의 형태를 띄기 위해 bit연산자인 "|"를 사용합니다.
if (myObject['foo1'] === true)
myBitNumber = myBitNumber | HAS_FOO1;
if (myObject['foo2'] === true)
myBitNumber = myBitNumber | HAS_FOO2;
if (myObject['foo3'] === true)
myBitNumber = myBitNumber | HAS_FOO3;
if (myObject['foo4'] === true)
myBitNumber = myBitNumber | HAS_FOO4;
console.log(myBitNumber.toString(2));
// 1010
/*
* 비트연산 숫자는 이제 "1010"이라는 값을 가집니다.
* 왜냐하면 두번째 값과 네번째 값이 true이기 때문입니다.
* 이렇게 표현할 수도 있습니다:
*
* | fourth | third | second | first | <= Attribute
* | 1 | 0 | 1 | 0 | <= True/false
*
*/
myBitNumber값을 미리 선언한 상수와 비교하여 해당 프로퍼티의 값을 도출하는 것이 가능합니다. 이제 테스트 해봅시다. 속성에 대해 비트연산 숫자를 체크하고 있다면, 체크할 수 있는 상태가 4가지가 있습니다.
우리의 숫자가 단일의 명확한 특성을 가지고 있는지, 어떤 주어진 특성의 배열을 가지고 있는지, 비트 번호에 지정된 값만 가지고 있는지, 비트 번호에 지정된 값만 포함되어 있는지 확인해봅시다.
검사 하는 모든 프로퍼티 중 하나가 true인가
1010 & 1001 // 1000 - 교집합 개념
1010 | 1001 // 1011 - 합집합 개념
if (myBitNumber & HAS_FOO1) {}
// 1010 & 0001, 0000, false
if (myBitNumber & HAS_FOO2) {}
// 1010 & 0010, 0010, true
검사 하는 모든 프로퍼티 중 하나 이상이 true인가
if (myBitNumber & (HAS_FOO1 | HAS_FOO2)) {}
// 1010 & 0011, 0010, true
if (myBitNumber & (HAS_FOO1 | HAS_FOO3)) {}
// 1010 & 0101, 0000, false
검사 하는 모든 프로퍼티가 true인가
if (myBitNumber == (HAS_FOO2 | HAS_FOO4)) {}
// 1010 == 1010, true
if (myBitNumber == (HAS_FOO2 | HAS_FOO3 | HAS_FOO4)) {} // 1010 == 1110, false
검사 하는 모든 프로퍼티가 true인가
if (myBitNumber == (myBitNumber | (HAS_FOO2 | HAS_FOO4))) {}
// 1010 == 1010, true
if (myBitNumber == (myBitNumber | (HAS_FOO2 | HAS_FOO3 | HAS_FOO4))) {}
// 1010 == 1110, false
true/false 속성을 효율적으로 저장하고 비교하기 위해 비트 연산자들을 사용한 함수의 예제입니다. 업데이트하고 유지보수하기 간단합니다. 그리고 모델의 프로퍼티가 많아질 때, 다른 특성을 추가하여 검사할 때도 용이합니다. 가장 눈여겨 보아야할 부분은 바이너리를 통하여 오브젝트의 참/거짓을 판별할 수 있다는 점 입니다.