1. JavaScript란?
자바스크립트는 동적인 언어로, 인터프린터로 런타임시 코드를 한 줄씩 번역해서 실행을 한다. 그래서 동적타입(Dynamin Type)이라고 불린다.
여기서 정적타입은 대표적인 예로 C+, JAVA 등이 있으며, 이는 컴파일러가 필요하다.
컴파일러란 코드 작성 후 코드의 모든 데이터 타입을 정적으로 정한다.
그래서 컴파일러를 가지고 있는 프로그래밍 언어는 정적 타입(Static type)이다.
Java Script에서 데이터 타입이 있는데 크게 두가지로 나뉜다.
원시 타입(primitive)과 객체 타입(object)으로 분류가 된다.
1-1 원시타입(primitive)
단일 데이터로, 선언을 하게 되면 Data(global)과 Stack(local)에 저장이 된다.
이는 식별자(변수)를 통해 접근할 수 있으며, 원시데이터와 함께 Data, Stack에 저장이 된다.
이 값은 변수에 직접 저장이 되며, 변수간에 값이 복사 될 때는 해당 값의 복사본이 새로운 변수에 할당이 된다.
let a = 1;
let b = a;
b = 2;
위와 같은 코드로 원시 타입은 Copy by Value라고 값 자체가 복사가 되어진다.
b에 2라는 값을 할당하게 된다면, 메모리 주소에 2가 저장이 되어진다. 하지만 a와는 별개로 저장이 된다.
즉 같은 메모리 주소가 아닌 서로 다른 독립적인 메모리주소에 저장이 되며, a의 값도 저장이 되어 있고, b의 값도 저장이 되어있다.
그래서 console.log(a)를 하였을 때 1이라는 값이 반환된다.
원시타입에는 크게 6가지로 분류가 되는데,
number, string, boolean, null, undefined, symbol 가 있다.
1. Number
let interger = 123; // 정수
let negative = =123; // 음수
let double = 1.23; // 실수
let binary = 0b1111011; // 2진수
let octal = 0o173; // 8진수
let hex = 0x76; // 16진수
console.log(0 / 123); // 0
console.log(123 / 0); // Infinity
console.log(123 / -0); // -Infinity
console.log(123 / 'text'); // NaN (Not a Number)
let bigInt = 12345678901234567890123456789012345678901234567890n; // 정말 큰 숫자를 표현할 때 마지막에 n을 붙혀서 bigInt라고 표시해줘야함
console.log(bigInt);
위와 같이 number는 이렇게 표시한다.
2. String
// 문자열타입
let string = '안녕하세요';
string = `안녕!`;
console.log(string); // `안녕`
// 특수 문자 출력하는 법
string = '"안녕!"';
console.log(string); // "안녕"
string = '안녕!\n자바야!\t\t내 이름은;
console.log(string);
// 템플릿 리터럴 (Template Literal)
let id = '도영';
let greetings = '"안녕!, ' + id + '😀\n즐거운 하루보내요!"';
greetings = `안녕, ${id}😀
즐거운 하루 보내요!`
console.log(greetings);
위와 같이 된다. \n은 줄바꿈, \t는 탭이다.
그리고 만약 문자를 나타낼때 '', ""를 그대로 표현하고 싶다면 위와 같이
string = '"안녕"' ; 식으로 또 감싸주면 된다.
템플릿 리터럴는 긴 문장과 문자열 내에 변수 및 표현식 사용을 하려고 할 때 이용한다.
문자열 내에게 변수 및 표현식 사용하려면
`$(expression)` 이렇게 하면 된다.
let id = 'User123';
let greetings = `안녕, ${id}😀
즐거운 하루 보내요!`;
console.log(greetings);
백틱(``)을 이용하여 사용하면 된다. 그러면 \t, \n(이스케이프 시퀀시) 등을 사용 안해도 편하게 줄바꿈 및 탭을 할 수 있다.
3. Boolean
// 불리언 타입
let 참 = true;
let 거짓 = false;
console.log(참);
console.log(거짓);
// 활용예시;
let isFree = true;
let isActivated = false;
let isEntrolled = true;
console.log(isActivated);
// Falshy 거짓인 값
console.log(!!0);
console.log(!!-0);
console.log(!!'');
console.log(!!null);
console.log(!!undefined);
console.log(!!NaN);
// !! -> 이 연산자는 false, true 인 값 Boolean으로 변환
// Truthy 참인 값
console.log(!!1);
console.log(!!-1);
console.log(!!'text');
console.log(!!{});
console.log(!!Infinity);
4. Null, Undefined
// null, undefined
let variable;
console.log(variable);
variable = null;
console.log(variable);
let activeItem; // 아직 활성화된 아이템이 있는지 없는지 모르는 상태!
activeItem = null; // 활성화된 아이템이 없는 상태!
console.log(typeof null); // object -> JS에서 object안에 메모리상에 null이라는 걸로 정의해줌, 비어있는 상태
console.log(typeof undefined); // undefined -> 아무것도 정의가 되지 않은 상태
null 과 undefined는 비슷해보이지만, 서로 다른 의미를 가지고 있다.
null은
의도적으로 '값이 없음'을 나타낼때 사용한다. 개발자가 변수에 '아무것도 없음' 또는 '비어있음'을 명시적으로 표현하고 할 때 null을 할당한다. null은 객체가 없는 것을 의도적으로 표현하는데 주로 사용되며, 자료형 검사를 할때는 object라고 표시가 된다. 이는 아래에 예시와 함께 설명하겠다.
undefined는
변수가 선언이 되었으나, 아직 값이 할당이 되지 않은 상태를 나타낸다. JS 엔진이 변수를 초기화할 때 기본적으로 사용하는 값이다. 함수에서 아무 값도 반환하지 않았을 때 undefined 값을 가진다. undefined는 시스템이 변수에 자동으로 할당하는 값으로, 개발자가 의도적으로 undefined를 변수에 할당할 수 있지만, 일반적으로 null을 사용하여 '값이 없음'을 표현한다.
let emptyValue = null; // 개발자가 명시적으로 값이 없음을 표시
let uninitializedValue; // 아무 값도 할당되지 않음, 기본적으로 undefined가 할당됨
console.log(emptyValue); // null
console.log(uninitializedValue); // undefined
여기서 자료형 검사 라고 하면 typeof 연산자를 사용했을 때 타입을 확인 하는 것인데 아래와 같이 null을 할당했을 때, 타입을 보면 object라는 값으로 나온다. undefined의 경우는 undefined로 나온다는 사실을 알 수 있다.
let emptyValue = null;
console.log(typeof emptyValue); // object
let uninitializedValue;
console.log(typeof uninitializedValue); // "undefined"
5. Symbol
Symbol은 고유하고 수정불가능한(immutable) 데이터 타입으로, 주로 객체 속성의 키로 사용된다. 각 Symbol 값은 다른 모든 Symbol 값과 다르게 취급이 된다. 이는 속성 이름 충돌의 위험 없이 객체에 고유한 속성을 추가할 수 있다.
Symbol을 생성하기 위해서는 Symbol() 함수를 사용한다. 이 함수는 선택적으로 문자열을 인자로 받을 수 있는데, 이 문자열은 디버깅 목적으로만 사용한다. 두 Symbol이 같은 문자열을 인자로 받았다고 해도, 이들은 서로 다른 고유한 Symbol로 생성이 된다.
let symbol1 = Symbol('description');
let symbol2 = Symbol('description');
console.log(symbol1 === symbol2); // false
Symbol은 객체 속성의 키로 주로 사용한다. Symbol로 생성된 프로퍼티 키는 for..in 루프나 object.keys() 메서드 등으로 접근할 수 없기 때문에, 객체 속성 중 일부를 숨기고 싶을때 유용하다. 하지만 object.getOwnPropertySymbols() 메서드를 통해서는 해당 Symbol 속성들을 찾을 수 있다.
let mySymbol = Symbol();
let obj = {
[mySymbol]: "value"
};
console.log(obj[mySymbol]); // "value"
Symbol 또한 글로벌 심볼 레지스트리에 저장되어, 애플리케이션 전체에서 공유할 수 있다. Symbol.for() 메서드를 사용하여 생성하거나 접근할 수 있다. 이 메서드는 주어진 문자열에 대응하는 심볼을 검색하고, 존재하지 않으면 새로운 심볼을 생성한다. 이렇게 하면 여러 코드 부분에서 동일한 심볼을 공유할 수 있다.
Symbol은 고유한 토큰을 생성할 필요가 없을때, 예를 들어 객체의 프라이빗한 속성을 만들고 싶을 때 유용하게 사용된다.
여기서 좀 더 자세한 설명을 하자면 mySymbol이라는 변수에 Symbol() 함수를 할당한다. 그리고 obj라는 객체 리터럴을 생성한다. 그리고 mySymbol 변수를 키로 사용하기 위해 []를 사용하여 value라는 문자열을 할당해준다. 그래서 obj[mySymbol]를 통해 접근하면, mySymbol 심볼로 설정된 속성의 값, 즉 'value'를 얻을 수 있다.
여기서 []는 배열을 선언할 때 사용하지만,
계산된 속성명(Computed Property Names) : 객체 리터널 내에서 대괄호를 사용하여 계산된 속성명을 정의할 수 있다. 이는 주로 동적으로 키 이름을 결정할 때 사용된다.
let keyName = 'name';
let obj = {
[keyName]: 'John Doe'
};
console.log(obj.name); // 출력: John Doe
6. typeof
typeof 는 데이터 타입을 확인 한다.
JAVA의 경우는 Int variable = 0; 식으로 숫자로 할당, 컴파일러 할 때 Int라는 타입으로 정적으로 정해진다. 한번 타입이 할당되면 다른 타입을 할당할 수 없다. 그래서 코드 작성 시 다른 타입을 지정해주려고 하면 오류가 난다.
그래서 JAVA는 strong type이라고 한다.
JavaScript의 경우는 dynamic, weakly typed programming language 라고 한다.
아래와 같이 할당된 값에 따라 타입이 결정된다.
let variable;
console.log(typeof variable); // undefined
variable = '';
console.log(typeof variable); // string
variable = 123;
console.log(typeof variable); // number
variable = {};
console.log(typeof variable); // object
variable = function () { };
console.log(typeof variable); // function
variable = Symbol();
console.log(typeof variable); // symbol
console.log(typeof 123); // number
console.log(typeof '123'); // string
1-2 객체타입(object)
컴퓨터 메모리는 크게 스택(Stack)과 힙(Heap) 영역으로 나눌 수 있는데, 위에 설명처럼 원시타입의 데이터는 Stack 메모리에 저장이 된다.
객체 타입의 데이터는 Heap에 저장이 되는데, 객체, 배열과 같은 복합 데이터 타입들이 여기에 해당이 되다. 이 경우에는 변수에 저장되는 것은 데이터의 실제 값이 아닌, 해당 데이터가 저장된 힙 메모리의 주소(참조)이다.
변수는 힙 메모리에 저장된 객체의 시작 주소를 가르키게 된다. 이 주소를 통해 해당 객체에 접근하고, 객체 내부의 값을 접근하거나 변경할 수 있다.
만약 객체 데이터에 다른 변수를 할당하게 된다면, 메모리 주소(참조)가 복사 되어 두 변수는 같은 객체를 가리키게 된다.
let apple = {
id:1234
key:'secret-key'
}
위와 같은 코드로 객체 타입이 있다. 객체 접근 하려면 아래와 같이 입력을 하면 된다.
console.log(apple);
console.log(apple.id);
console.log(apple.key);
let apple = {
name = 'apple',
}
let orange = apple;
위와 같이 메모리 주소에 객체가 저장되어 있는데 apple이라는 변수로 이 메모리 주소를 가리키고 있다.
여기에 orange 라는 변수에 apple 이라는 변수 안에 할당되어져있는 메모리주소가 복사 되어진다.
이는 Copy by reference라고 한다.
쉽게 말하자면 객체 자체를 복사하는 것이 아닌, 객체를 가리키고 있는 힙에 저장된 메모리 주소(참조)가 재할당이 된다.
즉, 객체는 메모리 셀 안에 메모리주소(레퍼런스)가 들어있기 때문에 레퍼런스가 복사가 되어 재할당이 된다.
이해하기 쉽게 설명하자면 호텔 방이 있다고 가정하에, a라는 방에는 탁자와 의자가 있다. 근데 b라는 방에 재할당을 하고 싶을때 탁자와 의자를 놔두는 것이 아닌 a라는 방 자체를 복사해서 b에 놓는다고 보면 된다. a는 메모리 주소(참조)라고 생각하면 되고, 탁자와 의자는 객체 데이터라고 보면 될 거 같다.
2. JavaScript 변수
여기서 변수 이름에 대해 고민이 많을 것이다. 여기서 사용 할 수 있는 몇가지 규칙이 있다.
- 라틴 문자(0-9, a-z, A-Z), _(언더바)
- 대소문자 구분하기
- camelCase(likeThis)
- 한국어 ❌ 영어로만 사용하기
- 예약어 ❌(let if, let for; 등등)
- 숫자로 시작 ❌
- 이모지 사용 ❌
- 여러개의 변수를 1, 2, 3으로 숫자로 구분하는 것 ❌
- 예를 들면 let music1, let music2, let music3 이러한 방식은 비추, 최대한 의미있고 구체적인 이름으로 작성하기
나쁜 예시
let num = 10;
let music1;
let music2;
등등
좋은 예시
let backgroudMusic;
let clickMusic;
등등
이게 아니면
let musicBackground; 이러한 식으로 지어도 됨
3. let 과 const
변수 선언에 있어 크게 3가지로 할 수 있다.
var, let, const가 있는데 var의 경우는 최대한 사용 안하는 걸로 해서 설명 추가하겠다.
3-1. let
let 의 경우는 블록 레벨 스코프로 재선언은 불가능하지만, 재할당이 가능
3-2. const
const 의 경우, 상수 또는 상수변수 / 변수라고 하며
블록 레벨 스코프, 재선언 불가능, 재할당 불가능. 상수나 변경되지 않는 값을 선언할 때 사용한다.
const를 사용하게 되면 변수에는 재할당이 불가능하다. 즉 메모리 셀에 다른 메모리 주소를 담을 수 없다
하지만, 메모리 주소가 담고 있는 객체(object)는 변경이 가능하다.
const person = { name: 'John' };
person.name = 'Doe'; // 가능
person = { name: 'Jane' }; // TypeError: Assignment to constant variable.
3-3. var와 let의 차이점
var와 let은 둘 다 변수를 선언하기 위해 사용된다. 하지만 몇가지 차이점이 있다.
- 스코프(Scope) : var는 함수 스코프(function scope)를 가지는 반면, let은 블록 스코프(block scope)를 가지고 있다. 블록 스코프는 변수를 선언한 블록(중괄호 '{}' 내부)으로 변수의 접근을 제한한다.
if (true) {
var varVariable = "Visible";
let letVariable = "Not Visible";
}
console.log(varVariable); // "Visible"
console.log(letVariable); // ReferenceError: letVariable is not defined
- 호이스팅(Hoisting) : JS에서 변수 선언(또는 함수 선언)을 코드의 최상단으로 끌어올리는 것처럼 동작하는 특성을 말한다. 이는 코드 실행 전에 변수와 함수 선언이 메모리에 저장되기 때문에 발생한다.
- var 로 선언된 변수의 호이스팅 : var 키워드로 선언된 변수는 호이스팅 될 때 undefined로 초기화된다. 이는 변수가 선언된 위치와 관계없이 함수의 최상단 또는 스크립트의 최상단으로 끌어올려진다는 것을 의미한다. 그러나 변수에 할당된 실제 값은 호이스팅되지 않으며, 변순 선언 이후의 코드에서만 사용이 가능하다.
console.log(varVariable); // undefined
var varVariable = "Visible";
- let과 const의 호이스팅 : let과 const로 선언된 변수도 호이스팅 되지만, TDZ에 의해 선언 전에 접근 할 수 없다. 이는 변수가 선언된 블록의 시작부터 실제 변순 선언문까지 접근할 수 없는 구간을 만든다. 변수에 접근하려고 시도하면 참조 오류(ReferenceError)가 발생된다.
console.log(letVariable); // ReferenceError: Cannot access 'letVariable' before initialization
let letVariable = "Not Visible";
- 임시 사각지대(Temporal Dead Zone, TDZ) : TDZ는 let과 const로 선언된 변수가 호이스팅되어도, 실제 선언문 이전에는 변수에 접근할 수 없는 구간을 말한다. 이 구간에서 변수에 접근하려고 하면 참조 오류가 발생한다. TDZ의 목적은 변수 사용의 안정성을 높이고, 선언 전에 변수를 사용하는 실수를 방지하는데 있다. TDZ는 변수 선언문이 실행되어 변수가 초기화되는 순간 종료된다. let과 const는 선언과 동시에 초기화 되므로, 선언문 이후부터 변수를 안전하게 사용할 수 있다.
3-4. 왜 var를 사용하지 않는 것이 좋은가?
- 블록 스코프 부재 : var는 블록 스코프를 지원하지 않아 예상치 못한 범위에서 변수가 접근이 가능할 수 있다. 이는 코드의 복잡성을 증가시키며, 버그를 유발할 수 있다.
- 호이스팅 문제 : var 선언된 변수는 호이스팅이 되어 스코프의 최상단에서 선언된 것처럼 행동한다. 이로 인해 변수의 선언 위치와 상관없이 코드의 어디에서든 변수에 접근할 수 있다. 이는 가독성과 유지보수성을 저해한다.
- 중복 선언 허용 : var를 사용하면 같은 스코프 내에서 변수를 중복 선언할 수 있다. 이는 실수로 변수 값을 덮어쓰거나 오류가 발생할 수 있다.
'JavaScript' 카테고리의 다른 글
JavaScript 제어문 (1) | 2024.03.25 |
---|---|
JavaScript 연산자 (1) | 2024.03.19 |
원시 값과 참조 값 (0) | 2023.11.03 |
API & fetch (0) | 2023.07.04 |
async & await (0) | 2023.07.04 |