JavaScript

[웹코딩 가이드] 홈

이제 웹프로그래밍 언어의 최고봉, 자바스크립트를 살펴봅니다 diagram-arrow-down

html은 웹문서의 내용을 구성하고 의미를 부여하는 데 사용하는 마크업 언어 이며, Css는 그 html 문서에 원하는 모양을 입혀주기 위한 스타일 규격 이다. 한편, javaScript는 웹문서에서 동적으로 추가, 제거되고 변경되는 컨텐츠를 만들고 조작할 수 있도록 해주는 스크립트 언어 이다


자바스크립트는 많이 어렵습니다. 순서대로만 설명할 수도 없고, 그렇게 배워나갈 수도 없습니다. 지나갔다, 다시 돌아오고,, 다시 나아가고,,, 하면서 수없는 반복을 통해 익혀나가야 합니다 ㅡㅡ; 러시아 혁명가 레닌이 쓴 유명한 팜플렛 [1보 전진, 2보 후퇴](= 한걸음 더 나아가기 위한 2보 후퇴)가 생각나는군요 혹시나 이 말에 의문을 품는 분이 계실까 해서 덧붙이는데.. [1보 후퇴 for 2보 전진]이 아닙니다 ^^


시작하기

는 html 문서에 적용될 때, 사용자와의 상호작용을 제공하는데.. 자바스크립트의 가장 일반적인 용도는 웹브라우저의 DOM을 통해 html과 Css를 동적으로 변경하는 것이다!

웹문서 내부에서 스크립트 작성하기
스크립트는 html 문서 내부에서 작성해주거나, 또는 외부 스크립트 파일을 불러올 수도 있는데.. html 문서(예컨대, _js.html)의 </body> 바로 위 <script> .. </script> 내부에서 인라인 스크립트 코드로 작성해줄 수 있다
[ _js.html 파일 ]
                                        
                                            <body>
                                                인라인 스크립트 작성하기
                                            
                                                <script>
                                                    console.log("이스케이프 문자: \u03c0") // 이스케이프 문자: π
                                                    console.log("웃는 얼굴 이모지: \u{1F600}") // 웃는 얼굴 이모지: 😀
                                                </script>
                                            </body>
                                        
                                    

위 스크립트 코드를 실행한 결과는 [브라우저 개발자 화면]의 [콘솔] 탭에서 확인할 수 있습니다 _js.html 파일을 실행한 뒤 마우스 우측버튼 단축메뉴의 <검사>를 클릭하면; [브라우저 개발자 화면]으로 들어가게 되고, 거기서 다시 [콘솔] 탭으로 찾아가면 됩니다..


* cf) 웹문서의 html 코드는 일반적으로 위에서 아래로 순서대로 로드되고 실행된다. 따라서, 적용하려는 html 및 Css 코드보다 스크립트가 먼저 불려지면 오류가 발생할 수 있다. 그런 이유로 스크립트 코드는 </body> 직전에 넣어주어야 한다!

                                    
                                        <body>
                                            ..
                                        
                                            <!-- 1. html 코드 내부에 넣는 스크립트 코드는
                                                이벤트 리스너에서 'DOMContentLoaded' 속성을 사용해주어야 한다
                                            -->
                                            <script>
                                                document.addEventListener("DOMContentLoaded", () => { // DOM이 다 로드된 이후.. ← 이는 defer와 같다!
                                                    // 스크립트 코드 실행
                                                })
                                            </script>
                                        
                                            <!-- 2. </body> 바로 직전에 넣어준 스크립트 코드는
                                                'DOMContentLoaded'를 사용하지 않아도 defer와 같은 방식으로 작동한다!
                                            -->
                                            <script>
                                                const buttons= document.querySelectorAll("button")
                                        
                                                for(const button of buttons) {
                                                    button.addEventListener("click", createParagraph);
                                                }
                                            </script>
                                        </body>
                                    
                                

이 경우, 모든 html 코드를 읽어온 이후 스크립트를 불러오게 되지만, 문제는 DOM을 모두 불러오기 전에는 스크립트의 로딩과 분석 또한 완전히 중단된다는 점이다!

외부 스크립트 파일 불러오기
js 스크립트 파일을 만들어(예컨대, _js.js) 그 안에서 스크립트 코드를 작성한 뒤, _js.html 문서에서 <script>의 src 속성으로 _js.js 파일을 불러온다 <script>의 src 속성은 URL 을 값으로 받기에 다른 웹서버에 있는 코드(예건대, 인터넷 광고 등..)도 가져올 수 있으며, 인라인 스크립트 코드에 비해 웹 페이지 분석 및 렌더링 속도도 더 빠르다!
[ 외부 스크립트 파일 불러오기 ]
                                        
                                            // _js.js 파일
                                            console.log("이스케이프 문자: \u03c0") // 이스케이프 문자: π
                                            console.log("웃는 얼굴 이모지: \u{1F600}") // 웃는 얼굴 이모지: 😀
                                        
                                    
                                        
                                            <body>
                                                스크립트 파일 불러오기
    
                                                <script src="_js.js"></script>
                                            </body>
                                        
                                    

외부에서 불러오는 <script>의 src 속성은 스크립트 코드를 html 문서에서 분리하여 단순화한다는 점 외에도, 단 한번만 불러오면 웹페이지 전체에서 이 코드를 공유할 수 있고, 따라서 서브 페이지들에서도 스크립트를 따로 불러오지 않고 브라우저 캐시에서 가져다 쓸 수 있다는 장점이 있다!

➥ async 대 defer

asyncdefer는 모두 브라우저가 페이지의 다른 내용을 불러오는 동안 스크립트를 별도 쓰레드에서 불러오므로 스크립트를 가져오는 동안에도 페이지 로딩은 중단되지 않는다 - 단, 양자간에는 미묘한 차이점이 존재한다 HTML 명세서 스크립트 로딩 비교표 참조

                                        
                                            <!-- 1. 외부 스크립트 파일 불러오기: 'defer' 속성 -->
                                            <script src="js/default-js.js" defer></script>
                                        
                                    

defer 스크립트는 순서를 유지한 채로 모든 컨텐츠를 다 불러온 이후에 실행된다. 따라서, 다른 스크립트에 의존하거나 DOM 로딩을 필요로 하는 경우에 사용된다 이는 </body> 바로 직전에 스크립트 코드를 넣어주는 경우와 같다!

                                        
                                            <!-- 2. 외부 스크립트 파일 불러오기: 'async' 속성 -->
                                            <script src="js/default-js.js" async></script>
                                        
                                    

async 스크립트는 다운로드가 끝나는 즉시 '비동기적'으로 실행된다. 그 동안 현재 페이지의 문서 분석은 계속 해나가되, 페이지 렌더링은 일시적으로 중단된다 - 따라서, 스크립트를 즉시 실행해야 하고 종속성이 없는 경우에 적합하다!

스크립트 코딩가이드

콘솔 API
은 JavaScript 웹 애플리케이션을 테스트하고 디버그하는 도구로서, 그 결과물은 웹브라우저의 [브라우저 개발자 화면](F12 키)으로 들어가서 [콘솔] 탭을 찾아가서 확인할 수 있는데, console.log(..);는 스크립트의 로그값(곧, 코드 실행의 결과물)을 [브라우저 개발자 화면]의 [콘솔] 탭에 표시해준다 참고로, console.debug/info/warn/error(); 등은 모두 console.log();의 별칭에 다름 아니다!
                                    
                                        const a= [2, 4, 6];
                                        console.log("길이", a.length, "임"); // 길이 3 임 ← ,는 공백으로 연결한다!
                                        console.log("길이" + a.length + "임"); // 길이3임 ← +는 공백없이 붙여서 연결한다!
                                    
                                        const name= 'Lee', height= 170;
                                        console.log("이름은 %s, 키는 %d임", name, height); // 이름은 Lee 키는 170 ← 서식 문자열 변수 지정!
                                    
                                

서식 문자열이 주어지면; %s, %f, %d, %o는 각각 문자열, 실수, 정수, 객체로 변환하여 표시해준다!

[브라우저 개발자화면]의 [콘솔]에 실행 결과를 표시해주는 console.log(..);와는 달리, document.write("내용");는 '살아있는' html DOM에 (실제로! 스크립트 코드를 삽입하여)내용 을 출력한다

                                                
                                                    document.write("<h5>String</h5>"); // 문자열에 html 태그 적용
                                                    document.write(`10 + 2 = ${10 + 2}`); // 템플릿 안에서 수식 계산
                                                
                                            
콘솔 메시지창
alert("알림 메시지");는 콘솔창에 알림 메시지 를 보여주며, confirm("확인 메시지");확인 메시지 (와 함께 [확인/취소] 버튼)을 표시한다(리턴 값: true/false). 한편, prompt("입력 프롬프트"[, "초기값"]);는 사용자 입력 프롬프트 (및 초기값 )를 나타낸다:
                                    
                                        
                                    
                                
                                    
                                        /* 스크립트 코드 */
                                        let weatherPic= document.querySelector('#today-weathers') // 이벤트 대상 ← 위 질문 버튼

                                        function cPic() { // 이벤트 처리함수
                                            const res= prompt("오늘 날씨 질문: ", "좋아요? 나빠요?") // 질문, 초기값 ← [확인] 시는 입력한 값이, [취소] 시는 null이 res에 들어간다

                                            if (res) alert(`음~ ${res}`) // 대답한 경우; 답한 내용을 표시한다
                                            else confirm("엥~ 그냥 나가나요?") // 취소를 누른 경우
                                        }

                                        weatherPic.onclick= cPic // 이벤트 대상(pic)에서 클릭 이벤트가 발생하면; 이벤트 처리함수 cPic()을 호출한다
                                    
                                
스크립트 코딩 가이드
JavaScript는 기본적으로 대/소문자를 구분하며, 여러 칸 공백은 하나의 공백 문자로 간주하고 여러 줄 뉴라인이나 캐리지 리턴, 라인피드 모두 하나의 줄바꿈으로 간주한다. 한편, 세미콜론(;)은 하나의 문이 끝났음을 알리는데, 줄바꿈이 있다면; 생략할 수 있다
                                    
                                        a= 1 // 줄바꿈이 있어 세미콜론 생략 가능
                                        b= 10
                                        x= 1; y= 10 // 한 라인에 작성할 때는 앞쪽 세미콜론은 생략할 수 없다!
                                    
                                

자바스크립트가 줄바꿈을 세미콜론으로 취급하는 경우는 묵시적인 세미콜론을 추가하지 않고서는 코드를 분석할 수 없을 때, 곧 줄바꿈 다음에 오는 공백 아닌 문자를 현재 문에 이어진다고 판단할 수 없을 때이다 예컨대, ([로 시작하는 문이 이어질 때 세미콜론으로 구분되지 않는다면; 하나의 연결된 문으로 해석될 소지가 있다!

매개변수 리스트 다음에 나오는 화살표 함수(=>)는 한 줄에 작성해야 한다. return 문 다음에 표현식이 붙는 경우도 반드시 한 줄에 작성해야 한다: return true;
                                    
                                        (a, b) => a + b // 화살표 함수(=>)는 앞의 매개변수 리스트와 같은 줄에 작성해야 한다!

                                        return (a + b) // 리턴문(return) 다음에 표현식(a + b)이 붙는 경우는 한 줄로 작성해야 한다!
                                    
                                

break 문이나 continue 문 또한 마찬가지이다!

자바스크립트 코드는 (사람이 읽을 수 있는)유니코드 문자셋으로 작성되지만, ASCII 문자만으로 유니코드 문자를 표현하는 이스케이프 시퀀스 또한 사용할 수 있다 이스케이프 시퀀스는 주로 문자열 리터럴이나 정규식 리터럴에서 사용된다!
                                    
                                        console.log("이스케이프 문자: \u03c0") // π
                                        console.log("웃는 얼굴 이모지: \u{1F600}") // 😀
                                    
                                

이스케이프 시퀀스는 \u로 시작하여 4개의 16진수 숫자를 적거나, 또는 (16비트 이상이 필요한 이모지를 표시하기 위하여)1~6자리의 숫자를 넣어주는 중괄호({}) 문법으로 작성한다

식별자는 상수나 변수, 속성, 함수, 클래스에 쓰인 이름을 말하는데, 알파벳 대/소문자나 _, $로 시작되어야 한다. 이름은 숫자로 시작할 수 없으며, 공백이나 기호 등도 들어갈 수 없다. 또한, 스크립트 자체에서 쓰는 예약어도 이름으로 쓸 수 없다 덧붙여서, 일반적인 변수 이름의 맨 앞에 _은 사용하지 않는 것이 좋다 - 이는 자바스크립트의 코딩 철학에서 특별한 의미를 나타내는 데 사용되므로 코드를 읽을 때 불필요한 혼동을 불러올 수 있다!

여러 단어로 이루어진 이름을 지을 때는 보통 camelCasesnake_case 표기법이 사용되는데, (예컨대, 사용자정의 변수 이름에는)kebab-case 표기법을 사용하고, (예컨대, 사용자정의 함수 이름에는)PascalCase 표기법을 사용하는 식으로 일관성을 유지하는 것도 코드 작성 및 읽기에 도움이 될 수 있다!

표현식과 문

이 '실행'을 통해 어떤 동작을 수행하라는 지시라면; 표현식은 '평가'를 통해 값으로 반환해야 하는 문이며, 따라서 그 값을 변수나 배열의 요소, 객체의 프로퍼티에 할당할 수 있다!

표현식이란?
스크립트가 실행되면 표현식( 으로 나타낼 수 있는 것은 모두 표현식이다!)이 평가 되고( 을 계산한다 - 다른 일은 하지 않는다!), 수행 되는데( 을 갖지 않지만, 주어진 표현식에 의거하여 상태를 바꾼다!), 그보다 앞서 letconst, function, class 등의 데이터 타입 선언이 이루어진다 이 타입 선언은 데이터의 타입 을 알리고 이름 을 부여해서 나중에 참조 할 수 있도록 한다
                                    
                                        /* 변수의 선언 및 정의 ← 변수를 정의하면서 값을 할당해주면; 그 자체 문이다! */
                                        const radius= 2 // const 변수 radius를 선언하면서 값 2를 할당한다
                                        console.log(radius) // 2
                                        
                                        /* 함수 이름(area)으로 (인수 radius의 값 2를 전달하면서)area 함수를 호출한다 */
                                        area(radius); // 12.566370614359172
                                        
                                        /* 함수의 선언 및 정의 ← 함수는 '호이스팅'된다! */
                                        function area(radius) { /* 함수 객체({ .. })를 생성하여 이름(area)을 붙여준다 ← 전달된 인수가 있으면; 매개변수(radius)로 받는다 */
                                            // 전달받은 매개변수(radius)를 써서 작업을 수행하고(Math.PI * radius * radius),
                                            return Math.PI * radius * radius // 그 결과물을 호출자에게 돌려준다(retrun 문)
                                        }
                                    
                                

함수는 선언하는 즉시 맨 위로 끌어올려지고('호이스팅'), 그리하여 함수가 정의되기 이전이라도 그 이름으로 함수를 호출할 수 있다!

표현식이란 어떤 값으로 평가되는 무엇 인데, 숫자(1)나 문자열 리터럴("안녕?"), 변수명(radius)도 (그에 할당된 값으로 평가되므로)표현식이다. 나아가, 연산자로 연결된 x+y 또한 어떤 결과값을 나타내는 표현식이다. 스크립트 예약어인 true, false, null, undefinedthis 역시 표현식이고, 배열의 요소에 접근하거나(ary[index]) 객체의 프로퍼터에 접근하는 것(obj.key), 함수 정의문(function() { .. }), 객체 생성자 함수(new Object();) 또한 표현식이다
1. 함수호출 표현식: Fnc(args); 먼저 Fnc(= 함수명)를, 이어서 args (= 인자)를 평가한 다음, Fnc 함수의 본체({ .. })를 실행하게 된다 호출한 함수 내부에서 return 문을 반환한다면; 그 값이 함수 호출 표현식의 이 되고, 아니라면; undefined 값이 반환된다!
2. 메서드호출 표현식: Obj.sort(); sort(); 메서드에 접근하는 Obj(객체 또는 배열)는 해당 메서드 본체 안에서 this 키워드의 이 되고, 자신을 호출한 객체에 작용하게 된다!
3. 프로퍼티접근 표현식: 객체.식별자에서 앞의 표현식이 먼저 평가되고, 이어서 .식별자 를 찾고 그 값이 표현식의 전체 이 된다. 객체[프로퍼티키]배열[인덱스번호] 접근에서는 내부 표현식을 평가하여 문자열로 변환하고, 그것이 전체 표현식의 으로 된다 앞의 표현식이 null 이나 undefined 라면; 타입에러가 발생한다!
문이란?
표현식은 '평가'를 통해 으로 바뀌고, 은 '실행'을 통해 어떤 동작 을 수행하는데, '부수 효과'가 있는 표현식을 평가하는 것(= 표현문)은 그 자체로 문이 될 수 있다. 나아가, 변수를 선언하거나 함수를 정의하는 것 또한 일종의 문이라고 할 수 있다
1. 스크립트에서 가장 기본이 되는 문은 조건문인데, if 문은 조건 을 평가하여 trusy한 값 이라면; 작업을 수행하고, falsy한 값 이라면; (아무 일도 하지 않고, 그냥 나가서)아래 코드로 내려간다
[ if 문 ]
                                        
                                            if(조건) 코드; 
                                        
                                    

조건 을 '평가'한 '값'이 참이라면; 코드 를 수행하고, 아니라면; 그냥 건너뛰어 아래로 내려간다

2. if .. else 문은 조건 을 평가하여 각각의 방향으로 분기한다
[ if .. else 문 대 조건 연산자 ]
                                        
                                            if(조건) 코드1; // 조건에 맞으면; 코드1을 수행한다
                                            else 코드2; // 아니라면; 코드2를 수행한다
                                        
                                    

if .. else 문삼항 조건 연산자를 써서 보다 간결하게 작성할 수 있다: 조건 ? 코드1 : 코드2; 조건 이 'trusy한 값'이면; 코드1, 'falsy한 값'이면; 코드2 를 수행한다

3. if 문을 중첩할 수도 있고, if .. elseif .. else 문은 순차적으로 조건 을 평가하면서 내려간다
[ 중첩 if 문, if .. elseif .. else 문 ]
                                        
                                            if(조건) { // 조건이 참이면; 코드1을 수행한다
                                                코드1;
                                            } else { // 아니라면; 여기로 온다
                                                if(조건2) 코드 2; // 조건2가 참이면; 코드2를 수행한다
                                                else 코드3; // 아니라면; 여기로 온다
                                            }
                                        
                                    
                                        
                                            if(조건) 코드1; // 조건이 참이면; 코드1을 수행한다
                                            else if (조건2) 코드2; // 아니라면; 여기로 온다
                                            else if (조건3) 코드3; // 역시 아니라면; 여기로 온다
                                            else 그외; // 위 모든 조건을 만족하지 못하는 경우; 여기로 온다!
                                        
                                    

참고로, if 등의 조건문을 쓰는 대신, 조건 연산자 ?나 논리 연산자 ||&&를 적절히 활용하면; 보다 간결한 코드를 작성할 수 있다!

4. if .. elseif .. else 문이 순차적으로 조건 을 평가하면서 내려가는 반면, switch .. case 문은 조건 을 평가하여 맞는 case 로 분기한다
[ switch .. case 문 ]
                                        
                                            switch(조건) { // 조건값(=== 비교)에 따라 맞는 case로 분기한다..
                                                case 1: 실행문; // break가 없으므로, 계속 다음 케이스를 수행한다..
                                                case 2: 실행문; // 조건에 맞는 case라면; 작업을 수행하고,
                                                    break; // 작업 수행 후에는 switch문을 빠져나간다!
                                                case 3: 실행문;
                                                    return "리턴"; // 실행문 안에 return문이 들어 있다면; break는 생략할 수 있다!
                                                default: 실행문; // 조건에 일치하는 case 값이 하나도 없을 때 여기로 온다
                                            }
                                        
                                    

switch 문의 case 값에 표현식을 사용하는 것도 가능하지만, 숫자나 문자열 리터럴을 쓰는 것이 안전하고 알기 쉽다!

5. for 루프문은 스크립트에서 가장 많이 사용되는 중요한 문인데, 카운터 변수 초기값 을 시작점으로 하여 조건 을 평가하여 블록 안으로 들어가 작업을 수행하고, 증감식 으로 초기값을 증감시키면서 (조건에 부합하는 한도 내에서)반복 작업을 수행해나간다
                                    
                                        const ary= [1, 2, 3] // 배열 변수 ary 선언 및 정의

                                        // for 루프문으로 배열 ary의 각 요소 값을 0으로 바꾼다
                                        for(let i=0; i < ary.length; ary[i++]=0) { // 카운터 변수 선언 및 초기화; 조건 평가; 증감 카운터
                                            ; // 빈 문 ← for 루프에서 이 ;만으로도 필요한 일을 수행할 수 있다!
                                        }
                                        console.log(ary) // [0, 0, 0] ← for 문의 증감식(ary[i++]=0)에 의해 배열의 각 요소값으로 들어갔다!
                                    
                                

* cf) 부수 효과가 있는 표현식과 대입 할당문(및 증가/감소 연산자가 붙은 변수), 객체의 프로퍼티를 삭제하는 delete 연산자, 함수호출 또한 '부수 효과'가 있어 사실상의 '문'으로 볼 수 있다!

변수와 스코프

변수는 숫자나 문자열과 같은 을 담는 컨테이너인데( 이 아니라 값을 담는 컨테이너 임!), 변수는 반드시 선언한 이후에 사용해야 한다 선언 없이 할당 하는 경우; Reference Error!가 발생하며, 값을 주지 않고 변수를 선언하기만 하면; 그 변수에는 일단 Undefined 값이 할당된다!

변수의 선언 및 정의
변수선언이란 식별자 (let)를 붙여서 그 존재(num)를 알리는 것이며(let num), 정의는 선언과 동시에 을 부여하는 것이라고 할 수 있는데(let num2= 2), (값 할당 없이)변수를 선언만 하면; 스크립트는 내부적으로 이 변수에 undefined 값을 주어 초기화하며(이제, 변수에 접근할 수 있다!), 이후 실제로 변수에 할당하는 이 실행되면(num= 1); 이제 비로소 변수에 (실제적 의미가 있는) 이 들어가게 된다:
                                    
                                        let num // let 식별자를 붙여서 변수 num을 '선언'한다 ← num은 undefined 값으로 '초기화'된다! 

                                        let num2= 2 // 변수 num2를 '선언'하면서 동시에 '값'도 넣어준다 ← 변수의 '정의'
                                    
                                

[관리자모드] 콘솔창에서 num을 쳐보십시오.. 변수를 '선언'만 하고 을 넣지 않았으므로 undefined 값이 나와야합니다! 변수 num2 과 함께 정의하였으니 들어있는 값 2 가 나올겁니다..

                                    
                                        num= 1 // 위에서 선언된 변수 num에 값 1을 할당한다 ← 위에서 미리 존재를 알렸던 (선언만 한)변수 num에 추후 값을 할당한다!
                                    
                                

이제 값을 넣었으니 num을 치면 1이 나와야합니다.. 이어서, num3도 쳐보십시오. 선언한 적도, 정의한 적도 없는 변수이니 ReferenceError: num3 is not defined라고 나올겁니다.. 선언하지 않은 것(= 존재하지 않는 것)은 값을 넣을 상자(= 변수) 자체가 없다는 것이며(ReferenceError!), 정의된 값이 없다는 것은 상자(= 선언된 변수)는 있지만 그 안은 비었다는 것(undefined)을 의미한다!


* cf) 스크립트에서 표현하고 다룰 수 있는 의 종류를 타입 type 이라고 하며, 프로그램에서 을 나중에 사용하려면 변수저장 할당 해야 한다. 변수에는 이름이 있고, 프로그램은 변수명 을 통해 참조한다. 한편, 리터럴직접 넣어준 데이터 값 이다: 3 (숫자), "hello, world" (문자열), true/false (참/거짓), null 등..

