Blog

언어의 기초

March 30, 2014

언어의 기초

이 포스팅은 "프론트엔드 개발자를 위한 자바스크립트(2013 인사이트, 한선용 옮김)"에서 발췌 요약한 것이며, 인사이트와 저작권에 대한 부분을 의논하여 사전 허락을 받은 것입니다. 자세한 내용은 페이스북 자바스크립트 제대로 하기 스터디 그룹해당 포스트를 참조하시기 바랍니다.

3장. 언어의 기초

3.1 문법

대소문자 구분

변수, 함수 이름, 연산자 모두 대소문자를 구분한다.

식별자(Identifiers)

'식별자'란 변수, 함수, 프로퍼티, 함수 매개변수의 이름이다. 식별자는 한 개 이상의 문자로 표기하며, 첫 번째 문자는 반드시 글자나 밑줄(_), 달러기호($) 중 하나이다.

ECMAScript는 관습적으로 카멜 케이스로 쓴다. 카멜 케이스란 첫번째글자는 소문자로 쓰고 단어가 바뀔 때는 바뀐 단어의 첫 글자를 대문자로 쓰는 표기법이다. 반드시 써야하는 것은 아니지만 ECMAScript 내장 함수와 객체가 카멜 케이스로 표기되어 있어서 따르길 권한다.

  • 키워드와 예약어 및 true, false, null은 식별자로 사용할 수 없다.

주석(comments)

ECMAScript는 한 줄 주석과 블록 주석 모두 C언어 스타일로 표기한다.

// 한줄 주석

/*
* 블록 주석은 /*로 시작하고,
* 그 반대 */로 끝난다.
*/

두번 째와 세 번째 줄에있는 *는 가독성을 위해 추가한 것이며 기업 애플리케이션에서 선호한다.

스트릭트 모드(Strict Mode)

ECMAScript 5에서 도입하였는데, 안전하지 않는 동작에서는 에러를 반환하도록 한다.

전체 스크립트에 스트릭트 모드를 적용하려면 다음 문장을 스크립트 최상단에 추가한다.

"use strict";

함수 단 하나만 적용하려면 다음 선언(pragma)을 함수 본문 맨 앞에 추가한다.

function doSomething(){
    "use strict";
    // 함수 본문
}

IE 10+, 파이어폭스 4+, 사파리 5.1+, 오페자 12+, 크롬에서 지원한다.

문장(Statements)

ECMAScript에서 각 문장은 세미콜론으로 종료한다. 꼭 써야하는 것은 아니지만 여분의 공백 제거, 압축 시 문법 에러, 성능 향상 등의 이유로 세미콜론으로 종료해라.

C 언어 스타일 문법을 써서 여러 문장을 코드 블록으로 합칠 수 있다. {로 시작하여 }로 끝난다. 실행한는 문장이 하나뿐이더라도 에러의 원인이 되기도 하므로 코드블록을 써라.

3.2 키워드와 예약어

ECMAScript의 키워드와 예약어는 식별자 이름이나 프로퍼티 이름으로 사용하지 마라.

3.3 변수

ECMAScript는 느슨한 변수타입을 사용하므로, 변수에 어떠한 타입의 데이터라도 저장할 수 있다. 모든 변수는 단순히 값에 대한 이름 붙은 플레이스홀더일 뿐이다.

변수를 정의 할 때에는 var 연산자 다음에 변수 이름을 쓴다. var는 키워드이며, 변수 이름은 식별자이다. 변수를 초기화하지 않으면 특별한 값 undefined 가 할당되며, 다음과 같이 변수 선언과 동시에 값을 할당할 수 있다.

var message = "h1";

var 연산자는 변수를 로컬 스코프에서 정의한다. 함수 안에서 var 키워드를 써서 변수를 정의하면 해당 변수는 함수를 종료하는 즉시 파괴된다.

var 연산자를 생략하면 변수를 전역으로 정의할 수 있으며, 함수를 호출하는 즉시 변수가 정의되며, 함수 외부에서도 이 변수에 접근할 수 있다. 그러나 이런 패턴은 피해라.

