Simplify

[javascript] 1. javascript 기본 개념 본문

Web & Server/javascript

[javascript] 1. javascript 기본 개념

Simplify - Jonghun 2015. 7. 26. 18:50

1.1 리터럴

프로그램은 데이터를 가공하고 가공된 결과를 저장소(메모리, 파일, 데이터베이스 등)에 저장하고 다시 저장소에서 읽어와서 메모리에 올리는 등 실행되는 동안 수 많은 데이터 가공 작업을 하게 된다.

var v = 2 // 숫자
var v = "2" // 문자열
var v = '2' // 문자열
var v = true // 불린

이처럼 코드 상에 직접 값이 표현되는 방식을 리터럴(literal) 이라고 한다. 프로그램의 코드에 직접 코딩돼 있더라도 데이터의 타입에 따라 "리터럴" 표현이 다르기 때문에 프로그래밍 언어는 각 값의 타입을 구분할 수 있다.

자바스크립트에는 모든 타입의 값을 코드 상에서 표현할 수 있는 리터럴이 각각 존재한다. 앞에서 보여준 것과 같은 숫자, 문자열, 불린값 외에도 객체, 배열, 정규식 객체를 나타내는 리터럴 표현도 있다.

{ p1 : 2, p2 : "2" } // 객체의 리터럴 표현
{ 1, 2, 3, 4 } // 배열 객체의 리터럴 표현

코드상에서 데이터 값을 표현하는 방식을 리터럴 이라고 한다. 자바스크립트의 모든 데이터 타입에는 리터럴 표현이 있다.


1.2 변수

데이터 값을 리터럴로만 표현할 수 있는 것은 아니다. 

alert( (1 + 2) + 3);

위 처럼 간단한 연산의 경우, 결과를 출력하는 과정에서 불필요하게 변수에 저장하는 과정을 거칠 필요가 없다. 그러나 복잡한 연산의 경우, 임시로 저장해 가며 중간 단계의 값을 저장하는 것이 가독성에도 좋다.

v = 1 + 2;
alert(v + 3);

위 코드에서 v 처럼 임시로 값을 저장할 수 있는 요소를 변수(variable) 이라고 한다. (다른 언어들과 비슷하다.)

프로그램이 실행되면 변수는 메모리에 공간을 차지하게 된다. 메모리 공간을 할당받으려면 어느 정도의 메모리를 할당받아야 하는지 정해주는 것이 효율적이고, 그런 정보를 제공하기 위해서 데이터 타입(data types)을 정의한다. 다른 언어들과 마찬가지로 javascript 도 여러 가지의 데이터 타입을 제공한다.

변수의 메모리 크기는 데이터 타입에 따라 결정된다.


1.3 데이터 타입

모든 프로그래밍 언어는 각자 지원하는 기본적인 데이터 타입이 있다. 자바스크립트 또한 다른 언어에서도 지원하는 3가지 기본적인 원시 데이터 타입(primitive data type)인 숫자(number), 문자열(string), 불린(boolean) 타입을 지원한다. 그리고 nullundefined라는 2가지 추가적인 데이터 타입을 지원한다. (자바스크립트에서는 모든 종류의 숫자를 내부적으로는 "number"로만 인식한다)

1, 1.2, 0xff

모든 종류의 숫자를 내부적으로 "number"로만 인식한다는 것은 모든 수가 동일한 크기의 변수에 저장된다는 의미이다.

자바스크립트에서는 원시데이터 타입으로 숫자, 문자열, 불린 그리고 null, undefined를 정의하고 있다.

자바스크립트도 객체지향을 지원하는 만큼 다른 객체지향 프로그래밍 언어에서처럼 객체(object)라는 데이터 타입을 지원한다. 숫자, 문자열 같은 원시 타입과 객체가 모여서 다른 객체를 구성할 수 있다. 숫자, 문자열 같은 원시 타입과 객체가 모여서 다른 객체를 구성할수 있다. 객체를 구성하는 멤버에는 고유한 이름이 부여돼 있어서 그 이름으로 접근할 수 있다. 따라서 각 구성 값이 정렬돼 있을 필요는 없다 한편 객체 중에는 객체를 구성하는 멤버의 이름이 없는 대신 넘버링되어 순차적으로 구성될 수도 있다. 이 경우 객체를 구성하는 특정 값에 접근하려면 숫자, 즉 인덱스를 이용한다. 이처럼 정렬된 객체의 구성값에 인덱스를 통해 접근할 수 있는 객체를 배열(array)이라고 한다. 