변수의 스코프
변수 스코프란 스크립트 코드에서 해당 변수가 정의된 영역(곧, 접근 가능한 범위)을 말하는데, let 변수const 상수는 if 문이나 for 문, 함수 본체의 블록({ .. }) 안에서만 접근 가능한 블록 스코프를 갖는다
                                    
                                        let num= 1
                                        console.log(num) // 1

                                        num= 2 // num에 새로이 값 2를 할당한다 ← let 변수는 언제든 값을 바꿀 수 있다!
                                        console.log(num) // 2
                                        
                                        const str= '문자열'
                                        str= '새 문자열' // 타입 에러! ← const 상수는 초기값을 바꿀 수 없다!
                                    
                                

let 변수는 언제든 값을 바꿀 수 있는 반면(곧, 변수 값 재할당이 가능하다!), const 상수는 초기값을 바꿀 수 없다(따라서, 선언과 동시에 값을 넣어주어야 한다!)

                                    
                                        function fnc() {
                                            for(let i=0; i < 10; i++) {
                                                ; // let 변수 i는 for 블록 내부에서만 유효하다!
                                            }

                                            return i // 여기서는 for 블록 내부의 i 값에 접근할 수 없다!
                                        }
                                        console.log(fnc()) // ReferenceError! i is not defined
                                    
                                

블록 바깥에서 블록 내부 스코프로는 접근할 수 없기에 ReferenceError!가 발생한다!


* cf) 블록 바깥에서 블록 내부 스코프로는 접근할 수 없지만, 블록 내부에서 블록 바깥 스코프로는 언제든 접근할 수 있다!

                                    
                                        function fnc() {
                                            let i; // fnc() 함수 내부 변수 i를 선언한다!

                                            for(i=0; i < 10; i++) {
                                                ; // for 블록 내부에서 바깥의 i 변수에 접근할 수 있다!
                                            }

                                            return i
                                        }
                                        console.log(fnc()) // 10
                                    
                                
➥ 렉시컬 스코프와 스코프 체이닝

렉시컬 스코프는 (런타임 중 함수 호출에 의해 결정되는 동적 스코프와는 달리) 함수 및 변수가 어디서 작성되었는가에 따라 그 경계가 결정된다 는 규칙으로서, 예컨대 함수 내부 중첩된 스코프에서 코드가 실행된 경우; 가장 안쪽의 스코프로부터 시작하여 위로 올라가면서 접근하는데(= 스코프 체이닝 찾으면; 거기서 검색을 중단한다!), 반대로 바깥 스코프에서 내부 스코프로는 접근할 수 없다는 것이다!

변수 호이스팅
변수 호이스팅이란 변수가 선언 될 때; (선언과 동시에 자신이 속한)스코프의 최상단으로 끌어올려져서 정의 되는 것을 의미하는데, 렉시컬 스코프 를 갖는 var 변수는 선언과 동시에 초기화도 함께 수행되고, 나아가 자신이 속한 스코프의 최상단으로 끌어올려지게 된다!
                                    
                                        function fnc() {
                                            /* ---
                                                아래 for 문의 var 변수 i는 선언과 함께 호이스팅되어 스코프(= fnc 함수)의 맨 위에 자리잡게 된다!
                                            - */
                                            for(var i=0; i < 10; i++) { /* var 변수 i는 선언과 동시에 호이스팅된다! */
                                                ; // 루프를 돌 때마다 i 값이 증가된다..
                                            } /* 루프를 다 돈 후, 최종 i 값이 함수 스코프의 맨 위로 호이스팅된다! */
                                                
                                            /* ---
                                                위 for() {..} 블록의 var 변수 i는 호이스팅되므로, for 블록 바깥으로 나가게 되고..
                                                이 fnc() {..} 함수 스코프의 맨 위에 자리잡은 i 값을 (같은 함수 블록 내부 영역이므로)여기서 접근할 수 있다!
                                            - */ 
                                            return i // 나중에 이 함수가 호출되면; 루프를 다 돈 i 값(= 10)을 반환한다!
                                        }

                                        /* ---
                                            이제 fnc 함수를 호출하여 리턴값을 돌려받는데..
                                            함수 내부 var 변수 i의 (이미 맨 위로 호이스팅된!)최종 결과값인 10을 돌려받게 된다! 
                                        - */
                                        fnc(); // 10 ← 함수 호출
                                    
                                

전역변수인 var 변수와는 달리, 블록변수인 let 변수const 상수선언초기화 가 분리되어 수행된다. 곧, 선언 시에는 스코프의 최상단으로 끌어올려지지만, 초기화 는 그 변수에 할당 된 이후에 수행되는 것이다!

let 변수const 상수는 함수 내 모든 곳에서 접근 가능한 var 전역변수와는 달리, 선언한 해당 블록({ .. }) 내부에서만 인식되며, 또한 변수 선언 시 호이스팅도 일어나지 않는다
                                    
                                        var topic= "전역변수"

                                        {
                                            topic= "블록변수" // 블록 바깥에 있는 var 전역변수 topic에 접근하여 변경할 수 있다!
                                        }
                                        console.log(topic) // 블록변수 ← 기존에 정의했던 topic 변수의 값이 바뀌었다!
                                    
                                
                                    
                                        var topic= "전역변수"

                                        { /* 블록변수 let는 블록 내부에서만 유효하다! */
                                            let topic= "블록변수" // 이름은 같지만, 블록 바깥의 전역변수 topic과는 전혀 무관한 새로운 블록 내부변수이다! 
                                            console.log(topic) // 블록변수 ← 먼저, 자신의 내부 블록에 접근한다!
                                        }

                                        console.log(topic) // 전역변수 ← 전역변수 topic의 값은 변함이 없다!
                                    
                                
* 각 박스를 클릭해보세요
                                                    
                                                        
                                                    
                                                
                                                    
                                                        #clickbox_testing { display: flex; justify-content: space-around; }
                                                        #clickbox_testing > div {
                                                            height: 4em; width: 8em; background-color: rgb(125 129 117 / 0.5);
                                                        }
                                                    
                                                
                                                    
                                                        
                                                    
                                                

var 변수 i 를 쓰면; 예상과는 달리, 어느 박스를 클릭하건 '박스: 4'가 나오게 된다! 이 문제는 var 대신 let 변수로 바꿔주면 해결된다!

데이터 타입

식별자(변수나 상수, 함수의 이름)와 리터럴(식별자 객체에 직접 넣어주는 값: 문자열, 숫자, 불린 등), 참조, 원시 데이터(String, Number, Boolean, Undefined, Null, Symbol) 대 참조 데이터(Array, Object 등..)의 개념

데이터 타입 확인
typeof value 연산자는 피연산자 value 의 타입을 (문자열로)반환하는데, 그 리턴값에는 number (숫자), string (문자열), boolean (참/거짓), object (객체, 배열, null), undefined (정의되지 않음), function (함수), symbol (심볼 데이터) 등이 있다
                                    
                                        const value= "문자열"
                                        typeof value === "string" ? value : value.toString(); // value 값을 가져오게 된다
                                        // 이는 ((typeof value) === "string") ? A : B와 같다!
                                        console.log(value) // "문자열"

                                        const value2= NaN
                                        typeof value2 === "string" ? value2 : value2.toString(); // value2 값을 문자열로 변환하여 가져온다
                                        console.log(value2) // "NaN"
                                    
                                

nullundefined 를 제외한 모든 값에는 문자열로 변환하는 toString(); 메서드가 존재하는데, 그 결과는 대개 String(); 메서드가 반환하는 값과 같다!

➥ Object 데이터형

객체와 배열, null 모두 object 자료형으로 나타나므로 객체의 구별 시에는 instanceof 연산자를 쓸 수 있고, null 타입 변수의 유형을 확인할 때는 일치 연산자 ===를 사용하여 변수의 값을 직접 확인해야 한다 동등 연산자 ==는 값(만!)으로 비교하는 반면, 일치 연산자 ===는 값과 데이터형이 (모두!)같은지를 비교한다

참같은 값 대 거짓같은 값
참같은 truthy 값 (true : 모든 객체 및 false를 반환하는 객체, 배열 및 빈 배열, 공백 문자열 " "' ') 대 거짓같은 falsy 값 (false : 0, -0, null, undefined, NaN, 빈 문자열 ""'')
                                    
                                        const ary= ['a', 'b']

                                        if(ary.indexOf('a')) // 배열 ary에서 'a'의 인덱스번호는 0번이고, '0'은 'falsy한 값'이다!
                                            alert("참같은 값"); // (위 조건의 결과가 '참같은 값'이었다면; 이 코드가 수행되는데)
                                        else
                                            alert("거짓같은 값"); // "거짓같은 값" ← 위 조건의 결과 '0'은 '거짓같은 값'이므로 이 코드가 실행된다!
                                    
                                

if(조건) { .. } 문은 조건 이 '참같은 값'이라면 내부 코드를 실행하고, '거짓같은 값'이라면 건너뛰게 되는데, 만약 '거짓같은 값'들을 명확히 구분해야 한다면; if(조건 === null) 식으로 명시적으로 비교해야 한다!


* cf) 객체나 배열이 비어 있는지(빈 객체 및 빈 배열 모두 '참같은 값'이다)를 확인하려면; [].length(배열의 길이), Object.keys({}).length(객체의 키 개수) 등의 방식을 사용해야 한다!

➥ undefined 대 null, NaN

undefinednull 모두 값이 없음 을 나타내는데, undefined 는 시스템 레벨에서; 변수나 객체의 속성, 함수의 인수 등에서 값이 주어지지 않은 상태를 뜻하며, null 은 프로그래밍 레벨에서; 아직 모르거나 더 이상 유효하지 않은 값으로서 주로 변수 초기화에 사용된다!

NaN숫자가 아님 을 뜻한다. NaN 은 수치 연산을 해서 정상적인 값을 얻지 못할 때 반환되는 값인데, 이 특별한 숫자형은 그 자신을 포함하여 다른 무엇과도 같지 않다 - 따라서, xNaN 인지를 알아보려면; isNaN(x); 메서드를 사용해야 한다. 한편, isFinite(x); 메서드는 xNaN, Infinity (양의 무한대), -Infinity (음의 무한대)가 아닐 때(곧, 숫자이거나 숫자로 변환할 수 있을 때) true 를 반환한다 참고로, 정수 여부 확인에는 isInteger(x); 메서드를 사용할 수 있다!