변수를 여러 개 선언하려면 쉼표로 구분하여 같은 문장에서 선언하고 초기화할 수 있다. 변수 사이에 줄바꿈과 들여쓰기로 읽기 쉽게 한다:

var message = "h1",
    found = false,
    age = 29;

3.4 데이터 타입

ECMAScript에는 Undefined, Null, Boolean, 숫자, 문자열의 다섯가지 기본적인 데이터 타입(원시 primitive 데이터 타입)이 있으며, 이름-값 쌍의 순서없는 목록 즉, 객체라는 복잡한 데이터 타입도 있다. 이 여섯 가지의 데이터 타입은 동적이어서 한 가지 데이터 타입이 여러 특성을 가질 수 있다.

typeof 연산자(Operator)

typeof 연산자를 통해 데이터 타입을 알 수 있으며, 값(변수)에 typeof 연산자를 적용하면 다음 문자열 중 하나를 반환한다.

  • 정의되지 않은 변수 : "undefined"
  • 불리언 : "boolean"
  • 문자열 : "string"
  • 숫자 : "number"
  • 함수를 제외한 객체 또는 null : "object"
  • 함수 : "function"

undefined 타입

기본적으로 초기화하지 않는 변수에는 항상 undefined 값이 할당된다. 그러나, 변수를 항상 초기화하는 습관을 갖으면 typeof에서 undefined를 반환했을 때 해당 변수가 초기화되지 않은 것인지, 정의되지 않은 것인지 바로 알 수 있다.

Null 타입

Null 타입의 값은 특별한 값인 null이다. null은 빈 객체를 가리키는 포인터이므로 null에 typeof를 호출하면 object를 반환하며, 객체를 사용해야 하지만 해당 객체를 이용할 수 없을 때 null을 사용하여 해당 변수가 객체를 자리키는지 명시적으로 확인할 수 있다.

불리언 타입

불리언 타입은 ECMAScript 에서 가장 많이 쓰이는 데이터 타입 중 하나이며 true와 false 두 가지 리터럴 값만 가진다. 숫자 타입과 달라, ture는 1이 아니며, false는 0이 아니다.

Boolean() 함수는 어떤 타입의 데이터에서도 호출할 수 있고 항상 불리언 값을 반환한다.

데이터 타입 true 값 false 값
불리언 ture false
문자열 비어 있지 않은 문자열 모두 " "(빈 문자열)
숫자 0이 아닌 모든 숫자, 무한대 포함 0, NaN
객체 모든 객체 null
Undefined 해당없음 undefined

if 문 같은 제어문은 타입을 자동으로 불리언으로 바꾸므로 위 표의 변환 규칙을 반드시 이해해야한다.

숫자 타입

기본적인 숫자 리터럴 형식은 10진법이다. 정수는 8진수 리터럴, 16진수 리터럴도 가능하지만 실제 계산시에는 항상 10진수로 변환하여 계산한다.

부동소수점 숫자

부등소수점 숫자를 표현하려면 반드시 소수점이 있어야 하며, 소수점 뒤에 숫자를 쓴다. .1도 가능하지만 권장하지 않는다.

부동소수점 숫자는 정수보다 메모리를 두 배로 소모하므로 소수점 뒤에 숫자가 없다면 정수로 변환된다.

대단히 크거나 작은 부동소수점 숫자를 표현할 때는 'e- 표기법(지수 표기법)'을 쓴다. 기본적으로 소수점 뒤에 0이 6개 이상 있는 모든 부동소수점 숫자를 지수 표기법으로 변환한다.

숫자 범위

메모리 제한 때문에 모든 숫자를 표현할 수 없다. ECMAScript로 표현할 수 있는 최소값은 브라우저마다 다르지만 보통 5e-324이다. 최대값은 보통 1.7976931348623157e+308이다. 이 범위를 벗어나면 자동으로 (-)Infinity로 변환된다.

NaN(Not a Number)

숫자를 반환할 것으로 의도한 조작이 실패했을 때 반환하는 특별한 값이며, 에러를 반환하는 것은 아니다.

