모던 자바스크립트 Deep Dive
[북스터디] 자바스트립트의 기본 개념과 동작원리 / 이웅모지음

개념을 이해한다는 것은 바로 용어를 정확히 이해하고 설명할 수 있다는 것이다.
용어의 의미를 정확히 설명할 수 없다면 개념을 제대로 이하히지 못한 경우가 많다.
4장_변수
변수는 하나의 값을 저장하기 위해 확보한 메모리 공간 자체 또는 그 메모리 공간을 식별하기 위해 붙인 이름을 말한다.
메모리
- 컴퓨터는 메모리를 사용해 데이터를 기억하고, CPU 를 사용해 연산한다.
- 메모리는 데이터를 저장할 수 있는 메모리 셀의 집합체이다.
- 메모리 셀 하나의 크기는 1바이트이다
- 각 메모리 셀은 고유의 메모리 주소를 갖는다
- 메모리에 저장되는 데이터는 2진수로 저장된다
10 + 20
10 과 20은 메모리 주소에 기억되고 CPU 는 이 값들을 읽어 연산을 수행했다. 그러나 CPU 가 만들어낸 숫자 값 30은 재사용할 수 없다. (30이 저장된 메모리 주소에 직접 접근하면 되겠지만 자바스크립트는 개발자의 직접적인 메모리 제어를 허용하지 않음)
따라서 변수를 사용해 값을 저장하고 참조한다.
var result = 10 + 20;
- 변수명 : 메모리 공간에 저장된 값을 식별할 수 있는 고유한 이름
- 변수값 : 변수에 저장된 값
- 할당 : 변수에 값을 저장
- 참조 : 변수에 저장된 값을 읽어들임
변수명은 첫아이 이름을 짓듯이 심사숙해서 지어야 함
식별자
- 식별자는 어떤 값을 구별해서 식별할 수 있는 고유한 이름을 말한다.
- 식별자는 값이 저장되어있는 메모리 주소와 매핑관계를 맺는다.
- 식별자는 값이 아니라 메모리 주소를 기억하고 있다.
- 선언에 의해 자바스크립트 엔진에 식별자의 존재를 알린다
변수선언
- 변수를 사용하려면 반드시 선언이 필요하다
- 변수선언은 값을 저장하기 위한 메모리 공간을 확보하고 변수이름과 메모리공간의 주소를 연결해서 값을 저장할 수 있게 준비하는 것이다.
var score;
변수를 선언함으로서 확보된 메모리 공간에는 자바스크립트 엔진에 의해 undefined 라는 값이 암묵적으로 할당되었다.
자바스크립트 엔진의 변수선언 단계
- 선언단계 : 변수 이름을 등록해서 자바스크립트 엔진에 변수의 존재를 알린다
- 초기화 단계 : 값을 저장하기 위한 메모리 공간을 확보하고 암묵적으로 undefined를 할당해 초기화한다.
- 초기화 : 변수가 선언된 이후 최초로 값을 할당하는 것
자바스크립트 엔진이 변수를 관리할 수 있도록 실행컨텍스트
에 등록하여 변수의 존재를 알린다. 만약 선언하지 않은 식별자에 접근하면 ReferenceError 가 발생한다.
- ReferenceError : 자바스크립트 엔진이 등록된 식별자를 찾을 수 없을 때 발생
변수 선언시점
변수 선언은 런타임이 아니라 그 이전 단계에서 먼저 실행된다.
런타임
소스코드가 한 줄씩 순차적으로 실행되는 시점
자바스크림트는 인터프리터 언어이다.
- 코드가 실행되는 단계인 런타임에 문 단위로 한 줄씩 중간코드인 바이트코드로 변환 후 실행한다.
- 인터프리터언어는 한 줄씩 바이트코드로 변환 후 즉시 실행한다
- 평가과정
- 소스코드를 실행하기 위한 준비를 한다.
- 변수선언문, 함수선언문 등 모든 선언문을 소스코드에서 찾아내 먼저 실행한다.
- 소스코드를 한줄씩 순차적으로 실행한다.
변수선언이 먼저실행되기 때문에 어디서든 변수를 참조할 수 있다.
이처럼 변수선언문이 코드의 선두로 끌어올려진것처럼 동작하는 자바스크립트 고유의 특징을 변수 호이스팅이라 한다.
console.log(score); //Reference Error 가 아닌 undefined 가 출력됨 var score;
할당
var score = 80
- 변수선언 : 런타임 이전에 실행됨
- 값의할당 : 런타임에 실행됨
자바스크립트 엔진은 변수 선언과 값의 할당을 2개의 문으로 나눠 각각 실행한다.
console.log(score); score = 80; // 할당 : 런타임에 실행됨 var score; // 선언 : 런타임 이전에 가장먼저 실행됨 console.log(score);
변수가 선언될 때 undefined 로 초기화된다. (맨 처음 값이 할당되는 것을 초기화라 한다)
score 에 값을 할당할 때 undefined 가 저장되어있던 메모리 공간을 지우고 그 공간에 80을 새롭게 저장하는것이 아니다.
할당 시 새로운 메모리 공간을 확보하고 그 곳에 할당값 80을 저장한다.
재할당
재할당은 현재 변수에 저장된 값을 버리고 새로운 값을 저장하는 것이다.
const
값을 재할당 할 수 없는 상수. 재할당 시 TypeError 가 발생한다.
재할당 시 이전 메모리 공간의 값을 변경하는 것이 아니라 새로운 메모리 공간을 확보하고 새로운 값을 저장한다.
그럼 이전의 메모리 공간에 저장된 값은 어떻게 되는걸까 ?
아무도 사용하고있지 않은 값들은 가비지 콜렉터에 의해 메모리에서 자동해제되며, 그 시점은 알 수 없다.
식별자
식별자는 어떤 값을 구별해서 식별해낼 수 있는 고유한 이름을 말한다. (= 변수명)
예약어
예약어는 프로그래밍 언어에서 사용되고 있거나 사용될 예정인 단어를 말한다.
네이밍 컨벤션(naming convention)
네이밍 컨벤션은 하나 이상의 영어 단어로 구성된 식별자를 만들 때 가독성 좋게 단어를 한눈에 구분하기 위해 규정한 명명한 규칙
- 카멜 케이스
- 스네이크 케이스
- 파스칼 케이스
- 헝가리언 케이스
var strFirstName; var $elem = document.getElementById('myId'); // DOM 노드 var observable$ = fromEvent(document, 'click); // RxJS 옵저버블
- DOM(Document Object Model) 노드 : 웹페이지는 각 요소들을 노드로 나타내고 이 노드들은 트리구조로 연결되어있다
- Document 노드 : 문서 전체를 나타내는 최상위 노드
- Element 노드 : HTML 요소를 나타낸다
- RxJS(Reactive Extensions for JavaScript)
- 함수형 프로그래밍과 리액티브 프로그래밍 개념을 기반으로 하는 JavaScript 라이브러리
- 옵저버블은 데이터 스트림을 표현하는 객체이다.
코드 전체의 가독성을 높이려면 카멜케이스와 파스칼케이스를 따르는 것이 유리하다.
5장_표현식과 문
값
값은 표현식이 평가되어 생성된 결과를 말한다.
10 + 20;
다음 예제는 10 + 20 이 평가되어 숫자 값 30을 생성한다.
var sum = 10 + 20;
변수 sum 에 할당되는 것은 10 + 20 이 아니라 표현식이 평가된 결과인 숫자 30이다.
값은 다양한 방법으로 생성할 수 있는데, 가장 기본적인 방법은 리터럴을 사용하는 것이다.
리터럴
리터럴은 사람이 이해할 수 있는 문자 또는 약속된 기호를 사용해 값을 생성하는 표기법을 말한다.
- 아라비아 숫자
- 알파벳
- 한글
3
위는 숫자 리터럴이다. 이처럼 자바스크립트 엔진은 아라비아숫자 3을 평가해 숫자 값 3을 생성한다.
자바스크립트엔진은 런타임에 리터럴들을 평가해 값을 생성한다.
표현식
표현식은 값으로 평가될 수 있는 문이다.
표현식이 평가되면 새로운 값을 생성하거나 기존 값을 참조한다.
리터럴은 그 자체로 표현식이다. 평가되어 값을 생성하고있기 때문이다.
var score = 100; var score = 50 + 50; score; // 변수 식별자를 참조하면 변수 값으로 평가된다.
- 리터럴 표현식
- 값을 직접 표현하는 표현식
10 //숫자 리터럴 'Hello' //문자열 리터럴 true // boolean 리터럴 { name: "Sian", age:23 }; // 객체 리터럴
- 식별자 표현식(선언이 이미 존재한다고 가정)
- 변수나 함수 등의 이름을 사용해 값을 참조하는 표현식
- 값 자체가 아니라 해당 값이 저장된 메모리 위치를 가리키게 된다
sum person.name arr[1]
- 연산자 표현식
- 값을 연산하여 새로운 값을 만드는 표현식
10 + 20 sum = 10 sum !== 10
- 함수/메서드 호출 표현식
- 함수 또는 객체의 메서드를 호출하는 표현식
- 함수 호출 표현식은 함수나 메서드를 실행하고, 그 결과를 반환함
function greet(name){ return "Hello, " + name + "!"; } const message = greet("John");
위 코드에서 greet("John")은 greet 함수를 호출하여 인자로 "John"을 전달하고, 반환된 결과인 "Hello, John!"을 message 변수에 저장하는 함수 호출 표현식이다.
표현식과 표현식이 평가된 값은 동치(동등한 관계)이다. 따라서 값이 위치할 수 있는 자리에는 표현식도 위치할 수 있다.
문
문은 프로그램을 구성하는 기본 단위이자 최소 실행단위이다.
문은 여러 토큰으로 구성된다. 토큰이란 문법적인 의미를 가지며, 문법적으로 더 이상 나눌 수 없는 코드의 기본 요소를 의미한다.
문은 명령문이라고도 부르며, 문이 실행되면 명령이 실행되고 무슨일인가 일어나게된다.
- 선언문 : 변수 선언문을 실행하면 변수가 선언된다
- 할당문 : 값이 할당된다
- 조건문 : 저장한 조건에 따라 실행할 코드블록이 결정되어 실행된다.
- 반복문 : 특정 코드 블록이 반복 실행된다.
세미콜론과 세미콜론 자동삽입 기능
자바스크립트엔진은 소스코드를 해석할 때 문의 끝이라고 예측되는 지점에 세미콜론을 자동으로 붙여주는 세미콜론 자동삽입기능이 암묵적으로 수행된다.
표현식인 문과 표현식이 아닌 문
- 모든 표현식은 문의 일부이다
+------------------------+ | 모든 표현식 | +-----------+------------+ | v +------------------------+ | 문 | +-----------+------------+ | +----------------v----------------+ | | +----+-------+ +-----------+---------+ | 식별자 표현식 | | 함수/메서드 호출 표현식 | +----+-------+ +-----------+---------+ | | v v +----+---------+ +----------+----------+ | 리터럴 표현식 | | 연산자 표현식 | +--------------+ +---------------------+
- 표현식인 문 : 값으로 평가될 수 있는 문
- 할당문 : 그 자체가 표현식이지만 완전한 문이기도 하다.
- 표현식이 아닌 문 : 값으로 평가될 수 없는 문
- 변수 선언문 : 값으로 평가될 수 없다
var x; // 문 x = 100 // 표현식인 문
완료값
크롬 개발자 도구에서 표현식이 아닌 문을 실행하면 언제나 undefined 를 출력한다. 이를 완료값
이라 한다.
완료값은 표현식의 평가 결과가 아니기 때문에 다른 값과 같이 변수에 할당할 수 없고 참조할 수도 없다.
6장_데이터타입
데이터 타입은 값의 종류를 말한다. 자바스크립트의 모든 값은 데이터 타입을 갖는다.
자바스크립트의 7개 데이터 타입
모두 원시타입(primitive type)과 객체 타입(reference type)으로 분류할 수 있다
구분 | 데이터 타입 | 설명 |
---|---|---|
원시 타입 | 숫자 (Number) | 숫자, 정수와 실수 구분 없이 하나의 숫자 타입만 존재 |
문자열 (String) | 텍스트 데이터를 나타내는 타입 | |
불리언 (Boolean) | 논리적 참(true) 또는 거짓(false)을 나타냄 | |
null | 값이 없다는 것을 의도적으로 명시할 때 사용하는 값 | |
undefined | var 키워드로 선언된 변수에 암묵적으로 할당되는 값 | |
심볼 (Symbol) | 고유하고 변경 불가능한 값을 나타내는 타입입니다. (ES6) 에서 추가됨 | |
객체 타입 | 객체 (Object) | 여러 속성과 메서드를 가진 복잡한 데이터를 나타내는 타입 |
배열 (Array) | 순서가 있는 값들의 집합을 나타내는 타입 | |
함수 (Function) | 동작이 정의된 함수를 나타내는 타입 | |
날짜 (Date) | 날짜와 시간 정보를 나타내는 타입 | |
정규표현식 (RegExp) | 문자열에서 패턴을 검색하거나 매칭하는 타입 |
- number 1 : 산술 연산을 위해 생성한다
- string "1" : 텍스트를 화면에 출력하기 위해 생성한다
데이터 타입이 다른 두개의 1은
- 확보해야 할 메모리 공간의 크기도 다르고
- 메모리에 저장되는 2진수도 다르며
- 읽어들여 해석하는 방식도 다르다
숫자타입
자바스크립트는 하나의 숫자 타입, 실수만 존재한다.
console.log(1 === 1.0); console.log(4/2); //2 console.log(3/2); //1.5
문자열 타입
문자열은 0개 이상의 16비트 유니코드 문자의 집합이다.
유니코드 문자
모든 언어의 문자를 하나의 통합된 표준을 표한하기 위해 개발됨
- 유니코드 이전의 인코딩
- 한글 : EUC-KR
- 일본어 : Shift-JIS
이렇게 다른 인코딩 방식으로 인해 다국어 지원이 어려웠다. 유니코드는 이런 문제를 해결하기 위해 모든 문자를 공통된 코드 포인트로 매핑한다.
UTF-16
16 bit Unicode Transformation Format
UTF-16 은 유니코드 문자를 표현하는데 사용되는 문자 인코딩 방식이다.
UTF-16은 문자를 16비트(2바이트)로 표현한다.
문자열은 작은따옴표(''), 큰 따옴표(""), 백틱(``) 으로 텍스트를 감싼다.
문자열을 따옴표로 감싸지 않으면 자바스크립트 엔진은 식별자 같은 토큰으로 인식한다.
자바스크립트의 문자열은 원시타입이며, 변경불가능한 값이다.
템플릿 리터럴
ES6에 도입된 문자열 표기법
var template = `Template literal`; console.log(template); //Template literal
문자열을 결합할 때 + 연산자보다 템플릿 리터럴을 권장하는 이유
- escape 문자 사용 감소
const message = "Hello,\n" + "World!"; const message = `Hello, World!`; console.log(message);
- 형변환 문제 최소화
- 문자열과 변수를 결합할 때 자동으로 형 변환이 발생하지 않아서 발생할 수 있는 문제를 최소화 할 수 있음
- + 연산자를 사용해 문자열을 결합하는 경우, 변수와 문자열이 함께 사용될 때 자동으로 형변환이 일어나게 된다
const num = 42; const message = "The answer is: " + num; console.log(message); // "The answer is: 42"
위 예제에서 num 은 숫자 타입이지만 '+' 연산자를 사용해 문자열과 결합하면 자동으로 문자열로 형변환이 된다.
하지만 템플릿리터럴을 사용하는 경우 형 변환이 발생하지 않고 그대로 사용할 수 있다. (변수의 원래 타입을 유지)
const num = 42; const message = `The answer is: ${num}`; console.log(message); // "The answer is: 42"
따라서 템플릿 리터럴을 사용하면 형 변환으로 인한 의도치 않은 결과나 버그를 최소화할 수 있다.
라인피드와 캐리지리턴
... CRLF 문자를 통해 다음 페이지로 개행한다. ...
개행문자에는 라인피드(LF, Line Feed)와 캐리지 리턴(CR, Carrriage Return)이 있다. 이들은 과거 타자기에서 커서를 제어하는 방식에서 비롯된 것이다.
- 라인피드(\n) : 커서를 정지한 상태에서 종이를 한줄 올리는 것
- 캐리지리턴(\r) : 종이를 움직이지 않고 커서를 맨 앞줄로 이동하는 것
따라서 CRLF(\r\n)로 커서를 맨 앞으로 이동시키고 종이를 한줄 올림
boolean 타입
undefined 타입
var 키워드로 선언한 변수는 암묵적으로 undefined 로 초기화된다.
undefined 는 개발자가 의도적으로 할당하기 위한 값이 아니라 자바스크립트 엔진이 변수를 초기화 할 때 사용하는 값이다.
개발자가 의도적으로 변수에 undefined를 할당한다면 혼란을 줄 수 있으므로 권장하지 않는다. 값이 없는 것을 명시하고 싶을 때는 null 을 사용하도록 한다.
선언과 정의
undefined : 정의되지 않은
자바스크립트의 undefined 에서 말하는 정의란 변수에 값을 할당하는 것을 말한다.
C에서 선언과 정의는 '실제로 메모리 주소를 할당하는가' 로 구분한다.
자바스크립트의 경우 변수를 선언하면 암묵적으로 정의가 이뤄지기 때문에 선언과 정의의 구분이 모호하다.
null 타입
null 은 변수에 값이 없다는 것을 의도적으로 명시할 때 사용한다.
변수에 null을 할당하면 해당 변수와 연결된 객체 또는 값은 더 이상 필요하지 않은 것으로 판단되어 가비지 콜렉터의 대상이 되어 가비지 콜렉터(Garbage Collector)에 의해 제거된다.
var foo = 'Lee' foo = null;
null이 할당된 변수 foo는 더 이상 사용되지 않는 객체 또는 값이므로, 가비지 콜렉터에 의해 메모리가 해제된다.
함수 내부에서 변수를 선언해 변수의 스코프를 좁힌다. 함수 내부에서 선언된 변수는 함수가 실행을 마치면 스코프가 종료되면서 해당 변수는 자동으로 메모리에서 해제된다 (스코프를 이용한 변수 소멸)
function defScope() { var foo = 'Lee'; // 변수 foo를 선언하고 값 'Lee'를 할당 console.log(foo); // 출력: Lee foo = null; // 변수 foo에 null을 할당하여 소멸시킴 console.log(foo); // 출력: null } defScope(); // 함수 호출 // console.log(foo); // 에러! foo 변수가 존재하지 않음
이러한 스코프 관리 방식을 통해 변수의 불필요한 메모리 점유를 줄일 수 있다.
자바스크립트는 대소문자를 구별하므로 null은 Null, NULL 과 다르다. Null이나 NULL은 일반적으로 프로그래밍 언어에서 식별자이다.
심벌타입
심벌은 변경불가능한 원시 타입의 값이다. 심벌 값은 다른 값과 중복되지 않는 유일무이한 값이다.
따라서 주로 이름이 충돌할 위험이 없는 객체의 유일한 프로퍼티 키를 만들기 위해 사용한다.
심벌 이외의 원시 값은 리터럴을 통해 생성하지만 심벌은 Symbol 함수를 호출해 생성한다. 이때 생성된 값은 다른 값과 절대 중복되지 않는 유일무이한 값이다.
심벌의 주요 사용 사례는 다음과 같습니다: 1. 객체의 고유한 식별자로 사용하기: 심벌은 항상 고유하므로, 객체의 키로 사용하면 이름 충돌을 방지할 수 있습니다. 2. 내장된 프로퍼티 오버라이딩: 내장된 객체의 메소드를 덮어쓰는데 유용하게 사용할 수 있습니다. 3. 숨겨진 프로퍼티 생성: 객체의 프로퍼티를 심벌로 정의하면 일반적인 방법으로 접근할 수 없어 은닉성을 확보할 수 있습니다.
// 1. 객체의 고유한 식별자로 사용하기 const key1 = Symbol(); const key2 = Symbol("description"); // 옵션으로 설명을 추가할 수 있습니다. const obj = { [key1]: "value1", [key2]: "value2", }; console.log(obj[key1]); // "value1" console.log(obj[key2]); // "value2" // 2. 내장된 프로퍼티 오버라이딩 const originalToString = Symbol.prototype.toString; Symbol.prototype.toString = function () { return `Custom ${originalToString.call(this)}`; }; const sym = Symbol("mySymbol"); console.log(sym.toString()); // "Custom Symbol(mySymbol)" // 3. 숨겨진 프로퍼티 생성 const person = { name: "John", age: 30, }; const ssn = Symbol("ssn"); person[ssn] = "123-45-6789"; console.log(person.ssn); // undefined console.log(person[ssn]); // "123-45-6789"
객체타입
js의 데이터타입은 크게 원시타입과 객체 타입으로 분류된다.
앞의 6가지 데이터 타입 이외의 값은 모두 객체 타입이다.
자세한 내용은 11장에서
데이터 타입의 필요성
데이터 타입은 왜 필요한걸까 ?
- 값을 저장해야 할 때 확보해야하는 메모리 공간의 크기를 결정하기 위해
- 값을 참조할 때 한번에 읽어 들여야 할 메모리 공간의 크기를 결정하기 위해
- 메모리에서 읽어들인 2진수를 어떻게 해석할지 결정하기 위해
데이터 타입에 의한 메모리 공간의 확보와 참조
값은 메모리에 공간을 확보하여 메모리에 저장된다. 메모리 공간을 확보하기 위해 얼만큼의 메모리 공간을 사용해야 낭비와 손실 없이 값을 저장할 수 있는지 알아야 한다.
자바스크립트 엔진은 데이터 타입에 따라 메모리 공간을 확보한다.
var score = 100;
변수 score 를 통해 값 100이 저장되어있는 메모리 공간의 주소를 찾아갈 수 있다. (값이 저장되어있는 메모리 공간의 선두 메모리셀
의 주소를 찾아갈 수 있음)
이때 값을 참조하려면 한번에 읽어 들여야 할 메모리 공간의 크기(메모리 셀의 개수=바이트 수)를 알아야 한다.
컴퓨터는 얼만큼의 메모리 셀을 읽어들여야하는지 어떻게 알 수 있을까 ?
score 에는 숫자 타입의 값이 할당되어있어 js 엔진은 score 변수를 숫자 타입으로 인식한다.
숫자 타입은 8바이트 단위로 저장되므로 8바이트 단위로 메모리 공간에 저장된 값을 읽어들인다.
데이터 타입에 의한 값의 해석
메모리에서 읽어들인 2진수를 어떻게 해석하느냐 !
모든 값은 메모리에 2진수의 나열로 저장된다. 메모리에 저장된 값은 데이터 타입에 따라 다르게 해석될 수 있다.
동적 타이핑
동적 타입 언어와 정적 타입언어
js는 정적 타입 언어와 다르게 변수를 선언할 때 타입을 선언하지 않는다.
js의 변수에는 어떤 데이터 타입의 값이라도 자유롭게 할당할 수 있어 정적타입언어에서 하는 데이터 타입과 개념이 다르다.
정적 타입언어는 변수에 선언한 타입에 맞는 값만 할당할 수 있다.
char c; int num;
js에서는 값을 할당하는 시점에 타입이 동적으로 결정된다.
- 자바스크립트의 변수는 선언이 아닌 할당에 의해 타입이 결정된다(타입추론).
- 재할당에 의해 변수의 타입은 동적으로 변할 수 있다.
대표적은 동적언어는 자바스크립트, 파이썬, PHP 등이 있다.
동적 타입 언어와 변수
동적 타입 언어의 특정인 데이터 타입에 편리하지만 구조적인 단점이 있다.
- 변수 값을 추적하기 어려울 수 있다
- 값을 확인하기 전에 타입을 확인할 수 없다
- 자바스크립트 엔진에 의해 암묵적으로 타입이 변환될 수 있다
따라서 동적타입언어는 유연성은 높지만 신뢰성이 떨어진다.
변수 사용 시 주의사항
- 변수는 꼭 필요한 경우에 한해 제한적으로 사용
- 스코프는 최대한 좁게 만든다
- 전역변수는 최대한 사용하지 않는다
- 변수보다 상수를 사용한다
- 변수명을 잘 짓자 !