데이터 타입 변환
자바스크립트는 동적 타입의 언어로, 다른 언어와 달리 변수의 데이터 유형을 지정할 필요가 없다 자바스트립트는 값의 타입에 대해 매우 관대하여, 스스로 상황에 맞추어 묵시적으로 '참같은 값'은 true 로, '거짓같은 값'은 false 로 변환하여 작업을 진행한다!
1. 예컨대, 숫자로의 변환이 필요할 때; 숫자로 인식할 수 있는 문자열은 숫자로 변환하고, 아니라면 NaN 으로 변환하는 식인데.. 약간 모호하긴 합니다만, 코드를 잘 살펴보시면 규칙을 이해할 수 있을겁니다
                                    
                                        /* [숫자와 (숫자형)문자열]의 결합: '+'는 무조건 문자열로 연결된다! */
                                        console.log("번호: " + 10) // "번호: 10" ← "번호: "는 숫자로 변환하여 계산할 수 없기에, 숫자 10을 문자열로 변환하여 문자열로 연결한다!
                                        console.log(100 + '200') // "100200" ← [숫자 + 문자열 숫자]도 무조건 문자열로 연결된다!
                                        console.log("10" + "3") // "103"

                                        /* '-', '*', '/' 등에서는 좀 다르게 작동한다! */
                                        console.log("10" * "3") // 30 ← 문자열을 숫자로 변환한 뒤 계산한다!
                                        console.log('200' - 100) // 100 ← 문자열을 숫자로 변환한 뒤 계산한다!
                                    
                                
                                    
                                        /* 변수값으로 할당 시 */
                                        let n= 1 - "zero" // 문자 "zero"는 숫자로 변환할 수 없으므로, 변수 n 값은 NaN이 된다!
                                        console.log(n) // NaN ← 숫자가 아님!

                                        /* NaN과 문자열의 결합 */
                                        console.log(n + "zero") // "NaNzero" ← NaN은 문자열로 변환할 수 있으므로 문자열 "zero"와 연결하여 문자열로 반환한다!
                                    
                                

숫자로의 변환 시 true1 로, falsenull 및 빈 문자열 ""0 으로, undefined문자열NaN 으로 변환된다


* cf) 숫자를 문자열로 변환하는 것은 문자열 병합으로 충분하지만, 배열에서는 toString(); 메서드를 통해 각 요소를 문자열로 바꾼 다음, 쉼표 등으로 연결한 문자열을 반환 받을 수 있기에 유용하게 사용할 수 있다!

                                    
                                        const arr= [1, true, false, "hello"]
                                        arr.toString(); // "1,true,false,hello" ← arr의 각 요소를 문자열로 변환한 뒤, (콤마로 구분하여)연결한다
                                    
                                
2. 스크립트의 기본 데이터 타입인 String, Number, Boolean 객체에는 값만 저장되는 것이 아니라 해당 기본 타입에 특화된 값을 저장하며(= 프로퍼티), 또한 함수 형태의 특정 기능(= 메서드)을 제공하는 역할을 한다 곧, 기본 데이터 타입의 변수들도 이들을 위해 정의된 표준 프로퍼티와 메서드들을 객체처럼 호출할 수 있다!
[ 스크립트 내장 메서드를 이용한 명시적 데이터 타입변환 ]
                                        
                                            Number("3") // 3 ← 문자열을 숫자로 변환
                                            String(false, true) // "false", "ture" ← 불린을 문자열로 변환
                                            Boolean("") // false ← 빈 문자열을 불린 데이터 타입으로 변환!
                                            Boolean(" ") // true ← 공백 문자열을 불린 데이터 타입으로 변환!
                                        
                                    
[ 스크립트 연산자에 의한 묵시적 데이터 타입변환 ]
                                        
                                            x= 1
                                            console.log(x + "") // "1" ← + 연산에서 피연산자 중 하나가 문자열이면; 다른 피연산자(x)도 문자열로 변환된다!
                                            console.log(x - 0) // 1 ← 마이너스 연산자와 함께 사용하여 x를 숫자로 변환한다!

                                            x2= -1
                                            console.log(-x2) // 1 ← 단항 +, - 연산자는 피연산자 x2를 숫자로 변환한다!

                                            x3= 1
                                            console.log(!x3) // false ← 부정 연산자 !는 피연산자 x3를 불린 데이터로 변환한다!
                                            console.log(!!x3) // true ← 이중부정 연산자 !!는 피연산자 x3를 불린 데이터로 변환한다!
                                        
                                    
[ 변수값 할당을 이용한 동적 데이터 타입 변환 ]
                                        
                                            let a= 10 // 숫자 데이터
                                            a= '문자' // 숫자 변수 a에 문자열을 넣음으로써 데이터 타입을 동적으로 변환한다!
                                            console.log(a) // "문자"
                                        
                                    

* cf) 객체인 배열이나 함수와는 달리, 원시 데이터인 숫자와 문자열, 불린은 속성을 가질 수 없지만, 앞에 new 키워드를 붙여 자료형 변환 함수인 Number();, String();, Boolean(); 메서드를 쓰면 객체로 변환하여 쓸 수 있게 된다: const hi= new String("안녕하세요?"); 이제 인스턴스 변수 hiString 객체로 다룰 수 있고, String 객체의 메서드를 이용할 수 있다!

연산자 1

스크립트에서 연산자는 하나 이상의 피연산자 를 써서 을 만드는 행위라 할 수 있는데.. 스크립트는 피연산자가 truthy한 값 으로 평가되면; 1 을, falsy한 값null 로 평가되면; 0 을 반환하고, undefined 및 계산할 수 없는 수식은 NaN 을 반환한다

부정 연산자: !, !!
부정연산자 !이중 부정연산자!!는 피연산자의 불린(= 참/거짓) 값을 반대로 반전시키는데, 보통 기존 값을 불린 타입으로 변환할 때 사용한다
                                    
                                        const num= 0 // falsy한 값
                                        console.log(!num, !!num) // true false
                                        
                                        const n_null= null // falsy한 값
                                        console.log(!n_null, !!n_null) // true false
                                        
                                        const u_underfined= undefined // falsy한 값
                                        console.log(!u_underfined, !!u_underfined) // true false
                                        
                                        const x= '' // falsy한 값
                                        console.log(!x) // true
                                    
                                

* cf) 불린 값을 숫자로 바꿀 때는 조건 연산자 ?를 사용할 수 있다

                                    
                                        const b= true // trusy한 값
                                        const n= b ? 1 : 0 // b가 trusy한 값이면; 1을, 아니라면; 0을 리턴한다
                                        console.log(n) // 1
                                    
                                
부호 연산자: +, -
부호 연산자 +, -는 숫자의 부호를 바꾸는데, 이 때 (숫자로 된 문자열이라면)숫자로의 변환 작업도 함께 이루어진다
                                    
                                        const str= '5' // 문자열
                                        const x= -str // (숫자로된 문자열의 숫자로의 데이터 변환과 함께)부호 변환
                                        console.log(x) // -5
                                    
                                

참고로, 변수가 아닌 리터럴 숫자 데이터 앞에 붙을 경우에는 그저 양수, 음수를 의미할 뿐이다!

증감 연산자: ++, --
증감 연산자 ++, --는 앞에 붙으면; 먼저 자신을 증감시킨 다음 계산하며, 뒤에 붙으면; 먼저 계산 결과를 반환한 다음에 자신을 증감시킨다
                                    
                                        let a= 1
                                        let b= a++ // 먼저, b에 1을 넣어주고(b= 1), 자신을 1만큼 증가시킨다(a= 2)
                                        console.log(a, b) // 2, 1
                                        
                                        let c= ++a // 자신을 1만큼 증가시켜서(a= 3) c에 넣어준다(c= 3) 
                                        console.log(a, c) // 3, 3
                                    
                                

* cf) 참고로, ++xx= x+1과 항상 같지는 않다!

                                    
                                        x= 1
                                        console.log(x++) // 1 ← 1을 반환한 뒤, 자신을 1만큼 증가시킨다(x= 2)
                                        console.log(++x) // 3 ← 먼저 1만큼 증가시킨 뒤(x= 2+1), 그 값을 반환한다
                                    
                                        x= "1"
                                        console.log(x= x+1) // "11" ← 문자열 x에 1을 문자열로 연결하여 반환한다!
                                    
                                

++ 연산자는 항상 피연산자를 (할 수 있다면)숫자로 변환해서 계산할 뿐, 문자열 병합을 수행하지는 않는다!


(* 여기서부터는 연산자의 우선 순위가 높은 쪽에서 낮은 쪽으로 내려가는데, 괄호 연산자 ()는 연산자의 우선 순위를 높이거나 명확히 하기 위해 사용된다 곧, ()는 위 단항 연산자들을 제외하고는 우선 순위가 가장 높다!)

산술 연산자: **, %, /, *, -, +
산술 연산에서는 피연산자 를 평가해서 (필요하다면)숫자로 변환하여 계산을 수행하는데, 숫자가 아니고 숫자로 변환할 수도 없다면; 피연산자는 NaN 으로 변환된다. 한편, 정수끼리 나누어도 결과는 실수값이 되며(자바스크립트는 64비트 실수 형식을 사용해 숫자 데이터를 다룬다!), 실수 간의 나머지 연산도 가능하다(5 % 1.5 // 0.5). **는 제곱 연산자다: 2 ** 3 // 2의 3승

* cf) + 연산자는 피연산자가 모두 숫자이면 더하고(덧셈 연산), 모두 문자열이면 병합하며(문자열 연결), 문자열 + 숫자 (및 숫자 + 문자열) 이라면; 숫자를 문자열로 변환한 뒤, 하나로 연결하여 연결된 문자열로 반환한다

                                    
                                        console.log(1 + '2weak') // 12weak ← 문자열 병합
                                        console.log(1 + 2 + "문자열") // 3문자열 ← 산술 연산 이후 문자열로 병합
                                        console.log(1 + (2 + "문자열")) // 12문자열 ← 문자열로 병합
                                    
                                
                                    
                                        console.log(true + true) // 2 ← true를 1로 변환한 뒤 산술 연산
                                        console.log(1 + null) // 1 ← null을 0으로 변환한 뒤 산술 연산
                                        console.log(1 + undefined) // NaN ← undefined를 NaN으로 변환한 뒤 산술 연산을 하되, NaN으로는 산술 연산을 할 수 없으므로 NaN을 리턴한다!
                                    
                                
산술할당 연산자: **=, *=, /=, %=, +=, -=
산술할당 연산자는 산술 계산 이후 결과값을 좌변으로 할당한다. 곧, a += ba= a + b와 같다
                                    
                                        let a= 1, b= 2 // 콤마 연산자 ,는 앞에서 뒤로 순차적으로 해석해나간다!
                                        console.log(a += b) // 3 ← 이는 a= (a + b)와 같다!

                                        const c= 1, d= 2 // const 상수는 값을 변경할 수 없다!
                                        console.log(c += d) // Uncaught TypeError: Assignment to constant variable.
                                    
                                

* cf) 참고로, 문자열의 경우; +=는 계속적으로 뒤에 덧붙여진다. 곧, 문자열 연결이 되는데, 이는 스크립트로 <ul>(및 <li>) 리스트를 작성하는 경우 등에 유용하게 쓰인다!

연산자 2

비교 연산자: <, <= 및 >, >=
비교 연산자는 순서가 있는 데이터 타입(숫자나 문자열)의 크기(숫자) 및 우선 순위(문자는 16bit ASCII 값)를 비교한다 (비교는 숫자나 문자열에 대해서만 가능하므로)피연산자를 숫자나 문자열로 변환한 다음에 평가가 이루어진다!
예컨대, + 연산자가 문자열을 우선시하는 반면(하나라도 문자열이면; 모두 문자열로 변환하여 연결한다!), 비교 연산자는 숫자를 우선시하여 두 피연산자 중 하나라도 숫자라면 나머지 연산자도 숫자로 변환하여 비교하려 시도하고, 두 연산자가 모두 문자열인 경우에만 문자열로 비교하는 식이다!

* cf) 문자열 비교시의 순서는 유니코드에서 정의한 것으로, 특정 언어나 지역에서 받아들이는 순서 개념과는 다를 수 있다. 또한, 영문 대/소문자를 구별하며 대문자의 ASCII 값이 소문자의 ASCII 값보다 작다는 것도 염두에 두어야 한다 참고로, String.localeCompare(); 메서드는 해당 지역의 상식에 맞는 알파벳 순서를 판단 기준에 포함하며, Intl.Collator API를 쓰면; 보다 더 상식적인 문자열 비교를 할 수 있다!

일치 연산자 대 동등 연산자: ===, ==
일치 연산자 ===, !==는 두 값이 일치하는지/일치하지 않는지 여부를 판단하는데, 자료형과 값 모두가 정확히 일치하는지, 객체의 참조가 일치하는지 여부를 따진다. 반면, 동등 연산자 ==, !=는 자료형은 따지지 않고 값이 같다고 볼 수 있는지(trusy)/틀리다고 볼 수 있는지(falsy)만 비교한다
                                    
                                        console.log(1 == '1') // true ← '=='는 숫자 값만으로 비교한다!
                                        console.log(1 === '1') // false ← '==='는 숫자 값만 아니라 데이터 타입까지 비교한다!                                        

                                        console.log("1" == true) // true ← 먼저, 불린 true를 숫자 1로 바꾸고, 이어서 문자열 "1"을 숫자 1로 바꾼 다음 비교한다!
                                    
                                

* cf) 보통은 일치 연산자를 쓰는 것이 권장되지만, 가끔은 동등 연산자도 유용하게 쓰일 수 있다 - 예컨대, 특정 값이 비어 있음을 판단하는 경우에 활용할 수 있다!

                                    
                                        function isEmpty(a) {
                                            if(a == null) return; // a 값이 null이라면; 아무 일도 하지말고 그냥 리턴하라!
                                            else { 그렇지 않으면; 이 코드를 수행하라 }
                                        }
                                    
                                

동등 연산자 ==는(자료형은 따지지 않기에) null == undefined, "0" == 0, "0" == false, "1" == true를 모두 참으로 간주한다!

논리 연산자: &&, ||
논리 연산자 ||는 가령, true || ++x 연산에서는 앞쪽이 이미 참이므로 뒤의 ++x 계산은 수행하지 않고(= 단축 평가), false || ++x 연산에서는 앞쪽이 거짓이므로 뒤의 ++x 계산이 수행된다(= 부수 효과)
                                    
                                        let x= 0

                                        const result= true || x++ // '참같은 값'이 앞에 오면; 앞쪽만(!) 평가하여 반환한다 ← 단축 평가!
                                        console.log(result, x) // true 0

                                        const result2= false || x++ // '거짓같은 값'이 앞에 오면; 뒤쪽을 평가하여 반환한다 ← 부수 효과!
                                        console.log(result2, x) // 0 1 ← 먼저, result2에는 기존 x 값이 반환되고, 이후에 x 값이 증가된다!
                                    
                                
                                    
                                        let x= 0

                                        const opt= {} || ++x // 빈 객체 {}는 Trusy한 값이므로; opt에는 {}가 들어가고, x 값은 그대로이다 ← 단축 평가!
                                        console.log(opt, x) // {} 0

                                        const opt2= '' || ++x // 빈 문자열 ''는 Falsy한 값이므로; x는 1이 증가하고, opt2에는 증가한 x 값이 들어간다 ← 부수 효과!
                                        console.log(opt2, x) // 1 1
                                    
                                

불린( true/false ) 피연산자를 사용하면 논리 연산자는 항상 불린 값으로 반환한다. 그러나 피연산자가 불린이 아니라면; 그 결과를 결정한 값으로 반환한다!

논리 연산자 &&는 가령, false && ++x 연산에서는 앞쪽이 이미 거짓이므로 뒤쪽 ++x는 건너뛰고(= 단축 평가), true && ++x 연산에서는 앞쪽이 참이므로 뒤쪽 ++x 계산을 수행한다(= 부수 효과)
                                    
                                        const p= { name: 'Kim', age: 18 }

                                        console.log(null && p.age) // null ← 앞쪽 null이 이미 거짓이므로, 뒤쪽은 건너뛴다(= 단축 평가)
                                        console.log({} && p.age) // 18 ← 빈 객체는 참이므로, 뒤쪽을 평가하여 반환한다(= 부수 효과)
                                    
                                
if 문조건 에서 논리 연산자 ||&&를 적절히 사용하면; if 문을 복잡하게 중첩하지 않고도 간결한 조건문을 작성할 수 있다
                                    
                                        function isLeap(y) { // 윤년 확인 함수
                                            if((y%400 === 0) || (y%4 === 0) && (y%100 !== 0)) return "윤년임!";
                                            else return "윤년 아님!";
                                        }
                                        alert(isLeap(2025)) // 윤년 아님!
                                    
                                

참고로, &&||보다 연산자의 우선순위가 높은데, 이런 경우에는 ()를 사용해주는 것이 알아보기 쉽다: (y%400 === 0) || ((y%4 === 0) && (y%100 !== 0))


* cf) if 문논리 연산자를 써서 표현할 수도 있는데, 아래는 객체를 초기화하는데 쓰인 각각의 코드이다

                                    
                                        let opt= true
                                        if(opt) opt= {} // 조건이 참이므로, opt에는 빈 객체 {}가 들어간다
                                        // opt= opt && {} ← (앞이 참이기에)뒤쪽도 살펴야 하므로, 부수 효과가 발생하여 opt는 {} 값으로 초기화된다!

                                        let opt2= false
                                        if(! opt2) opt2= {} // 조건이 참이므로, opt2에는 {}가 들어간다
                                        // opt2= opt2 || {} // ← (앞이 참이 아니기에)뒤쪽도 살펴야 하므로, 부수 효과가 발생하여 opt2는 {} 값으로 초기화된다!
                                    
                                