NaN이 포함된 모든 조작은 항상 NaN을 반환하며, 심지어 NaN끼리도 서로 일치하지 않는다. isNaN() 함수는 해당 값이 '숫자가 아닌 값'인지 검사한다. 문자열 "10"이나 불리언 값은 숫자도 바꿀 수 있다.

숫자 변환

숫자가 아닌 값을 바꾸는 함수는 어떤 데이터 타입에도 쓸 수 있는 Number(), 문자열을 숫자로 바꾸는 parseInt()와 parseFlaot() 함수 세 가지이다.

Number()함수로 문자열을 숫자로 바꿀 때는 여러 규칙을 기억해야 한다.

정수 형태의 문자열을 숫자로 바꿀 때 보통 parseInt()을 사용하며, var num = parseInt("10", 10)의 형태로 사용한다.

parseFlaot() 함수는 두 번째 소수점과 같은 잘못된 부동소수점 숫자를 만날 때까지 파싱을 계속하여, 그 이후 문자열은 무시한다. 리딩제로는 항상 무시한다. 또한, 항상 10진수 기준으로 파싱하기 때문에 16진수는 항상 0을 반환한다.

문자열 타입

문자열 데이터 타입은 16비트 유니코드 문자의 연속이다. 문자열은 큰따옴표나 작은따옴표로 감싸서 표현한다. PHP에서는 큰따옴표와 작은따옴표를 다르게 해석하지만 ECMAScript는 완전히 똑같게 해석한다. 그러나 짝은 맞아야 한다.

문자 리터럴
문자열의 성질

ECMAScript에서 문자열은 불변(immutable)이다. 즉, 문자열이 만들어지면 그 값을 바꿀 수 없다. 변수에 저장된 문자열을 바꾸려면 기존의 문자열을 파괴하고 다음과 같이 해당 변수에 새 문자열을 채워야 한다.

var lang = "Java";
lang = lang + "Script";
문자열로 변환

값을 문자열로 바꾸는 방법은 거의 모든 값에 존재하는 toString() 메서드를 사용하는 것과 호출할 값이 null이나 undefined일 가능성이 있을 경우 String() 함수를 사용하는 두 가지 방법이 있다.

객체 타입

객체는 데이터와 기능의 집합이다. 객체는 new 연산자 다음에 새로 만들 객체 타입의 이름을 써서 만든다.

var o = new Object();

Object 타입은 이 타입에서 파생하는 모든 객체의 원형이기 때문에 Object 타입의 인스턴스는 Object 타입의 프로퍼티와 메서드를 전부 상속한다.

3.5 연산자(OPERATORS)

단항 연산자(Unary Operators)

단 하나의 값에만 적용되는 연산자이며, 가장 단순한 연산자이다.

증감 연산자

C 언어에서 차용한 것이며, 피연산자의 앞에 쓸 수도 있고 뒤에 쓸 수도 있다. ++를 피연산자 앞에 쓰면 피연산자에 1을 더하며, --를 쓰면 피연산자에서 1을 뺀다.

증감 연산자를 변수 앞에 쓰면 변수의 값을 바꾼 다음 문장을 계산한다. 변수 뒤에 쓰면 문장을 계산한 다음에 적용되는 중요한 차이가 있다.

단항 플러스와 단항 마이너스

비트 연산자(Bitwise Operators)

가이드에서 언급한 대로 건너뜀

불리언 연산자

불리언 연산자에는 NOT과 AND, OR 세가지가 있다.

논리 NOT

논리 NOT 연산자는 느낌표!로 표시하며 모든 값에 적용할 수 있다. 피연사자를 불리언 값으로 변환한 다음 그 결과를 부정하며, 다음과 같이 동작한다.

피연산자 반환값
객체 false
빈 문자열 true
비어있지 않은 문자열 false
숫자 0 true
0이 아닌 숫자(Infnity 포함) false
null true
NaN true
undefined true

논리 NOT 연산자를 연달아 두 개 쓰면 Boolean() 함수를 쓴 것과 같은 효과가 있다.

논리 AND

앰퍼샌드 2개&&로 나타내며 값 두 개에 적용한다. 피연산자가 불리언 값이 아니어도 사용할 수 있으며, 그 동작은 약간 복잡하다;;

