[JavaScript]자바스크립트의 타입& 값

2021. 7. 6. 21:52Web_Programming/JavaScript

 

 

이번 포스팅에서는 자바스크립트의 타입과 값에 대한 내용을 다뤄보겠습니다. 웹 프로그래밍을 공부하고 프로젝트를 하다보니, 자바스크립트에 대한 기초적인 부분과 깊은 내용들이 부족하다고 느껴 좀더 이론적으로 공부를 시작했습니다. 

공부할 교재를 찾던 중 "You Don't Know JS"[카일 심슨] 이 교재가 유명하고 내용을 잘 다뤄놨다고 해서 공부를 시작했습니다.


💻 타입

자바스크립트는 동적언어라고 해서 타입이 없는 것이 아니라, 

자바스크립트 엔진&개발자 모두가 값을 다른값과 분별할 수 있는 "고유한 내부 특성의 집합"을 뜻합니다.

 

1.1 타입, 그 실체를 이해하자

어떤 형태로든 거의 모든 자바스크립트 프로그램에서 강제변환이 일어나므로 타입을 확실하게 인지하고 사용하는 것이 중요합니다.

 

1.2 내장 타입

자바스크립트의 내장 타입으로는 아래의 종류들이있습니다.

  • null
  • undefined
  • boolean
  • number
  • string
  • object
  • symbol

그리고 이중 object를 제외한 타입들을 원시타입(Primitives)( : 객체가 아니면서 메서드도 가지지 않는 데이터)이라 합니다.

 

🔹 typeOf

typeOf연산자를 통해서 값들을 타입(ex."number")을 문자열로 반환받을 수 있는데요,

 

하지만 위의 내장 타입들중 null의 경우는 "object"로 반환이 됩니다.null은 "falsy한 유일한 원시값이지만, 타입은 object인 특별한 존재"입니다.그래서 null을 정확히 확인하기 위해선, 아래와 같이 확인할 수 있습니다.

var a=null;
(!a && typeOf a === "object"); //true

 

추가적으로, typeOf 연산자는 function 또한 "function"으로 반환해줍니다.여기서 function은 object의 하위 타입으로서 '호출 가능한 객체(callable Object)'라 명시되어있습니다.

 

배열의 경우는 그냥 "object"로 반환을 합니다.배열 또한 object의 하위 타입으로 일반적인 Object와 다르게 숫자 인덱스를 가지고, 추가특성(length 프로퍼티가 자동으로 관리) 등 을 지닙니다.

 

1.3 값은 타입을 가진다

자바스크립트에서는 타입강제(Type Enforcement)를 하지 않아, 변수에는 따로 타입이 없습니다.

 

변수는 "언제라도, 어떤 형태의 값이라도 할당이 가능"합니다.변수에 typeOf연산자를 수행하면,

이는 "변수에 들어있는 값의 타입"을  반환해주는 것으로 볼 수 있습니다.

 

🔹 값이 없는 [undefined] vs 선언되지 않은 [undeclared]

자바스크립트에서 undefinedundeclared는 완전히 다른 개념으로 사용됩니다.

 

undefined: 접근 가능한 스코프에 변수가 선언되었으나 "현재 아무런 값도 할당되지 않은"상태

undeclared : 접근 가능한 스코프에 "변수 자체가 선언조차 되지 않은 상태"

 

+브라우저 error에서 ReferenceError : b가 정의 되어있지 않습니다. 의 경우는 undefined가 아닌 undeclared를 의미합니다.

 

하지만, typeOf연산 결과는 undefined와 undeclared 모두 "undefined"로 반환됩니다.

이는 typeOf의 안전가드로, referenceError를 방지하기 위해 유용합니다.


💻 값

2.1 배열

자바스크립트 배열은 다른언어들과 달리 "어떤 타입의 값이라도" 담을 수 있습니다.

또한, 배열의 크기는 미리 선언하지 않고도 사용가능합니다.

추가적으로 빈 슬롯이 있는 sparse 배열을 다루 때에는 빈 슬롯이 undefined를 나타내어 혼란을 줄 수 있어 유의해야합니다

.

var a=[]
a[0]=1;
a[2]3;

a[1];//undefined
a.length;//3

 

배열도 하나의 객체이기 때문에, "키/프로퍼티 문자열"을 추가할 수 있습니다.

+ 하지만 배열의 length에는 영향을 주지 않습니다.

❗ 여기서 주의 해야할 부분은 키값으로 10진수 숫자를 문자열로 넣으면 마치 숫자 키(인덱스)를 사용한 것같은 결과가 초래된다는 점입니다.

var a=[];
a[0]=1;
a["foobar"] =2;

a.length;//1
a["foobar"];//2
a.foobar;//2

