[Javascript] 변수형과 자료형
1. 지역변수, 전역변수
- 전역변수 : window 내에서 어디에서든 사용가능
- 지역변수 : 해당 함수에서만 사용 가능
(1) 함수 밖에서 var 변수명; 으로 선언한 변수는 전역변수이다.
(2) 함수 안에서 변수명; 으로 선언한 변수는 지역변수이다.
(3) 지역변수가 우선권을 가지기 때문에 지역변수가 출력된다.
(4) 전역변수 사용시 window.변수명 또는 this.변수명 으로 표현
<script>
str = "전역변수";
var str2 = "var 전역변수"; //(1)
window.onload = function(){
var str = "지역변수"
var str3 = "새로운 지역변수"
str4 = "전역변수" //(2)
console.log("-----------------str호출------------------")
console.log(str); //지역 (3)
console.log(this.str); //전역 (4)
console.log(window.str); //전역
console.log("-----------------str2호출------------------")
console.log(str2); //전역 --> 지역변수 안에 없어서 자동으로 전역변수에서 찾는다.
console.log(this.str2); //전역
console.log(window.str2); //전역
console.log("-----------------str3호출------------------")
console.log(str); //지역변수
console.log(this.str); //undefined
console.log(window.str); //undefined
console.log("-----------------str4호출------------------")
console.log(str); //전역
console.log(this.str); //전역
console.log(window.str); //전역
}
</script>
2. var, let, const
- var : 선언된 위치 내부의 블록 전체에서 사용할 수 있는 범위의 변수 선언
블록 내부에서 재선언하여도 새로운 변수가 생성되지 않는다. - let : 선언된 블록 영역만 사용할 수 있는 제한 범위 변수
값 재할당 가능
재선언X - const : let과 같이 블록 범위에서만 사용할 수 있음
상수의 값은 재할당을 통해 변경 X
재선언 X
<script>
function constTest(){
var x = 1;
const y = 3;
if(true){
var x = 2;
const y = 2;
//y = 2; --> 에러난다. -->위에 y랑 다른 블록이라서 적용 안됨!
console.log("[var]내부에서 선언된 x : "+ x); //2
console.log("[const]내부에서 선언된 y : "+ y); //2
}
//y=4; --> const는 재할당 안됨!
//const y = 4; --> 재선언 안된다.
console.log("[var]외부에서 선언된 x : "+ x); //2 -->if문에서 새로 var 생성했는데도 1이 아닌 2가 나온다. 즉 var는 전체 영역에서 쓰는 변수
console.log("[const]외부에서 선언된 y : "+ y); //3
}
<script>태그 내부의 const y와 if문 내부의 const y는 다른 변수이다.
따라서 if문에서 y를 const y = 2;로 바꿔줘도 if문 밖에서 출력해보면 "1"이 출력된다.
그리고 상수의 개념으로 받기 때문에 새로운 값으로 할당도 안된다.
let은 var와 달리 const의 제한범위와 같지만 x=3; 으로 값을 재할당할 수 있다.
3. 자료형
- prototype
- __proto__
자바스크립트는 느슨한 타입 (loosely typed) 언어, 혹은 동적 (dynamic) 언어이다. 그 말은, 변수의 타입을 미리 선언할 필요가 없다는 뜻이다. 타입은 프로그램이 처리되는 과정에서 자동으로 파악될 것이다. 또한 그 말은 같은 변수에 여러 타입의 값을 넣을 수 있다는 뜻이다.
즉 자바스크립트에서 자료형별로 변수타입이 지정하는 것이 아니라, 리터럴에 의해서 자료형이 결정된다.
1) 기본자료형(Primitive type) : immutable value
- Boolean
- Null
- Null타입은 딱 한가지 값, null을 가질 수 있다.
- Undefined
- 값을 할당하지 않은 변수는 undefined값을 가진다.
- Number
- 정수만을 표현하기 위한 특별한 자료형은 없다.
- +Infinity, -Infinity, NaN(Not a Number)와 같은 값도 표현할 수 있다.
- 42 / 0 --> Infinity (0으로 나눴을 때)
- 42 / -0 --> Infinity (-0으로 나눴을 때)
- String
- Symbol
2) 객체(Objects) : 주소를 참조하는 참조변수
- Arrays
- 배열은 Array.prototype을 상속받으므로 배열을 다룰 때 편한 indexOf와 push같은 함수를 사용할 수 있다.
- Dates
3) typeof( ) : 피연산자의 자료형을 나타내는 문자열 반환
- typeof(배열객체) -> object 반환되는데 이는 아래에서 확인 가능하다.
- typeof(null) : object
typeof(undefined) : undefined - null과 undefined 모두 값이 없는데 typeof를 하면 undefined는 내 예상대로 나오지만 null은 object가 나온다. 이는 typeof에서 null을 체크하지 못해 생기는 버그이다.
그렇다면 좀더 정확하게 null을 얻을 수는 없을까? 그러기 위해서는 아래 4번을 참고해보자. - 객체변수인 user의 name속성을 가져오고 싶다면 어떻게 해야할까?
area1.innerHTML += "객체 속성접근 : "+ user.name + "<br>" 이렇게 "변수명.속성명" 으로 알 수 있다.
<button onclick="testType();">자료형 테스트</button>
<br><br>
<div id="area1" class="area"></div>
<script>
function testType(){
//문자열 변수
var name = "유재석";
console.log(name);
console.log(typeof(name)); //string
//숫자 변수
var age = 20;
console.log(age);
console.log(typeof(age)); //number
//논리 변수
var check = true;
console.log(check);
console.log(typeof(check)); //boolean
//배열변수
var hobby = ["축구","농구","야구"]
console.log(hobby); //__proto__ : array __proto__: object
console.log(typeof(hobby)); //object 객체!
//객체 변수
var user ={
name : "유재석",
age : 20,
id: "user01"
}
console.log(user);
console.log(typeof(user));
//함수변수
var testFunction = function testFunction(num1, num2){
var sum = num1+num2;
}
console.log(testFunction)
console.log(typeof(testFunction)); //function
function Person(name,first,second){
this.name=name;
this.first=first;
this.second=second;
}
console.log("Person"+Person); //function
console.log(typeof(Person));
//정의되지 않은 변수
var apple;
console.log(apple);
console.log(typeof(apple)); //undefined
var area1 = document.getElementById("area1");
area1.innerHTML += "문자열 변수 : "+ name + "<br>";
area1.innerHTML += "숫자 변수 : "+ age + "<br>";
area1.innerHTML += "논리 변수 : "+ check + "<br>";
area1.innerHTML += "배열 : "+ hobby + "<br>";
area1.innerHTML += "배열인덱스[0] : "+ hobby[0] + "<br>";
area1.innerHTML += "객체 : "+ user + "<br>";
area1.innerHTML += "객체 속성접근 : "+ user.name + "<br>";
area1.innerHTML += "함수 : "+ testFunction + "<br>";
area1.innerHTML += "정의되지않은 변수 : "+ apple + "<br>";
}
위 콘솔 출력결과는 아래 코드의 결과이다.
hobby의 __proto___와 user의 __proto__값을 볼 수 있다.
hobby는 array의 prtotype객체에서 공유프로퍼티를 받고있는데(__proto__ : Array(0)) 또 세모표시를 눌러서 안에를 확인해보면 array의 prtotype객체에서 공유프로퍼티는 __proto__:Object를 가지고 있다. 즉 프로토타입의 최상위에서 Object의 프로토타입객체에게 상속받는 것이다.
//배열변수
var hobby = ["축구","농구","야구"]
console.log(hobby); //__proto__ : array __proto__: object
console.log(typeof(hobby)); //object 객체!
//객체 변수
var user ={
name : "유재석",
age : 20,
id: "user01"
}
console.log(user);
console.log(typeof(user));
4. Obejct.prototype.toString : object 의 타입을 반환해주는 메소드
아래와 같이 Function.prototype.call이나 Function.prototype.apply을 같이 사용해줘야 한다.
toString만 써주면 될 것 같은데 왜 call을 사용해줘야 할까?
call은 전달받은 인자를 호출한 메소드내에서 참조하게끔 만들어 주는 역할을 하기 때문이다.
즉, Object.prototype.toString.call(hobby) 에서 call의 인자로 hobby를 넘기면 toString의 this는 hobby가 된다. 그래서 [object Array]를 반환할 수 있다.
<h3>동적타입테스트</h3>
<button onclick="testDynamicType();">실행확인</button>
<script>
function testDynamicType(){
var testVariable =123;
console.log(typeof(testVariable)+" : "+testVariable)
testVariable ="안녕하세요";
console.log(typeof(testVariable)+" : "+testVariable)
console.log(getTypeTest(testVariable)) //string
console.log(getTypeTest(null)) //***null***
console.log(getTypeTest(undefined)) //undefined
console.log(typeof(null)); //***object*** --> 둘다 값 없는데 object로 나옴
console.log(typeof(undefined)); //undefined
var hobby = ["축구", "농구", "야구"];
console.log(hobby) //배열
console.log(typeof(hobby)); //object
console.log(getTypeTest(hobby)); //array
console.log(hobby.__proto__) //array
console.log(hobby.toString(hobby)) //축구 농구 야구
console.log(hobby.__proto__.toString.call(hobby)) //축구 농구 야구
console.log(Array.prototype.toString.call(hobby)) //축구 농구 야구
console.log(hobby.__proto__.__proto__.toString.call(hobby)) //[object Array]
console.log(Object.prototype.toString.call(hobby)) //[object Array]
console.log(Object.prototype.toString.call(testVariable)) //[object String]
//프로토타입 체인
}
//null명확히 체크하려면
function getTypeTest(val){
return Object.prototype.toString.call(val).slice(8,-1)
//return Object.prototype.toString.call(val) -->이렇게하면 --> [Object String]
//배열 - toString 재정의 되어있다.(자바에서처럼)
//배열 - object에서 있던 tostring이 배열에서 재정의가 되어있어서 문자열로 받을 수 있다.
//prototype : 상위에서 내려줘서 사용가능한 메소드
//_proto_: 상위메소드 보여줌
//배열 최상위 -> object이라서 object 메소드 정의하지 않아도 사용할 수 있다.
}
</script>
- hobby.__proto__. 와 Array.prototype 모두 Array의 prototype객체를 가리킨다.
- hobby에는 toString 메소드가 없지만 결과가 나오는 동일하게 나오는 이유는 자바스크립트 내부에서 해당 객체에 메소드가 없는 경우 prototype객체에 가서 찾기 때문이다.
console.log(hobby.toString(hobby)) //축구 농구 야구
console.log(hobby.__proto__.toString.call(hobby)) //축구 농구 야구
console.log(Array.prototype.toString.call(hobby)) //축구 농구 야구
- 위와 비슷한데 아래는 해당객체의 내부 값이 아닌 자료형을 문자열로 반환해준다.
- toString() 메서드는 Object에서 비롯된 모든 객체에 상속된다. 그래서 이 메서드가 사용자 지정 개체에서 재정의되지 않으면 toString()은 "[object type]"을 반환한다. 여기서 type은 object type이다.
- Object.prototype.toString.call( ) 에 null을 인자로 주면 [object null]이,
Object.prototype.toString.call( ) 에 undefiend을 인자로 주면 [object undefined]가 출력된다.
즉 위 3번에서 typeof의 한계를 해결했다고 볼 수 있다.
console.log(hobby.__proto__.__proto__.toString.call(hobby)) //[object Array]
console.log(Object.prototype.toString.call(hobby)) //[object Array]
console.log(Object.prototype.toString.call(testVariable)) //[object String]
console.log(getTypeTest(null)) //***null***
console.log(getTypeTest(undefined)) //undefined
배열인 hobby.toString()은 배열의 값을 가져오고, Object.prototype.toString은 왜 [object Array]를 반환하는가?
이는 자바에서처럼 toString이 hobby.__proto__가 가리키는 배열의 prototype객체가 obejct의 toString()메소드를 재정의했기때문이다. 재정의되지 않았다면 Object로 부터 상속받은 기본값을 반환하여 [object type]을 반환하게 된다.
5. prototype 와 __proto__ 의 차이
- 자바스크립트는 프로토타입 기반 언어이다. 그렇다면 prototype이란 무엇인가?
- prototype : 다른 객체에 공유 프로퍼티(메서드 포함)을 제공하는 객체이다.
- 자바스크립트의 모든 객체는 자신의 부모를 가리키는 참조링크인 prototype이 존재한다.
- 객체는 자신의 prototype에 접근할 때 "__proto__"를 이용한다.
- 자바스크립트의 모든 객체는 Object 객체의 프로토타입을 기반으로 확장되었기 때문에 연결의 끝은 항상 Object이다.
1) prototype 과 __proto__예시
자바스크립트에서 "함수"는 아주 독특하다. 자바스크립트에서 "함수"는 객체이다!
함수는 객체이기 때문에 property(속성)을 가질 수 있다.
- Person함수가 생성될때 Person의 prototype도 생성된다.
person의 prototype도 객체이므로 __proto__를 갖고있다!! - Person객체에 내부적으로 prototype이라는 property가 생기고 그 속성은 person의 prototype을 가리킨다.(Person.prototype)
- person의 prototype도 constructor라는 property를 생성해서 Person객체를 가리킨다.
- 서로간에 상호참조를 하고 있다.
- var kim = new Person("kim",10,20) -> kim객체 생성하면 kim객체에는 "__proto__"라는 속성이 생성된다.
"__proto__"속성은 kim이라는 객체를 생성한 person의 prototype을 가리킨다. - 즉 Person의 prototype에 접근하기
- Person.prototype
- kim.__proto__
참고 및 출처
1. https://opentutorials.org/module/4047/24629
2. https://mygumi.tistory.com/312
4. https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Object/toString