논리 OR

파이프 두개||로 표현한다. 피연산자가 불리언 값이 아니라도 사용할 수 있으며, 그 동작은 약간 복잡하다;;

다음과 같이 그 행동을 이용해서 변수에 null이나 undefined가 저장되지 않게 할 수 있다:

var myObject = preferredObject || backupObject;

곱셈 관련 연산자(Multiplicative Operators)

곱셈, 나눗셈, 나머지의 세가지 곱셈 관련 연산자가 있다.

곱셈

아스테리스크*를 써서 나타내며 두 숫자를 곱하는 데 쓰인다.

나눗셈

슬래시/ 기호로 표현하며, 첫 번째 피연산자를 두 번째 피연산자로 나눈다.

나머지(Modulus)

퍼센트 기호%로 나타내며 나눗셈을 적용하고 나머지를 반환한다.

덧셈 관련 연산자

덧셈

피연산자 중 하나가 문자열이라면 다음 규칙을 따르며, 아래 예제와 같은 흔한 실수를 피해야 한다.

  • 피연산자가 모두 문자열이라면 두 번째 문자열을 첫 번째 문자열에 합한다.
  • 피연사자 중 하나가 문자열이라면 다른 피연산자를 문자열로 변환하고 두 문자열을 합친다.
var num1 = 5;
var num2 = 10;
var message = "The sum of 5 and 10 is " + num1 + num2;
alert(message);     // "The sum of 5 + 10 is 510"

이를 피하기 위해서는 (num1 + num2)와 같이 괄호로 감싸 괄호 안을 먼저 계산한 후에 그 결과를 문자열에 합쳐야 한다.

뺄셈

관계 연산자(Relational Operators)

관계 연산자에는 미만<과 초과>, 이하<=와 이상>= 연산자가 있다. 두 값을 비교하여 불리언 값을 반환한다.

피연산자 중 하나가 숫자라면 다른 피연산자를 숫자로 변환한 후 비교하며, 하나가 불리언이라면 숫자로 바꾼 후 비교한다.

피연산자가 모두 문자열이면 서로 대응하는 문자의 문자 코드를 비교하는데, 알파벳의 대문자 전체가 소문자의 문자 코드보다 작기 때문에 대소문자를 전부 통일한 다음 비교하는 등의 주의가 필요하다.

NaN과 비교는 항상 false를 반환한다.

동일 연산자(Equality Operators)

ECMAScript에서는 연산자를 두 벌로 분리해서 '동일' '비동일' 연산자는 비교 전에 타입을 변환하며, '일치' '불일치' 연산자는 타입 변환 없이 비교하는 것으로 정했다.

동일, 비동일(Equal and Not Equal)

동일 연산자는 ==로 표시하며 동일하면 true를 반환한다. 비동일 연산자는 !=로 나타내며 동일하지 않으면 true를 반환한다. 피연산자를 비교하기 전에 변환하며, 이를 종종 '타입 강제(type coercion)'라 부른다.

  • null과 undefined는 동일하며, 이 둘은 다른 값으로 변환하여 평가하지 않는다.
  • 피연산자 중 하나가 NaN이라면 동일 연산자는 false를, 비동일 연산자는 true를 반환한다. 피연산자가 모두 NaN이라도 동일 연산자는 항상 false를 반환한다. NaN은 NaN과 같지 않다.
  • 피연산자가 모두 객체라면 객체끼리 비교하는 것이 아니라 객체에 대한 참조를 비교한다.
일치, 불일치(Identically Equal and Not Identically Equal)

일치 연산자는 ===로 나타내며 피연산자의 타입을 변환하지 않아도 같을때에만 true를 반환한다. 불일치 연산자는 !==로 나타내며 피연산자의 타입을 변환하지 않는 상태에서 일치하지 않을 때에만 true를 반환한다.

null과 undefined는 비슷한 값이므로 null == undefined는 true를, null === undefined는 false를 반환한다.

동일/비동일 연산자보다는 일치/불일치 연산자를 써라!

3항 연산자(Conditional Operator)

variable = boolean_expression ? true_value : false_value;