a["13]=42;
a.length; //14

그래서, 배열의 인덱스에는 확실히 숫자만 사용하고, 이외에는 객체로 대용하는 것이 좋습니다.

 

+유사배열 -> 배열

유사배열을 배열로 변환할 때는 

Array.prototype.slice.call(/*유사배열*/)로 값을 복사해와서 배열로 변환하거나,

+ Array.prototype.slice.call(/*유사배열*/) ==Array.prototype.slice.call(/*유사배열*/,0)

 

Array.from(/*유사배열*/)을 이용하여 변환 할 수 있습니다. (ES6이후)

 

2.2 문자열

자바스크립트에서 문자열과 문자배열은 같지 않습니다.

비록, 둘다 length 프로퍼티, indexOf(), concat()과 같은 메소드를 가지지만

 

문자열은 "Immutable" 불변, 배열은 "Mutable" 가변 값입니다.

 

추가적으로 인덱스를 통한 문자열 접근의 경우는 일부는 아예 문법에러로 인식하기도 합니다.

(CharAt(/*인덱스*/)로 접근)

 

문자열의 메소드는 문자열의 값을 변경하는 것이 아닌, "새로운 문자열을 생성한 후 반환"하는 것입니다.

var a="foo"
c=a.toUpperCase();
a===c;//false

배열 메소드를 문자열에 사용하는 방법은 Array.prototype.map.call(/*문자열*/, "-")과 같이 배열로 변환하여 사용 가능합니다. (불변 메소드에 한해서)

 

❗ 문자열의 순서를 뒤집는 reverse() 가변 메소드의 경우는 문자 배열에서만 사용가능합니다.

그래서 문자열을 뒤집고 싶다면,아래와 같이 문자열 -> 배열 -> 문자열로 변환하여 가능합니다.

var c = "foo"
	.split("")//["f", "o", "o"]
    .reverse()//["o", "o", "f"]
    .join("")//"oof"

 

2.3 숫자

자바스크립트는 정수, 부동 소수점 숫자 모두 number타입으로 표시합니다.

 

🔹 숫자 구문

자바스크립트 숫자 리터럴은 10진수 리터럴로 표시합니다.

- 소수점 앞 정수가 0이면 생략가능

- 소수점 이하가 0이면 생략가능

- 소수점 이하 0은 생략

var a = 0.42; //.42
a = 42.0; //42.(지양하는 방법)
a = 42.300;
a; //42.3
a = 42.0
a; //42

- 아주 크거나 작은 값은 지수형으로 표시 (toExponential()메소드 결과와 동일)

var a= 5E10;
a; //50000000000
a.toExponential(); //"5e+10"

 

숫자 값은 Number객체로 박싱이 가능하여 Number.prototype의 메소드 또한 사용가능합니다.

ex. toFixed() : 지정한 소수점 이하 자릿수까지 숫자를 반환 , toPrecision() : 유효숫자 개수를 지정하여 반환

 

- 2진, 8진, 16진법으로도 나타낼 수 있습니다. (대문자 사용은 지양)

0o363; //243의 8진수
0b11110011; //243dml 2진수

 

🔹 작은 소수 값

자바스크립드의 이진 부동 소수점 숫자의 부작용으로 아래와 같은 문제가 있습니다.

0.1+0.2 === 0.3 //false

0.1+0.2 결과 를 정확히 말하면 0.30000000000000004에 가깝기 때문입니다. 

이를 처리하는 일반적인 방법으로는 반올림 오차를 허용처리 하는 것입니다.

 

미세한 오차 값을 나타내는 Number.EPSILON (=2^-52) 을 사용하여 값의 차이가 입실론 값보다 작다면 동등하다고 처리 할 수 있습니다.

 

🔹 안정한 정수 범위

위와 같은 표현방식 문제들로 정수는 Number.MAX_VALUE 보다 훨씬 작은 범위로 안전값의 범위' 2^53-1 '가 정해집니다.

Nuber.isInteger() 와 Nuber.isSafeInteger() 메소드를 통해 정수와 안전한 정수의 여부를 체크 할 수 있습니다.

 

 

2.4 특수 값

🔹 값 아닌 값

undefined와 null은 타입과 값이 항상 같고, null의 경우는 식별자에 값의 할당이 불가능합니다.

하지만 undefined는 null과 달리 식별자에 값을 할당 할 수는 있지만 지양하는 방법입니다.

- void 연산자

void는 어떤 값이든 무효로 만들어 주어 결괏값을 undefined를 얻을 수 있습니다. 

 

🔹 특수 숫자

NaN (The not number, number)

- 수학 연산 시 두 피연산자가 "전부 숫자가 아닐 경우"의 결과로 숫자가 아님을 뜻함.

- 하지만, NaN은 숫자 집합 내에서의 에러 사항으로 typeOf NaN은 "number"을 반환.

- 또한 NaN은 자신과도 동등하지 않아, 동등 여부를 "==="으로 판별할 수 없음.

 

이를 판별하기 위해 isNaN()함수가 있지만

이는 number타입이 아니라면 모두 true를 반환하는 문제가 있습니다.

 

그래서 이를 해결한 것이 Number.isNaN()함수 (ES6이후)

var a= 2/"foo"; //NaN
var b = "foo";

window.isNaN(a); //true
window.isNaN(b); //true

Number.isNaN(b) //false

 

무한대

자바스크립트는 유한 숫자 표현식을 사용하여 무한대의 값을 다룰 수 있습니다.

var a = 1/0; //Infinity
var b = -1/0; //-Infinity

+무한대/무한대 는 NaN을 나타낸다.

 

영(0)

자바스크립트는 변숫값이 0에 도달하여 부호가 바뀌는 순간 즉 잠재적인 정보소실을 방지하기 위해 음의 영 (-0)을 다룹니다.

var a = 0 /-3; //-0

- 음의 영은 문자열로 변환시에는 , 0으로 보여지고,

- 음의 영을 문자열에서 숫자로 바꾸면 그대로 보여진다.

- 추가적으로, 비교연산시에도, 0과 동일하게 처리된다.

 

Object.is()를 통해서 NaN과 -0을 간편히 판별가능 (ES6)

var a = 2/"foo"; //NaN
var b = -3*0; //-0

Object.is(a, NaN); //true
Object.is(b, -0); //true
Object.is(b, 0); //false
/*
if(!Object.is){
	Object.is = function(v1, v2){
    	if(v1 === 0 && v2 === 0){
        	return 1/v1 === 1/v2; // 1/-0:Infinity 1/0:Infinity  
        }
        if(v1 !== v1){
        	return v2!==v2; //NaN!==NaN
        }
        return v1 === v2;
    }
}
*/

 

2.5 값 vs 레퍼런스

자바스크립트에서의 레퍼런스는 다른 변수를 참조하는 것이 아닌 다른 변수의 레퍼런스를 공유하여 개별적으로 참조.

  • 스칼라 원시값(null, undefined, string, number, boolean, symbol)의 경우, 언제나 "값-복사"방식
  • 합성값(객체, 함수)는 반드시 "레퍼런스-복사" 방식

🔹 값-복사

var a = 2;
var b = a;
b++;
a; //2
b; //3

스칼라 원시 값은 값-복사 방식으로 b에는 a와 다른 사본의 레퍼런스가 자리잡아, b값 변경에 a는 영향을 받지 않는다.

 

🔹 레퍼런스-복사

var c = [1, 2, 3];
var d = c;
d.push(4);
c; //[1, 2, 3, 4]
d; //[1, 2, 3, 4]

해당 배열은 c와 d모두 레퍼런스-복사 방식으로 동일한 [1, 2, 3]을 공유

따라서 d로 인해 값변경된 레퍼런스가 c또한 공유하고 있어, 동일하게 나타난다.

 

❗ 하지만, 이후에 d가 다른 레퍼런스를 참조 하게 되고 값을 변경한다면, c와 d는 별개의 레퍼런스를 참조하므로, 전혀 영향을 주지 않는다.

 

함수 인자의 레퍼런스 복사도 동일하게 일어난다.

함수 내에서 참조를 변경하고, 값을 변경하면 인자로 들어갔던 외부 값에는 영향을 안준다.

function foo(x){
	x.push(4);
    x; //[1, 2, 3, 4]
    x=[4, 5, 6];
    x.push(7);
    x; //[4, 5, 6, 7]
}
var a= [1, 2, 3];
foo(a);
a; //[1, 2, 3, 4];

 

레퍼런스-복사 👉 값-복사

만약 함수 인자로 들어간 합성값에 영향을 주고 싶지 않다면, a.slice()와 같이 새로운 배열을 복사하여 값-복사 방식으로 전달 할 수 있다. 

 

값-복사 👉 레퍼런스-복사

스칼라 원시 값을 합성값(객체, 배열)에 감싸 넘기면 레퍼런스-복사 처럼 원시값에 영향을 줄 수도 있다.

function foo(wrapper){
	warpper.a=42;
}

var obj={ a:2 };
foo(obj);
a; //42

+ 하지만, Number객체의 경우 

공유된 원시값 변경시, Number객체에서 자동 언박싱 되어 변경후, 다시 Number객체로 바뀌어 버린다.

따라서 , 기존 원시값은 불변의 원본 Nunber의 객체를 생성한다.

 

 


포스팅 마치겠습니다. :-D

반응형