자바스크립트에서 객체 타입만큼 중요한 데이터 타입이 있는데, 바로 함수(function) 객체다. 함수는 흔히 알고 있는 호출 가능한 루틴으로서의 함수를 비롯해 데이터로서의 함수, 다른 객체를 생성하는 요소로서의 함수가 있다. 함수도 객체이긴 하지만 역할에 따라 자바스크립트 언어에는 함수를 다루는 특별한 문법이 정의돼 있다. 이 밖에도 날짜/시간(Date), 정규식(RegExp) 같은 타입이 있다.

원시 타입 : 숫자, 문자열, 불린, null, undefined

객체, 배열

함수

기타 : 날짜, 정규식 등


1.4 var 변수

자바스크립트에서 변수를 선언할 때는 var 를 사용한다. 그해서 흔히 "var 변수"라고 부르기도 한다. 

var a;

var a, b;

var a = 1, b = 2;

두 번째 예처럼 같은 var 를 이용해 여러 개의 변수를 선언할 수도 있다. 또는 세 번째 예 처럼 변수를 선언하면서 초기값을 할당할 수도 있다. 한 문장이 종료됐음을 나타내기 위해 세미콜론(;) 으로 마무리 짓는다. 세미콜론을 붙이는 것은 반드시 필요한 것은 아니지만 좋은 습관이다.

자바스크립트가 다른 일반 언어와 다른 점은 var 변수에 어떤 타입의 값이라도 할당할 수 있다는 점이다. 

var v1 = 1;
v1 = "하나";

자바스크립트의 var 변수에는 문자열, 숫자 같은 기본적인 타입 값 뿐만 아니라 어떠한 사용자 정의 객체라도 할당할 수 있다. 이것이 가능한 이유는 다른 언어와는 다른 타입 체계를 채용했기 때문이다. 자바스크립트를 흔히 약한 타입의 언어(weakly typed language)라고 한다. 

약한 타입의 언어는 강력한 타입의 언어(strongly typed language)와 대비되는 용어로서 강력한 타입의 언어에서는 변수를 선언할 때 반드시 변수의 타입도 선언해야 하고 변수가 받아들일 수 있는 값의 타입도 선언된 타입과 일치해야 한다. 정수형 변수, 문자형 변수라고 표현하면 그 변수에는 각각 정수형 데이터, 문자형 데이터만 입력할 수 있다. 따라서 강력한 타입의 언어에서는 어떤 변수를 대상으로 하는 연산이 가능한지 여부를 컴파일러가 사전에, 즉 컴파일하기 전에 알 수 있다. 강력한 타입의 언어에서는 다음과 같이 컴파일하기 전에 변수의 타입을 선언해야 한다. 

정수형 v1 = 1;
문자열형 v2 = "2";
v2 + v1;

필요하다면 언어에 따라 v2 + v1 을 수행하기 위해 타입 변환이 일어날 수도 있다. 강력한 타입의 언어에서는 이런 타입 변환이 가능한지도 컴파일할 때 판별된다. 


1.5 값 타입의 데이터와 참조 타입의 데이터

a.num = 3;
b = "3";
c = a.num + b;

프로그램을 실행하다가 c = a.num + b 문장을 만났다고 하자. 자바스크립트는 우선 + 연산에 사용되는 좌우측 최종 값을 찾아가게 될 것이다. a.num 의 값을 찾아가 보자. "." 연산자 때문에 a 가 가지고 있는 속성 중에서 num을 찾는다. 그래서 그 값을 + 연산자의 좌측 값으로 사용하게 된다.

이러한 연산 과정에서 자바스크립트는 변수의 공간에 있는 값을 최종값으로 사용할 것인지(b의 경우), 아니면 다른 메모리를 참조하는 값으로 판단해야 할지(a의 경우)를 결정할 수 있어야 한다. 즉, 변수에 있는 데이터가 값 타입(value type)인지 참조 타입(reference type)인지 알 수 있어야 한다.

자바스크립트에서 제공하는 값 타입의 데이터는 숫자(number), 문자열(string), 불린(boolean)이 있고 조금 특수하게 "정의되지 않음", "객체가 없음" 이 있다. 이런 값 타입 데이터를 제외하고 자바스크립트에서 제공하고 사용자가 정의한 타입은 모두 참조 타입의 데이터라고 보면 된다.

자바스크립트에서 제공하는 값 타입의 데이터로는 숫자(number), 문자열(string), 불린(boolean), 그리고 "정의되지 않음", "객체가 없음"이 있다.

