본문 바로가기

Javascript

[JavaScript] 강제변환 - 추상 연산, Abstract operation

반응형

 

이 글의 내용은 자바스크립트의 강제변환의 좋고 나쁨을 충분히 이해하고,

자신의 프로그램에 적절한지 스스로 현명하게 판단할 수 있도록 하기위한 내용을 작성했다.

 

어떤 값을 다른 타입의 값을 바꾸는 과정이 명시적이면 '타입 캐스팅, Type Casting' 이고,

값이 사용되는 규칙에 따라 암시적이면 '강제변환, Coercion' 이라고 한다.

'타입 캐스팅'은 정적 타입 언어에서 컴파일 시점에, '강제 변환'은 동적 타입 언어에서 런타임 시점에 발생한다.

 

본 글은 '강제 변환'을 세부적으로 나눠 '명시적 강제변한'과 '암시적 강제변환'으로 구별할 것이며,

'명시적 강제변환'은 코드만 봐도 의도적으로 탕입변환을 일으킨다는 사실이 명백한 반면,

'암시적 강제변환'은 다른 작업 도중 불분명한 부수 효과로부터 발생하는 타입변환을 뜻함을 참고바란다.

 

var a = 42;
var b = a + ""; // 암시적 강제변환
var c = String(a); // 명시적 강제변환

typeof a; // "number"
typeof b; // "string"
typeof c; // "string"

 

추상 연산, Abstract operation


명시적/암시적 강제변환의 내용을 다루기 앞서 어떻게 값이 문자열, 숫자, 불리언 등의 타입이 되는지, 그 기본 규칙을 알아보자.

ES5 명세를 보면 변환 규칙의 '추상 연산'이 정의되어 있다.

 

ToString

문자열이 아닌 값을 문자열로 변환하는 작업은 ToString 추상 연산 로직이 담당한다.

내장 원시 값은 본연의 문자열화 방법이 정해져있다.

 

일반 객체는 특별히 지정하지 않으면 기본적으로 Obejct.prototype.toString()에 있는 toString() 메서드가 내부 [[Class]]를 반환한다.

엄밀히 말하면 객체에서 문자열로 강제변환 시에는 ToPrimitive 추상 연산 과정을 거치므로 참고하자.

 

숫자는 예상대로 그냥 문자열로 바뀌고 너무 작거나 큰 값은 지수 형태로 바뀐다.

배열은 기본적으로 재정의된 toString()이 있고, 문자열 반환시 모든 원소 값이 콤마(,)로 분리된 형태로 이어진다.

 

var a = 1.07 * 1000 * 1000 * 1000 * 1000 * 1000 * 1000 * 1000;
a.toString(); // "1.07e+21"


var a = [1, 2, 3];
a.toString(); // "1,2,3"

 

 JSON 문자열화 

  ToString은 JSON.stringify() 유틸리티를 사용해 어떤 값을 JSON 문자열로 직렬화하는 문제와도 연관된다.

  대부분 단순 값들은 직렬화 결과가 반드시 문자열이라는 점을 제외하고는,

  JSON 문자열화나 toString() 변환이나 기본적으로 같은 로직이다.

 

  그러나, JSON.stringify()는 인자가 undefined, 함수, 심벌 값이면 자동으로 누락시키며

  이런 값들이 만약 배열에 포함되어있으면 null로 바꾸고, 객체 프로퍼티에 있으면 지워버리기까지 한다.

  게다가 *환형 참조 객체를 넘기면 에러가 발생하므로

  직렬화하기 곤란한 객체 값을 문자열화하려면 *toJSON() 메서드를 따로 정의해야한다.

      *환형 참조 객체, Circular References Object: 프로퍼티 참조가 무한 순환되는 구조의 객체

      *toJSON() : 문자열화하기 적당한 JSON 안전 값으로 바꾸는 것으로 JSON 문자열로 바꾸는 것이 아님

 

JSON.stringify(undefined); // undefined
JSON.stringify(function(){}); // undefined
JSON.stringify([1, undefined, function(){}, 4]); // "[1,null,null,4]"
JSON.stringify({a:2, b:function(){}}); // "{"a":2}"

/** 환형 객체 참조 예시 */
var x = {};
var a = {
    b : 5959,
    c : x,
    d : function(){}
};
x.e = a;

JSON.stringify(a); // Uncaught TypeError: Converting circular structure to JSON

a.toJSON = function() { // 직렬화할 프로퍼티를 정의하는 함수를 설정
    return {b : this.b}; 
};

JSON.stringify(a); // "{"b":5959}"

 

 Tip 

   JSON.stringfy()의 두 번째 선택 인자로 배열 아니면 함수 형태의 대체자를 지정하여

   객체를 재귀적으로 직렬화하면서 필터링 하는 방법이 있다. 직렬화 과정에서 해당 키를 건너뛰려면 undefined를 하면 된다.

      - 대체자가 배열이면 전체 원소는 문자열이여하고, 각 원소는 객체 직렬화의 대상 프로퍼티명이다.

      - 대체자가 함수면 처음 한 번은 객체 자신, 그 다음엔 각 객체 프로퍼별로 하나씩 실행하면서 매번 키와 값 두 인자를 전달하한다.

 

var a = {
    b : 5959,
    c : "59",
    d : [1, 2, 3],
    e : undefined
}

