JavaScript 주요 문법(1)

김영준·2023년 6월 5일
0

TIL

목록 보기
1/91
post-thumbnail

변수와 상수

변수: 언제든 값을 바꿀 수 있는 공간, var, let 키워드를 사용한다.

상수: 변하지 않는 수, const 키워드를 사용한다.

자료형

  • Number: 숫자와 관련된 타입, 정수, 실수, NaN(숫자가 아님), Infinity(무한을 나타냄)
  • String: 문자열 타입, '' 혹은 "" 혹은 백틱을 통해 표현 가능 따옴표 안에 따옴표를 넣고 싶으면 백슬래시를 사용
  • Boolean: true, false 타입
  • Object: 여러값을 가질 수 있는 타입, key를 통해 값을 조회, key는 무조건 문자열이다.
  • Array: 여러값을 가질 수 있는 타입, index를 통해 값을 조회
  • Function: 함수를 의미, 함수도 변수에 할당 가능
  • Undefined: 변수, 상수가 선언되었지만 아무 값도 대입이 되지 않은 것
  • Null: null이라는 값을 사용자가 의도적으로 나타냄

메모리

메모리의 생존 주기는 다음과 같다.
1. 필요할 때 할당
2. 할당된 메모리를 사용 (읽기, 쓰기)
3. 더 이상 필요하지 않으면 해제

메모리 동작 흐름

let a = 1; // 변수의 고유 식별자를 생성하고 메모리에 주소를 할당하고 그 메모리 공간에 값을 넣음

let a = 1;
a = a + 1; //새로운 메모리에 할당

위 상황에서는 새로운 메모리 주소를 할당받고 해당 메모리 공간에 값을 새로 넣는다.

그 이유는 자바스크립트에서 원시 타입은 변경이 불가능하기 때문이다. 따라서 원시 타입을 변경할 때는 항상 메모리가 새로 할당된다.

원시 타입과 참조 타입의 차이점

자바 스크립트의 변수 타입에는 크게 Primitive type(원시 타입)과 Reference Type(참조 타입)으로 나누어져 있다.

  • 원시 타입(Primitive type)의 변수들은 데이터 복사가 일어날 때 메모리 공간을 새로 확보하여 독립적인 값을 저장한다. (string, number, bigint, boolean, undefined, symbol)

  • 참조 타입(Reference Type)은 메모리에 직접 접근이 아닌 메모리의 위치(주소)에 대한 간접적인 참조를 통해 메모리에 접근하는 데이터 타입이다. (배열, 객체, 함수)

Heap과 Call Stack의 차이점

자바스크립트 엔진은 Heap과 Call Stack으로 구성되어 있다.

  • Heap: 참조 타입이 들어감 (배열은 Object 타입으로 참조 타입에 들어간다.), 동적으로 크기가 변할 수 있다.
  • Call Stack: 원시 타입이 들어감, (값이 변하면 메모리도 변함)

그럼 반대로 참조 타입에서 값을 새로 할당하면 어떻게 되나?

원시 타입과 가장 큰 차이점은 변수의 크기가 동적으로 변한다는 것이다. 이러한 특징 때문에 Object의 데이터 자체는 별도의 메모리 공간(heap)에 저장되며, 변수에 할당 시 데이터에 대한 주소가 저장되기 때문에 자바스크립트 엔진이 변수가 가지고 있는 메모리 주소를 이용해서 변수의 값에 접근하게 되는 것이다.

배열이 상수로 선언되어도 push가 가능한 이유는 Heap에 있는 메모리를 변경하기 때문이다.

메모리 관리는 어떻게 하지?

메모리는 한정되어 있다. 계속 변수, 상수를 만들 때마다 메모리 공간은 줄어든다. 메모리가 꽉 차면 프로그램이 터지기 때문에 별도의 조치가 필요하다.

그럼 개발자가 직접 메모리를 관리하면서 사용해야 할까? 그렇지 않다.

