[TIL] 22.10.24

nana·2022년 10월 24일
0

TIL

목록 보기
14/50
post-thumbnail

오늘 배운 것 - Javascript


1. DOM (Document Object Model)

2. onkeydown

3. window.event

4. addEventListener

5. Scope (스코프)

  • 전역 스코프
  • 지역 스코프

6. var 변수의 스코프

  • 함수 레벨 스코프
  • 블록 레벨 스코프

실습) To-do 리스트 만들기




DOM (Document Object Model)

브라우저가 HTML 문서를 파싱하는 과정에서 document라는 객체가 생성된다.

Dom 객체는 Tree구조 형태를 가진다.

빨간색 : Element 노드
파란색 : Text 노드
보라색 : 속성 노드

모든 노드들은 객체의 형태를 가진다.
각자의 속성을 가지고 있기 때문에, 객체 메서드를 사용하여 값들을 가져올 수 있다.

onkeydown

input 태그의 onkeydown 속성은 키보드 버튼이 눌렸을 때 동작한다.

window.event

이벤트(버튼 클릭/누르기 등)를 확인할 수 있다.

addEventListener()

직접 생성한 이벤트를 추가해준다.

addEventListener('추가할 속성이름', 추가할 속성(익명함수 내부에 작성))

Scope (스코프)

변수 참조의 유효 범위를 말한다.

  • 전역 스코프 (global scope)
  • 지역 스코프 (local scope)

let x = 0;				// 전역 스코프
let y = 1;

const scopeTest = function () {		// 지역 스코프
	let z = 2;
    console.log(x);
    console.log(y);
}

console.log(x);
console.log(y);
console.log(z);		// 오류

예시에서 x, y는 전역 스코프에 선언된 변수이다. 전역 스코프에 선언된 변수들은 지역 스코프 내부에서 참조할 수 있다.

함수를 선언하면 함수 마다 지역 스코프가 생성된다. scopeTest 함수 안에 선언된 z는 지역 스코프에 선언된 변수이다. 때문에 전역 스코프에서 console.log(z)로 확인 시 오류가 발생한다.


var 변수의 스코프

var를 사용한 변수는 함수 레벨 스코프는 따르지만, 블록 레벨 스코프는 따르지 않기 때문에 값이 참조되어버린다.
따라서 var 사용은 지양하는 것이 좋다.

  • 함수 레벨 스코프
const sum = function () {
	var x = 0;
}

console.log(x)	// 오류
  • 블록 레벨 스코프 : 중괄호를 사용하여 생성되는 기능들 (if, for 등)
if () {
	var y = 0;    
}

console.log(y)	// 0. 정상적으로 값이 참조된다.

예시로 반복문일 경우 var = i 변수를 선언한 후, 반복문에서 0으로 재선언 해버리면 블록 레벨 스코프를 따르지 않기 때문에 0으로 참조된다.

var i = 100;

for (var i = 0; i <10; i++) {
	console.log(i)	// 0 1 2 3 ...
}

console.log(i)	// 10 (전역 변수 i가 아닌 반복문의 지역 변수 i를 참조한다.) 

반면에 let,const는 블록 레벨 스코프를 따른다.

let i = 100;

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

console.log(i)	// 100

호이스팅 (hoisting)


1) 변수의 호이스팅

console.log(varKeyword)		// undefined
var varKeyword = 'var is not safe'

var는 변수 선언하기 전에 사용하면 undefined 결과가 나타난다.

console.log(varKeyword)		// 오류 발생
let varKeyword = 'let is safe'

반면에, let은 변수를 선언하기 전에 사용하면 오류가 발생한다.

호이스팅이 발생하기 때문이다.
자바스크립트는 코드를 실행하기 전에 해석하고, 코드를 컴퓨터에 전달하기 위해 재배열 하는 과정을 거친다.
이때, var는 코드를 해석하는 과정에서 선언단계가 위쪽으로 올라가는데, 이를 호이스팅이라 한다.

var varKeyword;			// var는 선언이 위로 끌어올려진다.
console.log(varKeyword)		// undefined
varKeyword = 'var is not safe'

위의 예와 같이 마치 변수 선언부가 상단으로 올라가는 것처럼 동작한다.
호이스팅에 의해 undefined가 출력되고, 에러를 미연에 방지할 수 없게 된다.


2) TDZ (Temporal Dead Zone)

const, let도 호이스팅이 발생하지 않는 것이 아니다.
호이스팅이 발생하여도 TDZ 때문에 호이스팅이 발생하지 않는것처럼 여겨진다.

TDZ란?

변수는 보통 세가지의 단계를 거치게 된다.

선언단계 -> 초기화 단계 -> 할당단계

  • 선언 단계 : 선언한 변수를 식별자가 담기는 객체에 할당하는 단계
  • 초기화 단계 : 변수에 할당할 메모리 공간을 부여하는 단계
  • 할당 단계 : 정의된 변수에 데이터가 할당되는 단계