널 병합 연산자: ??
ES2020에서 채택된 Null 병합 연산자 ??는 'falsy'한 값인 ""0, NaN(falsy한 값이지만, 정의된 값들이다!)으로부터 nullundefined (정의되지 않은 falsy한 값들이다!)를 구분한다
                                    
                                        const x= 0 // 'falsy'한 값들: 0, "", NaN 대 null, undefined

                                        console.log(x || 10) // 10 ← x는 'falsy'한 값이므로, ||에서는 뒤쪽도 평가한다!
                                        console.log(x && 10) // 0 ← x는 'falsy'한 값이므로, &&에서는 뒤쪽은 건너뛴다!

                                        console.log(x ?? 10) // 0 ← x가 null이나 undefined는 아니므로, 뒤쪽은 건너뛴다!
                                    
                                
                                    
                                        let z // 값을 주지 않고 선언만 한 변수에는 'undefined'가 들어간다!
                                        console.log(z ?? 10) // 10  ← z 값이 'undefined'이므로, 뒤쪽 값을 넣어 초기화한다!
                                    
                                

곧, ?? 연산자는 앞 부분이 null 이나 undefined 라면(곧, 값이 정의되지 않은 경우); 뒤쪽을(평가하여) 반환하며(= 부수 효과), 아니라면; 앞쪽 값을 (그대로)반환하는 것이다(= 단축 평가)


* cf) Null 병합 연산자null 이나 undefined 처럼 실제로 값이 비어있는 경우에만 디폴트 값을 설정하고 싶을 때 유용하게 사용할 수 있다. 곧, a ?? b(a !== null && a !== undefined) ? a : b와 같다! 참고로, &&||?? 연산자간의 우선 순위는 명확하지 않으므로, 혼용 시에는 ()를 사용해주는 것이 좋다!

논리할당 연산자: ||=, &&=, ??=
ES2021에서 새로 채택된 논리할당 연산자는 논리 연산 이후의 결과값을 좌변으로 할당한다
                                    
                                        a ||= b // a= a || b
                                        a &&= b // a= a && b
                                        a ??= b // a= a ?? b
                                    
                                

연산자 3

연산자의 우선순위는 복잡한 표현식에서 어떤 순서로 작업을 수행할지를 규정하는데, 괄호 연산자 ()는 연산자의 우선 순위를 높이거나 명확히 하기 위해 사용된다 - 곧, ()! ++ 등의 단항 연산자를 제외하고는 우선 순위가 가장 높다 연산자의 우선 순위가 헷갈리는 경우, ()로 묶어주는 것이 최선이다!

할당 연산자: =
할당 연산자 =는 뒤에서부터 앞쪽으로 체인식으로 계산해오는데.. 이는 지수, 단항, 조건 연산자에서도 모두 같다
                                    
                                        
                                    
                                
                                    
                                        x= a ** b ** c // 지수 연산자: x= a ** (b ** c)
                                        y= ~-x // 단항 연산자: y= ~(-x)
                                        q= a ? b:c ? d:e // ? 연산자: q= a ? b : (c ? d:e)
                                    
                                
콤마 연산자: ,
콤마 연산자 ,는 표현식을 앞에서부터 뒤쪽으로 순차적으로 평가해나간다
                                    
                                        
                                    
                                

참고로, 연산자 중에서 (단항 연산자를 제외하고는)() 연산자의 우선 순위가 가장 높고, , 연산자는 가장 낮다!


* cf) 프로퍼티 접근 및 호출 표현식의 우선 순위는 다른 어떤 연산자보다도 우선 순위가 높다: 예컨대, typeof myProperty.functions[x](y) { .. }에서 typeof 연산자는 프로퍼티 접근(myProperty), 배열 인덱스([x]), 함수 호출(functions(y) { .. }) 이후에 이루어진다!

➥ 연산자의 우선순위와 하위 표현식

연산자의 우선 순위는 복잡한 표현식에서 어떤 순서로 작업을 수행할지를 규정하지만, 내부의 하위 표현식이 평가되는 순서까지 결정해줄 수는 없다!


표현식이란 어떤 값으로 평가되는 무엇 인데, 스크립트는 항상 각각의 표현식을 앞에서부터 뒤로 가면서 평가한다. 예컨대, w= x + y*z에서는 w 부터 x, y, z 순으로(이 순서는 바꿀 수 없다!) (각각의 표현식들을 내부적으로)평가한 다음(각각은 모두 '표현식'이다!), 연산자의 우선 순위에 따라(또는, 괄호가 있다면; 그에 맞게 우선 순위를 바꾸어서) 계산하여 최종적으로 w 에 할당하게 된다 가령, x 에서 z 가 사용하는 변수를 증가시키는 부수 효과가 발생한다면; 그 평가 결과가 z 에 반영될수 있다는 점은 고려할 필요가 있다!

문 기본

반복문
대체로 for 문반복 횟수배열의 인덱스 를 기준으로 할 때, while 문조건 을 기준으로 할 때 사용된다 참고로, for 문에서 초기 카운터 값과 증감 카운터 값에는 ,로 여러 문을 나열할 수도 있다!
[ for 루프 ]
                                        
                                            let sum= 0
                                            for(let i=1; i < 6; i++) { // 카운터 변수 선언 및 초기화; 조건 평가; 증감 카운터
                                                sum += i // 0+1, 1+2, 3+3, 6+4, 10+5
                                            } // i 값은 최종적으로 6이 된다 ← 마지막 i 값인 5를 반환한 뒤에 증가시킨다!

                                            console.log(sum) // 15
                                            console.log(i) // Uncaught ReferenceError: i is not defined ← 여기서는 for 블록 내부 변수 i에 접근할 수 없다!
                                        
                                    

여기서는 (루프의 바깥에서 sum 변수를 선언했기에)루프 바깥에서도 변경된 sum 값에 접근할 수 있다 기본적으로, { .. } 내부에서 블록 바깥으로는 접근할 수 있지만 블록 바깥에서 블록 내부로는 접근할 수 없다(= '블록 스코프')

[ while 루프 ]
                                        
                                            let ctr= 0 // 카운터 변수 선언 및 초기화
                                            while(ctr < 5) { // 먼저, 조건을 평가한다!
                                                console.log(ctr) // 0 1 2 3 4

                                                ctr++ // 증감 카운터
                                            } // ctr 값은 최종적으로 5가 된다!

                                            console.log(ctr) // 5
                                        
                                    

for 루프에서는 일단 루프로 들어가서 조건 을 평가하는 반면, while 루프에서는 먼저 조건 을 평가하여 루프에 들어갈지 말지를 결정한다는 점에서 차이가 있다!

[ do .. while 루프 ]
                                        
                                            let ctr= 0 // 카운터 변수 선언 및 초기화
                                            do { // 일단, 루프에 들어간다!
                                                console.log(ctr) // 0 1 2 3 4

                                                ctr++ // 증감 카운터
                                            } while (ctr < 5); // 나오면서, 조건을 평가한다!

                                            console.log(ctr) // 5
                                        
                                    

do .. while 루프에서는 일단 루프로 들어가서 작업을 수행한 뒤, 루프를 나온 뒤에 조건 을 평가하여 다시 루프에 들어갈지 말지를 결정한다!

[ 중복 for 루프문으로 테이블 작성하기 ]
                                        
                                            
                                        
                                    
탈출문
break 문은 (더 이상 루프를 도는 것이 의미가 없을 때) 중도에 루프를 빠져나가며, continue 문은 루프를 빠져나와 다음 조건을 검토한다(곧, 조건 결과에 따라 다시 루프로 들어가든 곧바로 루프에서 빠져나가든 한다)
[ break 문 대 continue 문 ]
                                        
                                            const arr= [1, 2, 3, 4, 5]
    
                                            const target= 3
                                            for(let i=0; i < arr.length; i++) {
                                                if(arr[i] === target) { // 원하는 것(변수 target의 값)을 찾으면;
                                                    alert(`목표물 ${target} 발견함, 바로 루프를 빠져나갑니다..`);
                                                    break; // 원하는 것을 찾았다면; 더 이상 루프를 돌지 않고 벗어난다
                                                }
                                            }
                                        
                                    
                                        
                                            
                                        
                                    

참고로, continue는 while 루프에서는 다시 돌아가 조건을 검토하지만, for 루프에서는 (먼저, 증가식을 평가한 다음에)조건 검토로 넘어간다는 점에서 차이가 있다!


* cf) return 문은 (함수 안에서만 쓰여)현재 함수를 즉시 빠져나가는데, 호출자에게 전달하는 값이 있으면; 그 값을 반환하고, 없으면; undefined 를 반환한다!

점프문
자바스크립트에서는 모든 문장에 라벨을 붙여 점프할 수 있는데, 반복문switch 문만 아니라 자신을 둘러싼 모든 문에서 사용 가능하다
[ 점프문 사용법 ]
                                        
                                            const a= [2, 4, 6, 8, 10], b= [1, 3, 5, 6, 8, 9, 10]

                                            loopEnd:
                                            for(let i=0; i < a.length; i++) { // a 배열 루프
                                                for(let j=0; j < b.length; j++) { // b 배열 루프
                                                    if(a[i] === b[j]) { // 배열 a와 b에서 같은 값을 찾으면;
                                                        alert(`같은 값이 있습니다: ${a[i]}`); // 같은 값이 있습니다: 6
                                                        break loopEnd; // 같은 값을 만나면; 더 이상 찾지 않고 곧바로 loopEnd로 점프한다!
                                                    }
                                                }
                                            }

                                            console.log("작업 끝!") // 작업 끝!
                                        
                                    

loopEnd: 라벨은 for 문의 밑에 배치되어서는 안된다(Uncaught SyntaxError! Undefined label 'loopEnd'). 비슷하게, 함수 내부에서는 함수 바깥의 라벨을 볼 수 없으므로 함수에서 break 문을 사용하는 것 또한 적절치 않다!

문 고급

이하에서는, 여러 문들을 다양하게 결합하면서 스크립트의 문에 관해 좀 더 살펴봅니다만, 아직 배우지 않은 부분은 대충 이해하면서 넘어가시고.. 나중에 다시 와서 살펴보십시오 한번에 다 적을 수도 없고, 한번에 다 배울 수도 없습니다. 반복하여 재차 돌아오고, 다시 나아가고 하는 과정을 수없이 거쳐야만 그나마 조금씩 자바스크립트 프로그래밍의 개념이 손에(머리에!) 잡힐겁니다 ㅡㅡ;

혼합문
문의 결합
[ for 문과 if 문의 결합 ]
                                        
                                            const namsan= ["칠불암", "백운암", "상선암"]

                                            let info= "남산의 암자들: "
                                            for(let i= 0; i < namsan.length; i++) { // 카운터 변수 선언 및 초기화; 조건 평가; 증감 카운터
                                                if(i === namsan.length - 1) // namsan 배열의 마지막 인덱스 요소에 이르면; ← 배열의 인덱스번호는 0부터 시작한다!
                                                    info += "그리고 " + namsan[i] + "." // '그리고'를 앞에 붙이고, 뒤에 '.'을 찍어준다
                                                else
                                                    info += namsan[i] + ", " // 해당 암자 이름(namsan[i])을 적고, ', '로 연결한다..
                                            }

                                            document.write(info) // 남산의 암자들: 칠불암, 백운암, 그리고 상선암.
                                        
                                    
[ while 문과 if 문의 결합 ]
                                        
                                            const namsan= ["칠불암", "백운암", "상선암"]

                                            let info= "남산의 암자들: "

                                            let i= 0; // 카운터 변수 초기값
                                            while(i < namsan.length) { // 배열 namsan의 길이(3)보다 작으면 루프에 들어간다 
                                                if(i === namsan.length - 1) // 배열의 마지막 요소에 이르면; ← 배열의 마지막 요소 인덱스번호는 [배열.length-1]이다!
                                                    info += "그리고 " + namsan[i] + "."; // 마지막 요소의 값 앞에 '그리고'를 붙이고, 뒤에는 '.'을 붙여준다
                                                else // 배열의 마지막 요소 전까지;
                                                    info += namsan[i] + ", "; // 각 요소의 값에다 ','를 붙이고.. 하나의 문자열로 연결해나간다

                                                i++; // 증감 카운터
                                            }

                                            document.write(info) // 남산의 암자들: 칠불암, 백운암, 그리고 상선암.
                                        
                                    
[ if 문과 for 루프문의 결합 ]
                                        
                                            let num= prompt("몇 단?", '1 ~ 9 사이의 숫자로 입력하세요: ')
                                            if(num === '' || isNaN(num)) { // 입력한 값이 없거나 숫자가 아니라면;
                                                alert("다시 바르게 입력하세요!");
                                            } else if(num < 1 || num > 9) { // 1 ~ 9 사이의 숫자가 아니라면;
                                                alert("구구단이라니까요!!");
                                            } else {
                                                for(let i=1; i < 10; i++) {
                                                    document.write(`${num}*${i}= ${num*i} `);
                                                } // 9*1= 9 9*2= 18 9*3= 27 9*4= 36 9*5= 45 9*6= 54 9*7= 63 9*8= 72 9*9= 81
                                            }
                                        
                                    
팩토리얼
팩토리얼은 당장 이해하기에는 많이 혼란스럽습니다. 이런게 있다 하면서 간단히 보시고.. 나중에 함수 등을 다 배운 연후에 다시 돌아오십시오 ㅡㅡ;
[ if 팩토리얼 대 for 팩토리얼 ]
                                        
                                            function fact(fact_num) { // if 팩토리얼
                                                let num= fact_num;

                                                if(num <= 1) return 1;
                                                else return num * fact(num-1); // 자신을 다시 호출한다: 5 * ((5-1) * (4-1) * (3-1) * (2-1) * 1)
                                            }
                                            console.log(`${fact(5)}`) // 120

                                            for(let i=1; i <= 5; i++) { // for 팩토리얼
                                                document.write(`${i}! = ${fact(i)}, `);
                                            } // 1! = 1, 2! = 2, 3! = 6, 4! = 24, 5! = 120,
                                        
                                    
[ while 팩토리얼 대 do .. while 팩토리얼 ]
                                        
                                            function fact1(num) {
                                                let k= 1, i= 1; // 초기화 값
                                                while(i < num) { // 조건에 맞으면;
                                                    k *= ++i; // 1x2x3x4x5 ← 증감식
                                                }

                                                return k
                                            }
                                            console.log(`${fact1(5)}`) // 120

                                            function fact2(num2) {
                                                let t= 0, j= num2 // 초기화 값
                                                do { // 일단, 루프에 들어가고..
                                                    t += j-- // 5+4+3+2+1 ← 증감식
                                                } while (j > 0); // 나오면서.. 조건 검토

                                                return t
                                            }
                                            console.log(`${fact2(5)}`) // 15
                                        
                                    

숫자 다루기

자바스크립트는 산술 연산자Math 객체Number 객체의 프로퍼티와 메서드로 숫자를 다룬다

숫자 리터럴과 Number 객체
스크립트에서 숫자 데이터는 정수와 함께 실수를 (대략적으로)표현하는데, 64비트 실수 형식을 사용해 숫자를 다룬다. 스크립트에 직접 기입하는 숫자(= 숫자 리터럴)에는 정수(예: 0)와 실수(예: 3.14)가 있다
1. Number 객체Number(); 메서드는 인수로 주어진 모든 것을 숫자로 바꿔서 반환하는데(true는 1, false와 null은 0, NaN, undefined, 배열과 객체는 NaN), 이는 숫자 데이터로 변환해줄 뿐, Number 객체의 인스턴스가 아니다: let num= Number("500") num 은 문자열 자료형이 아니라 (숫자로 바뀌어 반환된)숫자형 데이터이다!
                                    
                                        let num= "7" // 이 num은 숫자 데이터가 아니라 문자열 데이터 '7'이다!
                                        num += 1 // 문자열+숫자는 무조건 문자열로 결합된다!
                                        console.log(num) // '71' ← 문자열
                                        console.log(typeof num) // string ← 문자열 데이터

                                        let num2= "7" // 이 num2는 숫자 데이터가 아니라 문자열 데이터 '7'이다!
                                        num2= Number(num2) + 1 // Number() 메서드로 문자열을 숫자로 변환한 뒤 숫자 1을 더해준다!
                                        console.log(num2) // 8 ← 숫자
                                        console.log(typeof num2) // number ← 숫자 데이터
                                    
                                

* cf) Number(); 메서드는 엄격하게 오직 10진 정수에만 동작한다. 반면, parseInt("문자열"[, 진수]);, parseFloat("문자열");은 주어진 문자열 에서 숫자로 인식 가능한 한에서 정수, 실수로 변환하여 돌려주며(시작 부분에서 만나는 공백들은 건너뛰고, 숫자로 판단할 수 있는 부분만 변환한 뒤 나머지는 무시한다 - 시작부터 숫자가 아닌 것으로 판단되면 NaN 을 반환한다), 실수.toFixed(자릿수);는 실수를 주어진 소수점 이하 자릿수 만큼 (반올림하여)표시해준다. 한편, 숫자.toString([진수]);으로 숫자를 기수 (기본값: 10진수)에 맞게 변환한 뒤 문자열로 반환할 수도 있다

2. 자바스크립트는 산술연산 과정에서 0 으로 나누거나 오버플로(Infinity)나 언더플로(-Infinity)가 발생해도 에러를 일으키지 않는다. 한편, 00 으로 나누거나 숫자로 변환할 수 없는 값을 만나면; 특별한 값 NaN(= 숫자가 아님)을 반환한다 이는 모두 스크립트 전역상수이며, 동시에 Number 객체의 프로퍼티이기도 하다!
                                    
                                        let a= prompt("첫번째 숫자 입력: "), b= prompt("두번째 숫자입력: ")

                                        if(isNaN(a) || isNaN(b)) { // a나 b 중 하나라도 숫자가 아니라면;
                                            alert("정확히 입력해주세요!")
                                        } else { // 모두 숫자를 입력했으면;
                                            a= Number(a); // 입력받은 값을 숫자로 변환하여 변수 a에 값으로 넣는다
                                            b= Number(b); // prompt(); 함수는 문자열로 값을 반환하므로 Number(); 메서드를 써서 다시 숫자로 바꿔준 것이다!
                                            console.log(a + b) // 숫자 계산
                                        }
                                    
                                

