빌트인 객체

먼저 자바스크립트의 객체는 크게 표준 빌트인 객체, 호스트 객체, 사용자 정의 객체로 나뉜다.
여기서 표준 빌트인 객체는 ECMAScript 사양에 미리 정의되더 있는 객체로, 애플리케이션 전역의 공통 기능을 제공한다. 실행 환경에 관계 없이 언제나 사용할 수 있으며, 별도의 선언 없이 전역 객체의 프로퍼티로써 제공되므로 사용 가능하다.
호스트 객체는 ECMAScript 사양에는 없지만 실행 환경에 따라 제공되는 객체이다. 브라우저 환경에서는 DOM, BOM, Canvas 등등 클라이언트 사이드 Web API를 제공하고, Node.js에서는 고유의 API를 제공한다.
사용자 정의 객체는 말 그대로 사용자가 직접 정의한 객체를 말한다.

표준 빌트인 객체

표준 빌트인 객체는 Object, String, Promise, JSON 등 40여 개가 있으며, Math, JSON, Reflect를 제외하면 모두 생성자 함수 객체다. 이 객체로 생성한 인스턴스의 프로토타입은 그 객체의 prototype 프로퍼티에 바인딩된다. 예를 들면 new String로 생성했다면 String.prototype이 된다.
그런데 한 가지 의문점이 생긴다. 왜 원시값이 존재하는데도 숫자, 문자열, 불리언 객체를 생성하는 함수가 존재할까?

const str = 'hi';
console.log(str.length); //2
console.log(str.toUpperCase());//HI

이 예제에서 hi라는 원시값은 마치 객체처럼 동작하고 있다. 그 이유는 자바스크립트는 원시값을 .나 [ ]로 접근할 경우, 일시적으로 그것을 객체로 변환해주기 때문이다. 일시적으로 객체로 변환해 메서드를 실행하고 다시 원시값으로 되돌리는데 이 때 생성되는 임시 객체를 래퍼 객체라고 한다. 이 과정에서 그 원시값은 래퍼 객체의 내부 슬롯에 할당되고, 메서드를 실행한 뒤 원래대로 되돌린다. 그 과정을 예시로 보자.

const str = 'hi'; //str 식별자에 값을 할당한다. 그것은 문자열 원시값이다.
str.name = 'Kim'; //여기서 암묵적으로 래퍼 객체가 생성된다. 원래의 값 'hi'는 내부 슬롯 [[StringData]]
에 존재하고, name 프로퍼티가 동적으로 추가된다.
str.toUpperCase(); //str을 대문자로 변경하려고 한다. 그러나 이 역시 새로운 래퍼 객체가 생성되며 그것을
대문자로 바꾸게 되므로 HI로 바꾸려고 했다면 원하는 결과가 나오지 않을 것이다.
console.log(str.name); //위의 'Kim'을 출력하려고 하지만 나오지 않는다. 새롭게 생성된 래퍼 객체에는 없다.
console.log(typeof str, str); //str은 다시 'hi'를 가지고 있다. string, hi가 출력된다.

이렇게 잠깐 사용된 래퍼 객체는 가비지 컬렉션으로 이동하게 된다.

전역 객체

전역 객체는 코드가 실행되기 이전, 가장 먼저 생성되며 어떤 객체에도 속하지 않는 최상위 객체를 말한다. 브라우저 환경에서는 window, Node.js에서는 global이다. globalThis는 속한 환경에서 전역 객체를 가리키는 식별자이다.
전역 객체의 프로퍼티는 표준 빌트인 객체, 호스트 객체, var로 선언한 전역 변수와 전역 함수이다.(let, const로 선언한 전역 변수는 전역 객체의 프로퍼티가 아니고, 전역 렉시컬 환경의 선언적 환경 레코드 내에 존재한다.) 또한 전역 객체의 프로퍼티는 window나 global를 생략하고 참조할 수 있고, 의도적으로 전역 객체를 생성할 수는 없다.

그럼 몇 가지 빌트인 전역 함수에 대해서 정리해보자.
우선 eval은 JS 코드를 문자열로 인수로 받아 그것을 실행하는데, 여러 단점으로 인해 사용이 지양되고 있으므로 간단히 있다는 것만 짚고 넘어가자.
isNaN과 isFinite는 각각 인수가 NaN인지, 유한수인지 검사하는 함수이다. 암묵적 타입 변환을 진행한 후에 검사를 수행한다. 각각 NaN이면 true, 유한수이면 true를 반환한다.
parseInt와 parseFloat는 전달받은 문자열 인수를 실수, 정수로 해석하여 반환한다. 자료형은 Number이다. 여러 상황이 있으니 간단한 예시로 몇 가지를 살펴보자.

parseInt('10', 2); // 두 번째 인수 n은 첫 번째 인수를 n진수로 해석하고 10진수 정수로 반환하게 한다.
const x = 15;
x.toString(2); // n을 인수로 받아 n진수로 변환 후 문자열로 그 결과를 반환한다.
parseInt('0xf'); // 인수를 주지 않더라도, 0X나 0x로 시작하면 16진수로 해석한다.
parseFloat('asdf'); // 첫 번째 문자열이 숫자로 변환되지 않으면 NaN을 반환한다.
parseFloat('12 34 56'); // 공백으로 분리될 경우 첫 번째만 변환한다. 앞뒤 공백은 무시된다.
parseInt('20',2); // 역시 2진수에는 2가 없으므로 해석되지 않으므로 NaN이다.
parseInt('1A0'); // 해석되는 부분 까지만 반환한다. 여기선 1이다.

encodeURI와 decodeURI는 URI를 인코딩, 디코딩 해주는 함수이다.
encodeURI는 완전한 URI를 받아 이스케이프 처리(인코딩)을 해 반환하고,
decodeURI는 인코딩 된 것을 전달받아 그 이전으로 디코딩해 반환한다.
encodeURIComponent와 decodeURIComponent는 완전한 URI 대신 구성요소를 다루는 함수이다.
차이점이 있다면 encodeURI는 인수를 완전한 URI로 간주하여 쿼리스트링 구분자로 사용되는 =, ?, &은 인코딩하지 않지만 encodeURIComponent는 쿼리스트링의 일부로 간주하여 =, &, ?도 인코딩한다.
job=programmer의 경우 encodeURI는 그대로 두지만 encodeURIComponent는 job%3Dprogrammer가 되는 형태로 이해하면 될 것 같다.

마지막으로 strict mode에서도 잠깐 짚어본 암묵적 전역이 있다. 선언하지 않은 식별자에 값을 할당할 경우 (y = 20; 과 같은 경우) 그것은 변수가 아니고 전역 객체의 프로퍼티로 추가되는 것이다. 전역 변수가 아니고 전역 객체의 프로퍼티이므로 변수 호이스팅은 일어나지 않게 된다. 즉 delete 연산자로 삭제도 가능하다. (전역 변수는 프로퍼티의 일부이지만 delete로 삭제는 불가능하다.)

좋은 웹페이지 즐겨찾기