자바 스크립트는 우선 연산에 사용된 변수 a, b 가 값 타입 변수인지 참조 타입 변수인지 판별한다. 그래서 a 는 참조 타입이라고 판단하게 되고 해당 참조값이 가리키는 메모리로 이동해서 num 이라는 것을 찾는다. num 을 찾으면 그것이 값 타입의 데이터라는 것을 알게 되고, 그럼 num이 가지고 있는 값을 변환한다. b 는 값 타입의 데이터라는 판단을 하게 되고, b 에 저장된 데이터를 곧바로 반환한다. 자바스크립트는 + 연산에 사용되는 두 데이터의 타입이 좌측은 숫자, 우측은 문자열이라는 사실을 알게 된다. 그러면 자바스크립트가 가지고 있는 타입 간의 변환 규칙에 따라 숫자인 3을 문자열로 변환한다.

c = "3" + "3"

자바스크립트 엔진이 a.num, b 의 최종 값을 찾아가는 절차를 그림으로 그려보면 다음과 같다. 


자바스크립트가 사용하는 약한 타입 체계에서는 num이 a 객체의 멤버인지는 사전에 알 수 없고 알 필요도 없다. a 가 가리키는 곳으로 실제로 이동해서 그곳에 멤버 num이 있는지를 알게 되는 시기는 프로그램이 실행되는 런타임이 되고 나서다.

약한 타입 체계의 언어에서는 런타임이 돼서야 객체의 멤버가 존재하는지 확인할 수 있다.

자바스크립트에서 변수값을 검색해 나가는 과정이 실행단계에 수행된다는 것은 var 변수가 어떤 타입의 데이터라도 받아들일 수 있다는 이론의 근거로 볼 수 있다. 이런 이유로 자바스크립트에서는 객체에 없는 멤버를 조회할 때도 컴파일 단계에서는 에러가 발생하지 않고 실제로 실행해봐야 에러를 볼 수 있다.

이처럼 런타임이 되어서야 타입을 확인한다는 것이 바로 약한 타입 체계의 특징이다. 자바스크립트 객체의 멤버(속성, 메서드)는 모두 var 변수다.

예를 들어, name, setNewName을 멤버로 포함하는 사용자 정의 타입인 Person이 있다고 하자. 이것의 인스턴스를 생성한 후의 그림을 그리면 다음 그림과 비슷할 것이다. 

mySon 인스턴스의 name 속성은 값 타입의 변수이고, setNewName 메서드는 참조 타입의 변수다. 변수 setNewName 이 함수를 가리키고 있는 참조 타입의 변수라는 것도 런타임에 확인 할 수 있다. 즉 자바스크립트가 이 코드를 만나면 우선 변수 setNewName 이 가리키는 곳으로 이동하고 그런 다음 연산자"()" 때문에 그 위치에 정의된 코드 블록을 실행한다. 만약 그러한 코드 블록이 없다면 에러가 발생하는 것이다. 

만약 name() 처럼 name 에 ()를 사용해 name() 처럼 코드를 작성하더라도 컴파일할 때 에러가 발생하지는 않는다. 실제로 프로그램을 실행할 때 name 이 가리키는 곳에 가봤더니 그 곳에 실행할 수 있는 코드 블록이 없다는 사실을 알게 되면 에러가 발생한다. mySon 도 해당 객체에 대한 참조값이 할당된 var 변수다. 

자바스크립트 객체의 멤버는 런타임에 존재 여부 및 멤버 타입(속성, 메서드)을 확인할 수 있다.


1.6 프로그램 실행 단계

인스턴스가 메모리에 생성되는 과정은 일반 강력한 타입의 언어와 자바스크립트가 조금 차이가 있다. 

자바스크립트 프로그램을 시작하면 바로 프로그램이 실행되는 것이 아니라 프로그램의 전역 레벨에서의 파싱 단계를 거친다. 전역 레벨에서의 파싱이란 어떤 함수에도 포함되지 않은 변수와 어떤 함수에도 포함되지 않는 이름 있는 함수(named function)에 대해 함수명과 동일한 변수를 만들고 이 변수를 실행 코드가 담긴 함수에 대한 참조로 초기화하는 것을 말한다. 앞으로 '함수명과 동일한 이름의 변수' 를 함수변수라고 하자.

파싱 단계를 마치고 나면 프로그램이 실행되는데, 프로그램을 실행하다가 함수 호출을 만나면 해당 함수 레벨의 파싱 단계를 반복한다. 즉, 함수의 코드에서 그 함수의 지역 변수와 함수 변수를 정의하고 나서 비로소 함수 코드를 실행한다. 함수 호출을 만날 때마다 이런 파싱과 함수 실행이 반복되면서 프로그램을 끝까지 실행한다. 

파싱을 마치고 나면 해당 레벨의 var 변수와 함수 변수가 정의된다. 이 단계에서 코드 블록은 실행되지 않는다.

