Skip to main content

You don't know js Part I

전체 정리

챕터 1, 값#

typeof와 null#

  • JS는 typeof라는 연산자로 값의 타입명을 알아낼 수 있다. 근데 null은 object로 출력되는 버그가 있다

object의 하위타입#

  • function과 array도 object의 하위타입이다. 그래서 얘들도 속성값을 가질 수 있다.

undeclared vs undefined#

  • undeclared와 undefined는 절대 같지 않다. undefined는 선언된 변수에 할당할 수 있는 값인 반면,
    undeclared는 선언되지 않았다는 상태를 나타내는 표현이지, 할당할 수 있는 값이 아니다.
    근데 정작 JS부터 이 두 용어를 대충 섞어 써버린다. 선언되지 않은 경우 var is not defined라고 표현하기 때문이다.
  • typeof를 사용해서 에러를 발생시키지 않고 변수가 선언되었는지를 체크할 수 있다.

챕터 2, 타입#

배열과 유사배열#

  • 배열도 객체라서 문자열을 키값으로 사용할 수 있긴 하다. 다만 이 경우 length값이 증가하지 않는다.
  • 유사배열은 배열처럼 정수를 키로 가지고 있을 뿐, Array의 객체는 아니다. Array의 메서드를 사용하지 못한다.

문자열#

  • JS의 문자열은 문자배열이 아니다. 그러므로 배열의 메서드를 사용할 수 없다.
  • JS의 문자열은 불변값이므로 특정 위치의 문자만 바꿀 수 없다. 변경하고자 한다면, 새 문자열을 만들어야 한다.

숫자#

  • JS는 하나의 숫자 타입인 number를 사용한다. 이걸로 정수부터 실수까지 표현한다
  • 이진 부동소수점으로 나타낸 소수는 원래 수와 매우 유사할 뿐, 일치하지 않는다.
    따라서 JS에서 소수끼리 연산을 하면, 오차가 발생하는데, 이를 머신 엡실론이라고 부른다.
  • 다만 정수는 원래 수와 일치하게 표현할 수 있다.
    JS의 정수 최소, 최대값은 Number.MAX_SAFE_INTEGER, MIN_SAFE_INTEGER을 사용하면 구할 수 있다.
  • 0으로 나눈 결과는 Infinity이다.

NaN#

  • 숫자가 아님을 나타내는 경계값, 숫자에서 발생한 특별한 종류의 에러상황을 나타낸다.
  • NaN은 자기 자신과 비교해도 다르다고 나오는 특성이 있다.
  • window.isNaN은 문자열도 NaN으로 취급하는 반면, Number.isNaN은 그렇지 않다.

객체 & 레퍼런스#

  • 프리미티브 타입은 값이 복사되는 반면, 객체는 레퍼런스 값이 복사된다. 같은 레퍼런스값을 가지니, 같은 객체를 참조하게 된다
  • 객체 복사는 두가지 방법이 있다. 얕은 복사와 깊은 복사가 있는데, 얕은 복사는 속성을 그대로 복사하다보니, 객체인 경우 레퍼런스를 복사한다. 반면 깊은 복사는 속성이 가진 객체도 완전히 복사한다. 즉 새로운 객체를 생성한다.

Number 객체#

  • Number객체로 프리미티브 타입을 감싸는 경우, 타입은 object인데, 언제 언박싱이 발생할지 모른다.
  • 객체이긴 하지만 내부의 프리미티브 값을 변경할 수 없다.

챕터 3, 네이티브#

  • 네이티브라고 불리는 여러가지 내장타입이 있다.
  • String, Number같은것들이 있다.

네이티브는 사실 내장함수이다.#

  • 생성자처럼 사용할 수 있는데, 원시값을 감싼 객체가 반환된다. 그러므로 typeof의 결과도 object이다.
    console.log(typeof new String('1234'));

내부 [[Class]]#

  • typeof가 object 인 경우, [[Class]]라는 내부 프로퍼티가 추가로 붙는다.
  • 보통 해당 값과 관련된 내장 네이티브 생성자를 가리키는데, 그렇지 않을때도 있다.
  • 단순 원시값은 해당하는 객체 래퍼로 자동 박싱된다.
console.log(Object.prototype.toString.call([1,2,3]));
console.log(Object.prototype.toString.call(/asdf/));
console.log(Object.prototype.toString.call(null));
console.log(Object.prototype.toString.call(undefined));
console.log(Object.prototype.toString.call('asdf'));

래퍼 박싱#

  • 원시값은 속성 및 메서드가 없는데, JS는 원시값을 알아서 박싱해주다보니, 아래와 같이 사용할 수 있다.
    var a = 'a1234';
    console.log(a.length);
    • 필요에 따라 JS엔진이 알아서 암시적으로 박싱하므로, 개발자가 따로 최적화해줄 필요 없다고 한다.

래퍼 객체 주의사항#

  • boolean값 같은걸 래퍼로 감싸고, 그걸 가지고 조건문에서 참 거짓 여부를 따진다면,
    값이 존재하니 truthy하다고 나올 것이다. 내부 값이 false여도 말이다.
    이런 상황을 주의하자.
  • 값을 꺼낼 땐 valueOf를 사용하자