boolean_expression이 true면 변수 variable에 true_value를 저장하며, false면 false_value가 저장된다.

var max = (num1 > num2) ? num1 : num2;

위 예제에서는 max에 num1과 num2 중 큰 값이 저장된다.

할당 연산자(Assignment Operators)

단순한 할당은 =로 나타내며 단순히 값을 변수에 할당한다. 다음과 같이 복합 할당을 적용하여 줄여 쓸 수 있으나, 성능이 좋아지는 것은 아니다.

num = num + 10;
num += 10;      // 복합 할당으로 위와 같다.

쉼표 연산자

쉼표(,) 를 사용해 여러문장을 한 문장으로 합칠 때 쓴다.

3.6 문장(STATEMENTS)

ECMA-262에는 몇가지 문장이 정의되어 있는데 이를 '제어문: flow-control statements'이라 부르기도 한다. ECMAScript 대부분이 문장을 통해 정의되며 일반적으로 키워드와 연결되어 있다.

if 문

if (condition) statement1 else statement2

대부분은 언어에서 가장 많이 쓰이는 제어문이며, 조건에 해당하는 것이 불리언 값이 아니면 Boolean() 함수를 호출하여 불리언 값으로 바꾼다. 실행 코드는 한 줄의 코드라도 코드 블록으로 사용하라. if문을 연이어 쓸 수도 있다.

do-while 문

do { 
    statement
} while (expression);

do-while 문은 평가전 루프이며, 이는 루프의 종료 조건을 평가하기 전에 루프 본문을 실행한다는 뜻이다. 루프 본문을 적어도 한번은 실행한 후 빠져나가야 하는 상황에서 주로 사용한다.

while 문

while(expression) statement

while문은 평가 후 루프이며, 이는 루프 본문을 실행하기 전에 종료 조건을 평가한다는 뜻이다.

for 문

for (initialization; expression; post-loop-expression) statement

for 문은 평가후 루프인데, 루프에 들어가기 전에 변수를 초기화(initialization)할 수 있으며 루프 후 코드(post-loop-expression)도 지정할 수 있다. 조건 표현식이 true일 때만 실행하므로, while 문과 같이 루프 본문의 코드를 실행하지 않을 때도 있다. 루프 본문을 실행하면 루프 후 코드 역시 실행한다.

while 루프로 실행할 수 없는 일은 for 루프로도 불가능하다. for 루프는 단순히 루프 제어와 관련된 코드를 한곳에 모았을 뿐이다.

for 루프에서 반복을 결정하는 변수(i:iteration)를 반드시 for 루프의 변수 초기화 부분에서 var 키워드로 초기화해야 하는 건 아니다. for 루프 밖에서 초기화해도 된다. 또한, ECMAScript에는 블록 레벨 변수가 존재하지 않아 루프 안에서 변수를 선언하더라도 밖에서 이 변수에 접근할 수 있다.

옵션인 초기화, 조건 표현식, 루프 후 코드를 모두 생략하여 무한루프를 생성할 수도 있으며, 조건 표현식 하나만 명시하면 while 루프처럼 동작한다.

for-in 문

for (property in expression) statement

for-in 문은 엄격한 반복문이다. for-in 문은 객체의 프로퍼티를 나열하는데 사용한다.

문장 레이블

label: statement

문장에 레이블을 붙였다가 나중에 사용가능하며, 일반적으로 중첩된 루프에서 사용한다.

break 문과 continue 문

break 문과 continue 문을 통해 루프 내부의 코드 실행을 세밀히 조절할 수 있다. break 문은 즉시 루프에서 빠져나가 루프 다음 문장을 실행하는 반면, continue 문은 즉시 루프를 빠져나가지만 루프 실행은 지속된다.

break 문과 continue 문을 문자 레이블과 함께 사용하면 코드의 특정 위치로 이동할 수 있다.

루프는 전체적인 성능에 영향을 미치며, 개발자의 의도를 파악하기 어렵게 만들어 디버그나 유지 보수에도 문제가 생길 수 있으니 continue 문은 사용하지 마라.

with 문

with (expression) statement;