JSON.stringify(a); // "{"b":5959,"c":"59","d":[1,2,3]}"
JSON.stringify(a, ["b", "c"]); // "{"b":5959,"c":"59"}"
JSON.stringify(a, ["b", "c", "d", "e"]); // "{"b":5959,"c":"59","d":[1,2,3]}"
JSON.stringify(a, function(k, v) {
    if (k !== "c") return v;
}); // {"b":5959,"d":[1,2,3]}"

 

ToNumber

숫자가 아닌 값을 수식 연산이 가능한 숫자로 변환은 ToNumber 추상 연산 로직이 담당한다.

예시 ) true ⇢ 1, fasle ⇢ 0, undefined ⇢ NaN, null ⇢ 0

 

문자열은 대부분 숫자로 변환되며, 변환이 실패하는 경우 NaN이다. (단, 8진수를 나타낸 문자열은 일반 10진수로 처리한다.)

객체(혹은 배열)는 동등한 원시 값으로 변환(ToPrimitive 추상 연산) 후 그 결과값을 숫자로 강제 변환한다.

동등한 원시 값으로 변환 과정에는 valueOf() 메서드를 먼저 확인하고, 그렇지 않을 경우 toString()을 이용해 강제 변환한다.

만약 원시 값으로 바꿀 수 없을 땐 TypeError 오류를 발생한다.

 

ES5부터 [[Prototype]]이 null인 경우 대부분 Object.create(null)을 이용해 강제 변환이 불가능한 객체(valueOf(), toString() 메서드가 없는 객체)를 생성할 수 있다.

 

var a = [4, 2];
a.toString = function() {
    return this.join("");
};
ƒ () {
    return this.join("");
}

Number(a); // 42
Number([4,2]); // NaN

Number([]); // 0
Number(""); // 0
Number(null); // 0

Number(false); // 0
Number(true); // 1

 

ToBoolean

자바스크립트에서는 숫자는 숫자고, 불리언은 불리언으로 서로 별개다.

1을 true로, 0을 false로 (혹은 true를 1로, false를 0으로) 강제변환 할 수는 있지만 그렇다고 두 값이 똑같은 건 아니다.

 

var a = new Boolean(false);
var b = new Number(0);
var c = new String("");

var d = Boolean(a && b && c);
d; // true

d = a && b && c;
d; // String {""}

 

인자 타입 결과값
Undefined false
Null false
Object true
Number 인자가 +0, -0, NaN이면 false, 그 외에는 true
String 인자가 공백 문자열("")이면 flase, 그 외에는 true
Boolean 인자 값과 동일(변환 안함)

 

그렇다면 true/false가 아닌 값('falsy'한 값이 되는 경우)을 불리언에 강제변환 했을때 false가 되며, falsy한 경우는 아래와 같다.

반대로 아래의 목록이 아니라면 응당 'truthy'한 값이 되는 것이다.

 

- undefined

- null

- false

- +0, -0, NaN

- ""

 

 Falsy 객체 

  Falsy 객체가 무엇일까? falsy한 값을 감싼 객체란 뜻일까. 전혀 아니다.

  사실 이 객체는 순수 자바스크립트의 일부가 아니다.

  일반적인 자바스크립트 의미 뿐만 아니라 브라우저만의 특이한 작동 방식을 가진 값을 생성하는 경우가 있는데, 이것이 falsy 객체이다.

 

  falsy 객체는 평범한 객체처럼 작동할 것 같지만, 불리언으로 강제변환하면 false이다.

  

  이 객체의 대표적인 예는 document.all 이다.

  이는 '비표준'이며, 이미 오래전에 비권장/폐기 되었지만 레거시 소스에 남겨져 있어

  현대 IE에는 이를 해결할 방법으로 자바스크립트의 타입 체계를 바꿔 document.all을 falsy인 것 처럼 돌아가게 바꿨다..

  따라서 브라우저 때문에 말도 안되는 비표준(falsy 객체)가 자바스크립트에 더해지게 된 것이다.

 


[타입(typeof)] chati.tistory.com/138

 

[JavaScript] 타입(typeof) : 배열, 문자열, 숫자, 값이 아닌 값

JavaScript 내장 타입 원시 타입, primitives null falsy한 유일한 원시값이지만, 타입은 'object' 인 특별한 존재 undefined 값이 없는 변수 값이거나, 선언되지 않은 변수 값 - 값이 없은 undefined : 접근 가..

chati.tistory.com

[강제변환 - 명시적 강제변환, Explicit Coercion] chati.tistory.com/153

 

[JavaScript] 강제변환 - 명시적 강제변환, Explicit Coercion

이 글의 내용은 자바스크립트의 강제변환의 좋고 나쁨을 충분히 이해하고, 자신의 프로그램에 적절한지 스스로 현명하게 판단할 수 있도록 하기위한 내용을 작성했다. 어떤 값을 다른 타입의 ��

chati.tistory.com

[강제변환 - 암시적 강제변환, Implicit Coercion] chati.tistory.com/154

 

[JavaScript] 강제변환 - 암시적 강제변환, Implicit Coercion

이 글의 내용은 자바스크립트의 강제변환의 좋고 나쁨을 충분히 이해하고, 자신의 프로그램에 적절한지 스스로 현명하게 판단할 수 있도록 하기위한 내용을 작성했다. 어떤 값을 다른 타입의 ��

chati.tistory.com

 


[참고] You Don't Know JS 타입과 문법, 스코프와 클로저 - 카일 심슨

 

반응형

❥ CHATI Github