var a = new Boolean(false);
if(!a) {
console.log('ping1');
}
if(!a.valueOf()) {
console.log('ping2');
}

Wrapper객체는 주의해서 사용 할 것#

  • 정말 필요해서 쓰는게 아니라면, 그냥 원시 타입을 사용하는게 낫다.
    위에서 언급한 Boolean문제처럼, 굳이 valueOf()를 써야 한다거나 하는 문제를 사전에 피하도록 하자.
  • Boolean말고도 Array도 특이한데, 인자로 3을 넣으면 빈 슬롯 배열이 만들어진다. 미리 값이 채워진 배열을 원한다면, Array.apply 함수를 사용하자.

Object(), Function(), RegExp()#

  • Function의 경우 동적으로 함수를 만들어내야 하는 상황이라도 있는게 아니라면, 쓸 일이 없다.
  • 나머지도 마찬가지이다. 그냥 리터럴로 표현하는게 훨씬 나으니, 그렇게 사용하자.
  • Object의 경우, 리터럴로 한번에 여러 속성정의가 가능한데, 한번에 하나씩 프로퍼티를 지정할 이유가 없다.
  • RegExp의 경우, 읽기 편한건 물론, JS엔진이 실행 전에 정규표현식을 미리 컴파일하고 캐시하기 때문에,
    더더욱 RegExp()를 사용할 일이 없다.
  • 다만, Function과 마찬가지로 동적으로 정규표현식을 만든다면 필요할 수 있다.

Date(), Error()#

  • date와 error는 리터럴 형식이 없어서 사용해야한다.

네이티브 프로토타입#

  • 내장 네이티브 생성자는 각자 .prototype을 가진다.
  • 프로토타입을 통해 객체에 정의된 메서드에 접근가능하다.

프로토타입을 디폴트값을 사용하는 경우#

  • 함수의 프로토타입은 빈 함수, 정규표현식, 배열 등 등, 각자의 프로토타입은 각 타입의 디폴트값이다.
    console.log(Array.prototype);
    console.log(Function.prototype);
    console.log(RegExp.prototype);
    • 이 점을 활용하여, 각 타입의 디폴트값으로 활용할 수 있다.

챕터 4 강제변환#

  • 변환은 암시적으로도, 명시적으로도 할 수 있다.
var a = 42;
console.log(typeof (a + ""));
console.log(typeof String(a));
console.log(typeof a.toString());
console.log(typeof Number('42'));

boolean값#

  • JS의 경우, boolean으로 강제변환 시 모든 가능한 수가 정의되어있다.
  • 그래서 아래의 경우 false로 강제변환된다. | 강제변환 시 false가 되는 값들 | | - | | undefined | | null | | false | | 0 | | NaN | | "" |

falsy 객체#

  • 객체는 truthy하다. 근데 falsy객체가 따로 있다.
  • 레거시 코드때문에 그렇다. 대표적으로 document.all이 있다.
  • 웹페이지의 모든 요소를 가져오는 기능을 제공하는데, 비표준이다.
    하지만. 여기에 의존하는 레거시 코드가 많다보니 함부로 제거할 수 없었다.
  • 근데 이걸 가지고 비표준 IE를 감지하는 수단으로도 줄곧 써왔다.
  • document.all을 없애버릴 수 도 없는데, 강제변환시 false가 나와야 한다. 반환값이 객체인데도!
  • 그래서 document.all은 falsy하게 되었다...
    if(!document.all) {
    console.log('document.all is falsy!');
    console.log(document.all);
    }

명시적 강제변환#

  • Number(), String()등을 사용하여 명시적으로 변환한다. new 붙이지 않는다.
  • 연산자를 사용하는 방법도 있다.
    console.log(typeof (42 + ''));
    console.log(typeof (+'42'));
  • 날짜를 숫자로 변환할 수 있다. 반면 숫자를 날짜로 변환하지는 못한다.
var d = new Date(2022, 5, 12);
console.log(d);
console.log(+d);

틸드#

  • indexOf의 결과, 찾지 못한 경우 -1을 반환하는 점을 이용하여, 틸드연산자와 조합하여 boolean으로 강제변환하기도 한다.
  • 틸드를 두번 써서 비트를 두번 뒤집어서 내림을 구현하기도 하는데, 음수일때 올림을 해버리는 결과가 나오니 주의해야 한다.

문자열 파싱#

  • 파싱은 좌에서 우로 읽으면서, 숫자가 아닌 값을 만나면 멈추는 반면, 강제 변환은 NaN을 던진다.
console.log(Number.parseInt('42px'));
console.log(Number('42px'));

JS에서의 논리 and, or 연산자#

  • 결과값이 boolean이 아닌 피연산자 중 한쪽의 값이다.
  • 대충 아래와 같이 동작한다
    • a || ba ? a : b
    • a && ba ? b : a
  • 근데 그냥 삼항연산자 쓰는게 낫지 않은지...?0

느슨한 비교와 엄격한 동등 비교#

  • 느슨한 비교는 강제변환을 허용하지만, 엄격한 비교는 허용 하지 않는다.
var a = '42';
var b = 42;
console.log(a == b);
console.log(a === b);
var a = null;
var b = undefined;
console.log(a == b);
console.log(a === b);