본문은 JavaScript 언어에 대한 이해를 높이기 위해 "You Don't Know JS(카일 심슨 저)"를 읽고 공부한 내용을 정리한 글입니다. 공부가 목적이기 때문에 필요에 따라 생략이나 수정, 추가된 부분이 있을 수 있습니다.
1. 네이티브
네이티브란 특정 환경(브라우저 등의 클라이언트 프로그램)에 종속되지 않은, ECMAScript 명세의 내장 객체를 말한다. 예를 들어 Object, Math, Function, Array, Window, Button 중 네이티브가 아닌 것은 Window, Button 두 가지다. 좀 더 간단히 말해서 네이티브는 내장 함수이다.
다음은 가장 많이 쓰이는 네이티브들이다.
- String ( )
- Number ( )
- Boolean ( )
- Array ( )
- Object ( )
- Function ( )
- RegExp ( )
- Date ( )
- Error ( )
- Symbol ( ) - ES6에서 추가
네이티브는 자바와 유사하게 다음과 같이 생성자처럼 사용할 수 있다.
const s = new String("Hello World!");
console.log(s.toString()); // "Hello World!"
하지만, 실제로 생성되는 결과물은 원시 값을 감싸는 객체 래퍼이다. 아래 코드를 보자.
(new String ("abc") ) 생성자의 결과는 원시 값 "abc"를 감싼 객체 래퍼다. typeof 연산자로 이 객체의 타입을 확인해보면 자신의 감싼 원시 값의 타입(string)이 아닌 object의 하위 타입에 가깝다.
const a = new String("abc");
typeof a; // "object", "String"이 아님!
a instanceof String; // true
Object.prototype.toString.call(a); // "[object String]"
2. 내부 [ [ Classs ] ]
typeof가 'object'인 값(배열 등)에는 [ [ Class ] ]라는 내부 프로퍼티가 추가로 붙는다. 여기서의 'Class'는 전통적인 클래스 지향 개념에서의 클래스라기보단 내부 분류법의 일부로 보는 것이 옳다. 또한, 이 프로퍼티는 직접 접근할 수 없고 아래와 같이 Object.prototype.toString( )라는 메소드에 값을 넣어 호출함으로써 존재를 엿볼 수 있다.
Object.prototype.toString.call([1, 2, 3]);
// "[object Array]"
Object.prototype.toString.call(/regex-literal/i);
// "[obejct RegExp]"
원시 값에도 마찬가지로 내부 [ [ Class ] ]가 있다.
- null, undefined의 경우 Null ( ), Undefined ( )와 같은 네이티브 생성자는 없음에도 불구하고 내부 [ [ Class ] ] 값이 있다.
- 그 밖의 string, number, boolean 같은 단순 원시 값은 이른바 박싱(Boxing) 과정을 거친다. 내부 [ [ Class ] ] 값이 각각 String, Number, Boolean으로 표시된 것으로 보아 단순 원시값은 해당 객체 래퍼로 자동 박싱된다는 것을 알 수 있다.
Object.prototype.toString.call( null );
// "[object Null]"
Object.prototype.toString.call( undefined );
// "[object Undefined]"
Object.prototype.toString.call( "abc" );
// "[object String]"
Object.prototype.toString.call( 42 );
// "[object Number]"
Object.prototype.toString.call( true );
// "[object Boolean]"
3. 래퍼 박싱하기
객체 래퍼는 아주 중요한 용도로 쓰인다. 원시 값엔 프로퍼티나 메소드가 없으므로 .length, .toString( )으로 접근하려면 원시 값을 객체 래퍼로 감싸줘야 하지만, 자바스크립트가 자동적으로 이것을 해주므로 다음과 같은 코드가 가능하다.
const a = "abc";
a.length; // 3
a.toUpperCase(); // "ABC"
이때, 루프 조건 i < a.length처럼 빈번하게 문자열 값의 프로퍼티/메소드를 사용해야 한다면 자바스크립트 엔진이 암시적으로 객체를 생성하지 않도록 처음부터 값을 객체로 갖고 있고 있어야 한다고 생각할 수 있다.
하지만, 이는 틀린 생각이다. 오래전부터 브라우저는 이런 흔한 경우를 스스로 최적화하기 때문이다. 오히려 개발자가 직접 객체 형태로 '선 최적화(Pre-Optimize)'를 한다면 성능이 더욱 저하될 수 있다.
직접 객체 형태로 써야 할 이유는 거의 없다. 필요시 엔진이 알아서 암시적으로 박싱하게 하는 것이 낫다.
new String("abc") -> "abc"
new Number(42) -> 42
4. 언박싱
박싱이 있다면 당연히 언박싱도 존재한다. 객체 래퍼의 원시 값은 valueOf( ) 메소드로 추출할 수 있다.
const a = new String("abc");
const b = new Number(42);
const c = new Boolean(true);
a.valueOf(); // "abc"
b.valueOf(); // 42
c.valueOf(); // true
5. Date( ) and Error ( )
확실히 필요해서 쓰는 게 아니라면 생성자는 부수효과 방지를 위해 가급적 사용하지 않는 것이 좋고, 리터럴 형태로 값을 생성하는 것이 좋다.
하지만, 네이티브 생성자 Date( )와 Error( )는 리터럴 형식이 없으므로 다른 네이티브에 비해 유용하게 사용할 수 있다. 자세한 사용은 아래 MDN 문서에서 확인할 수 있다.
Date( )
https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Date
Error( )
https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Error
6. Symbol( )
심볼(Symbol)은 ES^에서 처음 선보인, 새로운 원시 값 타입이다. 심볼은 충돌 염려 없이 객체 프로퍼티로 사용 가능한, 특별한 유일 값이다(절대적으로 유일함이 보장되지는 않는다). 주로 ES6의 특수한 내장 로직에 쓰기 위해 고안되었지만, 필요에 따라 직접 심벌을 정의할 수도 있다.
MDN 문서에 설명에 따르면 심볼에 대해 다음과 같이 말하고 있다.
Symbol( )로부터 반환되는 모든 심볼 값은 고유합니다. 심볼 값은 객체 프로퍼티에 대한 식별자로 사용될 수 있습니다. 이것이 심볼 데이터 형식의 유일한 목적입니다.
아래의 코드를 보면 같은 코드(Symbol('fool'))를 비교한다 하더라도 반환 값이 고유하기 때문에 false를 반환함을 알 수 있다.
const symbol1 = Symbol();
const symbol2 = Symbol(42);
const symbol3 = Symbol('foo');
console.log(typeof symbol1);
// expected output: "symbol"
console.log(symbol2 === 42);
// expected output: false
console.log(symbol3.toString());
// expected output: "Symbol(foo)"
console.log(Symbol('foo') === Symbol('foo'));
// expected output: false
7. 네이티브 프로토타입
내장 네이티브 생성자는 각자의 .prototype 객체를 가진다(Array.prototype, String.prototype 등). 중요한 점은 prototype 객체에는 해당 객체의 하위 타입별로 고유한 로직이 담겨 있다는 점이다.
문자열 원시 값을 박싱으로 확장한 것까지 포함하여 모든 String 객체는 기본적으로 String.prototype 객체에 정의된 메소드에 접근할 수 있다.
8. 정리
- 자바스크립트는 원시 값을 감싸는 객체 래퍼, 즉 네이티브(String, Number, Boolean 등)를 제공한다.
- 객체 래퍼에는 타입별로 쓸 만한 기능이 구현되어 있어 편리하게 사용할 수 있다(예: String#trim( ), Array#concat( ) 등)
- "abc" 같은 단순 스칼라 원시 값이 있을 때, 이 값의 프로퍼티나 메소드를 호출하면 자바스크립트는 자동으로 원시 값을 '박싱'하여 필요한 프로퍼티/메소드를 쓸 수있게 도와준다.
'프로그래밍 언어 > JavaScript + TypeScript' 카테고리의 다른 글
[You Don't Know JS] 5. 스코프(Scope) (0) | 2021.07.02 |
---|---|
[You Don't Know JS] 4. 문법 (0) | 2021.06.29 |
[스크랩] JavaScript(ES6), TypeScript, Node.js 개발자를 위한 무료 e북 4가지 (0) | 2021.06.22 |
[You Don't Know JS] 2. 값(Value) (0) | 2021.06.22 |
[You Don't Know JS] 1. 타입(Type) (0) | 2021.06.21 |