오늘 수강한 강의 : 【한글자막】 100일 코딩 챌린지 - Web Development 부트캠프
오늘의 강의 정리 📗
전역변수 window
- 브라우저에 활성화된 윈도우와 관련된 많은 유틸리티 정보과, 기능이 저장되어있음.
- 브라우저의 탭을 의미하기도 함.
- 보안상의 이유로 웹사이트에서 코드를 실행할 때에는 윈도우 객체를 이용해 현재 열고있는 탭의 정보에 엑세스 해야함(다른탭 X)
- 브라우저 안의 모든 요소들이 소속된 객체로, 최상위에 있기 때문에 어디서든 접근이 가능(전역 변수임)
- var을 이용해 전역 변수를 만들면 window 객체에 key와value가 저장됨.
- let , const 사용시 window 객체에 추가되지 않음.
var a = "apple";
console.log(window.a); //"apple"
let b = "banana";
console.log(window.b); //undefined
const c = "strawberry";
console.log(window.c); //undefined
window객체의 대표적인 속성
- closed
- console
- defaultStatus
- document
- frameElement
- frames
- history
- innerWidth
- length
- localStorage
- location
- name
- navigator
- opener
- outerHeight
- outerWidth
- pageXOffset
- pageYOffset
- parent
- screen
- screenLeft
- screenTop
- screenX
- screenY
- sessionStorage
- scrollX
- scrollY
- self
- status
- top
window객체의 메서드
- alert()
- atob() : base-64로 암호화된 문자열을 암호해독
- blur() : 현재 창에서 focus 제거
- btoa() : base064로 문자열을 암호화
- clearIInterval() : setInterval()로 설정된 타이머 지우기
- clearTimeout() : setTImeout()으로 설정된 타이머 지우기
- close()
- confirm()
- focus()
- getComputedStyle()
- getSelection()
- matchMedea()
- moveBy()
- moveTo()
- open()
- print()
- prompt()
- requestAnimationFrame()
- resizeBy()
- resizeTo()
- scroll()
- scrollBy()
- scrollTo()
- setInterval() :지정된 간격(밀리초)로 함수를 호출하거나 표현식을 평가
- setTimeout() : 지정된 밀리초 후에 함수를 호출하거나 표현식을 평가
- stop() : 창 로드 정지
전역변수 document
- 웹페이지 그 자체를 의미
- DOM트리의 최상위 객체
- 웹페이지에 존재하는 HTML 요소에 접근하고자 할 때에는 반드시 Doocument객체로부터 시작해야 함.
document객체의 역할
- property로 HTML 문서의 전반적인 특성을 나타냄
- 메소드로 DOM 객체 검색
- 메소드로 새로운 DOM객체 생성
- 메소드로 HTML문서의 전반적 제어 지원
document 객체의 주요 property
- title
- body
- head
- URL
- location
- refferr
document객체의 주요 컬렉션
- images
- links
- forms
docment객체의 주요 메서드
- getElementyId() : 아이디 명으로 첫번째 DOM 객체 반환
- getElementyTagName() : 특정 태그명을 가진 모든 태그 컬렉션을 반환
- getElementyClassName() : 특정 클래스명을 가진 모든 클래스 컬렉션 반환
- querySelector() : 제공된 CSS 선택자(ID 선택자, 태그 유형 선택자, 클래스 선택자, 결합 선택자 등)에 의해 충족/선택된 첫번째 HTML 요소를 선택
- 객체.onclick=funtion(){실행할 코드} : 이벤트 핸들러 추가
- open() : 모든 컨텐츠를 지우고 새로운 HTML 콘텐츠를 쓸 수 있도록 엶
- write(), writeln() : document에 HTML 콘텐츠 삽입.
- close() : document객체에 있는 HTML 콘텐츠를 브라우저에 출력하고 더이상 쓰기 입력을 받지 않음.
- createElement(“태그 이름”) : (동적 HTML 문서 구성) HTML 태그의 DOM 객체를 생성하는 코드.
- 부모객체.appendChild(DOM객체) : 부모객체에 마지막 자식으로 삽입
- 부모객체.insertBefore(DOM객체,
[
, 기준자식]
) : 기준자식 앞에 삽입 - 부모객체.removeChild(자식객체) : 자식객체 삭제
DOM(Document Object Model) 문서 객체 모델
- 문서 객체 모델(DOM, Document Object Model)은 XML이나 HTML 문서에 접근하기 위한 일종의 인터페이스
- 문서 내의 모든 요소를 정의하고, 각각의 요소에 접근하는 방법을 제공
- 즉, 자바스크립트 같은 스크립팅 언어가 쉽게 웹페이지에 접근하여 조작 할 수 있게끔 연결시켜주는 역할.
- DOM의 종류
- Core DOM : 모즌 문서 타입을 위한 DOM 모델
- HTML DOM : HTML문서를 위한 DOM 모델
- HTML 문서의 계층적 구조와 정보를 표현하며, 이를 제어 할 수 있는 API, 즉 프로퍼티와 메서드를 제공하는 트리 자료구조
- XML DOM : XML문서를 위한 DOM 모델
HTML 과 DOM
- console.dir(document) : 자바스크립트 object를 확인 할 수 있는 방법.
스크립트를 올바르게 로드하기
- 이전날에 다루었던 styledComponent와 script를 올바른 때에 호출하는 방법도 JS안의 코드와 DOM요소의 로드 순서가 영향을 주지 않도록 하는 방법 중 하나지만, script태그를 head안에 위치시키면서 스크립트 실행을 페이지 로딩보다 후순위로 지정하는 방법도 있다.
script태그 - defer 속성
- 페이지가 모두 로드된 후에 해당 외부 스크립트가 실행됨
- 보통 DOM 전체가 필요한 스크립트나 실행 순서가 중요할 때(B 스크립트는 A 스크립트 이후에 실행되어야 할 때) 적용
script태그 - async 속성
- 브라우저가 페이지를 파싱되는 동안에도 스크립트가 실행됨
- 방문자 수 카운터 혹은 광고 스크립트 등과 같이 독립적으로 작동하는 스크립트에 적용
- async 속성은 명시되어 있지 않고 defer 속성만 명시된 경우
- 브라우저가 페이지의 파싱을 모두 끝내면 스크립트가 실행됨
- async 속성과 defer 속성이 모두 명시되어 있지 않은 경우 : 브라우저가 페이지를 파싱하기 전에 스크립트를 가져와 바로 실행
DOM 드릴링
- DOM객체의 속성(ex… firstElementChild)을 통해 DOM을 탐색하는 작업
Node 탐색
자식 노드 탐색
Node.prototype.hasChildNodes :
(텍스트 노드 포함)자식 노드 존재 확인(true / false)
탐색할요소명.children.length :
(텍스트 노드를 제외한) 자식 요소 노드 존재여부 확인(true/false)
탐색할요소명.childElementCount :
(텍스트 노드를 제외한) 자식 요소 노드 존재여부 확인(숫자/0false)
Node.prototype.childNodes :
자식 노드를 모두 탐색하여 DOM컬렉션 객체인 NodeList에 담아 반환.*
NodeList에는 요소 노드 뿐 아니라 텍스트 노드도 포함되어 있을 수 있음
Element.prototype.children :
자식 노드 중 요소 노드만 탐색하여 DOM컬렉션 객체인 HTMLCollection에 담아 반환*
HTMLCollection에는 텍스트 노드가 포함되지 않음.
부모 노드 탐색
Node.prototype.parentNode
cf) 텍스트 노드는 부모노드일 수 없다. 텍스트 노드는 DOM트리의 리프노드(최종단 노드)이기 때문.
형제 노드 탐색
Node.prototype.previousSibling
텍스트 노드를 포함한 노드 중 자신의 이전 형제 노드를 탐색해 HTMLElement를 상속받은 객체를 반환한다.
Node.prototype.nestSibling
텍스트 노드를 포함한 노드 중 자신의 다음 형제 노드를 탐색해 HTMLElement를 상속받은 객체를 반환한다.
Element.prototype.previousElementSibling
요소 노드 중 자신의 이전 형제 노드를 탐색해HTMLElement를 상속받은 객체를 반환한다.(IE9 이상의 브라우저에서 동작)
Element.prototype.nextElementSibling
요소 노드 중 자신의 다음 형제 노드를 탐색해 HTMLElement를 상속받은 객체를 반환한다.(IE9 이상의 브라우저에서 동작)
Node 정보 취득
Node.prototype.nodeType
- 노드 객체의 종류 즉 노드 타입을 나타내는 상수 반환
- Node.ELEMENT_NODE : 1
- Node.TEXT_NODE : 3
- Node.DOCUMENT_NODE : 9
Node.prototype.nodeName:
- 노드의 이름을 문자열로 반환
- 요소노드 : 대문자 문자열로 태그이름(“UL”, “LI”,.) 반환
- 텍스트 노드 : “
#
text” 반환 - 문서 노드 : “
#
document” 반환
text node 접근 / 수정
Node.prototype.nodeValue
- 텍스트 노드가 아닌 노드를 참조하면 null을 반환
- setter, getter 모두 존재하는 접근자 프로퍼티
- 즉, nodeValue에 값을 할당하면 노드의 값 즉 텍스트를 변경 할 수 있음.
HTML 콘텐츠 조작(Manipulation)
- HTML 콘텐츠를 조작(Manipulation)하기 위해 아래의 프로퍼티 또는 메소드를 사용할 수 있음.
- 마크업이 포함된 콘텐츠를 추가하는 행위는 크로스 스크립팅 공격(XSS: Cross-Site Scripting Attacks)에 취약하므로 주의가 필요
textContent(IE9 이상)
- 요소의 텍스트 콘텐츠를 취득 또는 변경(마크업은 무시됨)
- textContent를 통해 새로운 텍스트를 할당하면, 텍스트를 변경 할 수 있음.
- 순수한 텍스트만 지정해야 함
- 마크업을 포함시키면 문자열 그대로 출력됨
<!DOCTYPE html>
<html>
<head>
<style>
.red { color: #ff0000; }
.blue { color: #0000ff; }
</style>
</head>
<body>
<div>
<h1>Cities</h1>
<ul>
<li id="one" class="red">Seoul</li>
<li id="two" class="red">London</li>
<li id="three" class="red">Newyork</li>
<li id="four">Tokyo</li>
</ul>
</div>
<script>
const ul = document.querySelector('ul');
// 요소의 텍스트 취득
console.log(ul.textContent);
/*
Seoul
London
Newyork
Tokyo
*/
const one = document.getElementById('one');
// 요소의 텍스트 취득
console.log(one.textContent); // Seoul
// 요소의 텍스트 변경
one.textContent += ', Korea';
console.log(one.textContent); // Seoul, Korea
// 요소의 마크업이 포함된 콘텐츠 변경.
one.textContent = '<h1>Heading</h1>';
// 마크업이 문자열로 표시된다.
console.log(one.textContent); // <h1>Heading</h1>
</script>
</body>
</html>
innerText
- 요소의 텍스트 콘텐츠에 접근 가능
- 사용하지 않는 것이 좋음
- 비표준 사항이기 때문
- CSS에 순종적(CSS에 의해 visibility:hidden으로 지정되어 있다면, 텍스트가 반환되지 않음)
- CSS를 고려해야함으로 textContent보다 느림
innerHTML
- 해당 요소의 모든 자식요소를 포함하는 모든 콘텐츠를 하나의 문자열로 취득 가능(문자열 포함)
- 해당 프로퍼티를 사용해 마크업이 포함된 새로운 콘텐츠를 지정하면 새로운 요소를 DOM에 추가 할 수 있음
- 마크업이 포함된 콘텐츠를 추가하는 것은 크로스 스크립팅 공격(XSS: Cross-Site Scripting Attacks)에 취약
const ul = document.querySelector('ul');
// innerHTML 프로퍼티는 모든 자식 요소를 포함하는 모든 콘텐츠를 하나의 문자열로 취득할 수 있다. 이 문자열은 마크업을 포함한다.
console.log(ul.innerHTML);
// IE를 제외한 대부분의 브라우저들은 요소 사이의 공백 또는 줄바꿈 문자를 텍스트 노드로 취급한다
/*
<li id="one" class="red">Seoul</li>
<li id="two" class="red">London</li>
<li id="three" class="red">Newyork</li>
<li id="four">Tokyo</li>
*/
const one = document.getElementById('one');
// 마크업이 포함된 콘텐츠 취득
console.log(one.innerHTML); // Seoul
// 마크업이 포함된 콘텐츠 변경
one.innerHTML += '<em class="blue">, Korea</em>';
// 마크업이 포함된 콘텐츠 취득
console.log(one.innerHTML); // Seoul <em class="blue">, Korea</em>
// 크로스 스크립팅 공격 사례
// 스크립트 태그를 추가하여 자바스크립트가 실행되도록 한다.
// HTML5에서 innerHTML로 삽입된 <script> 코드는 실행되지 않는다.
// 크롬, 파이어폭스 등의 브라우저나 최신 브라우저 환경에서는 작동하지 않을 수도 있다.
elem.innerHTML = '<script>alert("XSS!")</script>';
// 에러 이벤트를 발생시켜 스크립트가 실행되도록 한다.
// 크롬에서도 실행된다!
elem.innerHTML = '<img src="#" onerror="alert(\'XSS\')">';
DOM 조작
- HTML 콘텐츠 조작의 경우 여러 제한점(리스크)가 있기 때문에 보통 화면의 동적 변화가 필요한 경우 아래처럼 DOM을 직접 조작함
- 요소 노드 생성 createElement() 메소드를 사용하여 새로운 요소 노드를 생성한다. createElement() 메소드의 인자로 태그 이름을 전달한다.
- 텍스트 노드 생성 createTextNode() 메소드를 사용하여 새로운 텍스트 노드를 생성한다. 경우에 따라 생략될 수 있지만 생략하는 경우, 콘텐츠가 비어 있는 요소가 된다.
- 생성된 요소를 DOM에 추가 appendChild() 메소드를 사용하여 생성된 노드를 DOM tree에 추가한다. 또는 removeChild() 메소드를 사용하여 DOM tree에서 노드를 삭제할 수도 있다.
createElement(tagName)
- 태그 이름을 인자로 전달하여 요소를 생성
- HTMLElement를 상속받은 객체 반환
createTextNode(text)
- 텍트스를 인자로 전달, 텍스트 노드 생성
- Text객체 반환
appendChild(Node)
- 인자로 전달한 노드를 마지막 자식 요소로 DOM트리에 추가
- 추가한 노드 반환
insertBefore(Node, [
, 기준자식]
)
- 인자로 전달한 노드를 DOM트리에서 기준자식의 앞쪽에 추가
removeChild(Node)
- 인자로 전달한 노드를 DOM트리에서 제거
// 태그이름을 인자로 전달하여 요소를 생성
const newElem = document.createElement('li');
// const newElem = document.createElement('<li>test</li>');
// Uncaught DOMException: Failed to execute 'createElement' on 'Document': The tag name provided ('<li>test</li>') is not a valid name.
// 텍스트 노드를 생성
const newText = document.createTextNode('Beijing');
// 텍스트 노드를 newElem 자식으로 DOM 트리에 추가
newElem.appendChild(newText);
const container = document.querySelector('ul');
// newElem을 container의 자식으로 DOM 트리에 추가. 마지막 요소로 추가된다.
container.appendChild(newElem);
const removeElem = document.getElementById('one');
// container의 자식인 removeElem 요소를 DOM 트리에 제거한다.
container.removeChild(removeElem);
insertAdjacentHTML(position, string)
- 인자로 전달한 텍스트를 HTML로 파싱하고 그 결과로 생성된 노드를 DOM트리의 지정된 위치에 삽입.
- position 종류
- ‘beforebegin’
- ‘afterbegin’
- ‘beforeend’
- ‘afterend’
<!-- beforebegin -->
<p>
<!-- afterbegin -->
foo
<!-- beforeend -->
</p>
<!-- afterend -->
const one = document.getElementById('one');
// 마크업이 포함된 요소 추가
one.insertAdjacentHTML('beforeend', '<em class="blue">, Korea</em>');
innerHTML vs. DOM 조작 vs. insertAdjacentHTML()
innerHTML
장점 | 단점 |
---|---|
DOM 조작 방식에 비해 빠르고 간편하다. | XSS공격에 취약점이 있기 때문에 사용자로 부터 입력받은 콘텐츠(untrusted data: 댓글, 사용자 이름 등)를 추가할 때 주의하여야 한다. |
간편하게 문자열로 정의한 여러 요소를 DOM에 추가할 수 있다. | 해당 요소의 내용을 덮어 쓴다. 즉, HTML을 다시 파싱한다. 이것은 비효율적이다. |
콘텐츠를 취득할 수 있다. |
DOM 조작 방식
장점 | 단점 |
---|---|
특정 노드 한 개(노드, 텍스트, 데이터 등)를 DOM에 추가할 때 적합하다. | innerHTML보다 느리고 더 많은 코드가 필요하다. |
insertAdjacentHTML()
장점 | 단점 |
---|---|
간편하게 문자열로 정의된 여러 요소를 DOM에 추가할 수 있다. | XSS공격에 취약점이 있기 때문에 사용자로 부터 입력받은 콘텐츠(untrusted data: 댓글, 사용자 이름 등)를 추가할 때 주의하여야 한다. |
삽입되는 위치를 선정할 수 있다. |
결론
innerHTML과 insertAdjacentHTML()은 크로스 스크립팅 공격(XSS: Cross-Site Scripting Attacks)에 취약
따라서 untrusted data의 경우, 주의하여야 한다.
텍스트를 추가 또는 변경시에는 textContent, 새로운 요소의 추가 또는 삭제시에는 DOM 조작 방식을 사용
이벤트 핸들링
참고 : [JS] 이벤트 루프(Event Loop)와 동시성(Concurrency)
대표적인 이벤트 종류
- load : 웹페이지의 로드가 완료되었을 때
- scroll : 사용자가 페이지를 위아래로 스크롤할 때
- keydown : 키를 누르고 있을 때
- keyup : 누르고 있던 키를 뗄 때
- keypress : 키를 누르고 뗏을 때
- click : 마우스 버튼을 클릭했을 때
- mouseover : 마우스를 요소 위로 움직였을 때 (터치스크린X)
- mouseout :마우스를 요소 밖으로 움직였을 때 (터치스크린X)
- focus :요소가 포커스를 얻었을 때
- blur :요소가 포커스를 잃었을 때
- input
- input 또는 textarea 요소의 값이 변경되었을 때
- contenteditable 어트리뷰트를 가진 요소의 값이 변경되었을 때
- change : select box, checkbox, radio button의 상태가 변경되었을 때
- submit : form을 submit할 때 (버튼 또는 키)
인라인 이벤트 핸들러 방식
- HTML 요소의 이벤트 핸들러 어트리뷰트에 이벤트 핸들러를 등록
- HTML과 Javascript는 관심사가 다르므로 분리하는것이 좋기 때문에, 더이상 사용되지 않는 방법
- 여러 개의 문을 전달할 수 있음
<!DOCTYPE html>
<html>
<body>
<button onclick="myHandler1(); myHandler2();">Click me</button>
<script>
function myHandler1() {
alert('myHandler1');
}
function myHandler2() {
alert('myHandler2');
}
</script>
</body>
</html>
- onclick과 같이 on으로 시작하는 이벤트 어트리뷰트의 값으로 함수 호출을 전달함
- 이벤트 어트리뷰트의 값으로 전달한 함수 호출이 즉시 호출되는 것은 아님
- 이벤트 어트리뷰트 키를 이름으로 갖는 함수를 암묵적으로 정의
- 함수의 몸체에 이벤트 어트리뷰트의 값으로 전달한 함수 호출을 문으로 가짐
이벤트 핸들러 프로퍼티 방식
- 인라인 이벤트 핸들러 방식처럼 HTML과 Javascript가 뒤섞이는 문제는 해결할 수 있는 방식
- 단점 : 하지만 이벤트 핸들러 프로퍼티에 하나의 이벤트 핸들러만을 바인딩할 수 있음
<!DOCTYPE html>
<html>
<body>
<button class="btn">Click me</button>
<script>
const btn = document.querySelector('.btn');
// 이벤트 핸들러 프로퍼티 방식은 이벤트에 하나의 이벤트 핸들러만을 바인딩할 수 있다
// 첫번째 바인딩된 이벤트 핸들러 => 실행되지 않는다.
btn.onclick = function () {
alert('① Button clicked 1');
};
// 두번째 바인딩된 이벤트 핸들러
btn.onclick = function () {
alert('① Button clicked 2');
};
// addEventListener 메소드 방식
// 첫번째 바인딩된 이벤트 핸들러
btn.addEventListener('click', function () {
alert('② Button clicked 1');
});
// 두번째 바인딩된 이벤트 핸들러
btn.addEventListener('click', function () {
alert('② Button clicked 2');
});
</script>
</body>
</html>
addEventListener 메소드 방식(IE 9 이상)
- IE 8 이하 -
attachEvent
메소드 사용
if (elem.addEventListener) { // IE 9 ~
elem.addEventListener('click', func);
} else if (elem.attachEvent) { // ~ IE 8
elem.attachEvent('onclick', func);
}
addEventListener
메소드를 이용하여 대상 DOM 요소에 이벤트를 바인딩하고 해당 이벤트가 발생했을 때 실행될 콜백 함수(이벤트 핸들러)를 지정
- 장점
- 하나의 이벤트에 대해 하나 이상의 이벤트 핸들러를 추가할 수 있음
- 캡처링과 버블링을 지원
- HTML 요소뿐만아니라 모든 DOM 요소(HTML, XML, SVG)에 대해 동작(브라우저는 웹 문서(HTML, XML, SVG)를 로드한 후, 파싱하여 DOM을 생성)
- addEventListener 메소드에서 지정한 이벤트 핸들러는 콜백 함수
- 이벤트 핸들러 내부의 this는 이벤트 리스너에 바인딩된 요소(currentTarget)를 가리킴(이벤트 객체의 currentTarget 프로퍼티와 같음).
function foo() {
alert('clicked!');
}
// elem.addEventListener('click', foo()); // 이벤트 발생 시까지 대기하지 않고 바로 실행된다
elem.addEventListener('click', foo); // 이벤트 발생 시까지 대기한다
<!DOCTYPE html>
<html>
<body>
<label>User name <input type='text'></label>
<em class="message"></em>
<script>
const MIN_USER_NAME_LENGTH = 2; // 이름 최소 길이
const input = document.querySelector('input[type=text]');
const msg = document.querySelector('.message');
function checkUserNameLength(n) {
if (input.value.length < n) {
msg.innerHTML = '이름은 ' + n + '자 이상이어야 합니다';
} else {
msg.innerHTML = '';
}
}
input.addEventListener('blur', function () {
// 이벤트 핸들러 내부에서 함수를 호출하면서 인수를 전달한다.
checkUserNameLength(MIN_USER_NAME_LENGTH);
});
// 이벤트 핸들러 프로퍼티 방식도 동일한 방식으로 인수를 전달할 수 있다.
// input.onblur = function () {
// // 이벤트 핸들러 내부에서 함수를 호출하면서 인수를 전달한다.
// checkUserNameLength(MIN_USER_NAME_LENGTH);
// };
</script>
</body>
</html>
JS - CSS 제어
- 객체.style.속성명 = “속성값”
- .style 규칙 : CSS에 "-"가 있는 경우 카멜케이스 적용
//getElementsByClassName
document.getElementsByClassName("class")[0].style.borderColor = "red";
//getElementById
document.getElementById("id").style.borderColor = "red";
//querySelector
document.querySelector(".class").style.borderColor = "red";
JS - class 제어
클래스 변경
- 객체.style.className = “클래스명1 클래스명2”
getElementsByClassName
document.getElementsByClassName("class")[0].className = "active";
getElementById
document.getElementById("id").className = "active";
querySelector
document.querySelector(".class").className = "active";
// 활용
let elm = document.getElementById('id');
if(elm.className === 'active'){
elm.className = 'inactive';
} else {
elm.className = 'active';
};
클래스 제어
- 객체.style.classList.메소드(‘클래스명’)
- add : 클래스 추가
- remove : 클래스 제거
- toggle : 토글
- replace : 대체
'Web_Backend > Javascript' 카테고리의 다른 글
[모던 자바스크립트 Deep Dive] 20장_strict mode (0) | 2022.03.24 |
---|---|
[모던 자바스크립트 Deep Dive] 19장_프로토타입 (0) | 2022.03.24 |
[모던 자바스크립트 Deep Dive] 18장_함수와 일급 객체 (0) | 2022.03.23 |
[모던 자바스크립트 Deep Dive] 17장_생성자 함수에 의함 객체 생성 (0) | 2022.03.22 |
[모던 자바스크립트 Deep Dive] 16장_프로퍼티 어트리뷰트 (0) | 2022.03.21 |
야나의 코딩 일기장 :) #코딩블로그 #기술블로그 #코딩 #조금씩,꾸준히
포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!