이때, 선언 단계와 초기화 단계 사이에 TDZ가 존재한다.
TDZ는 변수에 할당할 메모리가 부여되기 전 단계이다.

  • let, const가 호이스팅 영향을 받지 않는 이유는 변수가 TDZ에 위치하여 메모리를 할당받지 못하고, 이에 따라 에러가 발생하기 때문이다.

  • 반면, var은 선언과 초기화 단계가 동시에 발생하여 TDZ가 존재하지 않아 호이스팅에 의해 변수선언과 동시에 초기화가 진행된다.
    undefined(메모리는 할당되었지만, 데이터는 존재하지 않는 상태)를 반환하게 되는 것이다.

마찬가지로 함수 표현식도 TDZ의 영향을 받고, 함수 선언식은 TDZ의 영향을 받지 않는다.


3) 함수의 호이스팅

함수도 호이스팅이 발생한다.

함수 선언식으로 정의된 함수는 호이스팅이 발생한다.

fn1()

function fn1() {
	console.log("hoisting occurred")	// 코드가 실행된다.
}

함수 표현식은 호이스팅이 발생하지 않는다.

fn2()

function fn2() {
	console.log("error occurred")	// 오류 발생 (fn2 is not defined)
}

실습) To-do 리스트

구현하려는 기능

  1. to-do 리스트 기능을 만든다.
  2. input박스에 todo를 작성하고 엔터하면 리스트에 추가된다.
  3. todo가 완료된 경우, 체크박스 버튼을 눌러 취소선을 추가한다.
  4. 더블클릭하면 todo를 삭제한다.
  5. 여러개의 todo를 동시에 삭제하는 버튼을 만든다.
  6. 로컬스테이지로 데이터를 유지한다.
  7. 현재 지역의 날씨 데이터를 가져와 날씨에 맞는 배경화면을 만든다.
const todoInput = document.querySelector("#todo-input");
const todoList = document.querySelector("#todo-list"); // ul을 가져온다.

const savedTodoList = JSON.parse(localStorage.getItem("saved-items")); // 문자열로 변환된 JSON데이터 타입을 원본 데이터로 변환해준다..

const createTodo = function (storageData) {
  let todoContents = todoInput.value; // value값을 가져온다
  if (storageData) {
    // 매개변수가 들어 왔다면, 컨텐츠를 빼와서 텍스트 컨텐츠를 활용한다.
    todoContents = storageData.contents;
  }

  const newLi = document.createElement("li"); // 태그를 생성한다. (노드 생성)
  const newSpan = document.createElement("span");
  const newBtn = document.createElement("button");

  // addEventListener: 직접 생성한 이벤트를 추가해준다. ('추가할 이벤트이름', 추가할 속성)
  newBtn.addEventListener("click", () => {
    newLi.classList.toggle("complete"); // 버튼 눌렀을때, li태그에 complete라는 새로운 class를 추가해준다. toggle은 클릭할때마다
    saveItemsFn(); // 체크 버튼을 누를때 localstorage 저장을 위해 saveItemsFn 함수를 실행해준다.
  });

  // 더블 클릭시, 태그를 삭제해준다.
  newLi.addEventListener("dblclick", () => {
    newLi.remove();
    saveItemsFn(); // 더블 클릭시에도 local storage를 반영해준다.
  });

  // storageData에 complete가 true라면(취소선이 그어져 있으면), complete 클래스를 추가해준다.
  // 객체 + ? :optional chaining. 객체가 undefined나 다른 값인 경우 체크하지 않는다. (값이 정상인 값일때만 complete를 찾는다.)
  if (storageData?.complete) {
    newLi.classList.add("complete");
  }

  newSpan.textContent = todoContents; // 재할당을 위해 value값을 여기서 가져온다.
  newLi.appendChild(newBtn);
  newLi.appendChild(newSpan); // appendChild : 부모 태그에 자식 태그를 추가해준다.
  todoList.appendChild(newLi);
  todoInput.value = ""; // 할 일을 추가한 후 input창을 비워준다.
  saveItemsFn();
};

// input에 입력하고 엔터를 누르면 createTodo함수가 실행된다.
const keyCodeCheck = function () {
  if (window.event.keyCode === 13 && todoInput.value !== "") {
    // 버튼이 enter이고, input에 빈 값이 아닐 경우
    createTodo();
  }
};

// 전체삭제 버튼을 클릭하면 모든 태그들을 삭제한다.
const deleteAll = function () {
  const liList = document.querySelectorAll("li"); // querySelectorAll : 해당 태그들을 모두 가져온 후 배열에 담아준다.
  // 반복문으로 liList 배열 요소에 접근하여 모든 태그들을 삭제해준다.
  for (let i = 0; i < liList.length; i++) {
    liList[i].remove();
  }
  saveItemsFn();
};