자바스크립트의 숫자 형식 관련 메서드는 모두 숫자가 아니라 문자열을 반환한다 - 따라서, 숫자 형식을 바꾸는 건 실제로 표시하기 직전에 해야 한다! 한편, 숫자를 저장하거나 계산할 때는 따로 형식을 지정하지 않은 숫자 타입이어야 한다!


* cf) 자바스크립트에서 숫자는 모두 64bit 더블형 실수로 처리된다 - 따라서, 두 숫자의 일치 여부를 판별할 때는 주의해야 한다! 한편, 소수 부분을 버린 정수 부분만을 구하려는 경우는 Math.floor(숫자); 메서드를 사용하면 된다: const num= Math.floor(5/2) // 2 ← 소수점 이하는 버림!

수학/삼각 함수: Math 객체
Math 객체는 스크립트 전역객체이므로 따로 인스턴스를 생성할 필요없이, 곧바로 Math 객체의 속성이나 메서드를 사용하면 된다: Math.PI(= π 값)
                                    
                                        Math.abs(-10) // 10 ← 절대값
                                        Math.sqrt(16) // 4 ← 제곱근
                                        Math.pow(2, 10) // 1024 ← 2의 10승, 제곱 연산자를 써서 2 ** 10으로 하면 더 간결하다!
                                        Math.sign(2) // 1 ← 부호계산: -1, 0, 1
                                    
                                

수학/삼각 함수들은 주로 캔버스나 SVG 애니메이션 효과 등에서 많이 사용된다!

[ Math.min/max( .. ) ]
                                        
                                            Math.min(1, 5, 3, 9) // 1 ←  가장 작은 수
                                            Math.max(1, 5, 3, 9) // 9 ←  가장 큰 수
                                        
                                    
[ Math.random() ]
                                        
                                            const season= ['봄', '여름', '가을', '겨울']

                                            const seasonNum= Math.floor(Math.random() * season.length) // (0 ~ 0.999)*4 = 0/1/2/3
                                            console.log(season[seasonNum]); // 봄, 여름, 가을, 겨울 중에서 무작위로 나온다!
                                        
                                    

Math.random();01 사이의 무작위 수(0 <= 난수 < 1)를 생성한다!

[ Math.ceil/floor/round(x) ]
                                        
                                            Math.floor(Math.random()*100 + 1); // 1부터 100까지의 정수

                                            const x= 1, y= 10
                                            x + (y-x)*Math.random(); // 1 이상 10 미만 실수
                                            x + Math.floor((y-x)*Math.random()); // 1 이상 10 미만 정수
                                            x + Math.floor((y-x+1)*Math.random()); // 1 이상 10 이하 정수
                                        
                                    

Math.ceil/floor/round(x);x 에서 소수점 이하를 올림/버림/반올림 처리하여 정수로 반환한다!


* cf) 비밀번호 등 보안이 중요한 경우에는 crypto.getRandomValues( .. );에 타입지정 배열과 그 값을 인수로 전달하여 보다 안전한 난수 배열을 생성할 수 있다

                                    
                                        const ra16= crypto.getRandomValues(new Uint16Array(5)); // 16비트 정수 5개로 된 배열
                                        document.write(ra16.join(' ')); // 50016 64878 25229 61035 52674
                                    
                                

Unit16Array/Unit32Array(n);는 부호없는 16/32비트 정수 n 개가 든 배열을 생성한다!

문자열 다루기

스크립트에서 텍스트는 string 타입인데, 16비트 유니코드 값이 순서에 따라 이어진 형태이며(UTF-16 인코딩) 불변값이다!

문자열 리터럴
리터럴 문자열"문자열"'문자열' 식으로 사용하며, 문자열간(및 문자열과 숫자, 변수간) 연결하여 하나의 문자열로 만들 때는 + 연산자를 사용하면 된다
                                    
                                        console.log("Hello! " + "world") // Hello! world ← 문자열+문자열 연결

                                        const str= "string" // 문자열 변수 str에 "string"을 넣어준다
                                        console.log(12 + str[0]) // "12s" ← 12와 str 변수의 첫번째 문자(str[0])를 문자열로 연결한다!
                                        console.log(str + 12) // "string12" ← 문자열 str에 숫자 12를(문자열로 변환하여) 문자열로 연결한다!
                                        
                                        console.log(1 + 2 + "5") // "35" ← 먼저 1+2 계산을 수행한 뒤, 문자열로 결합한다!
                                        console.log("5" + 1 + 2) // "512" ← 문자열이 앞에 오면; 모두 문자열로 연결된다!
                                        
                                        console.log(`'10+2'는 ${10+2}임`) // '10+2'는 12임 ← (템플릿 리터럴에서는; 먼저)수식 계산 이후, 문자열로 연결한다!
                                    
                                
1. 문자열의 위치는 인덱스번호 0 번부터 시작하며, 그 길이(= 16bit 값의 개수)는 length 속성으로 확인할 수 있고, 각 문자에 접근할 때는 인덱스 번호를 사용할 수 있다
                                    
                                        console.log('String'.length) // 6 ← 문자열의 길이

                                        const str= "String"
                                        console.log(str[0]) // S ← 스크립트에서 인덱스 번호는 0부터 센다!
                                        console.log(str[str.length-1]) // g ← 인덱스번호는 0부터 시작하며, 따라서 맨 끝은 length-1이 된다!
                                        
                                        console.log("".length, " ".length, "  ".length) // 0 1 2 ← 빈 문자열 "" 대 공백 문자열 " ", "  "
                                    
                                

문자열은 배열은 아니지만 배열처럼 다룰 수 있는 객체로서, 배열처럼 인덱스 번호 로 각 문자에 접근할 수 있고, 그 길이를 재는 length 속성도 갖지만, 실제로는 배열이 아닌 객체(= 유사배열 객체)이다!

