프로그래밍인 컴퓨터에게 실행을 요구하는 일종의 커뮤니케이션이다. 이를 위해 먼저 무엇을 실행하기 원하는지에 대한 정의가 필요하다. 다시 말해, 프로그래밍에 앞서 문제(요구사항)을 명확히 이해한 후 적절한 문제 해결 방안의 정의가 필요하다.
이때 요구되는 것이 문제 해결 능력이다. 혹자는 문제 해결 능력을 알고리즘과 동일시하려는 경향이 있지만 반드시 그런 것은 아니다. 물론 문제 해결 능력의 함양에 있어 알고리즘 학습은 큰 도움이 되지만 문제 해결 능력은 더 큰 차원의 능력이다.
대부분의 문제(요구사항)은 복잡하며 명확하지 않을 수도 있다. 따라서 문제(요구사항)를 명확히 이해하는 것이 우선되어야 하며 복잡함을 단순하게 분해(Decomposition)하고 자료를 정리하고 구분(Modeling)해야 하며 순서에 맞게 행위를 배열해야한다.
즉, 프로그래밍이란 0과 1밖에 알지 못하는 기계가 실행할 수 있는 정도로 정확하고 상세하게 요구사항을 설명하는 작업이며 그 결과물이 바로 코드이다. 모허하고 대략적인 요구사항을 전달해도 우리의 머리 속에 있는 의도를 정확히 꿰뚫어 완벽하게 이해하는 컴퓨터는 존재할 수 없다.
우리는 문제 해결 방안을 고려할 때 컴퓨터의 입장에서 문제를 바라보아야 한다. 이때 필요한 것이 Computational thinking이다. 사람의 일반적인 사고 방식은 매우 포괄적이며 실생활에서 경험하고 있는 익숙한 사항에 대해 당연시하는 안이한 인식이 있다.
문제 해결 능력은 직감과 직관의 영역이라고 볼 수 있는데 이는 문제를 바라보는 우리의 사고와 경험에 영향을 받는다. 예를 들어 "듣다(Listen)" 라는 행위를 사람은 하나의 간단하고 당연한 기능으로 생각한다. 하지만 컴퓨터에게 이 행위를 설명하는 것은 단순치 않다. 그리고 사람은 소리의 크기를 "크다" 또는 "작다" 고 표현한다. 하지만 크다 또는 작다는 의미는 상대적인 개념으로 기준이 불명확하다. 컴퓨터에게는 양적 개념인 숫자를 사용하여 "현재 볼륨보다 1단계 크게 조정하라" 또는 "볼륨을 60으로 조정하라"고 명령해야 한다. 또한 "좋다", "붉다", "사랑"과 같은 관념적 개념은 컴퓨터에게 매우 난해한 개념이다. 사람은 지인의 얼굴을 보고 누구인지 바로 인지하지만 컴퓨터에게 이것은 매우 어려운 일이다. 허나 3470^9와 같은 계산은 사람에게는 쉽지 않지만 컴퓨터에게는 매우 쉬운 작업이다.
이처럼 컴퓨터와 사람은 사고, 인지의 방식이 다르다. 따라서 해결 과제를 컴퓨터의 관점으로 사고(Computational thinking)해야 한다. 이에는 논리적, 수학적 사고가 필요하게 되며 해결 과제를 작은 단위로 분해하고 패턴화하여 추출하며 프로그래밍 내에서 사용될 모든 개념은 평가 가능하도록 정의되어야 한다.
예를 들어 사람처럼 두발로 걷는 로봇을 위해 "걷다"라는 기능을 디자인을 하면
"걷다"라는 기능을 디자인하려면 판단하여야 하는 상태와 그 상태를 판단하는 시기, 그리고 판단 기준을 정의하여야 하며 이를 바탕으로 분해한 처리(Process)의 실행 여부를 결정한다. 예를 들어 장애물이란 무엇(크기, 움직임...)인지 어떤 범위 내에 있는 것인지 명확히 수치화하여 정의해야 한다.
이와 같이 문제 해결 능력을 바탕으로 정의된 문제 해결 방안은 컴퓨터에게 전달되어야 한다. 이때 명령을 수행할 주체는 컴퓨터이다. 따라서 인간이 이해할 수 있는 자연어가 아니라 컴퓨터가 이해할 수 있는 언어, 즉 기계어(Machine code)로 명령을 전달해야 한다.
인간이 기계어를 이해하여 직접 기계어로 명령을 전달하는 것은 매우 어려운 작업이다. 기계어는 우리가 사용하는 언어와는 너무나도 다른 체계를 가지기 때문이다. 심지어 비트 단위로 기술되어 있다.
아래는 x86 아키텍쳐 리눅스 환경에서 모든 프로그래머가 반가워하는 "Hello world"를 출력하는 기계어 코드이다.
7F 45 4C 46 01 01 01 00 00 00 00 00 00 00 00 00 02 00 03 00 01 00 00 00 35 40 B3 04 2C 00
00 00 00 00 00 00 00 00 00 00 34 00 20 00 01 00 00 00 00 00 00 00 00 40 B3 04 B2 0C EB 1C
62 00 00 00 62 00 00 00 05 00 00 00 00 10 00 00 48 65 6C 6C 6F 20 77 6F 72 6C 64 0A B9 4C
40 B3 04 93 CD 80 EB FB
직접 기계어로 명령을 전달하는 것을 대신할 가장 유용한 대안은 인간이 이해할 수 있는 약속된 구문(Syntax, 문법)으로 구성된 "프로그래밍 언어(Programming Language)"를 사용하여 프로그램을 작성한 후, 그것을 컴퓨터가 이해할 수 있는 기계어로 변환하여 주는 일종의 변역기를 이용하는 것이다. 이 일종의 변역기를 컴파일러(compiler) 혹은 인터프리터(interpreter)라고 한다.
언어(Language)는 자신의 생각을 상대에게 전달하는 방법으로 언어 공동체 내에서 이해될 수 있는 말의 집합이다.
언어는 자연어와 인공어로 구분할 수 있다. 프로그래밍 언어란 컴퓨터와의 대화(명령)에 사용되는 일종의 표현 수단으로 인간과 컴퓨터(컴파일러 또는 인터프리터) 모두가 이해할 수 있는 약속된 형태의 인공어이다.
아래는 "Hello world"를 출력하는 자바스크립트 코드이다. 위의 기계어 코드보다 인간이 이해하기 쉬운, 즉 읽기 쉬운 코드가 되었다.
console.log('hello world');
프로그래밍은 프로그래밍 언어를 사용하여 컴퓨터에게 실행을 요구하는 일종의 커뮤니케이션이다. 프로그래밍 언어는 Syntax(구문)와 Semantics(의미)의 조합으로 표현된다.
프로그래밍 학습은 일반적으로 프로그래밍 언어의 문법을 배우는 것부터 시작한다. 이는 외국어 학습과 유사하다고 할 수 있다. 그러나 이미 경험을 통해 알고 있겠지만 문법을 잘 안다고 해서 외국어를 잘한다고 말할 수는 없다.
외국어를 잘하려면 외국어 화자의 말이나 문장을 정확히 이해한 후, 문맥에 따른 적절한 어휘 선택, 그리고 순차적으로 결론을 향해 나아가는 문장 구성이 필요하다. 즉 문법에 맞는 문장을 구성하는 것은 물론 의미(Semantics)를 가지고 있어야 언어의 역할을 충실히 수행할 수 있다.
Colorless green ideas sleep furiously.
– 노엄 촘스키(Noam Chomsky)
MIT의 저명한 언어학자인 노엄 촘스키는 위 문장을 통해서 언어의 의미는 문맥에 있는 것이지 문법에 있는 것이 아니란 것을 지적하고 있다. 위 문장은 문법(Syntax)적으로 전혀 문제가 없지만 의미(Semantics)는 없다. 프로그래밍도 마찬가지다. 아래의 코드를 보면
const number = 'string';
console.log(number * number); // NaN
자바스크립트의 변수에는 어떠한 타입의 값이라도 할당할 수 있다. 따라서 위 예제는 문법적으로 전혀 문제가 없다. 하지만 의미적으로 옳지 않다. number라는 변수 명에 문자열이 할당되어 있기 때문이다. 의미적으로 number의 명에는 숫자를 할당하는 것이 옳다.
결국 문제 해결 능력을 통해 만들어낸 해결 방안은 프로그래밍 언어의 문법을 통해 표현된다. 즉, 작성된 코드는 해결 방안의 구체적 구현물이다. 그리고 이것은 프로그래밍의 언어의 문법에 부합하는 것은 물론이고 수행하고자 하는 바를 정확히 수행하는 것, 요구사항이 실현(문제가 해결)되어야 의미가 있다.
대부분의 프로그래밍 언어는 “변수와 값”, “키워드”, “연산자”, “표현식과 문”, “조건문”과 “반복문”에 의한 “흐름제어(Control flow)”, “함수” 그리고 “객체”, “배열” 등의 “자료구조”와 같은 문법을 제공한다.
프로그래밍 언어가 제공하는 문법을 적절히 사용하여 변수를 통해 값을 저장하고 참조하며 연산자로 값을 연산, 평가하고 조건문과 반복문에 의한 흐름제어로 코드의 실행 순서를 제어하고 함수로 재사용이 가능한 문의 집합을 만들며 객체, 배열 등으로 자료를 구조화한다.
결국 프로그래밍은 요구사항의 집합을 분석하여 적절한 자료구조와 함수의 집합으로 변환한 후, 그 흐름을 제어하는 것이다.
프로그래머가 해야 할 일은 문제를 해결하기 위한 방안을 고안하고 이것을 문법에 맞게 코드로 구현하는 것이다. 구현된 코드는 의도한 대로 정확히 동작하여 문제를 해결해야 한다. 이때 자신이 구현한 코드가 컴퓨터 내ㅐ부에서 어떻게 동작할 것인지 그리고 무엇을 돌려 줄 것인지 예측 가능해야 하며 이것을 동료에게 명확히 설명할 수 있어야 한다.
이를 위해서는 프로그래밍 언어의 기본 개념과 동작 원리를 정확히 이해하는 것이 중요하다. 기본 개념과 동작 원리를 이해하지 못한 상태에서 Copy & Paste 로 단순히 동작만 하는 코드를 만들고 그것에 만족한다면 여러분이 구현한 코드는 언제 무너져도 이상할 것이 없는 사상누각일 뿐이다. 신뢰할 수 없고 유지하고 보수하기 까다로운 코드가 될 것이다. 그리고 문제 해결 능력은 어느 선에서 성장을 멈추고 말 것이다.
기본 개념은 문맥에 맞는 정확한 용어의 사용은 오해를 불러 일으키지 않는 명확한 의사 소통을 가능케 한다. 이는 협업의 기본이며 필수 요소이다.
동작 원리의 이해는 코드의 동작을 예측할 수 있게 돕는다. 요구 사항을 코드로 구현하려면 당연히 자신이 작성하는 코드의 동작을 예측할 수 있어야 한다. 동작이 예측되지 않는 코드를 작성한다는 것은 말이 되지 않는다. 또한 에러를 발생시키는 코드를 만나면 에러가 발생하는 원인을 이해해야 디버깅이 가능하다. 이를 위해 코드의 동작을 예측할 수 있는 능력은 필수 불가결적 요소이다.
즉, 기본 개념과 동작 원리의 이해는 어렵고 생소한 용어들로 이루어진 기술적 의사소통을 가능케하고, 자신의 머리 속에서 코드를 실행시켜 볼 수 있는 능력을 갖게 한다. 이를 통해 다른 사람이 작성한 코드를 이해하는 것은 물론 의도 또한 파악할 수 있게 되어 보다 효과적인 코드를 생산할 수 있는 기본기를 쌓을 수 있다. 기본기는 아무리 강조해도 지나침이 없다.
빨리 가는 유일한 방법은 제대로 가는 것이다.
로버트 C. 마틴(Robert C. Martin), “클린 코드”의 저자
초창기 자바스크립트는 웹 페이지의 보조적인 기능을 수행하기 위해 한정적인 용도로 사용되었다. 이 시기에 대부분 로직은 주로 웹서버에서 실행되었고 브라우저는 서버로부터 전달받는 HTML과 CSS를 단순히 렌더링하는 수준이었다.
1999년, 자바스크립트를 이용해서 비동기적(Asynchronous)으로 서버와 브라우저가 데이터를 교환할 수 있는 통신 기능인 Ajax(Asynchronous JavaScript and XML)가 XHLHttpRequest 라는 이름으로 등장했다.
이전의 웹 페이지는 서버로부터 완전한 HTML을 전송 받아 웹페이지 전체를 렌더링하는 방식으로 동작했다. 따라서 화면이 전환되면 서버로부터 새로운 HTML을 전송받아 웹 페이지 전체를 처음부터 다시 렌더링하였다. 이는 변경이 없는 부분까지 포함된 HTML을 서버로부터 다시 전송 받기 때문에 불필요한 데이터 통신이 발생하고, 변경이 없는 부분까지 처음부터 다시 렌더링해야 하기 때문에 퍼포먼스 측면에서도 불리한 방식이다. 이로 인해 화면 전환이 일어나면 화면이 순간적으로 깜박이는 현상이 발생하고 이는 웹 애플리케이션의 한계로 받아들여 졌다.
Ajax의 등장은 이전의 패러다임을 획기적으로 전환했다. 즉, 웹 페이지의 변경이 필요 없는 부분은 다시 렌더링하지 않고, 서버로부터 필요한 데이터만을 전송 받아 변경이 필요한 부분만을 한정적으로 렌더링하는 방식이 가능해진 것이다. 이로 인해 웹 브라우저에서도 데스크톱 애플리케이션과 유사한 빠른 퍼포먼스와 부드러운 화면 전환이 가능케 되었다.
2005년, 구글이 발표한 Google Maps는 웹 애플리케이션 개발 프로그래밍 언어로서 자바스크립트의 가능성을 확인하는 계기를 마련했다. 웹 브라우저에서 자바스크립트와 Ajax를 기반으로 동작하는 Google Maps가 데스톱 애플리케이션과 비교해 손색이 없을 정도의 퍼포먼스와 부드러운 화면 전환 효과를 보여준 것이다.
2006년, jQuery의 등장으로 다소 번거롭고 논란이 있던 DOM(Documnet Object Model)을 보다 쉽게 제어할 수 있게 되었고 크로스 브라우징 이슈도 어느정도 해결되었다. jQuery는 넓은 사용자 층을 순식간에 확보했다. 이로 인해 당시 다소 까다로운 자바스크립트보다 배우기 쉽고 직관적인 jQuery를 더 선호하는 개발자가 양산되기도 했다.
Google Maps를 통해 가능성이 확인된 자바스크립트로 웹 애플리케이션을 구축하려는 시도가 늘어가면서 보다 빠르게 동작하는 자바스크립트 엔진이 요구되었다. 2008년 등장한 구글의 V8 자바스크립트 엔진은 이러한 요구에 부합하는 빠른 성능을 보여 주었다. V8 자바스크립트 엔진의 등장으로 자바스크립트는 데스크톱 애플리케이션과 유사한 사용자 경험(user experience)을 제공할 수 있는 웹 애플리케이션 개발 프로그래밍 언어로 정착하게 되었다.
V8 자바스크립트 엔진으로 촉발된 자바스크립트의 발전으로 말마암아 과거 웹 서버에서 수행되던 역할들이 클라이언트(브라우저)로 이동하였고, 이로써 웹 애플리케이션에서 프런트엔드 영역이 주목받는 계기가 되었다.
2009년, 브라우저에서만 동작하던 자바스크립트를 브라우저 이외의 환경에서 동작시킬 수 있는 자바스크립트 실행 환경인 Node.js의 등장으로 자바스크립트는 웹 브라우저를 벗어나 서버 사이드 애플리케이션 개발에서도 사용되는 범용 프로그래밍 언어가 되었다. 웹 브라우저에서만 동작하는 반쪽짜리 프로그래밍 언어 취급을 받던 자바스크립트는 이제 프론트엔드 영역은 물론 백엔드 영역까지 아우르는 웹 프로그래밍 언어의 표준으로 자리잡고 있다.
자바스크립트는 크로스 플랫폼을 위한 가장 중요한 언어로 주목받고 있다. 자바스크립트는 웹은 물론 모바일 하이브리드 앱(PhoneGap, Ionic), 서버 사이드(NodeJS), 데스크톱(Electron), 머신 러닝(TensorFlow.js), 로보틱스(Johnny-Five) 프로그래밍 언어로서 세계에서 가장 인기있는 프로그래밍 언어이다.
이제 웹 애플리케이션은 데스크톱 애플리케이션과 비교해도 손색없는 성능과 사용자 경험을 제공하는 것이 필수가 되었고, 개발 규모와 복잡도도 더불어 상승했다. 이전의 개발 방식으로는 복잡해진 개발 과정을 수행하기 어려워졌고, 이러한 필요에 따라 많은 패턴과 라이브러리가 출현하였다. 이는 개발에 많은 도움을 주었지만 유연하면서 확징이 쉬운 애플리케이션 아키텍처 구축을 어렵게 하였고 필연적으로 프레임워크가 등장하게 되었다. SPA(Single Page Application)가 대중화 되면서 Angular, React, Vue.js 등 다양한 SPA 프레임워크/ 라이브러리 또한 많은 사용층을 확보하고 있다.
ECMAScript는 자바스크립트의 표준 명세인 ECMA-262를 말하며 프로그래밍 언어의 타입, 값, 객체와 속성, 함수, 빌트인 객체 등 핵심 문법(core syntax)을 규정한다. 각 브라우저 제조사는 ECMAScript를 준수하며 브라우저에 내장되는 자바스크립트 엔진을 구현한다.
자바스크립트는 일반적으로 프로그래밍 언어로서 기본 뼈대를 이루는 ECMAScript와 브라우저가 별도 지원하는 클라이언트 사이드 Web API, 즉 DOM, BOM, Canvas, XMLHttpRequest, Fetch, requestAnimationFrame, SVG, Web Storage, Web Component, Web worker 등을 아우르는 개념이다.
클라이언트 사이드 Web API는 ECMAScript와는 별도로 World Wide Web Consortium (W3C)에서 별도의 명세로 관리하고 있다. 클라이언트 사이드 Web API의 자세한 내용은 MDN web docs: Web API를 참고하기 바란다.
자바스크립트는 HTML, CSS와 함께 웹을 구성하는 요소 중 하나로 웹 브라우저에서 동작하는 유일한 프로그래밍 언어이다. 자바스크립트는 기존의 프로그래밍 언어에서 많은 영향을 받았다. 기본 문법은 C, Java와 유사하고 Self에서는 프로토타입 기반 상속을, Scheme에서는 일급 함수의 개념을 차용했다.
자바스크립트는 개발자가 별도의 컴파일 작업을 수행하지 않는 인터프리터 언어(Interpreter language)이다. 대부분의 모던 자바스크립트 엔진(Chrome의 V8, FireFox의 Spidermonkey, Safari의 JavaScriptCore, Microsoft Edge의 Chakra 등)은 인터프리터와 컴파일러의 장점을 결합하여 비교적 처리 속도가 느린 인터프리터의 단점을 해결했다. 인터프리터는 소스코드를 즉시 실행하고 컴파일러는 빠르게 동작하는 머신 코드를 생성하고 최적화한다. 이를 통해 컴파일 단계에서 추가적인 시간이 필요함에도 불구하고 보다 빠른 코드의 실행이 가능하다.
자바스크립트는 명령형(imperative), 함수형(functional), 프로토타입 기반(prototype-based) 객체 지향 프로그래밍을 지원하는 멀티 패러다임 프로그래밍 언어이다.
비록 다른 객체지향 언어들과의 차이점에 대한 논쟁들이 있긴 하지만, 자바스크립트는 강력한 객체지향 프로그래밍 능력을 지니고 있다. 간혹 클래스(ES6에서 새롭게 도입되었다.), 상속, 정보 은닉을 위한 키워드 private 가 없어서 객체지향 언어가 아니라고 오해(자바스크립트는 가장 많은 오해를 받는 언어이다.)하는 경우도 있지만 자바스크립트는 클래스 기반 객체지향 언어보다 효율적이면서 강력한 프로토타입 기반의 객체지향 언어이다.
구글의 Chrome V8 자바스크립트 엔진으로 빌드된 자바스크립트 런타임 환경(Runtime Environment)인 Node.js의 등장으로 자바스크립트는 웹 브라우저를 벗어나 서버 사이드 애플리케이션 개발에서도 사용되는 범용 개발 언어가 되었다. 하지만 자바스크립트가 가장 많이 사용되는 분야는 역시 웹 브라우저 환경에서 동작하는 웹 페이지/애플리케이션이다.
대부분의 프로그래밍 언어는 운영체제(Operating System, OS)위에서 실행되지만 웹 애플리케이션의 자바스크립트는 브라우저에서 HTML, CSS와 함께 실행된다. 따라서 브라우저 환경을 고려할 때 보다 효율적인 자바스크립트 프로그래밍이 가능하다.
브라우저의 핵심 기능은 사용자가 참조하고자 하는 웹페이지를 서버에 요청(Request)하고 서버의 응답(Response)를 받아 브라우저에 표시하는 것이다. 브라우저는 서버로 부터 HTML, CSS, Javascript, 이미지 파일 등을 응답받는다. HTML, CSS 파일은 렌더링 엔진의 HTML 파서와 CSS 파서에 의해 파싱(Parsing)되어 DOM, CSSOM 트리로 변환되고 렌더 트리로 결합된다. 이렇게 생성된 렌더 트리를 기반으로 브라우저는 웹페이지를 표시한다.
자바스크립트는 렌더링 엔진이 아닌 자바스크립트 엔진이 처리한다. HTML 파서는 script 태그를 만나면 자바스크립트 코드를 실행하기 위해 DOM 생성 프로세서를 중지하고 자바스크립테 엔진으로 제어 권한을 넘긴다. 제어 권한을 넘겨 받은 자바스크립트 엔진은 script 태그 내의 자바스크립트 코드 또는 script 태그의 src 어트리뷰트에 정의된 자바스크립트 파일을 로드하고 파싱하여 실행한다. 자바스크립트의 실행이 완료되면 다시 HTML 파서로 제어 권한을 넘겨서 브라우저가 중지했던 시점부터 DOM 생성을 재개한다.
이처럼 브라우저는 동기(Sychronous)적으로 HTML, CSS, Javascript를 처리한다. 이것은 script 태그의 위치에 따라 블로킹이 발생하여 DOM의 생성이 지연될 수 있다는 것을 의미한다. 따라서 script 태그의 위치는 중요한 의미를 갖는다.
body 요소의 가장 아래에 자바스크립트를 위치시키는 것은 좋은 아이디어이이다. 그 이유는 다음과 같다.
변수(Variable)는 값(value)을 저장(할당)하고 그 저장된 값을 참조하기 위해 사용한다. 한번 쓰고 버리는 값이 아닌 유지(캐싱)할 필요가 있는 값은 변수에 담아 사용한다. 또한 변수 이름을 통해 값의 의미를 명확히 할 수 있어 코드의 가독성이 좋아진다.
변수는 위치(주소)를 기억하는 저장소이다 위치란 메모리 상의 주소(address)를 의미한다. 즉 변수란 메모리 주소(Memory address)에 접근하기 위해 사람이 이해할 수 있는 언어로 지정한 식별자(identifier)이다.
변수는 선언할 때 var 키워드를 사용한다. 할당 연산자 = 는 변수에 값을 할당하기 위해 사용한다.
아래의 코드는 x는 변수로 선언되었고 변수 x에 정수값 6이 할당되었다.
var x;
x = 6;
var str = "Hello World";
위 예제는 str 이라는 이름의 변수를 선언하고 문자열 리터럴 "Hello World"를 값으로 할당하였다. 이때 문자열 리터럴 "Hello World"는 문자열 타입의 값이다.
용어 | 의미 |
---|---|
데이터 타입(Data Type) | 프로그래밍 언어에서 사용할 수 있는 값의 종류 |
변수(Variable) | 값이 저장된 메모리 공간의 주소를 가리키는 식별자(identifier) |
리터럴(literal) | 소스코드 안에서 직접 만들어 낸 상수 값 자체를 말하며 값을 구성하는 최소 단위 |
'값'은 프로그램에 의해 조작될 수 있는 대상을 말한다. 값은 다양한 방법으로 생성할 수 있다. 가장 간단한 방법은 리터럴 표기법(literal notation)을 사용하는 것이다.
// 숫자 리터럴
10.50
1001
// 문자열 리터럴
'Hello'
"World"
// 불리언 리터럴
true
false
// null 리터럴
null
// undefined 리터럴
undefined
// 객체 리터럴
{ name: 'Lee', gender: 'male' }
// 배열 리터럴
[ 1, 2, 3 ]
// 정규표현식 리터럴
/ab+c/
// 함수 리터럴
function() {}
숫자, 문자열, 불리언과 같은 원시 타입의 리터럴은 다양한 연산자의 피연산자가 되어 하나의 값으로 평가될 수 있다. 이렇게 리터럴은 연산에 의해 하나의 값이 될 수 있다.
// 산술 연산
10.50 + 1001
자바스크립트의 모든 값은 데이터 타입을 갖는다. 자바스크립트는 7가지 데이터 타입을 제공한다.
자바스크립트는 C나 Java 와는 다르게 변수를 선언할 때 데이터 타입을 미리 지정하지 않는다. 다시 말해, 변수에 할당된 값의 타입에 의해 동적으로 변수의 타입이 결정된다. 이를 동적 타이핑이라 하며 자바스크립트가 다른 프로그래밍 언어와 구별되는 특징 중 하나이다.
// Number
var num1 = 1001;
var num2 = 10.50;
// String
var string1 = 'Hello';
var string2 = "World";
// Boolean
var bool = true;
// null
var foo = null;
// undefined
var bar;
// Object
var obj = { name: 'Lee', gender: 'male' };
// Array
var array = [ 1, 2, 3 ];
// function
var foo = function() {};
연산자(Operator)는 하나 이상의 표현식을 대상으로 산술, 할당, 비교, 논리, 타입 연산 등을 수행해 하나의 값을 만든다. 이때 연산의 대상을 피연산자(Operand)라 한다.
// 산술 연산자
var area = 5 * 4; // 20
// 문자열 연결 연산자
var str = 'My name is ' + 'Lee'; // "My name is Lee"
// 할당 연산자
var color = 'red'; // "red"
// 비교 연산자
var foo = 3 > 5; // false
// 논리 연산자
var bar = (5 > 3) && (2 < 4); // true
// 타입 연산자
var type = typeof 'Hi'; // "string"
// 인스턴스 생성 연산자
var today = new Date(); // Sat Dec 01 2018 00:57:19 GMT+0900 (한국 표준시)
피연산자의 타입은 반드시 일치할 필요는 없다. 이때 자바스크립트는 암묵적 타입 강제 변환을 통해 연산을 수행한다.
var foo = 1 + '10'; // '11'
var bar = 1 * '10'; // 10
키워드(keyword)는 수행할 동작을 규정한 것이다. 예를 들어 var 키워드는 새로운 변수를 생성할 것을 지시한다.
// 변수의 선언
var x =5 + 6;
// 함수의 선언
function foo (arg){
// 함수의 종료 및 값의 반환
return ++arg;
}
var i = 0;
// 반복문
while(1) {
if (i > 5){
// 반복문의 탈출
break;
}
console.log(i);
i++;
}
주석(Comment)은 작성된 코드의 의미를 설명하기 위해 사용한다.
코드는 읽기(이해하기) 쉬워야 한다.(가독성이 좋아야 한다.) 사용자가 작성한 코드를 다른 누군가가 읽는다면 "아니, 이게 뭐하는 코드야?" 라고 생각하는 순간이 있기 마련이다. 사용자가 해야 하는 일은 바로 그런 부분에 주석을 다는 것이다.(읽기 좋은 코드가 좋은 코드이다.)
한줄 주석은 //, 여러 줄 주석은 /* */의 사이에 작성한다. 주석은 해석기(parser)가 무시하며 실행되지 않는다.
// 주석(Comment)은 작성된 코드의 의미를 설명하기 위해 사용한다. 코드는 읽기(이해하기) 쉬워야 한다.
/*
주석(Comment)은 작성된 코드의 의미를 설명하기 위해 사용한다.
코드는 읽기(이해하기) 쉬워야 한다.
*/
하지만 과도한 주석은 오히려 가독성을 해칠 수 있다. 주석 없이도 읽을 수 있는 코드가 최선이다.
// Bad
// 변수 x는 나이를 나타낸다. x에는 정수 10을 할당한다.
var x = 10;
// Good
var age = 10;
프로그램(스크립트)은 컴퓨터(Client-side Javascript의 경우, 엄밀히 말하면 웹 브라우저)에 의해 단계별로 수행될 명령들의 집합이다.
각각의 명령을 문(statement)이라 하며 문이 실행되면 무슨 일인가가 일어나게 된다.
문은 리터럴, 연산자(Operator), 표현식(Experssion), 키워드(keyword) 등으로 구성되며 세미콜론(;)으로 끝나야 한다.
var x = 5;
var y = 6;
var z = x + y;
console.log(z);
문은 코드 블록(code block, {...})으로 그룹화할 수 있다. 그룹화의 목적은 함께 실행되어져야 하는 문을 정의하기 위함이다.
// 함수
function myFunction(x, y) {
return x + y;
}
// if 문
if(x > 0) {
// do something
}
// for 문
for (var i = 0; i < 10; i++) {
// do something
}
문은 일반적으로 위에서 아래로 순서대로 실행된다. 이러한 실행 순서는 조건문(if, switch)이나 반복문(while, for)의 사용으로 제어할 수 있다. 이를 흐름제어(Control Flow)라 한다. 또는 함수 호출로 변경될 수 있다.
var time = 10;
var greeting;
if (time < 10) {
greeting = 'Good morning';
} else if (time < 20) {
greeting = 'Good day';
} else {
greeting = 'Good evening';
}
console.log(greeting);
다른 언어와 달리 자바스크립트에서는 블록 유효범위(Block-level scope)를 생성하지 않는다. 함수 단위의 유효범위(Function-level scope)만이 생성된다.
표현식(Expression)은 하나의 값으로 평가(Evaluation)된다. 값(리터럴), 변수, 객체의 프로퍼티, 배열의 요소, 함수 호출, 메소드 호출, 피연산자와 연산자의 조합은 모두 표현식이며 하나의 값으로 평가(Evaluation)된다. 표현식은 결국 하나의 값이 되기 때문에 다른 표현식의 일부가 되어 조금 더 복잡한 표현식을 구성할 수도 있다. 아래의 예에서 5*10은 50으로 평가(연산)된다.
// 표현식
5 // 5
5 * 10 // 50
5 * 10 > 10 // true
(5 * 10 > 10) && (5 * 10 < 100) // true
자연어에서 문(Statement)이 마침표로 끝나는 하나의 완전한 문장(Sentence)이라고 한다면 표현식은 문을 구성하는 요소이다. 표현식은 그 자체로 하나의 문이 될 수도 있다.
// 선언문(Declaration statement)
var x = 5 * 10; // 표현식 x = 5 * 10를 포함하는 문이다.
// 할당문(Assignment statement)
x = 100; // 이 자체가 표현식이지만 완전한 문이기도 하다.
표현식과 문은 매우 유사하여 구별이 어려울 수 있다. 표현식은 평가되어 값을 만들지만 그 이상의 행위는 할 수 없다. 문은 var, function과 같은 선언 키워드를 사용하여 변수나 함수를 생성하기도 하고 if, for, while 문과 같은 제어문을 생성하여 프로그램의 흐름을 제어하기도 한다. 표현식을 통해 평가한 값을 통해 실제로 컴퓨터에게 명령을 하여 무언가를 하는 것은 문이다.
함수란 어떤 작업을 수행하기 위해 필요한 문(statement)들의 집합을 정의한 코드 블록이다. 함수는 이름과 매개변수를 갖으며 필요한 때에 호출하여 코드 블록에 담긴 문들을 일괄적으로 실행할 수 있다.
function square(number){
retrun number * number;
}
함수는 호출에 의해 실행되는데 한번만 호출할 수 있는 것이 아니라 여러번 호출할 수 있다.
// 함수의 정의(함수 선언문)
function square(number) {
return number * number;
}
// 함수의 호출
square(2); // 4
동일한 작업을 반복적으로 수행해야 한다면 (동일한 구문을 계속해서 중복해서 작성하는 것이 아니라) 미리 정의된 함수를 재사용하는 것이 효율적이다. 이러한 특성은 코드의 재사용이라는 측면에서 매우 유용하다.
자바스크립트는 객체(object)기반의 스크립트 언어이며 자바스크립트를 이루고 있는 거의 "모든 것"이 객체이다. 원시 타입(Primitives)을 제외한 나머지 값들(함수, 배열, 정규표현식 등)은 모두 객체이다.
자바스크립트 객체는 키(이름)과 값으로 구성된 프로퍼티(property)의 집합이다. 프로퍼티의 값으로 자바스크립트에서 사용할 수 있는 모든 값을 사용할 수 있다. 자바스크립트의 함수는 일급 객체이므로 값으로 취급할 수 있다. 따라서 프로퍼티 값으로 함수를 사용할 수도 있으며 프로퍼티 값이 함수일 경우, 일반 함수와 구분하기 위해 메소드라 부른다.
var person = {
name: 'Lee';
gender: 'male';
sayHello: function() {
console.log('Hi My name is ' + this.name);
}
};
console.log(typeof person); // object
console.log(person); // { name: 'Lee', gender: 'male', sayHello: [Function: sayHello] }
person.sayHello(); // Hi! My name is Lee
이와 같이 객체는 데이터를 의미하는 프로퍼티(property)와 데이터를 참조하고 조작할 수 있는 동작(behavior)을 의미하는 메소드(method)로 구성된 집합이다. 객체는 데이터(프로퍼티)와 그 데이터에 관련되는 동작(메소드)을 모두 포함할 수 있기 때문에 데이터와 동작을 하나의 단위로 구조화할 수 있어 유용하다.
자바스크립트의 객체는 객체지향의 상속을 구현하기 위해 "프로토타입" 이라고 불리는 객체의 프로퍼티와 메소드를 상속받을 수있다. 이 프로토타입은 타 언어와 구별되는 중요한 개념이다.
배열(Array)은 1개의 변수와 여러 개의 값을 순차적으로 저장할 때 사용한다. 자바스크립트의 배열은 객체이며 유용한 내장 메소드를 포함하고 있다.
var arr = [1, 2, 3, 4, 5];
console.log(arr[1]); // 2
프로그래밍은 변수를 통해 값을 저장하고 참조하며 연산자로 값을 연산, 평가하고 조건문과 반복문에 의한 흐름제어로 데이터의 흐름을 제어하고 함수로 재사용이 가능한 구문의 집합을 만들며 객체, 배열 등으로 자료를 구조화하는 것이다.
변수는 값의 위치(주소)를 기억하는 저장소이다. 값의 위치란 값이 위치하고 있는 메모리 상의 주소(address)를 의미한다. 즉, 변수란 값이 위치하고 있는 메모리 주소(Memory address)에 접근하기 위해 사람이 이해할 수 있는 언어로 명명한 식별자(identifier)이다.
메모리에 값을 저장하기 위해서는 먼저 메모리 공간을 확보해야 할 메모리의 크기(byte)를 알아야 한다. 이는 값의 종류에 따라 확보해야 할 메모리의 크기가 다르기 때문이다. 이때 값의 종류, 즉 데이터의 종류를 데이터 타입(Data Type)이라 한다.
예를 들어 1byte(8bit)로 표현할 수 있는 경우의 수, 즉 값의 총 개수는 256개(2의 8승)으로 아시크코디(ASCII)를 표현할 수 있으며, 4byte(32bit)로 표현할 수 있는 값의 총수는 4,294,967,296개(2의 32승)로 -2,147,483,648 ~ 2,147,483,647의 정수를 표현할 수 있다.
C나 Java 같은 C-family 언어는 정적 타입(Static/Strong Type) 언어로 변수 선언 시 변수에 저장할 값의 종류를 사전에 타입 지정(Type annotation)하여야 한다. 다음은 C에서 정수형 변수를 선언하는 예이다.
// 1byte 정수형: -128 ~ 127
char c;
// 4byte 정수형: -2,124,483,648 ~ 2,124,483,647
int num;
C언어의 경우, 4byte 정수형인 int형 변수 선언을 만나면 시스템은 이후 할당될 값과 상관없이 4byte의 메모리 영역을 확보한다. 이후 int형 변수에 할당할 때에는 int형 값을 할당해야한다. 다음은 C에서 정수형 변수에 문자열을 잘못 할당한 예이다.
int main(void) {
int num = 46;
char * str = "String";
num = "String"; // warning: incompatible pointer to integer conversion assigning to 'int' from 'char [7]'
return 0;
}
자바스크립트는 동적 타입(Dynamic/Weak Type)언어이다. 변수의 타입 지정(Type annotation)없이 값이 할당되는 과정에서 자동으로 변수의 타입이 결정(타입 추론, Type Inference)된다. 즉, 변수는 고정된 타입이 없다. 따라서 같은 변수에 여러 타입의 값을 자유롭게 할당할 수 있다.
var str = 'Hello';
var num = 1;
var bool = true;
var foo = 'string';
console.log(typeof foo); // string
foo = 1;
console.log(typeof foo); // number
데이터 타입(Data Type)은 프로그래밍 언어에서 사용할 수 있는 데이터(숫자, 문자열, 불리언 등)의 종류를 말한다.
코드에서 사용되는 모든 데이터는 메모리에 저장하고 참조할 수 있어야 한다. 데이터 타입은 데이터를 메모리에 저장할 때 확보해야 하는 메모리 공간의 크기와 할당할 수 있는 유효한 값에 대한 정보, 그리고 메모리에 저장디어 있는 2진수 데이터를 어떻게 해석할 지에 대한 정보를 컴퓨터와 개발자에게 제공한다.
데이터 타입은 한정된 메모리 공간을 효율적으로 사용하기 위해서, 그리고 2진수 데이터로 메모리에 저장된 데이터를 다양한 형태로 사용하기 위해 존재한다.
자바스크립트의 모든 값은 데이터 타입을 갖는다. ECMAScript 표준(ECMAScript 2015(6th Edition), 이하 ES6)은 7개의 데이터 타입을 제공한다.
자바스크립트에서 제공하는 7개의 데이터 타입은 크게 원시타입(primitive data type)과 객체 타입(object/reference type)으로 구분할 수 있다.
원시 타입의 값은 변경 불가능한 값(immutable value)이며 pass-by-value(값에 의한 전달)이다.
C나 Java의 경우, 정수와 실수를 구분하여 int, long, float, double 등과 같은 다양한 숫자 타입이 존재한다. 하지만 자바스크립트는 독특하게 하나의 숫자 타입만 존재한다.
ECMAScript 표준에 따르면, 숫자 타입의 값은 배정밀도 64비트 부동소수점 형(double-precision 64-bit floating-point format : -(2^53 -1) 와 2^53 -1 사이의 숫자값)을 따른다.
var integer = 10; // 정수
var double = 10.12; // 실수
var negative = -20; // 음의 정수
var binary = 0b01000001; // 2진수
var octal = 0o101; // 8진수
var hex = 0x41; // 16진수
2진수, 8진수, 16진수 리터럴은 메모리에 동일한 배정밀도 64비트 부동소수점 형식의 2진수로 저장된다. 자바스크립트는 2진수, 8진수, 16진수 데이터 타입을 제공하지 않기에 이들 값을 참조하면 모두 10진수로 해석된다.
console.log(binary); // 65
console.log(octal); // 65
console.log(hex); // 65
// 표기법만 다를뿐 같은 값이다.
console.log(binary === octal); // true
console.log(octal === hex); // true
자바스크립트의 숫자 타입은 정수만을 위한 타입이 없고 모든 수를 실수로 처리한다. 정수로 표시된다해도 사실은 실수다. 따라서 정수로 표시되는 수 끼리 나누더라도 실수가 나올 수 있다.
console.log(1 === 1.0); // true
var result = 4 / 2;
console.log(result); // 2
result = 3 /2;
console.log(result); // 1.5
추가적으로 3가지 특별한 값들도 표현할 수 있다.
var pInf = 10 / 0; // 양의 무한대
console.log(pInf); // Infinity
var nInf = 10 / -0; // 음의 무한대
console.log(nInf); // -Infinity
var nan = 1 * 'string'; // 산술 연산 불가
console.log(nan); // NaN
수학적 의미의 실수(實數, real number)는 허수(虛數, imaginary number)가 아닌 유리수와 무리수를 통틀은 말이지만 프로그래밍 언어에서 실수는 일반적으로 소수를 가리킨다.
문자열(string) 타입은 텍스트 데이터를 나타내는데 사용한다. 문자열은 0개 이상의 16bit 유니코드 문자(UTF-16) 들의 집합으로 대부분의 전세계의 문자를 표현할 수 있다. 문자열은 작은 따옴표('), 끝 따옴표(") 안에 텍스트를 넣어 생성한다. 가장 일반적으로는 작은 따옴표를 사용하는 것이다.
var str = "string"; // 큰 따옴표
str = 'string'; // 작은 따옴표
str = `string`; // 백틱(ES6 템플릿 리터럴)
str = "큰 따옴표로 감싼 문자열 내의 '작은 따옴표'는 문자열이다.";
str = '작은 따옴표로 감싼 문자열 내의 "큰 따옴표"는 문자열이다.';
C와 같은 언어와 다르게, 자바스크립트의 문자열은 원시 타입이며 변경 불가능(immutable)하다. 이것은 한 번 문자열이 생성되면, 그 문자열을 변경할 수 없다는 것을 의미한다.
var str = 'Hello';
str = 'world';
첫번쨰 구문이 실행되면 메모리에 문자열 'Hello'가 생성되고 식별자 str은 메모리에 생성된 문자열 'Hello'의 메모리 주소를 가리킨다. 그리고 두번째 구문이 실행되면 이전에 생성된 문자열 'Hello'를 수정하는 것이 아니라 새로운 문자열 'world'를 메모리에 생성하고 식별자 str은 이것을 가리킨다. 이때 문자열 'Hello'와 'world'는 모두 메모리에 존재하고 있다. 변수 str은 문자열 'Hello'를 가리키고 있다가 문자열 'world'를 가리키도록 변경되었을 뿐이다.
var str = 'string';
// 문자열은 유사배열이다.
for (var i = 0; i < str.length; i++) {
console.log(str[i]);
}
// 문자열을 변경할 수 없다.
str[0] = 'S';
console.log(str); // string
문자열은 배열처럼 인덱스를 통해 접근할 수 있다. 이와 같은 특성을 갖는 데이터를 유사 배열이라 한다.
str[0] = 'S'처럼 이미 생성된 문자열의 일부 문자를 변경해도 반영되지 않는다.(이때 에러가 발생하지 않는다). 한번 생성된 문자열은 read only 로서 변경할 수 없다. 이것을 변경 불가능(immutable)이라 한다.
그러나 새로운 문자열을 재할당하는 것은 물론 가능하다. 이는 기존 문자열을 변경하는 것이 아니라 새로운 문자열을 새롭게 할당하는 것이기 때문이다.
var str = 'string';
console.log(str); // string
str = 'String';
console.log(str); // String
str += ' test';
console.log(str); // String test
str = str.substring(0, 3);
console.log(str); // Str
str = str.toUpperCase();
console.log(str); // STR
불리언(boolean)타입의 값은 논리적 참, 거짓을 나타내는 true, false 뿐이다.
var foo = true;
var bar = false;
// typeof 연산자는 타입을 나타내는 문자열을 반환한다.
console.log(typeof foo); // boolean
console.log(typeof bar); // boolean
불리언 타입의 값은 참과 거짓으로 구분되는 조건에 의해 프로그램의 흐름을 제어하는 조건문에서 자주 사용한다.
비어있는 문자열과 null, undefined, 숫자 0은 모두 false로 간주된다.
undefined 타입의 값은 undefined 가 유일하다. 선언 이후 값을 할당하지 않은 변수는 undefined 값을 가진다. 즉, 선언은 되었지만 값을 할당하지 않은 변수에 접근하거나 존재하지 않은 객체 프로퍼티에 접근할 경우 undefined 가 반환된다.
이는 변수 선언에 의해 확보된 메모리 공간을 청므 할당이 이러우질 때까지 빈 상태(대부분 비어있지 않고 쓰레기 값(Garbage value)이 들어 있다.)로 내버려두지 않고 자바스크립트 엔진이 undefined로 초기화하기 때문이다.
var foo;
console.log(foo); // undefined
이 처럼 undefined 는 개발자가 의도적으로 할당한 값이 아니라 자바스크립트 엔진에 의해 초기화된 값이다. 변수를 참조했을 때 undefined가 반환된다면 참조한 변수가 선언 이후 값이 할당된 적이 없는 변수라는 것을 개발자는 간파할 수 있다. 그렇다면 개발자가 undefined를 할당해야 하는 경우가 있는가?
자바스크립트 엔진이 변수 초기화에 사용하여 이 값을 만약 개발자가 마음대로 할당한다면 undefined 의 본래의 취지에 어긋날 뿐더라 혼란을 줄 수 있으므로 권장하지 않는다. 만일 변수의 값이 없다는 것을 명시하고 싶을 때는 undefined가 아닌 null을 할당해야한다.
null 타입의 값은 null이 유일하다. 자바스크립트는 대소문자를 구별(case-sensitive)하므로 null은 Null, NULL 등과 다르다.
프로그래밍 언어에서 null은 의도적으로 변수에 값이 없다는 것을 명시할 때 사용한다. 이는 변수가 기억하는 메모리 어드레스의 참조 정보를 제거하는 것을 의미하며 자바스크립트 엔진은 누구도 참조하지 않은 메모리 영역에 대해 가바지 콜렉션을 수행할 것이다.
var foo = 'Lee';
foo = null; // 참조 정보가 제거됨
또는 함수가 호출되었으나 유효한 값을 반환할 수 없는 경우, 명시적으로 null을 반환하기도 한다. 예를 들어, 조건에 부합하는 HTML 요소를 검색해 반환하는 Document.querySelector()는 조건에 부합하는 HTML 요소를 검색할 수 없는 경우, null을 반환한다.
var element = document.querySelector('.myElem');
// HTML 문서에 myElem 클래스를 갖는 요소가 없다면 null을 반환한다.
console.log(element); // null
타입을 나타내는 문자열을 반환하는 typeof 연산자로 null 값을 연산해 보면 null이 아닌 object가 나온다. 이는 자바스크립트의 설계상의 오류이다.
var foo = null;
console.log(typeof foo); // object
따라서 null 타입을 확인할 때 typeof 연산자를 사용하면 안되고 일치 연산자(===)를 사용하여야 한다.
var foo = null;
console.log(typeof foo === null); // false
console.log(foo === null); // true
심볼(symbol)은 ES6에서 새롭게 추가된 7번째 타입으로 변경 불가능한 원시 타입의 값이다. 심볼은 주로 이름의 충돌 위험이 없는 유일한 객체의 프로퍼티 키(property key)를 만들기 위해 사용한다. 심볼은 Symbol 함수를 호출해 생성한다. 이때 생성된 심볼 값은 다른 심볼 값들과 다른 유일한 심볼 값이다.
// 심볼 key는 이름의 충돌 위험이 없는 유일한 객체의 프로퍼티 키
var key = Symbol('key');
console.log(typeof key); // symbol
var obj = {};
obj[key] = 'value';
console.log(obj[key]); // value
객체는 데이터와 그 데이터에 관련된 동작(절차, 방법, 기능)을 모두 포함할 수 있는 개념적 존재이다. 달리 말해, 이름과 값을 가지는 데이터를 의미하는 프로퍼티(property)와 동작을 의미하는 메소드(method)를 포함할 수 있는 독립적 주체이다.
자바스크립트는 객체(object) 기반의 스크립트 언어로서 자바스크립트를 이루고 있는 거의 "모든 것"이 객체이다. 원시 타입(Primitives)을 제외한 나머지 값들(배열, 함수, 정규표현식 등)은 모두 객체이다. 또한 객체는 pass-by-reference(참조에 의한 전달) 방식으로 전달된다.
변수(Variable)는 프로그램에서 사용되는 데이터를 일정 기간 동안 기억하며 필요한 때에 다시 사용하기 우해 데이터에 고유의 이름인 식별자(identifier)를 명시 한 것이다. 변수에 명시한 고유한 식별자를 변수명이라 하고 변수로 참조할 수 있는 데이터를 변수값이라 한다.
식별자는 어떤 대상을 유일하게 식별할 수 있는 이름을 말한다. 식별자에는 변수명, 함수명, 프로퍼티명, 클래스명 등이 있다.
변수는 var, let, const 키워드를 사용하여 선언하고 할당 연산자를 사용해 값을 할당한다. 그리고 식별자인 변수명을 사용해 변수에 저장된 값을 참조한다.
var score; // 변수의 선언
score = 80; // 값의 할당
score = 90; // 값의 재할당
score; // 변수의 참조
// 변수의 선언과 할당
var average = (50 + 100) / 2;
사람을 고유한 이름으로 구별하듯이 변수도 사람이 이해할 수 있는 언어로 지정한 고유한 식별자(변수명)에 의해 구별하여 참조할 수 있다. 데이터는 메모리에 저장되어 있다. 메모리에 저장된 데이터를 참조하려면 데이터가 저장된 메모리 상의 주소(address)를 알아야 한다. 식별자는 데이터가 저장된 메모리 상의 주소를 기억한다. 따라서 식별자를 통해 메모리에 저장된 값을 참조할 수 있다. 또한 변수명을 통해 데이터의 의미를 명확히 할 수 있어 코드의 가독성이 좋아지는 효과가 있다.
예를 들어 반지름의 길이가 2인 원의 넓이를 구해보자. 이때 원주율은 3.141592653589793라고 하자.
3.141592653589793 * 2 * 2; // 12.566370614359172
원의 넓이는 구하였으나 그 결과를 기억할 수 없다. 만약에 원의 넓이를 다시 사용해야 한다면 다시 구해야 한다. 변수를 사용하여 원의 넓이를 기억(캐싱)하고 기억된 원의 넓이를 재사용하여 높이가 5인 원기둥의 부피를 구해보자.
var circleArea = 3.141592653589793 * 2 * 2;
var cylinderVolume = circleArea * 5;
원주율 3.141592653589793은 재사용할 가능성이 크므로 변수에 저장한다. 원주율은 변하지 않는 상수이지만 자바스크립트는 상수를 별도 지원하지 않으므로 변수 이름을 대문자로 하여 상수임을 암시하도록 하자. 그리고 반지름과 원기둥의 높이도 값의 의미를 명확히하고 변화에 대처하기 쉽도록 변수에 저장하도록 하자.
원주율은 자바스크립트 빌트인 상수인 Math.PI를 통해 참조할 수 있다. 상수는 ES6의 const 키워드를 사용해 표현할 수 있다.
var PI = 3.141592653589793; // 상수
var radius = 2; // 변수
var circleArea = PI * radius * radius;
var cylinderHeight = 5;
var cylinderVolume = circleArea * cylinderHeight;
이처럼 변수는 애플리케이션에서 한번 쓰고 버리는 값이 아닌 값이 아닌 일정 기간 유지할 필요가 있는 값에 사용한다. 또한 변수를 사용하면 값의 의미가 명확해져서 코드의 가독성이 좋아진다.
변수의 존재 목적을 쉽게 이해할 수 있도록 의미있는 변수명을 지정하여야 한다.
var x = 3; // NG
var score = 100; // OK
변수명은 식별자(identifier)로 불리기도 하며 명명 규칙이 존재한다.
변수를 선언할 때는 var 키워드를 사용한다. 등호(=)는 변수에 값을 할당하는 할당 연산자이다.
var name; // 선언
name = 'Lee'; // 할당
var age = 30; // 선언과 할당
var person = 'Lee',
address = 'Seoul',
price = 200;
var price = 10;
var tax = 1;
var total = price + tax;
값을 할당하지 않은 변수 즉 선언만 되어 있는 변수는 undefined로 초기값을 갖는다. 선언하지 않은 변수에 접근하는 ReferenceError 가 발생한다.
var x;
console.log(x); // undefined
console.log(y); // ReferenceError
var 키워드로 선언한 변수는 중복 선언이 가능하다. 다시 말해 변수명이 같은 변수를 중복해 선언해도 에러가 발생하지 않는다.
var x = 1;
console.log(x); // 1
// 변수의 중복 선언
var x = 100;
console.log(x); // 100
위 예제의 변수 x는 중복 선언되었다. 이처럼 변수를 중복 선언하면 에러없이 이전 변수의 값을 덮어쓴다. 만약 동일한 변수명이 선언되어 있는 것을 모르고 변수를 중복 선언했다면 의도치 않게 변수의 값을 변경하는 부작용을 발생시킨다. 따라서 변수의 중복 선언은 문법적으로 허용되지만 사용하지 않는 것이 좋다.
자바스크립트는 동적 타입(dynamic/weak type)언어이다. 이것은 변수의 타입 지정(Type annotation)없이 값이 할당되는 과정에서 값의 타입에 의해 자동으로 타입이 결정(Type Inference)될 것이라는 뜻이다. 따라서 같은 변수에 여러 타입의 값을 할당할 수 있다. 이를 동적 타이핑(Dynamic Typing)이라 한다.
var foo;
console.log(typeof foo); // undefined
foo = null;
console.log(typeof foo); // object
foo = {};
console.log(typeof foo); // object
foo = 3;
console.log(typeof foo); // number
foo = 3.14;
console.log(typeof foo); // number
foo = 'Hi';
console.log(typeof foo); // string
foo = true;
console.log(typeof foo); // boolean
console.log(foo); // ① undefined
var foo = 123;
console.log(foo); // ② 123
{
var foo = 456;
}
console.log(foo); // ③ 456
var 키워드를 사용하여 선언한 변수는 중복 선언이 가능하기 때문에 위의 코드는 문법적으로 문제가 없다.
허나 ①에서 변수 foo는 아직 선언되지 않았으므로 ReferenceError: foo is not defined가 발생할 것을 기대했겠지만 콘솔에는 undefined가 출력된다.
이것은 다른 C-family 언어와는 차별되는 자바스크립트의 특징으로 모든 선언문은 호이스팅(Hoisting)되기 때문이다.
호이스팅이란 var 선언문이나 function 선언문 등 모든 선언문이 해당 Scope의 선두로 옮겨진 것 처럼 동작하는 특성을 말한다. 즉, 자바스크립트는 모든 선언문(var, lelt, const, function, function*, class)이 선언되기 이전에 참조 가능하다.
변수가 어떻게 생성되며 호이스팅은 어떻게 이루어지는지 좀더 살펴보며녀 변수는 3단계에 걸쳐 생성된다.
선언 단계(Declaration phase)
변수 객체(Variable Object)에 변수를 등록한다. 이 변수 객체는 스코프가 참조하는 대상이 된다.
초기화 단계(Initialization phase)
변수 객체(Variable Object)에 등록된 변수를 메모리에 할당한다. 이 단계에서 변수는 undefined로 초기화된다.
할당 단계(Assignment phase)
undefined로 초기화된 변수에 실제값을 할당한다.
var 키워드로 선언된 변수는 선언 단계와 초기화 단계가 한번에 이루어진다. 즉, 스코프에 변수가 등록되고 변수는 메모리에 공간을 확보한 후 undefined로 초기화된다. 따라서 변수 선언문 이전에 변수에 접근하여도 Variable Object에 변수가 존재하기 때문에 에러가 발생하지 않는다. 다만 undefined를 반환한다. 이러한 현상을 변수 호이스팅(Variable Hoisting)이라 한다.
이후 변수 할당문에 도달하면 비로소 값의 할당이 이루어진다.
앞에서 살펴본 예제를 호이스팅 관점에서 다시 보면
①이 실행되기 이전에 var foo = 123; 이 호이스팅되어 ①구문 앞에 var foo;가 옮겨진다.(실제로 변수 선언이 코드 레벨로 옮겨진 것은 아니고 변수 객체(Variable object)에 등록되고 undefined로 초기화된 것이다.) 하지만 변수 선언 단계와 초기화 단계가 할당 단계와 분리되어 진행되기 때문에 이 단계에서는 foo에는 undefined가 할당되어 있다. 변수 foo에 값이 할당되는 것은 2행에서 실시된다.
② 에서는 변수에 값이 할당되었기 때문에 123이 출력된다.
자바스크립트의 변수는 다른 C-family와는 달리 블록 레벨 스코프(block-level scope)를 가지지 않고 함수 레벨 스코프(function-level scope)를 갖는다. 단, ECMAScript 6에서 도입된 let, const 키워드를 사용하면 블록 레벨 스코프를 사용할 수 있다. 자세한 내용은 Scope를 참조하기 바란다.
함수 레벨 스코프(Function-level scope)
함수 내에서 선언된 변수는 함수 내에서만 유효하며 함수 외부에서는 참조할 수 없다. 즉, 함수 내부에서 선언한 변수는 지역 변수이며 함수 외부에서 선안한 변수는 모두 전역 변수이다.
블록 레벨 스코프(Block-level scope)
코드 블록 내에서 선언된 변수는 코드 블록 내에서만 유효하며 코드 블록 외부에서는 참조할 수 없다.
따라서 코드 블록 내의 변수 foo는 전역변수이므로 전역에 선언된 변수 foo에 할당된 값을 재할당하기 때문에 ③의 결과는 456이 된다.
ES5에서 변수를 선언할 수 있는 유일한 방법은 var 키워드를 사용하는 것이다. var 키워드로 선언된 변수는 아래와 같은 특징을 갖는다. 이는 다른 C-family 언어와는 차별되는 특징(설계상 오류)으로 주의를 기울이지 않으면 문제가 발생된다.
대부분의 문제는 전역 변수로 인해 발생한다. 전역 변수는 간단한 애플리케이션의 경우, 사용이 편리한 면이 있지만 불가피한 상황을 제외하고 사용을 억제해야 한다. 전역 변수는 유효 범위(scope)가 넓어서 어디에서 어떻게 사용될 지 파악하기 힘들다. 이는 의도치 않은 변수의 변경이 발생할 수 있는 가능성이 증가한다. 또한 여러 함수와 상호 의존하는 등 부수 효과(side effect)가 있을 수 있어서 복잡성이 증가한다.
변수의 유효 범위(scope)는 좁을 수록 좋다.
ES6는 이러한 var의 단점을 보완하기 위해 let과 const 키워드를 도입하였다.
값은 다양한 방법으로 생성할 수 있다. 여기서 말하는 다양한 방법이란 표현식을 말한다. 프로그래밍 언어에서 표현식은 중요한 역할을 한다. 다시 한번 표현식에 대해 살펴보자.
표현식(expression)은 리터럴, 식별자, 연산자, 함수 호출 등의 조합을 말한다. 표현식은 평가(evaluation. 표현식을 실행하여 하나의 값을 만드는 과정)되어 하나의 값을 만든다. 즉 표현식은 하나의 값으로 평가될 수 있는 문(statement)이다.
표현식은 리터럴 표현식, 식별자 표현식, 연산자 표현식, 함수/메소드 호출 표현식 등으로 나누어 볼 수 있지만 결국 평가되어 하나의 값을 만든다는 점에서 모두 동일하다.
// 리터럴 표현식
10
// 식별자 표현식
sum
// 연산자 표현식
10 + 20
// 함수/메소드 호출 표현식
square()
표현식은 평가되어 결국 하나의 값이 되므롤 표현식과 값은 동등한 관계, 즉 동치(Equivalent)이다. 다시 말해, 표현식은 값처럼 사용할 수 있다. 이것은 값이 위치할 수 있는 자리에는 표현식이 위치할 수 있다는 의미이다. 예를 들어 산술 연산자 +의 좌항, 우항에는 숫자 값이 위치해야 한다. 숫자 값으로 평가될 수 있는 표현식이라면 숫자 값 대신 사용할 수 있다.
var x = 10;
// 연산자 표현식
x + 30; // 식별자 표현식과 숫자 리터럴과 연산자의 조합
이처럼 표현식은 다른 표현식의 일부가 되어 새로운 값을 만들어 낼 수 있다. 연산자 표현식은 표현식을 결합해 새로운 값을 만들어 내는 가장 일반적인 표현식이다.
문(statement)은 자바스크립트 엔진에게 내리는 명령이다. 문이 실행되면 무슨 일인가가 일어나게 된다. 변수 선언문을 실행하면 변수가 선언이 되고, 할당문을 실행하면 할당이 된다. 조건문을 실행하면 주어진 조건에 따라 코드 블럭({...})의 실행이 결정되고, 반복문을 실행하면 코드 블록이 반복 실행된다.
// 변수 선언문
var x;
// 할당문
x = 5;
// 함수 선언문
function foo () {}
// 조건문
if (x > 5) { … }
// 반복문
for (var i = 0; i < 10; i++) { … }
문은 리터럴, 연산자, 표현식, 키워드 등으로 구성되며 세미콜론(;)으로 끝나야 한다.(코드 블록 {...}은 제외)
문의 끝에 붙이는 세미콜론은 옵션으로 쓰지 않아도 상관없닫. 자바스크립트 엔진이 스크립트를 해석할 때, 자바스크립트 엔진에는 문의 끝이라고 예측되는 지점에 세미콜론을 자동으로 붙여주는 세미콜론 자동 삽입 기능(ASI, automatic semicolon insertion)이 있기 때문이다. 하지만 세미콜론 자동 삽입 기능의 예측과 개발자의 예측과 다른 경우가 간혹 있다.
function foo () {
return
{}
}
console.log(foo()); // undefined
새미콜론을 반드시 붙여야 한다는 주장이 대다수를 차지하지만 붙이지 말아야 한다 주장도 설득력이 있다. 하지만 ESLint와 같은 정적 분석 도구에서도 세미콜론 사용을 기본으로 설정하고 있고 TC39(ECMAScript 기술 위원회)도 세미콜론 사용을 권장하는 분위기 이므로 세미콜론을 붙인다.
자바스크립트의 모든 코드는 문 또는 표현식이다. 자연어에서 문이 마침표로 끝나는 하나의 완전한 문장(Sentence)이라고 한다면 표현식은 문을 구성하는 요소이다. 표현식은 그 자체로 하나의 문이 될 수 있다.
표현식과 문은 유사하여 구별이 어려울 수 있다. 표현식은 평가되어 값을 만들지만 그 이상의 행위는 할 수 없다. 문은 var, let, const, function, class와 같은 선언 키워드를 사용하여 변수나 함수를 생성하기도 하고, if, while, for 문과 같은 제어문을 생성하여 프로그램의 흐름을 제어하기도 한다.
표현식의 역할을 값을 생성하는 것이다. 문의 역할을 표현식으로 생성한 값을 사용해 컴퓨터에게 명령을 내리는 것이다. 문에는 표현식인 문과 표현식이 아닌 문이 있다. 예를 들어 선언문은 값으로 평가될 수 없다. 따라서 표현식이 아닌 문이다. 하지만 할당문은 그 자체가 표현식인 문이다.
// 선언문(Declaration statement)
var x = 5 * 10; // 표현식 x = 5 * 10를 포함하는 문이다.
// 할당문(Assignment statement)
x = 100; // 이 자체가 표현식이지만 완전한 문이기도 하다.
위의 선언문은 표현식이 아닌 문이다. 다시 말해 값으로 평가될 수 없다. 따라서 선언문은 값처럼 사용할 수 없다.
var foo = var x = 5 * 10;
이에 반해 할당문은 그 자체가 표현식이다. 다시 말해 할당문은 표현식인 문이다.
크롬에서 DevTools에서 표현식이 아닌 문은 언제나 undefined를 반환하고, 표현식인 문은 언제나 값을 반환하다.
할당문은 표현식인 문이기 때문에 값처럼 사용할 수 있다.
var foo = x =100;
연산자(Operator)는 하나 이상의 표현식을 대상으로 산술, 할당, 비교, 논리, 타입 연산 등을 수행해 하나의 값을 만든다. 이때 연산즹 대상을 피연산자(Operand)라 한다.
// 산술 연산자
5 * 4 // 20
// 문자열 연결 연산자
'My name is ' + 'Lee' // "My name is Lee"
// 할당 연산자
var color = 'red'; // "red"
// 비교 연산자
3 > 5 // false
// 논리 연산자
(5 > 3) && (2 < 4) // true
// 타입 연산자
typeof 'Hi' // "string"
피연산자가 "값"이라는 명사의 역할을 한다면 연산자는 "값을 만든다"라는 동사의 역할을 한다고 볼 수 있다. 다시 말해, 피연산자는 연산의 대상이 되어야 하므로 값으로 평가할 수 있어야 한다. 연산자는 값으로 평가된 피연산자를 연산해 새로운 값을 만든다.
산술 연산자(Arithmetic Operator)는 피연산자를 대상으로 수학적 계산을 수행해 새로운 숫자 값을 만든다. 산술 연산을 할 수 없는 경우 NaN을 반환한다.
산술 연산자는 이상 산술 연산자와단항 산술 연산자로 구분할 수있다.
이항 산술 연산자는 2개의 피연산자를 대상으로 연산하여 숫자 타입의 값을 만든다.
모든 이항 산술 연산자는 피연산자의 값을 변경하는 부수 효과(Side effect)가 없다. 다시 말해, 어떤 산술 연산을 해도 피연산자의 값이 바뀌는 경우는 없고 단지 새로운 값을 만들 뿐이다.
이항 산술 연산자 | 의미 |
---|---|
+ | 덧셈 |
- | 뺄셈 |
* | 곱셈 |
/ | 나누셈 |
% | 나머지 |
5 + 2 // 7
5 - 2 // 3
5 * 2 // 10
5 / 2 // 2.5
5 % 2 // 1
단항 산술 연산자는 1개의 피연산자를 대상으로 연산한다.
증가/감소(++/--) 연산자는 피연산자의 값을 변경하는 부수 효과가 있다. 다시 말해, 증가/ 감소 연산을 하면 피연산자의 값이 바뀐다.
단항 산술 연산자 | 의미 |
---|---|
++ | 증가 |
-- | 감소 |
+ | 어떠한 효과도 없다. 음수를 양수로 반전하지도 않는다. |
- | 양수를 음수로 음수를 양수로 반전한 값을 반환한다. |
증가/감소(++/--) 연산자는 위치에 의미가 있다. 피연산자 앞에 위치한 전위 증가/감소 연산자(Perfix increment/decrement operator)는 먼저 피연산자의 값을 증가/감소시킨 후, 다른 연산을 수행한다. 피연산자 뒤에 위치한 후위 증가/감소 연산자(Postfix increment/decrement operator)는 먼저 다른 연산을 수행한 후, 피연산자의 값을 증가/감소시킨다.
var x = 5, result;
// 선대입 후증가 (Postfix increment operator)
result = x++;
console.log(result, x); // 5 6
// 선증가 후대입 (Prefix increment operator)
result = ++x;
console.log(result, x); // 7 7
// 선대입 후감소 (Postfix decrement operator)
result = x--;
console.log(result, x); // 7 6
// 선감소 후대입 (Prefix decrement operator)
result = --x;
console.log(result, x); // 5 5
'+' 단항 연산자는 피연산자에 대해 어떠한 효과도 없다. 음수를 양수로 반전하지도 않는다. 그런데 숫자 타입이 아닌 피연산자에 사용하면 피연산자를 숫자 타입을 변환하여 반환한다. 이때 피연산자를 변경하는 것은 아니고 숫자 타입을 변환한 값을 생성해서 반환한다. 따라서 부수 효과는 없다.
+10 // 10
+'10' // 10
+true // 1
+false // 0
'-' 단항 연산자는 피연산자의 부호를 반전한 값을 반환한다. + 단항 연산자와 마찬가지로 숫자 타입이 아닌 피연산자에 사용하면 피연산자를 숫자 타입을 변환하여 반환한다. 이때 피연산자를 변경하는 것은 아니고 부호를 반전한 값을 생성해서 반환한다. 따라서 부수 효과는 없다.
-10 // -10
-'10' // -10
-true // -1
-false // -0
'+' 연산자는 피연산자 중 하나 이상이 문자열인 경우 문자열 연결 연산자로 동작한다. 그 외의 경우 덧셈 연산자로 동작한다.
// 문자열 연결 연산자
'1' + '2' // '12'
'1' + 2 // '12'
// 산술 연산자
1 + 2 // 3
1 + true // 2 (true → 1)
1 + false // 1 (false → 0)
true + false // 1 (true → 1 / false → 0)
1 + null // 1 (null → 0)
1 + undefined // NaN (undefined → NaN)
위에서 살펴볼 것은 개발자의 의도와는 상관없이 자바스크립트 엔진에 의해 암묵적으로 타입이 자동 변환되기도 한다는 것이다. 위 예제에서 1 + true를 연산하면 자바스크립트 엔진은 암묵적으로 불리언 타입의 값인 true를 숫자 타입인 1로 타입을 강제 변환한 후 연산을 수행한다. 이를 암묵적 타입 변환(Implicit coercion) 또는 타입 강제 변환(Type coercion)이라고 한다. 앞서 살펴면 +/- 단항 연산자도 암묵적 타입 변환이 발생한 것이다.
할당 연산자(Assignment Operator)는 우항에 있는 피연산자의 평가 결과를 좌항에 있는 변수에 할당한다. 할당 연산자는 좌항의 변수에 값을 할당하므로 부수 효과가 있다.
할당 연산자 | 사례 | 동일 표현 |
---|---|---|
= | x = y | x = y |
+= | x += y | x = x + y |
-= | x -= y | x = x - y |
*= | x *= y | x = x * y |
/= | x /= y | x = x / y |
%= | x %= y | x = x % y |
var x;
x = 10; // 10
x += 5; // 15
x -= 5; // 10
x *= 5; // 50
x /= 5; // 10
x %= 5; // 0
var str = 'My name is ';
str += 'Lee'; // My name is Lee
표현식은 "하나의 값으로 평가된다"고 하였다. 그렇다면 할당 연산은 표현식인가?
var x;
console.log(x = 10); // 10
할당 연산은 변수에 값을 할당하는 부수 효과만 있을 뿐 값으로 평가되지 않을 것처럼 보인다. 허나 할당 연산은 하나의 값으로 평가되는 표현식이다. 할당 표현식은 할당된 값으로 평가된다. 위 예제의 경우 x에 할당된 숫자 값 100으로 평가된다. 따라서 아래와 같이 할당 연산 표현식을 다른 변수에 할당할 수도 있다.
var x, y;
y = x = 10; // 연쇄 할당(Chained assignment)
console.log(x, y); // 10 10
비교 연산자(Comparison Operator)는 좌항과 우항의 피연산자를 비교하여 불리언 값을 반환한다. if 문이나 for 문과 같은 제어문의 조건식에서 주로 사용한다.
비교 연산자 | 의미 | 사례 | 설명 |
---|---|---|---|
== | 동등 비교 | x == y | x와 y의 값이 같음 |
=== | 일치 비교 | x === y | x와 y의 값과 타입이 같음 |
!= | 부등 비교 | x != y | x와 y의 값이 다름 |
!== | 불일치 비교 | x !== y | x와 y의 값과 타입이 다름 |
문자열 연결 연산자에서 언급했드시 개발자의 의도와 상관없이 자바스크립트 엔진에 의해 암묵적으로 타입이 자동 변환되기도 한다. 이를 암묵적 타입 변환이라 부른다.
동등 비교(==) 연산자는 좌항과 우항의 피연산자를 비교할 때 암묵적 타입 변환을 통해 타입을 일치시킨 후 같은 값을 갖는지 비교한다. 따라서 동등 비교 연산자는 좌항과 우항의 피연산자가 타입은 다르더라도 암묵적 타입 변환 후에 같은 값일 경우 true를 반환한다.
// 동등 비교
5 == 5 // true
// 타입은 다르지만 암묵적 타입 변환을 통해 타입을 일치시키면 같은 값을 같는다.
5 == '5' //true
5 == 8 // false
결론부터 말하자면 동등 비교 연산자는 편리한 경우도 있지만 수많은 부작용을 일으키므로 사용하지 않는 편이 좋다.
'' == '0' // false
0 == '' // true
0 == '0' // true
false == 'false' // false
false == '0' // true
false == undefined // false
false == null // false
null == undefined // true
위 예제와 같은 코드를 작성할 개발자는 드물겠으나, 이처럼 동등 비교(==) 연산자는 예측하기 어려운 결과를 만들어낸다. 위 예제는 이해하려 하지 않아도 된다. 다만 동등 비교 연산자를 사용하지 말고 일치 비교 ㅇ ㅕㄴ산자를 사용하면 된다.
일치 비교(===) 연산자는 좌항과 우항의 피연산자가 타입도 같고 값도 같은 경우에 한하여 true를 반환한다.
// 일치 비교
5 === 5 // true
5 === '5' // false
일치 비교 연산자는 예측하기 쉽다. 위에 살펴본 동등 비교 연산자의 해괴망측한 예제는 모두 false를 반환한다. 일치 비교 연산자에서 주의할 것은 NaN이다.
NaN === NaN // false
NaN은 자신과 일치하지 않는 유일한 값이다. 따라서 숫자가 NaN인지 조사하려면 빌트인 함수 isNaN을 사용한다.
isNaN(NaN) // true
숫자 0도 주의를 해야한다
0 === -0
부동등 비교 연산자(!=)와 불일치 비교 연산자(!==)는 동등 비교 (==) 연산자와 일치 비교(===) 연산자의 반대 개념이다.
// 부동등 비교
5 != 8 // true
5 != 5 // false
5 != '5' // false
// 불일치 비교
5 !== 8 // true
5 !== 5 // false
5 !== '5' // true
대소 관계 비교 연산자는 피연산자의 크기를 비교하여 불리언 값을 반환한다.
대소 관계 비교 연산자 | 예제 | 설명 |
---|---|---|
> | x > y | x가 y보다 크다. |
< | x < y | x가 y보다 작다. |
>= | x >= y | x가 y보다 같거나 크다. |
<= | x <= y | y가 x보다 같거나 크다. |
// 대소 관계 비교
5 > 0 // true
5 > 5 // false
5 > 8 // false
5 < 0 // false
5 < 5 // false
5 < 8 // true
5 >= 0 // true
5 >= 5 // true
5 >= 8 // false
5 <= 0 // false
5 <= 5 // true
5 <= 8 // true
삼항 조건 연산자(ternary operator)는 조건식의 평가 결과에 따라 반환할 값을 결정한다. 자바스크립트의 유일한 삼항 연산자이며 부수 효과는 없다. 삼항 조건 연산자 표현식은 아래와 같이 사용한다.
조건식 ? 조건식이 ture일때 반환할 값 : 조건식이 false일때 반환할 값
물음표(?) 앞의 첫번쨰 피연산자가 조건식, 즉 불리언 타입의 값으로 평가될 표현식이다. 만약 조건식의 평가 결과가 불리언 값이 아니면 불리언 값으로 암묵적 타입 변환된다. 이때 조건식이 참이면 콜론(:) 앞의 두번째 피연산자가 평가되어 반환되고, 거짓이면 콜론(:)뒤의 세번째 피연산자가 평가되어 반환된다.
var x = 2;
// x가 짝수이면 '짝수'를 홀수이면 '홀수'를 반환한다.
// 2 % 2는 0이고 0은 false로 암묵적 타입 변환된다.
var result = x % 2 ? '홀수' : '짝수';
console.log(result); // 짝수
삼항 조건 연산자는 if ... else 문을 사용해도 동일한 처리를 할 수 있다.
var x = 2, result;
// x가 짝수이면 '짝수'를 홀수이면 '홀수'를 반환한다.
// 2 % 2는 0이고 0은 false로 암묵적 타입 변환된다.
if (x % 2) result = '홀수';
else result = '짝수';
console.log(result); // 짝수
하지만 if...else 문은 표현식이 아닌 문이다. 따라서 if...else 문은 값으로 평가할 수 없다. 하지만 삼항 조건 연산자 표현식은 값으로 평가할 수 있는 표현식이다. 따라서 삼항 조건 연사자식은 다른 표현식의 일부가 될 수 있어 매우 유용하다.
논리 연산자(Logical Operator)는 우항과 좌항의 피연산자(부정 논리 연산자의 경우, 우항의 피연산자)를 논리 연산한다.
논리 부정(!) 연산자는 언제나 불리언 값을 반환한다. 하지만 논리합(||) 연산자와 논리곱(&&)연산자는 일반적으로 불리언 값을 반환하지만 반드시 불리언 값을 반환해야 하는 것은 안디ㅏ.
논리 연산자 | 의미 |
---|---|
|| | 논리합(OR) |
&& | 논리곱(AND) |
! | 부정(NOT) |
// 논리합(||) 연산자
true || true // true
true || false // true
false || true // true
false || false // false
// 논리곱(&&) 연산자
true && true // true
true && false // false
false && true // false
false && false // false
// 논리 부정(!) 연산자
!true // false
!false // true
논리 부정(!) 연산자는 언제나 불리언 값을 반환한다. 단, 피연산자는 반드시 불리언 값일 필요는 없다. 만약 피연산자가 불리언 값이 아니면 불리언 타입으로 암묵적 타입 변환이 된다.
// 암묵적 타입 변환
!0 // true
하지만 논리합(||) 연산자와 논리곱(&&) 연산자의 연산 결과는 불리언 값이 아닐 수도 있다. 이 두 연산자는 언제나 피연산자 중 어느 한쪽 값을 반환한다.
// 단축 평가
'Cat' && 'Dog' // “Dog”
쉼표(,) 연산자는 왼쪽 피연산자부터 차례대로 피연산자를 평가하고 마지막 피연산자의 평가가 끝나면 마지막 피연산자의 평가 결과를 반환한다.
var x, y, z;
x = 1, y = 2, z = 3; // 3
그룹 ((...)) 연산자는 그룹 내의 표현식을 최우선으로 평가한다. 그룹 연산자를 사용하면 연산자의 우선 순위를 1순위로 높일 수 있다.
10 * 2 + 3 // 23
10 * (2 + 3) // 50
typeof 연산자는 자신의 뒤에 위치한 피연산자의 데이터 타입을 문자열로 반환한다. typeof 연산자가 반환하는 문자열은 7개의 데이터 타입과 일치하지는 않는다. typeof 연산자는 7가지 문자열 "string", "number", "boolean", "undefined", "symbol", "object", "function" 중에서 하나를 반환한다. "null"을 반환하는 경우는 없으며 함수의 경우 "function"을 반환한다.
typeof '' // "string"
typeof 1 // "number"
typeof NaN // "number"
typeof true // "boolean"
typeof undefined // "undefined"
typeof Symbol() // "symbol"
typeof null // "object"
typeof [] // "object"
typeof {} // "object"
typeof new Date() // "object"
typeof /test/gi // "object"
typeof function () {} // "function"
주의해야 할 것은 typeof 연산자로 null 값을 연산해보면 null이 아닌 "object"를 반환한단느 것이다. 이것은 자바스크립트의 첫 번째 버전에서 이렇게 설계된 것을 현재의 버전에 반영하지 못하고 있기 때문이다.
typeof null // "object"
따라서 null 타입을 확인할 때는 typeof 연산자를 사용하지 않고 일치 연산자(===)를 사용해야한다.
var foo = null;
console.log(typeof foo === null); // false
console.log(foo === null); // true
또 하나 주의해야 할 것은 선언하지 않은 식별자를 typeof 연산자로 연산해 보면 ReferenceError가 발생하지 않고 "undefined"를 반환한다.
typeof undeclared // "undefined"