with 문은 코드의 스코프를 특정 객체에 고정한다. 원래 의도는 특정 객체를 코드에서 매우 자주 참조할 때 편리하자는 것이었다.

with 문 내부의 변수는 우선 지역 변수로 간주한다. 지역 변수에서 찾을 수 없다면 with 문과 함께 쓴 객체의 프로퍼티 중에서 같은 이름으로 검색하여, 해당 이름을 프로퍼티로 평가한다.

스트릭트 모드에서는 with 문을 금지하며 문법 에러로 간주한다. with 문은 성능에 악영향이 있으며 디버그도 힘들어, 배포할 최종 코드에 with 문을 쓰지 마라.

switch 문

switch (expression) { 
    case value: statement
        break;
    case value: statement
        break;
    case value: statement
        break;
    case value: statement
        break;
    default: statement
}

if 문과 관련이 깊으며 C언어 기반의 문법과 매우 비슷하다. switch 문의 각 case는 '표현식이 value와 일치하면 statement를 실행하라'는 의미이다. break 키워드를 코드 실행을 멈추고 switch 문을 빠져나가라는 의미이며, 쓰지 않으면 다음 case를 계속 평가한다. default 키워드는 case 중 value로 평가되는 것이 없을 때 실행할 문장이다. 즉, else 문과 같은 구실을 한다.

case 문마다 break 문을 넣어서 다음 case까지 진행하지 않게 하라. 만약 다음 case 문까지 진행하기 해야 한다면, 주석(/* falls through */ 등)을 달아서 의도적으로 break 문을 생략했으며 실수가 아님을 분명히 한다.

ECMAScript의 switch 문은 모든 데이터 타입에서 동작하는 고유한 특징이 있어 문자열과 객체에서도 사용할 수 있으며, 값이 상수일 필요가 없으며 변수나 표현식도 쓸 수 있다.

switch 문은 일치 연산자(===)로 값을 비교하므로 타입 변환이 일어나지 않는다.

3.7 함수

함수는 문장을 캡슐화하여, 언제 어디서든 실행할 수 있게 하므로 모든 언어의 핵심이다. function 키워드로 정의하며 그 뒤에 매개변수와 함수 본문을 순서대로 쓴다.

function functionName(arg0, arg1, ... , argN) {
    statements
}

함수는 꼭 값을 반환하지 않아도 된다. 모든 함수는 return 문 뒤에 반환값을 써서 값을 반환할 수 있다. return 문 뒤의 코드는 결코 실행되지 않는다.

return 문 뒤에 반환값을 쓰지 않아도 되며, 이 때 함수는 return 문을 만나는 즉시 실행을 멈추고 undefined 값을 반환한다.

함수는 항상 값을 반환하거나 항상 반환하지 않게 만들어라. 즉, 반환에 일관성이 있어야 한다. 그렇지 않으면 디버그하기 어려울 수 있다.

스트릭트 모드에서의 함수 제한

  • 함수 이름과 매개변수 이름에 eval이나 arguments는 쓸 수 없다.
  • 서로 다른 매개변수에 결코 같은 이름을 쓸 수 없다.

매개변수의 이해

ECMAScript 함수는 매개변수 숫자를 따지지 않으며 데이터 타입도 체크하지 않는다.

함수는 arguments라는 객체를 하나 갖는데, 이 객체를 통해 매개변수의 값에 접근할 수 있다. arguments 객체는 각 매개변수를 대괄호 표기법, 첫 번째 매개변수는 arguments[0], 두 번째는 arguments[1] 형태로 접근하며 매개변수 개수를 length 프로퍼티를 통해 알 수 있다는 면에서 배열처럼 동작하기는 하지만 Array의 인스턴스는 아니다.

ECMAScript의 매개변수는 모두 값으로 넘겨야 한다. 매개변수를 참조 형식으로 전달할 수는 없다.

오버로딩 없음

ECMAScript에서는 같은 이름으로 함수를 여러 번 정의하면 마지막 함수가 해당 이름을 소유한다.

함수에 넘긴 매개변수의 타입과 숫자를 체크해서 그에 맞게 반응하는 방법으로 오버로딩을 흉내낼 수 있다.

관련 글들