Skip to main content

YOU DON'T KNOW JS - 2

2023-02-24#

Chapter3 네이티브#

Chapter4 강제변환#

개요#

책에서 네이티브에 대한 내용을 읽다가 프로토타입 내용이 있어서 조금 깊게 살펴보는 시간을 가졌다.

네이티브#

네이티브란 특정 환경에 종속되지 않은 ECMAScript 명세의 내장 객체를 뜻한다.

가장 많이 사용하는 네이티브는 아래 목록정도가 있다.

  • String()
  • Number()
  • Boolean()
  • Array()
  • Object()
  • Function()
  • RegExp()
  • Date()
  • Error()
  • Symbol()
const a = new String('abc');
console.log(typeof a); // "object"
a instanceof String
console.log(Object.prototype.toString.call(a)); // [object String]
tip

리터럴 표현식을 사용하지 않고 직접 네이티브 생성자를 사용하면 객체 래퍼로 표현되니 특별한 상황이 아니라면 리터럴 표기법을 사용하는것을 권장한다.

사실 원시값에는 프로퍼티나 메서드가 없어서 length, toString() 같은 메서드를 사용하려면 객체 래퍼로 감싸주어야 하는것이 맞다.

하지만, 자바스크립트 엔진이 호출 시점에 알아서 객체 래퍼를 박싱해주기 때문에 객체 래퍼로 감싸지 않아도 해당 메서드를 호출하고 사용할 수 있다.

how can toString()?#

여기서 보면 prototype 얘기가 많이 나오는데 이후에도 배우겠지만, prototype 프로퍼티는 자바스크립트 내부에서도 굉장히 광범위하게 사용된다.

모든 내장 생성자 함수에서는 prototype 프로퍼티를 정보를 참조해서 사용하고 있다.

let obj = {};
console.log(obj); // "[object Object]" ?
obj.toString();

위에 처럼 빈 객체를 만들면 toString() 메서드를 호출할 수 있는 이유는 prototype 안에 다양한 메서드가 구현되어있는 거대한 객체를 참조할 수 있기 때문이다.

object-prototype-1

즉, obj.toString()을 호출하면 Object.prototype에서 해당 메서드를 가져오게 되고 해당 메서드를 호출하게 되는것이다.

let obj = {};
console.log(obj.__proto__ === Object.prototype); // true
console.log(obj.toString === obj.__proto__.toString); //true
console.log(obj.toString === Object.prototype.toString); //true

주의할점은 obj.__proto__ 는 메서드 체이닝이 없다는 점이 있다.

결국 원시타입에서 해당 타입이 지원하는 메서드를 사용가능한 것은 호출시점에 자바스크립트 엔진이 해당 타입의 객체 레퍼를 감싸주면서 Object(그외 다양한 타입).prototype에 구현되어있는 정보를 참조해서 사용할 수 있는것이다.

const list = ['a', 'b', 'c'];
/*
** 1. 호출 시점에 new Array() 래퍼 객체가 박싱된다.
** 2. Array.prototype 에 내장되어있는 속성들을 참고한다.
** 3. Array.prototype 메서드 체이닝이 가능하기 때문에 문법적으로 이어서 메서드를 사용한다.
*/
list.map((v) => {
console.log(v);
return v;
});
.filter((v) => {
v === 'a'
});

array-prototype

다양한 내장 객체의 prototype#

간단한 예시로 배열 [1, 2, 3]을 만들면 new Array()의 디폴트 생성자가 내부에서 동작하여 Array.prototype이 배열 [1, 2, 3]의 프로토타입이 되고 개발자는 Array.prototype을 통해 배열 메서드를 사용할 수 있는 것이다.

이런 내부 동작은 자바스크립트 엔진 환경에서 메모리 효율을 높여주는 장점이 있기도 하다.

그리고 상속 트리 꼭대기엔 꼭대기엔 Object.prototype이 있어야 한다고 명세서에서 규정하고 있는데, 이런 명세 때문에 몇몇 사람들은 "모든 것은 객체를 상속받는다." 라는 말을 하기도 하는 것 같다.

let arr = [1, 2, 3]; // 사실 let arr = [1,2,3] 은 let arr = new Array(1,2,3); 과 같다.
// arr은 Array.prototype을 상속?
console.log(arr.__proto__ === Array.prototype); // true
// arr은 Object.prototype을 상속?
console.log(arr.__proto__.__proto__ === Object.prototype); // true
// 체인 맨 위엔 null이 있습니다.
console.log(arr.__proto__.__proto__.__proto__); // null

native-prototypes

체인상에 중복 메서드?#

생각을 해보면 상속 관계상 체인상에 중복 메서드가 있을 수 있다.

Array.prototype엔 요소 사이에 쉼표를 넣어 요소 전체를 합친 문자열을 반환하는 자체 메서드 toString() 있다.

그런데 Object.prototype에도 메서드 toString()이 있는데, 이렇게 중복 메서드가 있을 때는 체인 상에서 가까운 곳에 있는 메서드가 호출된다.

native-prototypes-array-tostring

원시값 프로토타입#

사실 원시값 중 문자열과 숫자, 불린값은 객체가 아니다. 그런데 이런 원시 타입 값의 프로퍼티에 접근하려고 하면 내장 생성자 String, Number, Boolean을 사용하는 임시 래퍼(wrapper) 객체가 생성된다.

임시 래퍼 객체는 이런 메서드를 제공하고 난 후에 사라진다.

래퍼 객체는 보이지 않는 곳에서 만들어지는데 엔진에 의해 최적화가 이뤄지기 때문에 효율적으로 사용이 가능한 것.

실제로 우리가 자바스크립트에서 리터럴 표현식으로 편하게 문법을 사용할 수 있는것도 이런 최적화와 지원이 있기 때문.

note

nullundefined에 대응하는 래퍼 객체는 없다. 특수 값인 nullundefined는 문자열과 숫자, 불린값과는 다르다. nullundefined에 대응하는 래퍼 객체는 없다. 따라서 nullundefined에선 메서드와 프로퍼티를 이용할 수 없으며, 프로토타입도 사용할 수 없다.

마무리#

어차피 모두 책을 읽고 내용을 알고있다고 생각이 되서 조금 더 깊은 내용을 정리해보았다.

확실히 내부적인 동작방식을 보면서 자바스크립트는 프로토타입 언어라고 아예 책 제목을 가진 책들도 있는데 점점 공감이 간다.

각 네이티브 타입의 프로토타입을 직접 추가하거나 선언하면 해당 타입을 호출할 때 사용할 수도 있는데, 그럴경우 다른 라이브러리나 프로토타입 명세를 덮어씌울 수 있다고 하니 절대 권장하지 않는다.

참고자료#