// Local storage
const saveItemsFn = function () {
  const saveItems = [];
  for (let i = 0; i < todoList.children.length; i++) {
    const todoObj = {
      contents: todoList.children[i].querySelector("span").textContent,
      complete: todoList.children[i].classList.contains("complete"), // 주어진 li태그들 안에 complete 클래스가 존재하는지 확인
    };
    saveItems.push(todoObj);
  }

  // 삼항연산자) 조건이 성립하는 경우 ? 뒤에 코드가 실행되고, 아닌 경우 : 뒤에 코드가 실행된다.
  saveItems.length === 0
    ? localStorage.removeItem("saved-items")
    : localStorage.setItem("saved-items", JSON.stringify(saveItems)); 
};

if (savedTodoList) {
  // storage데이터가 있다면,
  for (let i = 0; i < savedTodoList.length; i++) {
    createTodo(savedTodoList[i]);
  }
}

1) input에 엔터버튼을 눌렀을 때만 값을 가져온다.

  • window.eventkeyCode를 확인하여 어떤 버튼을 눌렀는지 확인한다.
  • 조건문으로 keyCode가 일치할때만 value값을 가져온다.

2) todo리스트에 들어갈 li와 span태그를 생성한다.

  • const newLi = document.createElement("li");
  • const newSpan = document.createElement("span");
  • 태그에 내용을 넣어준다.
    -> newSpan.textContent = inputValue;
  • 노드는 생성해주었지만, 조립이 되지 않은 상태이다.
    -> newLi.appendChild(newSpan);
    -> li 태그도 ul태그 안에 appendChild 해주어야 한다.
    -> todoList.appendChild(newLi);

3) todo가 완료된 경우, 체크박스 버튼을 눌러 취소선을 추가한다.

  • addEventListener로 체크박스 버튼을 누를 때 이벤트를 추가해준다.
  • 체크 버튼을 눌를때 li에 complete라는 클래스를 토글로 추가/삭제한다.
    -> newLi.classList.toggle("complete");
  • 이 후 css에서 취소선을 주는 속성을 적용한다.

4) 더블클릭하면 todo를 삭제한다.

  • addEventListner로 더블클릭 시 li를 삭제해준다.
    -> newLi.remove()

5) 여러개의 todo들을 동시에 삭제하는 버튼을 만들어 기능을 넣어준다.

  • document.querySselectorAll()로 li 태그들을 모두 가져오면 배열에 담기게 된다.
  • 반복문으로 배열에 접근하여 모든 태그들을 삭제해준다.

6) Local storage에 저장해준다.

  • span과 취소선이 그어져 있는 li들을 가져온다.
    ul안에 있는 li들을 todoList.children으로 찾고, span과 complete 클래스를 가진 li들을 classList.contains로 찾는다. todoObj라는 객체를 만들어 반복문 안에서 값들을 넣어준다.
    그리고 빈 배열에 만든 객체를 push해준다.

  • 빈 배열일 경우 데이터를 삭제하고, 빈 배열이 아닐 경우 local storage에 배열을 저장해주는 조건문을 삼항연산자로 작성한다.
    이때, 배열을 문자열로 변환하기 위해 JSON.stringify를 사용한다.
    JSON.stringfy는 배열 및 객체를 문자열로 변환해준다. (JSON 데이터 타입)

  • 문자열로 변환된 JSON데이터를 다시 원본 데이터로 변환할 수 있다.
    JSON.parse(localStorage.getItem("saved-items"));

  • 마지막으로 조건문을 통해 storage데이터가 있다면 createTodo함수를 사용하고, 매개변수로 저장된 saved-data를 불러오도록 한다.



오늘의 회고

Todo 리스트 만들기는 카운트다운 타이머 만들기보다는 비교적 덜 어려운것 같다. 그치만 local storage 부분에서 다시 이해가 안가기 시작하고..그 부분은 복습해야 겠다.
오늘 수업 중에서 그동안 아무것도 모르고 사용하던 document객체의 노드를 확인하는 과정이 너무 신기했다.

실습을 통해 appendChild로 노드를 추가하는 방법과, addEventListener로 이벤트를 직접 생성하는 방법, JSON.stringify로 배열 및 객체를 문자열로 변환해주는 방법 등 중요한 기능들을 배운것 같다. 꼼꼼하게 복습해서 잊어버리지 말아야겠다.

오늘 과제로 폴더 아이콘을 클릭했을 때, 하위에 존재하는 파일과 폴더가 보여지도록 하는 기능을 구현하는 과제가 주어졌다. tree노드에 대해 이해할 수 있도록 주어진 과제인데, parentNode를 찾아 querySelector로 자식 노드를 선택하면 해결할 수 있었다. 클릭 시 폴더가 열리는 과정은 구현했는데 다시 닫는 과정은 구현하지 못했다. 클래스명이 같아서 토글하면 뒤죽박죽으로 열리고 닫히게 되는데, 내가 구현하지 못한건지 과제가 여기까지가 아닌건지..

profile
프론트엔드 개발자 도전기

0개의 댓글