좀 더 정확히 말하면 전역 레벨의 파싱 단계에서 정의되는 변수와 함수 변수는 사실 루트 객체, 즉 웹 브라우저에서 프로그램이 실행되는 환경이라면 Window 객체의 멤버로 추가된다. 그리고 하뭇 레벨의 파싱에서는 해당 함수와 연결돼 있는 변수 스코프라는 객체의 멤버로 추가된다. 사실 루트 객체도 최상위 변수 스코프 객체다.

"객체 멤버로 추가" 된다는 것이 지금은 무슨 말인지 이해하기 어려울 수도 있다. 지금은 이렇게만 이해해 두자. 모든 변수, 즉 var 변수든 함수 변수든 그것이 속하는 객체가 있다. 프로그램에서 어떤 함수에도 속하지 않는 var 변수와 함수가 있다면 이것들은 런타임에 루트 객체에 속하게 되고, 함수에 속하는 변수와 내부 함수는 해당 함수와 연결된 변수 스코프 객체에 속하게 된다. 함수의 변수 스코프 객체에 대해서는 "변수 스코프" 장에서 자세히 다루겠다.

프로그램의 모든 var 변수와 함수 변수는 그것과 연관된 변수 스코프 객체의 멤버로 추가된다.

다음의 간단한 예제 코드를 통해 파싱 단계에서 무슨 일이 일어나는지 알아보자.

// 파싱 단계에서 변수 x가 메모리에 undefined로 정의됨
// 런타임에 0이 할당됨
var x = 0;
// 파싱 단계에서 변수 add가 정의되고 함수의 코드 블록에 대한 참조가 할당됨
// 파싱 단계에서는 함수 구현 코드가 실행되지는 않음
function add (a, b){
    // 런타임에 지역 변수 c에 a+b의 값이 할당됨
    var c = a + b;
    return c;
}

자바스크립트는 위 코드를 파싱하면서 코드에서 x 와 add 를 정의한다. 전역 변수 x 는 undefined 로 초기화되고 add 변수는 실행 코드가 담긴 함수의 참조로 초기화된다. 파싱 단계를 마치고 런타임이 되면 이전에 정의된 변수와 함수를 사용해 작성한 문장이 실행된다. 그럼 이제 다음과 같은 코드를 보자.

alert(square( 4 ));   // 16 출력
var square = 0;     // 파싱할 때 정의된 square 변수를 런타임에 덮어 쓴다.
function square( x ){  // 함수 정의
    return x * x;     
}
alert( square );    // 0 출력

이 과정을 그림으로 그리면 다음과 같은 절차로 실행된다.

코드를 실행하면 파싱 단계에서 전역 변수인 square 와 함수인 square 가 정의된다. 먼저 square 변수가 정의되고(①), 다음으로 square 함수가 정의되면서 square 변수를 덮어쓰게 된다(②). 자바스크립트에서는 변수 square 와 함수 square 를 메모리에 정의할 때 변수와 함수를 구분해서 별도로 관리하지 않는다. 관리하는 장소가 동일하므로 이름이 같으면 덮어쓰게 된다. 따라서 최종적으로 함수를 가리키는 square 만 남게 된다. 여기까지가 파싱 단계에서 일어나는 일이다. 

이제 코드가 실행되는 단계를 알아보자. square 는 함수를 정의하고 있는 코드 블록을 가리키고 alert( square( 4 )); 를 실행하면 16이 출력된다(③). 그러나 그다음 문장인 var square = 0; 을 통해 square 가 가리키고 있는 메모리에는 0 이 할당된다(④). 따라서 마지막 문장인 alert( square )는 0 을 출력한다(⑤).

자바스크립트에서는 변수와 함수를 구분해서 관리하지 않는다. 함수를 변수에 할당하든 변수를 함수에 할당하든 정상적인 작업이다. 따라서 컴파일할 때 함수와 변수는 언제든지 다른 함수와 변수에 의해 내용이 바뀔 수 있다.

자바스크립트에서는 변수명과 함수명을 별도로 구분해서 관리하지 않는다. 동일한 이름의 변수 또는 함수를 정의하면 이전에 정의된 내용을 덮어쓴다.

개인이 혼자 개발할 때는 변수명과 함수명을 같은 이름으로 명명하지 않을 것이다. 그러나 여러 사람이 함께 작업을 하거나 라이브러리를 사용하는 경우에는 이런 상황이 발생할 수 있다. 따라서 네임스페이스가 필요하다. 자바스크립트에서는 기본적으로 네임스페이스를 지정하는 방법을 제공하지 않으므로 직접 네임스페이스를 코딩해야 하며, 네임스페이스를 만드는 방법은 뒤에서 알아보겠다.



'Web & Server > javascript' 카테고리의 다른 글

[javascript] 2. 자바스크립트의 기본 문법  (0) 2015.12.02
Comments