자바스크립트 엔진에서는 Garbage Collector가 메모리를 관리해 준다. Garbage Collector는 Garbage Collection이라는 자동 메모리 관리 알고리즘을 통해 만들어진 객체로 사용하지 않는 메모리를 해제하는 역할을 한다.

그래서 개발자는 메모리에 대해 크게 신경 쓰지 않고 코딩이 가능하다.

표현식과 연산자

표현식이란?

어떠한 결과 값으로 평가되는 식


연산자의 종류
  • 할당 연산자: 오른쪽 표현식을 왼쪽 피연산자 값에 할당하는 연산자

  • 비교 연산자: 좌우 측 피연산자를 비교하는 연산자로 true 혹은 false를 반환

  • 산술 연산자: 덧셈, 뺄셈, 곱셈, 나눗셈을 하는 연산자로 Number를 반환

  • 비트 연산자: 비트를 직접 조작하는 연산자

  • 논리 연산자: Boolean을 통해 참과 거짓을 검증하는 연산자 (조건문과 반복문에서 자주 쓰임)

  • 삼항 연산자: 조건에 따라 값을 선택하는 연산자 (편의에 따라 조건문 대신 쓰임) 조건 ? 참 : 거짓

  • 관계 연산자: 객체에 속성이 있는지 확인하기 위한 연산자 ex) "name" in x; // x에 name이라는 속성이 있는지 확인

  • typeof: 피연산자의 타입을 반환하는 연산자로 문자열로 반환됨 ex) typeof x;

흐름 제어

흐름 제어는 Control Flow, Data Flow 2가지 방식으로 가능

  1. Control Flow: 조건이나 반복을 통해 상태를 제어
  2. Data Flow: 함수형 프로그램 방식으로 구현

조건문

조건이 맞을 때만 실행되는 문법이다.
false뿐만 아니라 undefined, null, 0, NaN, '' 모두 거짓으로 인식한다.

if문
괄호 안에 조건이 참이면 해당 블록을 실행

if (a > b) {
  console.log("a가 더 크다.");
} else {
  console.log("a는 b보다 작거나 같다.");
}

switch문
괄호 안 값에 따라 분기되는 문법으로 case, default와 함께 쓰인다. break를 꼭 적어야 한다. 아니면 다음 case도 실행됨

switch (){
    case "A":
        condole.log("A grade");
        break;
    case "B":
        condole.log("B grade");
        break;
    case "C":
        condole.log("C grade");
        break;
    case "D":
        condole.log("D grade");
        break;
    case "F":
        condole.log("F grade");
        break;
}

반복문

반복적인 작업을 지시하는 문법이다.

for문
가장 기초적인 반복문으로 초기문, 조건문, 증감문으로 이루어져 있다. 조건문의 결과가 거짓이 되면 반복이 종료된다.

for (let i = 0; i < 10; i += 1) {
  console.log(i);
}

while문
괄호 안 조건이 거짓이 될 때까지 반복된다.

let x = 0;
while (x < 10) {
  x += 1;
  console.log(x);
}

do-while문
while문과 다르게 먼저 진입 후 로직을 실행한 다음 조건을 검사한다.

let x = 0;

do {
  console.log("Fire");
} while (x > 10);

배열과 객체

배열

// 선언 방법
const arr1 = new Array(); // []
const arr2 = []; // []
const arr3 = [1, 2, 3, 4, 5]; // [1, 2, 3, 4, 5] 선언과 동시에 초기화
const arr4 = new Array(5); // [ <5 empty items> ] 길이가 5인 빈 배열 생성
const arr5 = new Array(5).fill(0); // 길이가 5인 배열의 원소를 모두 0으로 초기화
const arr6 = Array.from(Array(5), (value, index) => {
  return index + 1;
}); // [1, 2, 3, 4, 5]
const arr = [1, 2, 3, 4, 5];
arr.length(); // 5 배열의 길이
// 편의 함수
const arr1 = [1, 2, 3, 4, 5];
const arr2 = [6, 7, 8, 9, 10];
arr1.join(", "); // 구분자를 통해 문자열로 변경 "1, 2, 3, 4, 5"
arr1.reverse(); // 원소를 거꾸로 배열, 원본 배열이 변함 [5, 4, 3, 2, 1];
arr1.concat(arr2); // 배열을 합침 [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
// 배열 추가 삭제
const arr = [1, 2, 3, 4, 5, 6];
arr.push(7); // 마지막 자리에 7을 추가 [1, 2, 3, 4, 5, 6, 7]
arr.pop(); // 마지막 원소를 삭제 [1, 2, 3, 4, 5, 6]

