JS의 객체는 크게 3가지로 분류된다.
1. 표준 빌트인 객체
-ECMAScript 스펙에 정의된 객체. 환경에 관계없이 사용 가능.
호스트 객체
-ECMAScript에 정의되어 있지 않지만, 환경에 따라 추가적으로 제공하는 객체.
사용자 정의 객체
-사용자가 직접 정의한 객체.
아래 문서에 모든 표준 빌트인 객체가 명시되어 있다.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects
40여 개가 존재하는데, 이 중 Math
, Reflect
, JSON
는 정적 메소드만 제공하고, 나머지 모든 표준 빌트인 객체는 instance를 생성할 수 있는 생성자 함수 객체다.(프로토타입 메소드, 정적 메소드 둘 다 제공)
앞서 살펴봤듯 new
없이 호출하면 알아서 내부적으로 감지하고 new
를 붙여서 재호출 해준다.
// String 생성자 함수에 의한 String 객체 생성
const strObj = new String('Lee'); // String {"Lee"}
console.log(typeof strObj); // object
// Number 생성자 함수에 의한 Number 객체 생성
const numObj = new Number(123); // Number {123}
console.log(typeof numObj); // object
// Boolean 생성자 함수에 의한 Boolean 객체 생성
const boolObj= new Boolean(true); // Boolean {true}
console.log(typeof boolObj); // object
// Function 생성자 함수에 의한 Function 객체(함수) 생성
const func = new Function('x', 'return x * x'); // ƒ anonymous(x )
console.log(typeof func); // function
// Array 생성자 함수에 의한 Array 객체(배열) 생성
const arr = new Array(1, 2, 3); // (3) [1, 2, 3]
console.log(typeof arr); // object
// RegExp 생성자 함수에 의한 RegExp 객체(정규 표현식) 생성
const regExp = new RegExp(/ab+c/i); // /ab+c/i
console.log(typeof regExp); // object
// Date 생성자 함수에 의한 Date 객체 생성
const date = new Date(); // Fri May 08 2020 10:43:25 GMT+0900 (대한민국 표준시)
console.log(typeof date); // object
각 표준 빌트인 객체의 instance를 생성하고 .__proto__
를 찍어보면 해당 생성자 함수의 .prototype
과 동일한 결과가 도출된다.
예를 들어, 표준 빌트인 객체인 String
instance의 .__proto__
는 String.prototype
과 같다.
String().__proto__;
// String {'', constructor: ƒ, anchor: ƒ, at: ƒ, big: ƒ, …}
String.prototype;
// String {'', constructor: ƒ, anchor: ƒ, at: ƒ, big: ƒ, …}
아래처럼 찍어봐도 된다.
Object.getPrototypeOf(String()) === String.prototype; // true
string
, number
, boolean
등은 Primitive Values, 원시값이지만 동시에 표준 빌트인 생성자 함수도 존재한다.
원시값은 객체가 아니기 때문에 메소드가 없어야 하지만, 편의를 위해 원시값으로 메소드를 호출하면 임시로 빌트인 생성자 함수에 의해 임시 객체가 생성되고, 그 객체에 속한 메소드가 호출된다.
아래와 같은 식이다.
"a".toUpperCase(); // "A"
"a"
는 원시값인데도 메소드 호출이 가능한 것을 볼 수 있었다.(물론 프로퍼티 접근도 가능하다)
이처럼 따로 객체 생성 없이 알아서 임시 객체를 생성해주기 때문에 편리한데, 메소드 사용 이후에 다시 원시값으로 자동으로 돌려주기 때문에 성능 저하나 메모리 낭비가 없다.
이 임시 객체를 Wrapper Object, 래퍼 객체라고 부른다.
한가지 문제가 있었는데, 크롬 콘솔창에서 실험한 결과, 숫자 리터럴은 직접 호출하면 오류가 발생하는 것을 확인했다.(Ver. 108.0.5359.124/arm64)
1.toString(); // Uncaught SyntaxError: Invalid or unexpected token
정확히는 원시 타입 중, 정수 리터럴만 오류가 발생했다.
나머지 타입과 소수점이 포함된 숫자 리터럴은 정상적으로 작동한다.
1.0.toString(); // "1"
true.toString(); // "true"
정수 리터럴도 변수에 넣어서 사용하면 잘된다.
const n = 1;
n.toString(); // "1"
참고로 null
, undefined
는 래퍼 객체를 생성하지 않으므로 이렇게 사용할 수 없다.
String
래퍼 객체의 자동 생성 -> 소멸 과정을 자세히 살펴보자.
const str = "abc";
str.prop1 = "x"; // Wrapper 생성 & 소멸
console.log(str.prop1); // undefined -> Wrapper Object 소멸되어 undefined
String
리터럴로 생성되었지만, str.props1
프로퍼티를 할당하는 순간 String
래퍼 객체로 변환된다.
str
의 원래 값인 "abc"
은 str.prop1
에 접근하는 순간 임시 래퍼 객체의 [[StringData]]
내부 슬롯에 할당되고, str
은 임시 래퍼 객체를 가리킨다.
이후 str
은 다시 [[StringData]]
에 있는 문자열 리터럴을 가리킨다.(원래 값인 "abc"
)
그래서 str.prop1
을 출력하는 시점엔 "abc"
를 가리키고 있기 때문에 undefined
가 출력되는 것이다.
이후 래퍼 객체는 GC에 의해 Drop된다.
다른 원시 타입들도 마찬가지로 작동한다.
코드가 실행되기 이전 단계에 JS 엔진에 의해 가장 먼저 생성되는 특수한 객체.
어떤 객체에도 속하지 않는 최상위 객체다.
JS는 Chrome, Safari, Firefox 등의 브라우저와 Node.js, Deno, Bun 등의 런타임 등 여러 가지 다양한 환경에서 실행될 수 있는데, ES11 이전에는 실행 환경에 따라 전역 객체를 가리키는 변수의 이름이 window
, self
, this
, frames
, global
등 다양하게 파편화 되어 있었다.
그러나 ECMAScript2020, ES11에서 공식적으로 globalThis
가 전역 객체를 가리키도록 통일하면서 모든 환경에서 똑같은 변수명으로 전역 객체를 참조할 수 있게 되었다.
아래 코드를 각 환경에서 실행해보면 잘 동작하는 것을 알 수 있다.(globalThis
가 지원 되는 버전이라면)
// on Browser
globalThis === this; // true
globalThis === window; // true
globalThis === self; // true
globalThis === frames; // true
// on Node.js(after 12.0.0)
globalThis === this; // true
globalThis === global; // true
전역 객체는 Object
, String
등의 표준 빌트인 객체와 환경에 따른 호스트 객체(Web API or Node.js의 HOST API), 그리고 var
로 선언한 전역 변수, 전역 함수를 Property로 갖는다.
개발자에 의해 생성될 수 없으며, 전역 객체의 Property를 참조할 때 스코프에 따라 globalThis
를 생략할 수 있다.
단, var
가 아닌 let
, const
로 선언한 전역 변수는 전역 객체의 Property가 아니다.
이들은 보이지 않는 전역 렉시컬 환경의 선언적 환경 레코드 내에 존재하게 된다고 한다.(실행 컨텍스트에 대한 글 참조)
Infinity
무한대를 나타내는 숫자값
NaN
Not a Number를 뜻하는 숫자값으로 Number.NaN
과 같다.
undefined
원시 타입 undefined를 값으로 가짐
eval
보안 취약, 최적화 수행되지 않아 속도 느림.
사용 비권장.
isFinite
유한 숫자 여부를 확인 후 boolean
반환.
숫자가 아닌 경우 숫자로 타입 변환 후 검사 수행.
NaN
인 경우 당연히 false
반환.
isNaN
parseFloat
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/parseFloat
parseInt
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/parseInt
encodeURI
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/encodeURI
decodeURI
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/decodeURI
encodeURIComponent
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/encodeURIComponent
decodeURIComponent
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/decodeURIComponent
var x = 10; // global var
function foo() {
y = 20; // window.y = 20;
}
foo();
// 선언하지 않은 식별자 y를 전역에서 참조할 수 있다.
console.log(x + y); // 30
위 코드에서 y
는 foo
내부에서 사용되었다.
그러나 선언 없이 사용되어 함수, 전역 스코프 어디에서도 선언을 찾을 수 없는 상태다.
이 때 JS 엔진은 y = 20
을 window.y = 20
으로 처리하여 전역 객체의 Property로 동적 생성한다.
Keyword 없이 생성된 변수는 암묵적 전역 Property가 되는 것이다.
변수가 아닌 전역 객체의 Property이기 때문에 호이스팅조차 일어나지 않는다.
또한 전역 객체의 Property는 delete
로 삭제할 수 없음.
해당 오류는 strict mode
혹은 TypeScript를 사용하면 방지할 수 있다.
이런 것이 있다는 정도만 보고 넘어가자.
그리고 TypeScript를 사용하자.