2. 문자열 안에서 특수 문자를 써야 하는 경우, 이스케이프 시퀀스(\)를 써서 \`(그레이브), \'(작은 따옴표), \"(큰 따옴표), \\(역슬래시), \n(줄바꿈), \b(백스페이스), \t(), \v(세로 탭) 식으로 사용한다
                                    
                                        console.log("2행\nlines") // 두 행을 한 줄 코드로 작성함!
                                        // 2행
                                        // lines

                                        console.log(`
                                            2행
                                            lines
                                        `) // 템플릿 리터럴(` .. `)에서는 보이는대로 줄이 나뉜다 ← html의 pre 태그와 같다!

                                        console.log('1행\
                                        lines') // 1행lines ← '\'는 연결된 한 행의 문자열을 (가독성을 위해)행을 나누어 작성한다!
                                    
                                

곧, 리터럴 문자열 안에서 단독 \는 줄바꿈을 하지 않고 문자열이 이어짐을 의미한다!

3. 문자열을 비교할 때는 일치 연산자(===!==)가 기본이고 비교 연산자 또한 사용할 수 있다 (문자열의 16bit 값들로 비교가 수행되므로)두 문자열의 길이가 같으면서, 또한 정확히 같은 16bit 값으로 구성되었을 때만 일치하는 것으로 간주된다!
                                    
                                        const kim= "김", love= "\ud83d\udc99"
    
                                        console.log(kim, kim.length) // 김 1
                                        console.log(love, love.length) // 💙 2 ← 써로게이트 페어문자(= 2byte 문자)
                                    
                                
➥ 써로게이트 페어문자

써로게이트 페어문자는 스크립트의 기본 1byte(= 16bit) 유니코드 문자를 확장하여 2바이트(= 16bit + 16bit) 코드를 사용한다(길이: 2). 하지만, 문자열은 이터러블이고, 따라서 문자열에 for .. of 문이나 전개연산자 ...를 사용하면 (써로게이트 페어문자 또한)실제 문자로 순회하게 된다!

String 객체와 메서드 체이닝
let str= String(data);에서 str 인스턴스에는 인자 data 가 문자열 타입으로 바뀐 값이 들어가는데(true, false, NaN, null, underfined 모두 같은 이름의 문자열로 변환되며, [1, 2, 3] 은 "1,2,3"으로, {x:1, y:2} 는 "[object object]"로 변환된다), 이는 문자열 데이터로 변환해줄 뿐 String 객체의 인스턴스가 아니다!
                                    
                                        let str= "hello"

                                        console.log(str.toUpperCase()) // HELLO ← 여기서는 바뀐 값이 표시되지만;
                                        console.log(str) // "hello" ← 그래로 원본 값은 변함이 없다!
                                    
                                

인수로 주어진 str 을 일시적으로 String 객체로 변환하여 작업을 수행한 뒤, 임시 String 객체는 자동 삭제되고 원래의 "hello" 문자열은 여전히 원래대로 존재한다 직접 넣어준 문자열이나 숫자 등의 원시(리터럴) 데이터는 항구불변이다!


* cf) String 객체의 모든 메서드는 원시 문자열 자체를 변경하지는 않으면서 그 사본을 반환한다. 곧, 일단 생성된 리터럴 문자열은 읽기만 가능할 뿐(예컨대, 문자열의 인덱스를 이용하여 접근하여 변경해도)수정되지는 않는다 - 따라서, 이러한 '비파괴적' 메서드는 그 리턴 값을 활용해야 한다!

                                    
                                        let t= prompt("연락처: ", "") // 010-2000-2572
                                        alert(t.substring(0, t.length-4) + "****") // 010-2000-****
                                    
                                

곧, 원시 값의 데이터 변환을 위한 것이 아니라면; 원시 값을 처리할 때는 그 메서드를 바로 호출하여 사용하면 되고, 굳이 String 객체 등으로 변환하여 처리할 이유는 없다!

➥ 메서드 체이닝

메서드가 객체를 반환하면 반환한 객체의 메서드를 호출할 수 있고, 그러면 메서드를 . 연산자로 계속 연결하여 실행시킬 수 있는데, 이것이 바로 메서드 체이닝이다: 객체.메서드1( .. ).메서드2( .. );

String 객체의 메서드

String 객체의 메서드 정리

문자열 추출
Str.slice(시작 인덱스번호[, 끝 인덱스번호]);Str에서 시작 인덱스번호 부터 시작하여 끝 인덱스번호 직전까지의(생략 시는; 끝까지) 문자열을 반환한다
                                    
                                        let n= "0123456789";

                                        console.log(n.slice(0)) // 0123456789 ← [0]번 인덱스부터 끝까지
                                        console.log(n.slice(0, 3)) // 012 ← [0]번 인덱스부터 3자리
                                        console.log(n.slice(0, -3)) // 0123456 ← [0]번 인덱스부터 [-3] 직전까지
                                    
                                

음수(맨 끝이 -1)도 가능한데, 이 경우는 시작 인덱스번호 부터 끝 인덱스번호 직전까지 추출한다


* cf) Str.substring(시작 인덱스번호[, 끝 인덱스번호]); 또한 같다 단, slice와는 달리, 음수는 사용할 수 없다!

문자열 검색
Str.search("문자열");Str에서 문자열 과 일치하는 첫번째 문자열을 찾아 그 시작 인덱스번호를 반환하며, Str.match("문자열")Str에서 문자열과 일치하는 모든 문자열을 찾아서 배열로 반환한다(못 찾으면; null 을 반환한다)
Str.includes("문자열");
Str문자열(영문 대/소문자 구분)이 포함되어 있는가?
Str.startsWith/endsWith("문자열");
Str문자열(영문 대/소문자 구분)로 시작하는가/끝나는가?
Str.charAt(인덱스번호);
Str에서 인덱스번호 에 해당하는 문자을 찾아 반환한다 한편, charCodeAt(인덱스번호);는 찾은 문자의 ASCII 값으로 반환한다
Str.indexOf("문자열"[, 시작 인덱스번호]);
Str에서 문자열 과 일치하는(영문 대/소문자 구분) 첫번째 문자의 인덱스번호 를 반환하는데(못 찾으면; -1), 시작 인덱스번호 옵션으로 검색 시작 지점을 지정해줄 수도 있다 한편, lastIndexOf("문자열");는 뒤에서부터 찾는데, 뒤에서부터 찾아도 반환하는 인덱스번호 는 앞에서부터 센다!
                                    
                                        let lang= "javaScript";

                                        if(lang.indexOf("Script") !== -1) { // lang에 'Script'가 들어간 문자열이 포함되어 있다면; 
                                            console.log(lang) // javaScript
                                        }
                                    
                                
                                    
                                        const titles= "MDN - Resources for developers, by developers"
                                        const first= titles.indexOf("developers")
                                        const second= titles.indexOf("developers", first + 1) // 다음번 나오는 같은 문자열 찾기

                                        console.log(first, second) // 20 35
                                    
                                
문자열 조작
Str.replace/replaceAll("str1", "str2");은 각각 Str에서 str1 과 일치하는 첫번째/모든 문자열을 찾아 str2 로 대체하여 반환한다
                                    
                                        let phone= "010-1234-5678"
                                        phone.replace('-', '') // 0101234-5678 ← 첫 '-'를 찾아서 ''로 변경
                                        phone.replace('-', '').replace('-', '') // 01012345678 ← 두번째 '-'도 찾아서 ''로 변경

                                        phone.replaceAll('-', '') // 01012345678 ← 모든 '-'를 찾아서 ''로 변경
                                        console.log(phone) // 010-1234-5678 ← 원본은 변함이 없다!

                                        let phone2= "010-1234-5678"
                                        phone2= phone2.replace('-', '') // 결과를 변수로 저장 ← 변수값이 변경됨!
                                        console.log(phone2) // 0101234-5678 ← 원본이 변경되었다!
                                    
                                
Str.split("문자(열)");
Str에서 지정한 문자(열) 을 기준으로 분할하여 배열로 반환한다: "010-1234-5678".split("-") // ['010', '1234', '5678']
Str.concat("문자열");
Str 뒤에 문자열 을 연결하여 반환한다 참고로, 문자열 연결은 + 연산자로 결합하는게 더 간단하다: Str + "문자열"
Str.trim();
Str의 앞뒤쪽 공백 문자열 (공백, 탭, 줄바꿈 문자 등)을 제거하여 반환한다 한편 Str.trimStart/Left();Str.trimEnd/Right();는 각각 Str의 앞쪽과 뒤쪽 공백 문자열 을 제거하여 반환한다
Str.toLowerCase/toUpperCase();
Str소문자/대문자 로 변환하여 반환한다
Str.repeat(n);
Strn 번 반복하여 연결한 문자열을 반환한다
Str.padStart/padEnd(n[, 추가 문자열]);
Str의 앞/뒤에서(n 길이가 되도록) 추가문자열 로 반복하여 연결한 문자열을 반환한다
                                    
                                        "x".padStart(6, '*') // *****x 
                                        "x".padEnd(6, '*') // x***** 

                                        // 추가문자열 생략 시는; 빈 칸으로 채운다!
                                        "x".padStart(6) //      x 
                                    
                                

Str이 지정한 길이보다 더 길어지면; 문자열 변환은 이루어지지 않는다: "Hello, world!".padStart(6); 지정한 길이보다 더 길다!

나머지..
Str.normalize(["정규화방식"]);Str을 유니코드 NFC로 정규화하여 반환하는데, 인수를 주면 NFD, NFKC, NFKD 방식의 정규화도 가능하다!
Str.encodeURI/decodeURI("문자열");
문자열 을 인코딩/디코딩한다 숫자, -, _, ., !, ~, *, ', ", (, )는 이스케이프 처리한다!
Str.encodeURIComponent/decodeURIComponent("문자열");
문자열 을 인코딩/디코딩한다 \, ?, &, =, +, :, @, $, ;, #, ,도 이스케이프 처리한다!
Str.raw();
(템플릿 문자열인)Str의 원시 문자열 형식을 반환한다

Date 객체

스크립트에서 날짜나 시간을 다루려면; 먼저 Date 객체의 인스턴스를 생성해야 한다: const today= new Date();

날짜/시간 다루기: Date 객체
Date 객체의 생성자 함수 new Date();는 현재의 날짜 및 시간 정보를 가져오는데, 인자가 없으면; 현재의 날짜 및 시간을 나타내는 Date 객체가 반환된다
[ 날짜/시간 정보 가져오기 ]
                                        
                                            let today= new Date(); // 현재의 날짜/시간 정보를 가져온다
                                            console.log(today); // Thu Feb 27 2025 12:06:13 GMT+0900 (한국 표준시)

                                            let date= new Date("2025-1-1") // date1에 인자를 주어 날짜/시간 정보를 넣는다 ← "2025-01-01" 식으로 넣어도 된다!
                                            console.log(date1) // Wed Jan 01 2025 00:00:00 GMT+0900 (한국 표준시)
                                        
                                    

날짜/시간 정보를 넣어줄 때 다음과 같이 작성해줄 수도 있다: new Date(2025, 0, 1) // 2025년, 1월, 1일 ← 월은 0부터 센다!

[ 다양한 날짜/시간 표시형식 ]
                                        
                                            let d= new Date(2025, 0, 1); // 2025/1/1 00:00:00
                                            console.log(d) // Wed Jan 01 2025 00:00:00 GMT+0900 (한국 표준시)

                                            console.log(d.toString()) // Wed Jan 01 2025 00:00:00 GMT+0900 (한국 표준시) ← (문자열 데이터로 변환된)지역 시간
                                            console.log(d.toLocaleString()) // 2025. 1. 1. 오전 12:00:00 ← (각 국가별 형식으로 표기하는)지역 시간
                                            console.log(d.toDateString()) // Wed Jan 01 2025 ← (시간은 뺀)지역 시간
                                            console.log(d.toLocaleDateString()) // 2025. 1. 1. ← (날짜만의)지역 시간
                                            console.log(d.toTimeString()) // 00:00:00 GMT+0900 (한국 표준시) ← (시간만의)지역 시간
                                            console.log(d.toLocaleTimeString()) // 오전 12:00:00 ← (각 국가별 형식으로, 시간만의)지역 시간
                                            console.log(d.toUTCString()) // Tue, 31 Dec 2024 15:00:00 GMT ← UTC 시간
                                            console.log(d.toISOString()) // 2024-12-31T15:00:00.000Z ← ISO 8601 표준 형식(년-월-일T시:분:초:밀리초)의 UTC 시간
                                        
                                    
1. Date 객체로부터 날짜/시간 정보를 가져올 때는 get-, 설정할 때는 set- 으로 시작하는 메서드를 사용하는데, 각 메서드는 지역 시간대(getFullYear();)나 UTC 시간대(getUTCFullYear();)의 형태로 사용할 수 있다
                                    
                                        let today= new Date(); // 현재의 날짜/시간 정보를 가져온다
                                        console.log(today); // Thu Jun 12 2025 09:33:06 GMT+0900 (한국 표준시)

                                        const year= today.getFullYear(); // 4자리 연도 정보
                                        const month= today.getMonth() + 1; // 월 정보(0 ~ 11) ← 월은 0부터 시작한다!
                                        const day= today.getDate(); // 일 정보(1 ~ 31)

                                        const week= today.getDay(); // 요일 정보(0 ~ 6) ← 일요일이 0이다!
                                        const week_list= ['일', '월', '화', '수', '목', '금', '토'];
                                        const week_result= week_list[week]; // 요일 구하기

                                        const ymd= `${year}년 ${month}월 ${day}일 (${week_result}요일)`
                                        console.log(ymd); // 2025년 6월 12일 (목요일)
                                    
                                
                                    
                                        let today= new Date(); // 현재의 날짜/시간 정보를 가져온다
                                        console.log(today); // Thu Jun 12 2025 09:39:48 GMT+0900 (한국 표준시)

                                        today.setDate(today.getDate() + 7) // 날짜 설정: 7일 후
                                        console.log(today) // Thu Jun 19 2025 09:39:48 GMT+0900 (한국 표준시)

                                        today.setFullYear(today.getFullYear() + 1) // 연도 설정: 다음 해
                                        console.log(today) // Fri Jun 19 2026 09:39:48 GMT+0900 (한국 표준시)
                                    
                                
2. getTime();은 기준시(1970.1.1 00:00:00)로부터 경과한 시간(밀리초)을 가져오고, setTime( .. );은 기준시로부터 .. 까지 경과할 시간(밀리초)을 설정한다
                                    
                                        const date1= new Date("2025/01/01"), date2= new Date("2026/01/01");

                                        const date3= date2.getTime() - date1.getTime(); // 시간 간격
                                        const differ_date= date3/(24*60*60*1000); // 24시간x60분x60초x1000밀리초 ← 시간 간격을 날로 환산한다
                                        console.log(differ_date); // 365
                                    
                                
                                    
                                        const date= new Date();
                                        console.log(date) // Thu Jun 12 2025 09:48:43 GMT+0900 (한국 표준시)

                                        date.setTime(date.getTime() + 3000); // 3000밀리초(= 3초) 이후로 설정
                                        console.log(date); // Thu Jun 12 2025 09:48:46 GMT+0900 (한국 표준시)
                                    
                                
                                    
                                        
                                    
                                
3. Date.now();타임 스탬프를 밀리초 단위로 구하며, Date.parse( .. ); 메서드는 문자열을 인자로 받아 날짜와 시간으로 분석을 시도하고 타임 스탬프로 반환한다
                                    
                                        const start= Date.now();
                                        // .. ← 작업 수행
                                        const end= Date.now();

                                        console.log(`작업에 걸린 시간: ${end - start}ms`);
                                    
                                
날짜/시간 연산
Date 객체는 스크립트의 비교 연산자로 비교할 수 있고, 산술 연산자를 써서 날짜간 시간 간격(밀리초 단위)를 계산하는 것도 가능하다
1. 날짜간 년/월/일 차이를 계산할 때는 Date 객체set-get- 메서드를 사용하면 된다
                                    
                                        const date= new Date();
                                        console.log(date) // Thu Jun 12 2025 10:36:29 GMT+0900 (한국 표준시)

                                        date.setMonth(date.getMonth()+3, date.getDate()+15) // 3개월 15일 후
                                        console.log(date) // Sat Sep 27 2025 10:36:29 GMT+0900 (한국 표준시)
                                    
                                
                                    
                                        let today= new Date(), before= new Date('2020-6-1'), after= new Date('2026-5-31');

                                        let lastDay= today.getTime() - before.getTime(); // 시작일로부터 오늘까지의 경과 시간
                                        let nextDay= after.getTime() - today.getTime(); // 오늘부터 종료일까지 남은 시간

                                        let result1= Math.floor(lastDay / (1000*60*60*24)); // 경과한 시간을 일 수로 바꿈
                                        let result2= Math.ceil(nextDay / (1000*60*60*24)); // 남은 시간을 일 수로 계산

                                        console.log(`이사온 첫날부터 ${result1}일 지남, 계약 마지막날까지 ${result2}일 남음!`) // 이사온 첫날부터 1837일 지남, 계약 마지막날까지 353일 남음!
                                    
                                
2. 스크립트에서 날짜는 숫자로 저장되므로 숫자에 쓰는 연산자를 그대로 쓸 수 있고, 날짜 데이터를 숫자로 변환할 때는 valueOf(); 메서드를 사용하면 된다: console.log(new Date().valueOf()); // 1749692838181 ← 날짜 데이터의 숫자변환
                                    
                                        const date1= new Date(2025, 6, 1), date2= new Date(2026, 5, 31);
                                        console.log(date1 > date2) // false ← 날짜 데이터의 비교

                                        const days_differ= date2 - date1; // 날짜 데이터의 산술계산
                                        console.log("남은 일 수: " + days_differ/(1000*60*60*24)) // 남은 일 수: 365
                                    
                                

템플릿 리터럴

템플릿 리터럴
템플릿 리터럴(` .. `)에서는 일반 문자열 외에도 html 태그, 표현식 등을 쓸 수 있고, <pre> 태그와 같은 형태로 사용할 수도 있다
                                    
                                        
                                    
                                
                                    
                                        
                                    
                                

맨 앞의 \는 줄바꿈을 이스케이프한다 없애면; 줄바꿈한 뒤 안의 내용을 표시한다!

경주는 맑은 날

                                    
                                        
                                    
                                
                                    
                                        
                                    
                                
                                    
                                        
                                    
                                

이 코드는 html 문서의 중간에서 인라인 <script> .. </script> 코드 안에서 템플릿 리터럴로 <ul> 목록을 만들어준 것입니다!

➥ 템플릿 리터럴로 html 문서 코딩하기

문서를 만들어 <body> 내부에 아래 스크립트 코드를 (<script> .. </script> 안에)넣고 실행해보십시오..

                                        
                                            
                                        
                                    

위 스크립트 코드의 결과로 작성된 html 문서의 코드 구조는 [브라우저 개발자 화면]의 [Elements] 탭에서 확인할 수 있습니다!

➥ 전역객체와 전역상수, 전역함수 및 객체 생성자함수

스크립트가 시작될 때마다, 또는 브라우저가 새 페이지를 로드할 때마다 전역객체(Math, Date, String, Object, JSON, ..)와 전역상수(NaN, Infinity, -Infinity, undefined, ..)가 로드된다. 또한, 스크립트 내장 전역함수isNaN();, parseInt();, setTimeout();, setInterval(); 등과 스크립트의 객체 생성자함수Number(), String(), Object(), Array(), Date(), RegExp() 등 또한 전역적으로 사용할 수 있다!


브라우저에서는 window 란 이름으로 전역객체를 참조할 수 있고, 노드에서는 global 이란 이름으로 전역객체를 참조할 수 있는데.. ES2020에서 새로이 정의한 globalThis 는 어떤 환경에서든 전역객체를 참조하는 표준이다!

Array 배열

배열 Array객체 Object 모두 일종의 컨테이너인데, 배열은 길이 를 가지며 인덱스번호 를 통해 접근할 수 있는 반면, 일반적인 객체는 속성(키: 값)을 가지며 를 통해 에 접근할 수 있다

배열이란?
배열순서 가 있는 의 집합으로서, 각 은 배열 내에서의 위치를 가리키는 인덱스번호 를 갖는 요소들로서, 동적으로 커지거나 작아질 수 있고, '빈' 요소도 들어갈 수 있다. 배열은 언제든 요소(숫자, 문자열, 객체, 또 다른 배열 등)를 추가/제거하거나 각 요소의 값을 변경할 수 있으며, 배열의 요소 인덱스는 0 에서 시작하여 배열.length-1로 끝난다
[ 배열의 선언 및 정의 ]
                                        
                                            const arr= [] // 빈 배열 선언

                                            arr[0]= "서울" // 배열에 값 넣기
                                            arr[1]= "부산"
                                            arr[3]= "경주" // arr[2]는 건너뛰고, arr[3]에 값을 넣었다 

                                            console.log(arr.length) // 4 (배열의 길이, 곧 배열 내 요소 수)
                                            console.log(arr) // ['서울', '부산', empty, '경주'] ← arr[2]에는 배열의 '빈' 요소가 들어가 있다!
                                        
                                    
                                        
                                            const ary= [ // 리터럴 배열 선언 및 정의
                                                [1, 2, 3], // 중복 배열
                                                1, // 숫자
                                                '경주', // 문자열
                                                {id: "이름", pw: "비번"} // 객체
                                            ]

                                            console.log(ary[2]); // 경주 ← 대괄호 접근법으로 배열 요소의 값 가져오기    
                                            console.log(ary[0][2]); // 3 ← 대괄호 접근법으로 중복된 배열 내 요소의 값 가져오기    
                                            console.log(ary[3].id); // 이름 ← 객체인 배열 요소의 키 값 가져오기
                                        
                                    
                                        
                                            const num= 1
                                            const nums= [num, num+1, num+2] // 표현식을 써서 만든 배열의 요소들 ← 콤마 연산자는 앞에서부터 순차적으로 나아간다!

                                            console.log(nums) // [1, 2, 3]
                                        
                                    

* cf) 배열 생성 시 Array.of(값1[, 값2, ..]); 메서드를 사용할 수도 있는데, 이 메서드는 인수로 주어진 값[들] 을 배열의 요소로 만들어 반환한다: Array.of(); // 빈 배열 [] 생성, Array.of(1, 2, 3) // [1, 2, 3]

➥ 배열의 length 속성에 대하여

배열의 length 속성은 배열의 길이, 곧 배열 내 요소의 개수를 반환하는데, 모든 배열 메서드들은 이 속성을 기준으로 하여 자신의 위치를 정하게 된다

                                        
                                            const el= ['요소1', '요소2', '요소3']

                                            for(let i=0; i <= el.length-1; i++) { // 배열 요소의 앞으로부터 루프를 돌 때: 0, 1, 2
                                                document.write(`${el[i]} `) // 요소1 요소2 요소3
                                            }

                                            for(let j= el.length-1; j >= 0; j--) { // 배열 요소의 뒤로부터 루프를 돌 때: 2, 1, 0
                                                document.write(`${el[j]} `) // 요소3 요소2 요소1
                                            }
                                        
                                    

배열의 인덱스번호는 0 으로부터 시작하기에 마지막 요소의 인덱스번호는 배열.length-1이 된다!

배열 요소의 추가/제거
delete 연산자는 객체의 프로퍼티나 배열의 요소를 삭제하는데.. 피연산자를 객체의 프로퍼티 키나 배열의 인덱스 번호라고 간주한 채 작업을 수행하며, 성공하면 true 를 반환하고, 삭제할 수 없으면; (에러는 내지 않고!)false 를 반환한다
1. 배열에 요소를 추가할 때는; 간단히 새 인덱스에 을 넣어주면 되고, 제거 시는; delete 연산자를 사용할 수 있다
                                    
                                        let arr= [1, 2, 3] // 리터럴 배열 생성
                                        arr[3]= "요소추가" // 인덱스 [3]번에 요소 삽입
                                        console.log(arr) // [1, 2, 3, '요소추가']

                                        arr[2]= 0 // 인덱스 [2]번 요소의 값 변경
                                        console.log(arr) // [1, 2, 0, '요소추가']

                                        delete arr[2] // 배열의 인덱스 [2]번 요소 제거
                                        console.log(`배열은 [${arr}]이고, 길이는 ${arr.length}임`) // 배열은 [1,2,,요소추가]이고, 길이는 4임 ← length 값은 변함이 없다!
                                    
                                

delete 연산자로 배열의 특정 요소 값을 삭제할 수는 있지만, 해당 요소의 값만 undefined (= '빈 요소')로 설정될 뿐 배열의 길이는 달라지지 않는다 배열 내 요소 자체를 삭제하려면; Array 객체의 splice(); 메서드를 써야 한다!


* cf) 배열에서 '빈 요소'란 배열에 요소를 추가하거나 제거할 때, 또는 length 속성으로 길이를 설정해줄 때 undefined 값을 갖게 되는 요소를 말한다!

2. 배열.length= 길이; 식으로 배열의 길이 를 조절해줄 수도 있는데, 길이 값이 늘어나 (값 할당 없이)추가된 요소는 undefined 값을 갖게 되고, 길이 값이 줄어들어 남는 요소는 실제로 삭제된다!
                                    
                                        const arr= [1, 2, 3, 4, 5]

                                        arr.length= 3 // length 속성으로 배열의 길이를 줄인다 ← 뒷부분은 실제로 삭제된다!
                                        console.log(arr, arr.length) // [1, 2, 3] 3

                                        arr.length= 4 // length 속성으로 배열의 길이를 늘인다 ← 뒷부분은 '빈 요소'로 들어간다
                                        console.log(arr, arr.length) // [1, 2, 3, empty] 4
                                    
                                

* cf) Array 객체의 데이터 타입을 typeof 연산자로 확인하면 object 로 나오므로, 배열 여부를 확인하려면 Array.isArray(); 메서드를 사용해야 한다!

                                    
                                        console.log(typeof [], typeof {}, Array.isArray([])) // object object true
                                    
                                
배열의 인덱스 및 객체의 키: in 연산자
1. idx in 배열은 먼저, idx 가 해당 배열 의 인덱스번호에 해당하는지를 확인한다 배열에서 in 연산자의 앞쪽 피연산자는 인덱스번호로 간주되고, 스크립트는 이를 내부적으로 문자열로 변환하여 평가하게 된다!
                                    
                                        let arr= [1, 2, 3]

                                        console.log("0" in arr) // true ← 배열 arr에 0번 인덱스가 있다
                                        console.log(2 in arr) // true ← 좌측의 2는 문자열 "2"로 변환하여 평가한다!
                                        console.log(3 in arr) // false ← 배열 arr에 3번 인덱스는 없다!
                                    
                                
2. key in 객체key객체 의 속성키인지를 확인하는데, 객체에서 in 연산자의 앞쪽 피연산자 는(배열에서와는 달리!) 반드시 문자열 형태로 주어져야 한다
                                    
                                        let obj= { x: 1, y: 2 } // obj 객체의 선언 및 정의

                                        delete obj.x
                                        console.log(obj) // {y: 2} ← 배열에서와는 달리, 객체에서는 객체의 키(와 값) 자체가 삭제된다!
                                        console.log("x" in obj) // false
                                        console.log("y" in obj) // true
                                        console.log(x in obj) // Uncaught ReferenceError! x is not defined ← 객체의 키는 문자열 형태로 주어져야 한다!
                                    
                                

배열에서와는 달리, 객체에서는 in 연산자의 왼쪽 피연산자는 반드시 문자열 형태로 주어져야 한다 (스크립트 내부적으로 문자열로 간주하여 작업하는)배열의 인덱스번호와는 달리, 객체의 키는 그 자체로 문자열이며, 문자열이어야 한다!

3. delete 연산자와는 달리, in 연산자undefined 값은 없는 것으로 간주하며, 따라서 for .. in 문에서 (인덱스 번호에는 있어도)빈 요소 는 건너뛴다
                                    
                                        let ary= [1, 2, 3]
                                        ary[4]= 5 // [1, 2, 3, ,5] // 빈 요소(ary[3])에는 'undefined' 값이 할당된다!
                                        console.log(ary.length) // 5 ← 빈 요소도 length에 포함된다!
                                        
                                        for(let i=0; i < ary.length; i++) { // for 문 ← 루프 변수 i로 루프를 돈다
                                            document.write(`${i}:${ary[i]} `) // 0:1, 1:2, 2:3, 3:undefined, 4:5
                                        } // 배열에서 빈 요소도 인덱스 번호를 갖고, 값은 'undefined'이다!
                                        
                                        for(let j in ary) { // for .. in 문 ← 배열의 인덱스 번호로 루프를 돈다
                                            document.write(`${j}:${ary[j]} `) // 0:1, 1:2, 2:3, 4:5
                                        } // for .. in 문에서는 배열의 '빈 요소'는 건너뛴다!
                                    
                                
➥ 문자열 리터럴 대 배열

문자열 리터럴은 유니코드 문자들로 구성된 읽기전용 배열(= 유사배열 객체)처럼 동작한다. 그리하여, String 객체charAt(); 메서드 대신 배열의 [] 문법으로 개별 문자에 접근하는 것이 가능해진다!

                                        
                                            let str= "문자열"

                                            console.log(str.charAt(0)) // 문 ← String 객체의 메서드인 charAt() 메서드
                                            console.log(str[0]) // 문 ← 문자열에 배열처럼 접근할 수 있다!
                                        
                                    

문자열은 불변이므로, 배열처럼 취급한다 해도 읽기 전용이기에 배열 원본을 수정하는 파괴적 메서드들은 문자열에서 작동하지 않는다 단, 이 경우에도 에러는 일어나지 않고, 그저 조용히 실패할 뿐이다!

Object 객체

객체 Object 는 현실 세계의 사물을 모델링하는 코드 구조라고 할 수 있다. 예컨대, 상자너비: 30, 높이: 10, 폭: 20; 등의 정보로 나타낼 수 있는데, 으로 구성되는 객체는 이러한 정적 데이터를 다룰 때 특히 유용하다!

객체의 생성과 정의, 접근
객체속성 Property이름 Key 은 문자열로 넣어주고(그 자체로 문자열이므로 따옴표는 필요하지 않다 - 단, 빈 칸이나 하이픈이 들어가는 경우에는 따옴표가 필요하다!), Value 으로는 문자열(따옴표 안에 넣어주어야 한다) 및 숫자 외에도 값을 나타내는 어떤 표현식도 올 수 있다
                                    
                                        const p= { // 리터럴 객체 p
                                            name: 'jc', // 객체의 속성(키: '값')
                                            per: function() { return 'K' } // 객체의 메서드(키: 함수 객체)
                                            // 또는, per() { return 'K' } // 객체의 메서드 ← ES6 단축 표기법
                                        }

                                        console.log(`${p.per() + p.name}`) // Kjc
                                    
                                

* cf) ES6 문법에서는 기존 변수를 써서 객체를 생성할 수도 있다!

                                    
                                        const x= 1, y= 2

                                        const obj= { x, y } // 기존 변수를 써서 객체 생성하기 ← ES6
                                        console.log(obj) // {x: 1, y: 2}

                                        console.log(obj.x, obj.y) // 1 2
                                    
                                
1. 객체의 속성값에 접근할 때는 객체.키(. 연산자는 문자열에만 반응한다- 곧, 키는 단순한 문자열로 해석되는 식별자라야 한다!)나 객체['이름']('이름'은 문자열이거나 문자열로 평가되는 표현식이어야 하는데, 숫자는 속성의 이름에서건 값에서건 자동으로 문자열로 바뀌므로 상관없다!)을 쓸 수 있다
                                    
                                        let per= {} // 빈 객체 생성
                                        per= { // 빈 객체에 속성(이름: 값) 정의
                                            name: 'Kjc', rank: 3
                                        }
                                        
                                        per.star= '스타' // per 객체에 동적으로 star 속성을 추가하고, 값을 넣어준다
                                        console.log(`${per['name']}님은 제 마음 속의 ${per.star}입니다!`) // Kjc님은 제 마음 속의 스타입니다!
                                    
                                

객체의 속성 이름에 표현식(가령, -)이나 예약어가 포함되어 있거나 심볼, 숫자인 경우, 프로퍼티 이름이 계산의 결과일 때는 반드시 [] 연산자로 접근해야 한다!


* cf) 객체의 속성값에 [] 문법으로 접근할 때는 따옴표를 필요로 하지만, for .. in 문에서는; 루프를 도는 객체의 속성키(및 배열의 인덱스번호)는 이미 문자열 타입이므로 대괄호 안에서 따옴표는 필요하지 않다!

                                    
                                        const per= { name: 'Kim', age: 30, gender: '남' }
                                        console.log(`${per.name}: ${per['age']} `) // Kim: 30 ← 객체의 키에 접근할 때 []에서는 (. 문법에서와는 달리)따옴표로 둘러싸야 한다!

                                        for(let p in per) { // for .. in 문 내부에서는; 이 p(= 키)는 이미 문자열이므로, [] 안에 따옴표를 써서는 안된다!
                                            document.write(`${p}: ${per[p]} `)  // name: Kim age: 30 gender: 남
                                        }
                                    
                                

for(i in 객체/배열) { .. }에서 i 에는 할당 표현식의 왼쪽에 올 수 있는 것이라면 모두 가능한데, 배열인덱스번호객체속성키 에 해당한다 곧, for .. in 문에서 i 에는 모두 문자열로 들어간다!

➥ 연관 배열이란?

문자열을 인덱스로 사용하는 배열을 연관 배열이라고 하는데, 객체[]로 접근하는 경우가 바로 그렇다! 배열 요소에 접근할 때 사용되는 [] 접근자는 객체의 프로퍼티에 접근할 때 사용하는 []와 완전히 같은 방식으로 동작한다: 배열의 인덱스 번호로 사용되는 숫자는 스크립트 내부적으로 문자열로 변환되며, 객체의 키 또한 숫자를 쓰든 문자를 쓰든 스크립트 내부적으로는 모두 문자열로 변환된다!

2. 객체의 프로퍼티 접근 시, 객체.키로 접근할 때 키는 데이터 타입이 아니므로 프로그램에서 조작하는 것이 불가능하지만, 객체['이름']으로 접근 시; 문자열은 스크립트의 데이터 타입이므로 프로그램이 실행되는 중에도 생성하고 조작할 수 있게 된다!
                                    
                                        
                                    
                                
3. 객체의 속성값이 함수(= 메서드)일 경우 그 내부에서 자기 자신이 가진 속성을 출력하고자 할 때는 this 키워드를 써서 자신이 가진 속성임을 분명히 해야 한다!
                                    
                                        const pet= {
                                            name: '구름',
                                            eat(food) { // 전달받은 매개변수 '물'로 작업한다
                                                console.log(this.name + '은 ' + food + '을 먹고 산다') // 구름은 물을 먹고 산다 ← 이 this는 pet 객체 자신의 this이다!
                                            }
                                        }
                                        
                                        pet.eat('물') // pet 객체의 eat() 메서드를 호출하면서 인수 '물'을 전달한다!
                                    
                                

this 키워드는 자바스크립트에서 매우 중요하면서, 또 아주 어려운 개념인데.. 일단은, this 는 지금 실행 중인 코드를 지니고 있는 객체(곧, 위에서는 pet 객체 자신)라고 알아두면 됩니다

객체의 프로퍼티 확인 및 제거하기
속성 in 객체객체 에 열거 가능한 (상속받은 속성도 포함하여)특정 속성 이 있는지를 확인하며, delete 객체.속성객체 의 특정 속성 (속성의 값만 아니라 속성 그 자체)을 제거한다
                                    
                                        const per= { name: 'Kjc', age: 39, gen: '남' }

                                        delete per.local // 객체에 없는 속성을 제거하려 해도, 에러는 나지 않는다!

                                        /* in 연산자로 객체의 속성값에 접근할 때, 좌측 피연산자는 반드시 문자열 형태여야 한다! */
                                        if('gen' in per) delete per.gen // 객체에 접근할 때, 속성 키(gen)는 그 자체 문자열이다!
                                        console.log('gen' in per) // false ← gen 속성은 방금 지웠다!
                                    
                                

객체에 없는 속성을 제거하려 해도, 에러는 내지 않고 그저 조용히 지나칠 뿐이다 참고로, delete 연산자로 객체 및 배열 자체를 삭제할 수는 없다!


* cf) 객체.hasOwnProperty(속성)객체 가 특정 속성 을 (자체적으로)가지고 있는지를 확인하며, Reflect.ownKeys(객체)객체 가 지닌 모든 자체 프로퍼티 키를 반환한다 참고로, 객체의 프로퍼티 열거 시의 순서는 매우 복잡한 규정을 따르므로 배열과는 달리, 일정한 순서는 보장하기 어렵다!

프로퍼티 접근자
존재하지 않는 속성키(및 인덱스번호)로 접근하는 것은 에러가 아니다 - 단지 undefined 로 평가될 뿐이다. 하지만, 존재하지 않는 객체(나 속성)에서 프로퍼티를 찾는 것은 에러가 된다. 곧, 프로퍼티 접근 표현식은 . 접근자의 왼쪽이 null 이나 undefined 라면 실패한다: TypeError!
                                    
                                        const obj= { a: 1, b: 2, c: 3 }

                                        const is1= obj || obj.d || obj.d.value // obj가 참이므로 obj를 반환한다(= 단축 평가)
                                        console.log(is1) // {a: 1, b: 2, c: 3}

                                        const is2= obj && obj.d && obj.d.value // obj는 참이지만, obj.d는 없는 프로퍼티이므로 undefined를 반환한다 ← 그 뒤는 평가하지 않는다!
                                        console.log(is2) // undefined

                                        console.log(obj.d.length) // TypeError! ← obj.d는 없는 프로퍼티이므로 길이를 잴 수가 없다!
                                    
                                

Object 객체 (2)

➥ 조건부 프로퍼티 접근자 ?.

스크립트에서 프로퍼티를 가질 수 없는 값은 undefinednull 뿐인데, 일반적인 프로퍼티 접근법에서는 좌측 표현식이 undefinednull 이라면 타입에러가 발생하게 된다 - 이 문제를 해결하기 위해 생겨난 것이 바로 조건부 프로퍼티 접근자?.이다:

                                    
                                        let a= { b: null }
                                        console.log(a.b) // null ← a의 프로퍼티 b는 null 값을 갖고 있다!

                                        // console.log(a.b.c) // TypeError! ← c는 없다
                                        console.log(a.b?.c) // undefined ← a.b가 null이므로 여기서 멈춘다(단축 평가!)
                                    
                                

A?.b.cA?.[b][c]Aundefinednull 이라면; b 에 접근하려는 시도 없이(= 단축평가) undefined 로 평가하는 것이다!

                                    
                                        const user1= { name: 'Kim', age: 39 }
                                        const user2= { name: 'Lee', age: 59, work: { san: '남산', loc: '경주' } }

                                        console.log(user1.work?.loc, user2.work?.loc, user2.work?.san) // undefined '경주' '남산'
                                    
                                

앞에서 타입 에러를 내지 않고 그저 조용히 undefined 를 반환했기에, 여기서 멈추지 않고 계속해서 다음 코드로 넘어간다!

객체의 키/값 조회하기
Object.keys/values(객체)객체 가 지닌 (열거 가능한)속성의 을 배열로 반환하며(심볼은 건너뛰며, 열거불가 프로퍼티나 상속된 프로퍼티도 또한 제외된다), Object.entries(객체)객체 가 지닌 속성의 키 & 값을 중첩 배열로 반환한다:
                                    
                                        const obj= { x: 1, y: 2, z: 3 }

                                        let es1= ""
                                        for(let e of Object.keys(obj)) { // Object.keys(obj)
                                            es1 += e
                                        }
                                        console.log(es1) // xyz

                                        let es2= ""
                                        for(let e of Object.values(obj)) { // Object.values(obj)
                                            es2 += e
                                        }
                                        console.log(es2) // 123

                                        let es3= ""
                                        for(let [k, v] of Object.entries(obj)) { // 중첩 배열로 가져온 쌍으로 된 키와 값을 각각의 변수로 해체 할당한다!
                                            es3 += k + ":" + v + " " // 키와 값을 문자열로 결합한다!
                                        }
                                        console.log(es3) // x:1 y:2 z:3
                                    
                                
                                    
                                        let obj= { Seoul: 2, Pusan: 3, Gj1: 2, Ulsan: 1, Gj2: 3 }

                                        Object.keys(obj).filter(o => o.match(/^Gj/)).forEach(o => console.log(`${o}:${obj[o]}`)) // Gj1:2 Gj2:3
                                    
                                
                                    
                                        
                                    
                                

단지 객체가 소유한 프로퍼티 이름 및 값만 조회하려는 경우라면; Object.keys/values()가 더 적합하며, 키와 값 모두 가져오려면; Object.entries()를 사용할 수 있다 여기서는 hasOwnProperty() 체크가 필요하지 않기에 객체의 속성 키나 값, 키&값만 필요한 때에는 for .. in 루프보다 편리하다!

➥ 깊은 복사 대 얕은 복사

리터럴 데이터 타입(숫자, 문자열, 불린, null, undefined, Symbol)은 불변이고, 그 값을 전달할 때는 값 자체를 복사한다(= 깊은 복사). 반면, 참조 데이터 타입(객체 및 배열)은 객체 자체를 담고 있는 것이 아니라, 그 객체를 가리키는 주소를 갖고 있다 - 곧, 변수에 객체를 할당하면; 그 변수에는 해당 객체 자체가 아니라 그 객체가 저장된 곳의 주소가 들어가는 것이다(= 얕은 복사)

                                    
                                        let ary1= []
                                        let ary2= ary1 // ary2는 ary1이 가리키는 곳을 참조한다!

                                        ary1[0]= 0
                                        console.log(ary2[0]) // 0 ← ary1과 ary2는 모두 같은 곳을 참조한다!
                                    
                                
                                    
                                        let obj= { a: 1 }
                                        let p= obj // p는 obj를 참조한다
                                        obj.a= 2 // obj.a 값을 변경한다
                                        console.log(p.a) // 2

                                        obj= { a: 3 } // 이제 obj는 { a: 3 }를 저장한 다른 주소를 가리킨다 ← 이 obj는 처음 정의한 obj와는 같지 않다!
                                        p= obj // p는 obj를 참조한다 ← 이 p도 처음 정의한 p와는 달리, 다른 주소를 가리킨다!
                                        console.log(p.a) // 3
                                    
                                
                                    
                                        let obj= { a: 1 }

                                        function change(obj) {
                                            obj.a= 99 // 함수 내부에서 obj.a 값을 변경한다!
                                        }
                                        change(obj) // 객체 obj가 저장된 주소를 전달하면서 change 함수를 호출한다!

                                        console.log(obj.a) // 99

                                        let p= obj // p는 obj를 참조한다
                                        console.log(p.a) // 99
                                    
                                

✓   객체와 배열은 이 아니라 참조 로 비교한다. 객체나 배열을 변수에 할당한다는 것은 이 아니라 값이 담긴 주소 를 할당하는 것이다(곧, 객체나 배열을 변수에 할당한다고 해서 그 사본이 생기는 것은 아니다!). 객체나 배열의 사본을 만들기 위해서는 객체의 프로퍼티나 배열 요소를 직접 복사해주어야 한다:

                                    
                                        
                                    
                                

위 두 배열 aryary2 는 같은 을 담고 있을 뿐, 이 두 배열은 서로 다른 별개의 배열이다!

배열과 객체의 순회

for 루프가 카운터 변수의 숫자 로 루프를 도는 반면, for .. in 문은 배열의 인덱스(및 객체의 속성키)로, for .. of 문은 배열의 요소값(및 객체의 속성값)으로 루프를 돈다 - 곧, (인덱스번호가 없는)객체에서는 가 배열의 인덱스 역할을 하며, (키가 없는)배열에서는 인덱스 가 객체의 키 역할을 한다!

배열의 순회
1. 배열의 요소를 순회하는데는 for 문(카운터변수 사용), for .. in 문(인덱스번호 사용)과 for .. of 문(요소값 사용) 등을 사용할 수 있다:
                                    
                                        
                                    
                                

배열의 순회 시, 현재 다루는 배열이 '꽉찬' 배열인지, '빈' 요소들이 포함되어 있는지 확신할 수 없는 경우에는 체크 코드를 넣어줄 필요가 있다: if(arr[i] === undefined) continue; '빈' 요소 등 유효하지 않은 값은 건너뛰어 다음 조건 평가로 간다!

2. for .. in 문은 객체나 배열에 포함된 모든 프로퍼티에 대한 루프를 수행하는데, 모든 열거 가능한 속성을 (배열의 인덱스 및 객체의 키로)반복하면서 루프를 돈다:
                                    
                                        const obj= { x: 1, y: 2, z: 3 } // 객체의 선언 및 정의

                                        let keys= ""
                                        for(let key in obj) { // 객체의 키로 루프를 돈다
                                            if(! obj.hasOwnProperty(key)) continue; // 열거가능한 키가 아니라면(예컨대, 상속된 프로퍼티)는 건너뛴다!
                                            if(typeof obj[key] === "function") continue; // 객체의 메서드(= 함수)도 건너뛴다!

                                            keys += key; // 객체의 키를 하나의 문자열로 연결한다
                                        }
                                        console.log(keys) // xyz
                                    
                                

모든 객체의 프로퍼티에는 자신이 정의한 속성만 아니라 상위 객체로부터 '상속된'(및 '열거 불가능'으로 설정된) 속성 또한 존재한다!

                                    
                                        const obj= { x: 1, y: 2, z: 3 }
                                        let ary= []

                                        let i= 0
                                        for(ary[i++] in obj) { // obj의 키들을 순회하며 ary 배열의 요소로 넣는다!
                                            ;
                                        }
                                        console.log(ary) // ['x', 'y', 'z']

                                        for(let v of ary) { // ary 배열의 값인 v로 루프를 돈다
                                            document.write(v) // xyz
                                        }
                                    
                                

배열을 대상으로 작업할 때는 for .. in 루프가 아닌 for .. of 루프를 쓰는 것이 보다 간결하다!


for .. in 루프는 실제로 객체의 프로퍼티 전체를 열거하지는 않으며, 오직 열거 가능한 프로퍼티만 순회한다 - 심볼, 스크립트의 여러 내장 메서드들이 열거 불가능한 반면, 열거 가능한 상속된 프로퍼티가 루프의 순회 대상에 예기치않게 끼어들 수도 있다. 또한, 프로퍼티를 삭제하거나 새로이 생성하는 경우 등에도 애매한 문제가 발생할 수 있다. 따라서, 배열에서는 for .. in 문 대신 for .. of 문과 Object.keys() 등의 메서드를 조합해서 쓰는 것이 보다 안전하다!

3. 배열의 요소를 순회하는 데는 for .. of 문이 기본인데(빈 요소 및 존재하지 않는 요소에 대해서는 undefined 를 반환한다), 각 요소의 인덱스 번호도 필요하다면; entries() 메서드와 결합하여 쓸 수 있다:
                                    
                                        const names= ['K', 'j', 'c']

                                        let str= ""
                                        for(let letter of names) { // 배열의 요소값으로 루프를 돈다
                                            str += letter; // 배열의 각 요소값들을 문자열로 연결한다
                                        }
                                        console.log(str) // "Kjc" ← 문자열

                                        let str2= ""
                                        for(let [idx, letter] of names.entries()) { // 배열 names에서 인덱스번호와 값의 쌍을 배열로 가져온다
                                            str2 += (idx+ ":" + letter + " ") // 배열의 인덱스번호와 값의 쌍을 가져와 문자열로 연결한다
                                        }
                                        console.log(str2) // 0:K 1:j 2:c
                                    
                                

entries() 메서드는 배열 및 객체에서 '키'(또는, '인덱스번호')와 '값'의 쌍을 배열로 반환한다!

                                    
                                        const nums= [1, 2, 3] // 배열

                                        for(let i of nums) { // 배열의 값으로 순회한다
                                            document.write(i + " ") // 1 2 3
                                        }

                                        nums.forEach(num => document.write(`${[num] + " "}`)); // 1 2 3
                                    
                                

배열에서 값(만) 가져올 때는 Array 객체forEach() 메서드를 쓰면 코드가 한결 간결해진다!


배열.forEach(콜백함수(요소)) 메서드는 콜백함수 를 인수로 받아 배열 의 각 요소 를 순회하면서 특정 작업을 반복해나가게 된다 forEach() 메서드는 for .. of 문과는 달리, '빈 요소' 및 존재하지 않는 요소에 대해서는 undefined 를 반환하지 않고 그냥 건너뛰어 다음 요소로 넘어간다는 점에서 더욱 유용하다!

➥ 이터러블 객체

이터러블 iterable 이란 여러 자료들이 모인 컬렉션에서 각 항목을 한번에 하나씩 처리하는 것을 말하는데, 이터러블 객체반복 가능한 객체 (Symbol.iterator() 메서드를 내장하고서 이터레이터를 반환하는 객체: Array, String, MapSet 등)를 말하는데, for .. of 문으로 이터러블 객체의 각각의 으로 루프를 돌며 반복 처리할 수 있다:

                                    
                                        const season= ['봄', '여름', '가을', '겨울'] // 배열은 이터러블 객체다!

                                        for(let e of season) { // e: 이터러블 객체(season)의 요소값
                                            document.write(`${e} `) // 봄 여름 가을 겨울
                                        }
                                    
                                
                                    
                                        const names= [..."Kjc"] // 문자열을 분해하여 배열의 요소들로 할당한다 ← 전개(나열) 연산자
                                        console.log(names) // ['K', 'j', 'c'] ← 배열
                                    
                                

문자열을 각각의 문자들로 분해할 때는 전개나열 연산자를 쓰는 것이 쉽고 간결하다!

                                    
                                        let obj= {} // 빈 객체

                                        for(let e of "namsan") { // 문자열은 이터러블 객체이고, 각 문자들(e)로 루프를 돈다!
                                            if(obj[e]) obj[e]++ // 같은 키가 나오면; 그 키 값을 하나 증가시킨다
                                            else obj[e]= 1 // 처음 나올 때는 n: 1, a: 1, m: 1, s: 1 ← 중복된 문자가 나올 때는 위 코드가 수행된다!
                                        }
                                        console.log(obj) // {n: 2, a: 2, m: 1, s: 1}
                                    
                                

✓   이터러블 객체for .. of 문으로 컨텐츠를 순회하거나, ... 연산자로 분해하여 배열 초기화 표현식이나 함수 호출로 바꿀 수도 있고, 해체 할당과 함께 사용할 수도 있다!

                                    
                                        let sum= 0
                                        for(let i of [1, 2, 3]) { // 배열의 순회
                                            sum += i
                                        }
                                        console.log(sum) // 6

                                        let chars= [..."abc"] // 문자열 분해
                                        console.log(chars) // ['a', 'b', 'c']

                                        let nums= [1, 2, 3] // 배열
                                        console.log(Math.max(...nums)) // 3

                                        let [r, g, b, a]= Uint8Array.of(255, 0, 255, 128) // 해체 할당
                                        console.log(r, g, b, a) // 255 0 255 128
                                    
                                

배열의 해체할당과 결합

배열 및 객체의 복사 시는 (참조 주소를 전달하는)얕은 복사가 이루어지지만, 전개 연산자 ...를 써서 복제하는 경우에는 참조가 아닌 으로 복제되어 깊은 복사가 이루어진다!

배열의 해체할당과 결합
배열에서 해체 할당 분할 대입 을 이용하면 (객체의 해체 할당과는 달리)변수 이름은 마음대로 쓸 수 있는데, 이들은 배열의 인덱스 순서에 대응하므로 그 순서대로 할당해야 한다:
                                    
                                        let [x, y]= [1, 2, 3] // 3번째 요소는 버려진다!
                                        console.log(x, y) // 1 2
                                        
                                        let [x2, y2]= [1] // y2는 값이 없으므로 undefined가 된다!
                                        console.log(x2, y2) // 1 undefined
                                        
                                        let [z, , c]= [1, 2, 3] // 2번째 요소는 버려진다!
                                        console.log(z, c) // 1 3
                                    
                                
                                    
                                        let x=1, y=2; // 이 세미콜론은 반드시 필요하다!

                                        // 위 세미콜론이 생략되면; 다음 줄의 '['와 연결되어(곧, y=2[x, y]가 된다!) 구문 에러가 발생한다!
                                        [x, y] = [y, x] // x=2, y=1 ← 변수값 교체
                                        console.log(x, y) // 2 1
                                    
                                
                                    
                                        const ary= ["I", "love", "Javascript!"]

                                        function getAry([x, y, z]) { // 배열의 요소들로 해체 할당 ← 이름과 무관하게, 그저 순서대로 들어간다!
                                            return `${x} ${y} ${z}`
                                        }
                                        console.log(getAry(ary)) // I love Javascript!
                                    
                                
배열의 결합과 복제, 펼침 연산자
... 연산자를 쓰면 배열 및 객체의 결합이나 복제가 쉬워진다. 나아가, 배열을 함수의 인수로 전달할 때도 ...를 쓰면 간단히 인수 목록으로 분해하여 넣어줄 수 있고, 나아가 원본 변경 없이 인수를 추가해주기도 쉽다:
                                    
                                        const ary= [1, 2, 3, 4, 5]

                                        const arr2= [...ary] // 펼침 연산자로 배열 복제
                                        console.log(arr2) // [1, 2, 3, 4, 5]
                                        
                                        const arr3= [-1, 0, ...ary, 0, -1] // 배열의 병합 및 복제
                                        console.log(arr3) // [-1, 0, 1, 2, 3, 4, 5, 0, -1]
                                    
                                
                                                                           
                                        const city= ["3대 도시: ", "서울", "부산", "대구"]

                                        const [first, ...cities]= city; // 배열의 분해 및 복제 ← first에 ary 배열의 첫번째 요소가 변수로 들어가고, 나머지 요소는 새 배열 cities(["서울", "부산", "대구"])로 할당된다!
                                        console.log(first + cities.join("/")) // 3대 도시: 서울/부산/대구
                                    
                                
                                    
                                        const book= ['소소한이야기', 'Kjc', 20_000]

                                        function bookFnc(title, author, price){
                                            return `${title}: by ${author}, ${price}`
                                        }
                                        console.log(bookFnc(...book)) // 소소한이야기: by Kjc, 20000 ← ... 연산자를 써서 함수의 인자로 전달한다!

                                        const book3= ['소소한이야기2', 'Lee']
                                        console.log(bookFnc(...book3, 25_000)) // 소소한이야기2 by Lee 25000
                                    
                                

✓   for 루프는 배열을 복제하는 가장 기본적인 수단이다:

                                    
                                        const ary= [1, 2, 3, 4, 5]

                                        let arr2= []
                                        for(let i=0; i < ary.length; i++){ // for 루프로 배열 복제
                                            arr2[i]= ary[i]
                                        }
                                        console.log(arr2) // [1, 2, 3, 4, 5]
                                    
                                

배열 분해에 꼭 배열만 써야하는 것은 아니다. ... 연산자는 모든 이터러블 객체(곧, for .. of 문의 대상이 될 수 있는 객체)에서 작동된다:

                                    
                                        let [first, ...rest] = "Kjc" // 문자열의 분해 및 해체 할당
                                        console.log(first, rest) // K ['j', 'c']
                                    
                                

객체의 해체할당과 결합

해체 할당으로 객체의 프로퍼티에 접근하는 것은 변수 할당에 관한 문제를 해결할 수 있을 뿐만 아니라, 인수로 정적 데이터인 객체를 전달하기에 키-값의 순서를 신경쓰지 않아도 된다. 또한 다른 키-값을 꺼내는 것도 해체 할당에 새로운 변수를 추가하는 것으로 충분하다. 다른 시점에서 함수를 호출하는 것 또한 걱정하지 않아도 된다 - 다른 객체에 해당 변수가 없으면; 그저 undefined 가 반환될 뿐이다!

객체의 해체할당
객체의 해체 할당 시 속성 는 반드시 같아야 하며(아닌 것은 버려진다!), 같은 이름의 프로퍼티가 있다면; 나중에 오는 프로퍼티가 앞쪽 프로퍼티를 덮게된다:
                                    
                                        const obj= { city: "경주", san: "남산" }

                                        const { city, san } = obj // obj 객체를 해체하여 (이름이 같은)각각의 변수에 할당한다 ← 이것은 obj와는 전혀 다른 새로운 객체이다!

                                        obj.city= '서울' // obj 객체의 city 속성 값 변경
                                        console.log(obj.city, obj.san) // 서울 남산 

                                        console.log(city, san) // 경주 남산 ← 새로운 객체
                                    
                                
                                    
                                        const eat= { morning: ["미역국", "잡곡밥"] }

                                        const { morning: [a, b] } = eat
                                        // const { morning } = eat // 객체의 해체할당
                                        // const [a, b] = morning // 배열의 해체할당

                                        console.log(a, b) // 미역국 잡곡밥
                                    
                                
1. 해체 할당을 함수의 매개변수에 적용하면; 변수를 선언하지 않아도 마치 정보를 함수 몸체에서 할당한 것처럼 작동한다:
                                    
                                        const obj= { s1: "I", s2: "love" }

                                        function getObj({s1, s2, what="javaScript"}) { // 객체의 프로퍼티 해체할당 및 새 변수 추가
                                            return `${s1} ${s2} ${what}`
                                        }
                                        console.log(getObj(obj)) // I love javaScript!
                                    
                                

이렇게 해체 할당으로 들어오는 매개변수let 변수로 설정되므로 함수 본체 내부에서 해당 변수의 값을 변경하는 것도 가능하다!

2. 객체를 해체하여 변수로 할당하는 것과 반대로, 변수(및 함수)를 결합하여 새 객체를 생성할 수도 있다:
                                    
                                        const source= '마늘', meat= '치킨'

                                        const menuPrinting= function() {
                                            console.log(`${this.source} 맛이 들어간 ${this.meat}입니다!`) // 여기서 this는 자신을 호출한 bbq 객체를 가리킨다!
                                        }
                                        
                                        const bbq= { source, meat, menuPrinting } // 변수와 함수를 결합한 새로운 객체를 생성한다
                                        bbq.menuPrinting(); // 마늘 맛이 들어간 치킨입니다!
                                    
                                
3. ... 연산자해체 할당을 이용하면 객체에서 값을 꺼내고 다시 집어넣는 것이 가능해진다:
                                    
                                        const morning= "아침"
                                        const eat= {first: "미역국", last: "잡곡밥"}

                                        const foods= { // 변수와 객체를 결합하여 새로운 객체를 생성한다
                                            morning, ...eat // eat 객체의 해체 할당
                                        }
                                        console.log(foods) // {morning: '아침', first: '미역국', last: '잡곡밥'}
                                    
                                

✓   for .. of 문과 Object.entries(객체) 메서드를 함께 써서 객체 의 해체 할당을 할 수도 있다:

                                    
                                        let obj= { Kim: 1, Lee: 2 }

                                        for(const [name, value] of Object.entries(obj)) { // for .. of 루프를 이용한 해체 할당
                                            console.log(name + ": " + value) // Kim: 1 Lee: 2
                                        }
                                    
                                
객체의 결합 및 복제
1. ... 연산자를 사용하면 배열 및 객체의 결합이나 복제가 쉬워진다:
                                    
                                        let list= [ { color: "빨강"}, { color: "파랑"} ]

                                        const addColor= (c, list) => [...list, { color: c }] // ... 연산자 사용
                                        console.log(addColor("흰색", list)) // [{color: "빨강"}, { color: "파랑"}, {color: "녹색"}, {color: "흰색"}]
                                        console.log(list) // [{ color: "빨강"}, { color: "파랑"}, {color: "녹색"}] ← 원본은 그대로다!
                                    
                                
2. ...로 받는 함수의 매개변수는 항상 배열 형태로 들어오며, 함수의 매개변수나 배열의 해체 할당에서 ...는 반드시 맨 뒤에 놓여져야 한다!
                                    
                                        function directions(...args) { // ... 연산자로 받는 매개변수는 항상 배열로 들어온다!
                                            const [start, ...remaining]= args // 서울, ...remaining
                                            const [finish, ...stops]= remaining.reverse() // (remaining의 순서를 뒤집어서)부산, ...stops
                                            const stops2= stops.reverse() // 수원, 대전, 대구 순으로 다시 순서를 바꿔주기 위한 것임!

                                            console.log(`
                                                경부선 철도는 ${start}에서 출발하여.. 중간에 ${stops.length}개 도시(${stops2})를 거치면서, ${finish}에서 끝납니다!
                                            `) // 경부선 철도는 서울에서 출발하여.. 중간에 3개 도시(수원, 대전, 대구)를 거치면서, 부산에서 끝납니다!
                                        }

                                        directions("서울", "수원", " 대전", " 대구", "부산")
                                    
                                
3. 객체의 프로퍼티를 다른 객체로 복사하려면; 일반적으로는 다음과 같은 두가지 방식을 사용할 수 있다:
                                    
                                        let target= { x: 1, y: 3 }, src= { y: 2, z: 3 }

                                        for(let k of Object.keys(src)) { // src의 프로퍼티 키로 루프를 돈다
                                            target[k]= src[k] // target에 src의 키를 생성하고, src에서 가져온 값들을 넣는다
                                        }
                                        console.log(target) // {x: 1, y: 2, z: 3} ← 같은 이름의 프로퍼티는 덮고, 없는 프로퍼티는 새로 추가한다!
                                    
                                
                                    
                                        let target= { x: 1, y: 3 }, src= { y: 2, z: 3 }

                                        for(let k of Object.keys(src)) { // src의 프로퍼티 키로 루프를 돈다
                                            // 같은 이름의 프로퍼티는 건너뛰고, 없는 프로퍼티는 새로 추가한다!
                                            if(! (k in target)) { // target에 같은 키가 있다면; 건너뛴다!
                                                target[k]= src[k]; // 값 복사
                                            }
                                        }
                                        console.log(target) // {x: 1, y: 3, z: 3} ← 새로운 키만 추가한다!
                                    
                                
➥ 유사배열 객체

배열은 아니지만 배열처럼 사용할 수 있는 객체(예컨대, 문자열)가 바로 유사배열 객체인데, 이 유사배열 객체는 배열처럼 인덱스 로 요소에 접근할 수 있고 length 속성을 가지지만, 실제로는 배열이 아닌 객체로서 전개 연산자 ...를 쓰면 간단히 배열로 변환할 수 있다:

                                    
                                        const aryLike= "문자열"// 문자열은 유사배열 객체다!

                                        let ary
                                        ary= [...aryLike] // 1. 전개 연산자 사용
                                        console.log(ary) // ['문', '자', '열'] ← 배열로 들어간다!

                                        ary= Array.from(aryLike) // 2. Array.from() 메서드 사용
                                        console.log(ary) // ['문', '자', '열'] ← 배열로 들어간다!
                                    
                                

Array.from(aryLike[, 콜백함수]); 메서드를 쓰면; Arylike(이터러블 객체 및 유사배열 객체)를 인자로 받아 새 배열로 만들어 반환하되, 콜백함수를 두번째 인자로 주어 각 요소를 이 함수로 전달하여 처리한 값을 배열로 반환할 수 있다!

  • 경주
  • 남산
  • 삼릉
                                                    
                                                        
                                                    
                                                
                                                    
                                                        
                                                    
                                                

Array.from() 메서드에서 콜백 함수 를 두번째 인수로 줄 수도 있지만, 그보다는 배열의 map() 메서드를 써서 메서드 체이닝으로 연결하는 것이 더 간결하다!

wave