arr.unshift(0); // 첫번째 자리에 0을 추가 [0, 1, 2, 3, 4, 5, 6]
arr.shift(); // 첫번째 원소를 삭제 [1, 2, 3, 4, 5, 6]

arr.slice(2, 4); // 3, 4 삭제, 시작 인덱스와 끝 인덱스 - 1, 원본 배열이 변하지 않음
arr.splice(2, 2); // 3, 4 삭제, 시작 인덱스와 삭제 할 개수, 원본 배열이 변함
// 배열 순회

// for
for (let i = 0; i < 5; i += 1>){
    console.log(arr[i]);
}

// for of
for (const item of arr){
    console.log(item);
}

객체

// 객체 선언
const obj1 = new Object(); // {}
const obj2 = {}; // {}
const obj3 = { id: 1, name: "홍길동" }; // { id: 1, name: "홍길동" }
// 객체 추가
const obj = { id: 1, name: "홍길동" };
obj["email"] = "abc@naver.com"; // { id: 1, name: "홍길동", email: "abc@naver.com" }
obj.phone = 01012345678; // { id: 1, name: "홍길동", email: "abc@naver.com", phone: 01012345678 }

// 객체 삭제
delete obj.phone; // { id: 1, name: "홍길동", email: "abc@naver.com" }

// key 유무 확인
console.log("email" in obj); // true
console.log("phone" in obj); // false

// key 출력
console.log(Object.keys(obj)); // ["id", "name", "email"]

// value 출력
console.log(Object.values(obj)); // [1, "홍길동", "abc@naver.com"]

// for in: 키를 이용해서 객체를 순회
for (const key in obj) {
  console.log(key, obj[key]); // 각각 key와 value를 출력
}

스코프와 클로저

스코프

유효 범위라고도 부르며 변수가 어느 범위까지 참조되는지를 뜻한다.

var: 함수 범위 스코프
let, const: 블록 범위 스코프

var를 사용하면 호이스팅 되어 개발자가 예상치 못한 오류가 생길 수 있다.

클로저

함수가 선언된 환경의 스코프를 기억하여 함수가 스코프 밖에서 실행될 때에도 기억한 스코프에 접근할 수 있게 만드는 문법

은닉화: 클로저를 이용하여 내부 변수와 함수를 숨길 수 있다. 반환된 함수들로만 값을 조작할 수 있기 때문에 이로 인해 개발자의 실수를 줄일 수 있다.

function counting() {
  let i = 0;
  for (i = 0; i < 5; i++) {
    setTimeout(function () {
      console.log(i);
    }, i * 100);
  }
}

counting();

위 상황에서 함수 호출 시 5가 5번 출력된다. 그 이유는 setTimeout의 대기 시간이 끝나서 callback 함수가 실행된 시점에는 루프가 종료되어 i가 5가 되었기 때문이다.

해결 방법

// 즉시 실행 함수를 이용하여 루프마다 클로저를 만들어서 해결
function counting() {
  let i = 0;
  for (i = 0; i < 5; i++) {
    (function (number) {
      setTimeout(function () {
        console.log(number);
      }, number * 100);
    })(i);
  }
}

counting();
// let을 사용해서 해결, let은 블록 수준 스코프기 때문에 매 루프마다 클로저가 생성된다.
function counting() {
  let i = 0;
  for (let i = 0; i < 5; i++) {
    setTimeout(function () {
      console.log(i);
    }, i * 100);
  }
}

counting();
profile
꾸준히 성장하는 개발자 블로그

0개의 댓글