JavaScript) Start..

여기서는 웹프로그래밍의 최고봉이라고 할 수 있는 자바스크립트를 살펴봅니다

자바스크립트는 웹문서에 동적인 움직임을 주기위해 만들어진 프로그래밍 언어로서, 자바스크립트로 작성한 프로그램이 스크립트인데, 브라우저에 내장된 자바스크립트 엔진은 스크립트 코드를 읽어들여서 분석하고(= 파싱), 컴퓨터가 알 수 있는 기계어로 번역하여(= 컴파일), 그 코드 내용을 실행해나가게 된다!

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

JavaScript 시작하기

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

웹문서 내부에서 스크립트 작성하기
웹문서의 html 코드는 일반적으로 위에서 아래로 순서대로 로드되고 실행된다. 따라서, 적용하려는 html 및 Css 코드보다 스크립트가 먼저 불려지면 오류가 발생할 수 있다. 그런 이유로 스크립트 코드는 </body> 직전에 넣어주어야 한다:
[ html 문서 내부에서 스크립트 작성하기 ]
                                        
                                            <body>
                                                ..
                                            
                                                <!--
                                                    1-1. html 코드 내부에 넣는 스크립트 코드는 이벤트 리스너에서 'DOMContentLoaded' 속성을 사용해주어야 한다
                                                -->
                                                <script>
                                                    document.addEventListener("DOMContentLoaded", () => { // DOM이 다 로드된 이후.. ← defer와 같다!
                                                        // 스크립트 코드 실행
                                                    })
                                                </script>
                                            
                                                <!--
                                                    1-2. </body> 바로 직전에 넣어준 스크립트 코드는 'DOMContentLoaded'를 사용하지 않아도 defer와 같은 방식으로 작동한다!
                                                -->
                                                <script>
                                                    const buttons= document.querySelectorAll("button")
                                            
                                                    for(const button of buttons) {
                                                        button.addEventListener("click", createParagraph);
                                                    }
                                                </script>
                                            </body>
                                        
                                    

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

외부 스크립트 소스 불러오기
외부에서 불러오는 <script>의 src= 속성은 스크립트 코드를 html 문서에서 분리하여 단순화한다는 점 외에도, 단 한번만 불러오면 웹페이지 전체에서 이 코드를 공유할 수 있고, 따라서 서브 페이지들에서도 스크립트를 따로 불러오지 않고 브라우저 캐시에서 가져다 쓸 수 있다는 장점이 있다:
[ html 문서에 외부 스크립트 불러오기 ]
                                        
                                            <!-- 2-1. 외부 스크립트 파일 불러오기: 'defer' 속성 -->
                                            <script src="js/default-js.js" defer></script>

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

<script>의 src= 속성은 URL 을 값으로 받기에 다른 웹서버에 있는 코드(예건대, 인터넷 광고 등..)도 가져올 수 있으며, 인라인 스크립트 코드에 비해 웹 페이지 분석 및 렌더링 속도도 더 빠르다!

➥ async 대 defer

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

  • defer 스크립트는 순서를 유지한 채로 모든 컨텐츠를 다 불러온 이후에 실행된다. 따라서, 다른 스크립트에 의존하거나 DOM 로딩을 필요로 하는 경우에 사용된다 이는 </body> 바로 직전에 스크립트 코드를 넣어주는 경우와 같다!
  • async 스크립트는 다운로드가 끝나는 즉시 '비동기적'으로 실행된다. 그 동안 현재 페이지의 문서 분석은 계속 해나가되, 페이지 렌더링은 일시적으로 중단된다 - 따라서, 스크립트를 즉시 실행해야 하고 종속성이 없는 경우에 적합하다!
스크립트 코딩 가이드 라인
자바 스크립트는 기본적으로 대/소문자를 구분하며, 여러 칸 공백은 하나의 공백 문자로 간주하고 여러 줄 뉴라인이나 캐리지 리턴, 캐리지리턴&라인피드 모두 하나의 줄바꿈으로 간주한다
✓   세미콜론(;)은 하나의 문이 끝났음을 알리는데, 줄바꿈이 있다면; 생략할 수 있다. 자바스크립트가 줄바꿈을 세미콜론으로 취급하는 경우는 묵시적인 세미콜론을 추가하지 않고서는 코드를 분석할 수 없을 때, 곧 줄바꿈 다음에 오는 공백 아닌 문자를 현재 문에 이어진다고 판단할 수 없을 때이다 예컨대, ([로 시작하는 문이 이어질 때 세미콜론으로 구분되지 않는다면; 하나의 연결된 문으로 해석될 소지가 있다!
                                    
                                        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자리의 숫자를 넣어주는 중괄호({}) 문법으로 작성한다

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

여러 단어로 이루어진 이름을 지을 때는 보통 camelCase 표기법 및 snake_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 문)
                                        }
                                    
                                

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


위 스크립트 코드를 실행한 결과는 [브라우저 개발자 화면][콘솔] 탭에서 확인할 수 있습니다. 위 스크립트 코드를 작성한 가칭 _js.html 파일을 실행한 뒤 마우스 우측버튼 단축메뉴의 <검사>를 클릭하면; [브라우저 개발자 화면]으로 들어가게 되고, 거기서 다시 [콘솔] 탭으로 찾아가면 됩니다 html에서 스크립트를 적용하는 방식 및 콘솔창 등에 관해서는 본 html 강좌의 부분을 참조하십시오..

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

조건trusy한 값 이면; 코드1 을, falsy한 값 이라면; 코드2 를 수행한다!

3. if 문을 중첩할 수도 있고, if .. elseif .. else 문은 순차적으로 조건 을 평가하면서 내려간다:
[ 중첩 if 문 ]
                                        
                                            if(조건) { // 조건이 참이면; 코드1을 수행한다
                                                코드1;
                                            } else { // 아니라면; 여기로 온다
                                                if(조건2) 코드 2; // 조건2가 참이면; 코드2를 수행한다
                                                else 코드3; // 아니라면; 여기로 온다
                                            }
                                        
                                    
[ if .. elseif .. else 문 ]
                                        
                                            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 루프문은 스크립트에서 가장 많이 사용되는 중요한 문인데, 카운터 변수 초기값 을 시작점으로 하여 조건 을 평가하여 블록 안으로 들어가 작업을 수행하고, 증감식으로 초기값을 증감시키면서 (조건에 부합하는 한도 내에서)반복 작업을 수행해나간다:
                                    
                                        
                                    
                                

부수 효과가 있는 표현식과 대입 할당문(및 증가/감소 연산자가 붙은 변수), 객체의 프로퍼티를 삭제하는 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에 추후 값을 할당한다!
                                    
                                

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


스크립트에서 표현하고 다룰 수 있는 의 종류를 타입 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!가 발생한다!


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

                                    
                                        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의 값은 변함이 없다!
                                    
                                

스크립트에는 함수 스코프블록 스코프가 있는데, 이 스코프 에 의해 변수나 매개변수에 접근할 수 있는 영역이 달라진다. 함수 스코프는 선언된 함수 내부의 모든 변수와 함수들을 포함하며, 이때 var 전역변수는 중첩된 블록 내부로 들어가더라도(호이스팅되어 맨 위로 끌어올려지므로) 함수 내부 어디에서건 접근할 수 있다. 반면, let 변수const 상수는 해당 블록 안에서만 접근할 수 있다!

박스 클릭:
                                                    
                                                        
                                                    
                                                
                                                    
                                                        #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(); // 이는 ((typeof value) === "string") ? A : B와 같다! 
                                        console.log(value) // "문자열"
                                    
                                        const value2= NaN
                                        typeof value2 === "string" ? value2 : value2.toString();
                                        console.log(value2) // "NaN"
                                    
                                

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


삼항 조건 연산자 조건? 코드1 : 코드2;조건 이 'trusy한 값'이면; 코드1 을, 'falsy한 값'이면; 코드2 를 수행하는데, 이는 아래 if .. else 문과 같다:

                                    
                                        if(조건) 코드1; // '조건'이 참이면; '코드1'을 수행하라
                                        else 코드2; // 아니면; '코드2'를 수행하라 
                                    
                                
➥ 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) 식으로 명시적으로 비교해야 한다 덧붙이자면; 객체나 배열이 비어 있는지(빈 객체 및 빈 배열 모두 '참같은 값'이다)를 확인하려면; [].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 로 변환하여 작업을 진행한다:
                                    
                                        /* [숫자와 (숫자형)문자열]의 결합: '+'는 무조건 문자열로 연결된다! */
                                        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"와 연결하여 문자열로 반환한다!
                                    
                                

예컨대, 숫자로의 변환이 필요할 때; 숫자로 인식할 수 있는 문자열은 숫자로 변환하고, 아니라면 NaN 으로 변환하는 식인데.. 약간 모호하긴 합니다만, 코드를 잘 살펴보시면 규칙을 이해할 수 있을겁니다 ㅡㅡ;


숫자로의 변환 시 true1 로, falsenull 및 빈 문자열 ""0 으로, undefined문자열NaN 으로 변환된다. 한편, NaN 은 수치 연산을 해서 정상적인 값을 얻지 못할 때 반환되는 값인데, 이 특별한 숫자형은 그 자신을 포함하여 다른 무엇과도 같지 않다!


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

                                    
                                        const arr= [1, true, false, "hello"]
                                        arr.toString(); // "1,true,false,hello" ← arr의 각 요소를 문자열로 변환한 뒤, (콤마로 구분하여)연결한다
                                    
                                
[ 스크립트 내장 메서드를 이용한 명시적 데이터 타입 변환 ]
                                        
                                            Number("3") // 3 ← 문자열 "3"을 숫자 3으로 변환!
                                            String(false) // "false" ← 불린값 false를 문자열 "false"로 변환!
                                            String(true) // "ture" ← 불린값 true를 문자열 "true"로 변환!
                                            Boolean("") // false ← 빈 문자열을 불린 데이터 타입으로 변환!
                                            Boolean(" ") // true ← 공백 문자열을 불린 데이터 타입으로 변환!
                                        
                                    
[ 스크립트 연산자에 의한 묵시적 데이터 타입 변환 ]
                                        
                                            x + "" // + 연산에서 피연산자 중 하나가 문자열이면; 다른 피연산자(x)도 문자열로 변환된다!
                                            x - 0 // 마이너스 연산자와 함께 사용하여 x를 숫자로 변환한다!
                                            +x // 단항 +, - 연산자는 피연산자 x를 숫자로 변환한다!
                                            !!x // 부정 연산자 !(= 부정), !!(= 이중 부정)는 피연산자 x를 불린 데이터로 변환한다!
                                        
                                    
[ 변수값 할당을 이용한 동적 데이터 타입 변환 ]
                                        
                                            let a= 10 // 숫자 데이터
                                            a= '문자' // 숫자 변수 a에 문자열을 넣음으로써 데이터 타입을 동적으로 변환한다!
                                            console.log(a) // "문자"
                                        
                                    

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


✓   스크립트의 기본 데이터 타입인 String, Number, Boolean 객체에는 값만 저장되는 것이 아니라 해당 기본 타입에 특화된 값을 저장하며(= 프로퍼티), 또한 함수 형태의 특정 기능(= 메서드)을 제공하는 역할을 한다 - 곧, 기본 데이터 타입의 변수들도 이들을 위해 정의된 표준 프로퍼티와 메서드들을 객체처럼 호출할 수 있다!

숫자 다루기

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

숫자 리터럴과 Number 객체
스크립트에서 숫자 데이터는 정수와 함께 실수를 (대략적으로)표현하는데, 64비트 실수 형식을 사용해 숫자를 다룬다. 스크립트에 직접 기입하는 숫자(= 숫자 리터럴)에는 정수(예: 0, 0xff)와 실수(예: 3.14, 2.4e3)가 있다
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 ← 숫자 데이터
                                    
                                

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) // 숫자 계산
                                        }
                                    
                                

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


자바스크립트에서 숫자는 모두 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
                                        Math.log(Math.E) // 1 ← Math.E의 로그값 계산
                                    
                                

수학/삼각 함수들은 주로 캔버스나 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 에서 소수점 이하를 올림/버림/반올림 처리하여 정수로 반환한다!


비밀번호 등 보안이 중요한 경우에는 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" 문자열은 여전히 원래대로 존재한다 직접 넣어준 문자열이나 숫자 등의 원시(리터럴) 데이터는 항구불변이다!


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

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

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

➥ 메서드 체이닝

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

Str.substr(시작 인덱스번호[, 개수])Str에서 시작 인덱스번호 부터 시작하여 개수 만큼(생략 시는; 끝까지)의 문자를 찾아서 반환한다: "0123456789".substr(0, 3) // 012

Str.slice(시작 인덱스번호[, 끝 인덱스번호])Str에서 시작 인덱스번호 부터 시작하여 끝 인덱스번호 직전까지의(생략 시는; 끝까지) 문자열을 반환한다 음수(맨 끝이 -1)도 가능한데, 이 경우는 시작 인덱스번호 부터 끝 인덱스번호 직전까지 추출한다:

                                                
                                                    let lang= "0123456789";

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

Str.substring(시작 인덱스번호[, 개수])도 같지만, slice()는 음수도 사용할 수 있다는 점에서 차이가 있다!

Str.search("찾을 문자열")Str에서 찾을 문자열 과 일치하는 첫번째 문자열을 찾아 그 시작 인덱스번호를 반환하며, Str.match("찾을 문자열")Str에서 찾을 문자열과 일치하는 첫번째 문자열을 찾아서 배열로 반환한다(못 찾으면; null 을 반환한다)

Str에서 지정한 문자(열) 을 기준으로 분할하여 배열로 반환한다: "010-1234-5678".split("-") // ['010', '1234', '5678']

Str 뒤에 문자열 을 연결하여 반환한다

참고로, + 연산자로 결합하는게 더 간단하다: Str + "문자열"

Strn 번 반복하여 연결한 문자열을 반환한다

각각 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의 앞뒤쪽, 앞쪽, 뒤쪽의 공백 문자열 (공백, 탭, 줄바꿈 문자 등)을 제거하여 반환한다

Str에서 찾을 문자열 (영문 대/소문자를 구분한다!)과 일치하는 첫번째 문자의 인덱스번호 를 반환하는데(못 찾으면; -1), 시작 인덱스번호 옵션으로 검색 시작 지점을 지정해줄 수도 있다. 한편, Str.lastIndexOf("찾을 문자열")는 뒤에서부터 찾는데, 뒤에서부터 찾아도 반환하는 인덱스번호 는 앞에서부터 센다:

                                                
                                                    let lang= "javaScript";

                                                    if(lang.indexOf("Script") !== -1) { // '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문자열 (영문 대/소문자를 구분한다)로 시작하는가/끝나는가?

Str문자열 (영문 대/소문자를 구분한다)을 포함하는가?

Str소문자 / 대문자 로 변환하여 반환한다

Str에서 인덱스번호 에 해당하는 문자, 문자의 ASCII 값을 반환한다

Str을 유니코드 NFC로 정규화하여 반환하는데, 인수를 주면 NFD, NFKC, NFKD 방식의 정규화도 가능하다!

문자열 의 인코딩/디코딩: 숫자, -, _, ., !, ~, *, ', ", (, )는 이스케이프 처리한다!

문자열 의 인코딩/디코딩: \, ?, &, =, +, :, @, $, ;, #, ,도 이스케이프 처리한다!

(템플릿 문자열인)Str의 원시 문자열 형식을 반환한다

Str의 앞/뒤에서 (n 길이가 되도록) 추가문자열 로 반복하여 연결한 문자열을 반환한다:

"x".padStart(6, '*') // *****x "x".padEnd(6, '*') // x***** // 추가문자열 생략 시는; 빈 칸으로 채운다! "x".padStart(6) // x // Str이 지정한 길이보다 더 길어지면; 문자열 변환은 이루어지지 않는다! console.log("Hello, world!".padStart(6)) // Hello, world! ← 지정한 길이보다 더 길다!

템플릿 리터럴

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

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

                                    
                                        
                                    
                                

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

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

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

                                    
                                        
                                
                                

이미 스스로 코딩하여 확인해보셨겠지만(?), 이렇게 가 생성됩니다 위 스크립트 코드의 결과로 작성된 html 문서의 코드 구조는 [브라우저 개발자 화면]의 [요소] 탭에서 확인할 수 있습니다!

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( .. )은 기준시(1970.1.1 00:00:00)로부터 .. 까지 경과할 시간(밀리초)을 설정한다:
                                    
                                        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()는 UTC 기준시로부터 현재까지 지난 시간(= 타임 스탬프)을 밀리초 단위로 구하며, Date.parse( .. ) 메서드는 문자열을 인자로 받아 날짜와 시간으로 분석을 시도하고 타임 스탬프로 반환한다:
                                    
                                        const start= Date.now();
                                        // .. ← 작업 수행
                                        const end= Date.now();
                                        console.log(`작업에 걸린 시간: ${end - start}ms`);
                                    
                                

경주는 맑은 날

                                    
                                        
                                    
                                
                                    
                                        
                                    
                                

국제화 API

Intl 객체에는 여러 생성자와 국제화 생성자 및 기타 언어에 민감한 함수에 공통된 기능이 포함되어 있는데, 이를 종합하여 언어에 민감한 문자열 비교, 숫자 서식, 날짜 및 시간 서식 등을 제공하는 국제화 API를 구성한다

Intl.NumberFormat API
1. Intl.NumberFormat([지역코드, {옵션 객체}]).format()은 각국의 언어에 맞는 숫자 서식에 맞게 표시해주는데, 달리 지역코드옵션 을 주지 않으면; 해당 지역에 맞는 표기법으로 표시해준다:
[ Intl.NumberFormat 사용법 1 ]
                                        
                                            const number= 3500

                                            new Intl.NumberFormat().format(number) // 3,500 ← 기본값: 해당 지역에 맞는 표기법으로 표시된다!
                                            new Intl.NumberFormat("ko-KR", {style: "currency", currency: "KRW"}).format(number) // ₩123,457 ← 한국 원화로 표기
                                        
                                    

format() 메서드는 Intl.NumberFormat 객체와 결합된다(날짜, 문자열 등의 다른 국제화 관련 클래스도 마찬가지이다!). 따라서, 형식 객체를 참조하는 변수를 만들고 그 변수에서 format() 메서드를 호출할 필요 없이, 바로 변수에 할당하고 그 변수를 독립된 함수처럼 사용할 수 있다!

2. 원하는 지역과 옵션을 써서 Intl.NumberFormat 객체의 인스턴스를 만들고, format() 메서드에 숫자 를 전달해 적절한 형식의 숫자로 된 문자열을 얻을 수 있다:
[ Intl.NumberFormat 사용법 2 ]
                                        
                                            let pounds= Intl.NumberFormat("en", { style: "currency", currency: "GBP" })

                                            console.log(pounds.format(1000)) // £1,000.00                                
                                        
                                    

{옵션객체}의 첫번째 인자인 style: "값"으로는 decimal (정수.소수 형식 - 기본값) percent (퍼센티지) 및 currency (화폐 형식)를 사용할 수 있는데, currency 에서는 ISO 화폐코드 또한 지정해주어야 한다!


✓   {옵션객체}의 두번째 인자인 currency: "값" 에는 다음과 같은 옵션들을 사용할 수 있다:

  • useGrouping: false 천 단위 구분자 사용하지 않음
  • minimumInterDigits: n 숫자의 정수 부분 자릿수 실제 숫자보다 지정된 자릿수가 크면; 앞에 0 을 붙여서 맞춘다!
  • minimumFractionDigits/maximumFractionDigits: n 숫자의 소수점 아래 부분 최소(기본값: 0)/최대(기본값: 3) 자릿수 지정 실제 숫자보다 지정된 자릿수가 크면; 뒤에 0 을 붙여서 맞추며, 작으면; 반올림한다!
Intl.DateTimeFormat API
Intl.DateTimeFormat([지역코드, {옵션객체}]).format()은 각국의 언어에 맞는 날짜 및 시간 서식에 맞게 표시해주는데, 달리 지역코드옵션 을 주지 않으면; 해당 지역에 맞는 표기법으로 표시해준다:
[ Intl.DateTimeFormat 사용법 ]
                                        
                                            let date= new Date(Date.UTC(2025, 6, 7)) // 월은 0부터 시작한다!

                                            console.log(new Intl.DateTimeFormat().format(date)) // 2025. 7. 7. ← 기본값: 해당 지역에 맞는 표기법으로 표시된다!
                                            console.log(new Intl.DateTimeFormat("ko-KR").format(date)) // 2025. 7. 7. ← 한국에서는 년/월/일 순서
                                        
                                    
                                    
                                        let options= { // 긴 날짜 서식에 더해 요일 추가
                                            year: "numeric", month: "long", day: "numeric", weekday: "long"
                                        }
                                        console.log(new Intl.DateTimeFormat("ko-KR", options).format(today)) // 2025년 7월 7일 월요일

                                        options= { // 숫자로 된 날짜와 시간
                                            year: "numeric", month: "numeric", day: "numeric", hour: "numeric", minute: "numeric", second: "numeric"
                                        }
                                        console.log(new Intl.DateTimeFormat("default", options).format(today)) // 2025. 7. 7. 오후 9:29:36 ← 옵션을 지정하면서 지역은 브라우저 기본값을 사용할 때: 'default' 지정

                                        options= { // 오전/오후 시간 표시가 필요할 때
                                            hour: "numeric", dayPeriod: "short"
                                        }
                                        console.log(new Intl.DateTimeFormat("ko-KR", options).format(today)) // 오전 9시
                                    
                                

✓   옵션값 키워드 정리:

  • year: numeric (4자리: 2005), 2-digit (항상 2자리: 05)
  • month: numeric (가급적 짧은 숫자), long (전체 이름: January), short (약어: Jan)
  • day: numeric (가급적 짧은 숫자), 2-digit (항상 2자리)
  • hour, minute, second: numeric (가급적 짧은 숫자), 2-digit (항상 2자리)
  • hour12: true / false (12시간제로 표시할 지 여부)
                                    
                                        let date= new Date(Date.UTC(2025, 6, 7)) // 월은 0부터 시작한다!

                                        console.log(new Intl.DateTimeFormat().format(date)) // 2025. 7. 7. ← 기본값: 해당 지역에 맞는 표기법으로 표시된다!
                                        console.log(new Intl.DateTimeFormat("ko-KR").format(date)) // 2025. 7. 7. ← 한국에서는 년/월/일 순서
                                    
                                

나중에 더 필요해지면; 최신 API를 사용하는 날짜-시간 관련 JavaScript 라이브러리인 도 참조하십시오.. 이를 쓰면 좀 더 쉽고 다양하게 날짜/시간을 다룰 수 있습니다!

Intl.Collator API
Intl.Collator 객체를 생성하고, compare() 메서드를 sort() 메서드에 전달하면; 지역에 적합한 순서로 정렬하는 것이 가능해진다 compare() 메서드와 sort() 정렬 함수는 정확히 같은 방식으로 작동한다!
  • usage: sort/search Collator 객체를 사용하는 방법 지정 기본값인 sort 는 엄격한 비교이며, search 는 느슨한 비교이다
  • sensitivity: base/accent/case/variant 문자열 비교시 대소문자와 악센트를 감안할지 여부 지정 base 는 기본 글자만 비교하며, variant (이는 sort() 정렬에서의 기본값이다)는 악센트와 대소문자까지 엄격히 비교한다
  • ignorePunctuation: true 공백과 구두점을 무시하고 비교한다
  • numeric: true 정수 또는 정수가 포함된 문자열을 숫자 순서로 비교한다
  • caseFirst: upper/lower 대문자/소문자를 앞에 둔다 이는 배열의 sort() 메서드의 기본 동작이며, 대문자가 소문자보다 앞에 오는 유니코드 순서와는 다르다!
                                    
                                        // 사용자의 지역에 맞게 정렬하는 기본 비교 함수
                                        const collator= new Intl.Collator().compare
                                        console.log(["a", "z", "A", "Z"].sort(collator)) // ['a', 'A', 'z', 'Z']
                                        console.log(["다", "마", "가", "하"].sort(collator)) // ['가', '다', '마', '하']

                                        // 파일 이름에 숫자가 포함되는 경우가 많으므로 따로 정렬해야 한다:
                                        const f_name= new Intl.Collator(undefined, { numeric: true }).compare
                                        console.log(["Page10", "Page5"].sort(f_name)) // ['Page5', 'Page10']

                                        // 대상 문자열과 비슷한 문자열을 모두 찾는다:
                                        const fuzzy= new Intl.Collator(undefined, { sensitivity: "base", ignorePunctuation: true }).compare
                                        let str= ["food", "fool", "Foo Bar"]
                                        console.log(str.findIndex(s => fuzzy(s, "foobar") === 0)) // 2 ← 찾은 요소의 인덱스번호
                                        console.log(str.findIndex(s => fuzzy(s, "f00bar") === 0)) // -1 ← 찾지 못함
                                    
                                

국제화 API의 첫번째 인자로 undefined 값을 주면; 기본값으로 설정된다!


✓   대부분의 전역객체와는 달리, Intl은 생성자가 아니므로 new 연산자와 함께 사용하거나 Intl 객체를 함수로 호출할 수 없다. Math 객체와 마찬가지로 Intl의 모든 프로퍼티와 메서드는 정적이다!

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

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


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

스크립트 연산자 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
                                    
                                

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

                                    
                                        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
                                    
                                

++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승

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

                                    
                                        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.
                                    
                                

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

스크립트 연산자 2

비교 연산자: <, <= 및 >, >=
비교 연산자는 순서가 있는 데이터 타입(숫자나 문자열)의 크기(숫자) 및 우선 순위(문자는 16bit ASCII 값)를 비교하는데, (비교는 숫자나 문자열에 대해서만 가능하므로)피연산자를 숫자나 문자열로 변환한 뒤 평가하게 된다:

✓   예컨대, + 연산자가 문자열을 우선시하는 반면(하나라도 문자열이면; 모두 문자열로 변환하여 연결한다!), 비교 연산자는 숫자를 우선시하여 두 피연산자 중 하나라도 숫자라면 나머지 연산자도 숫자로 변환하여 비교하려 시도하고, 두 연산자가 모두 문자열인 경우에만 문자열로 비교하는 식이다!

✓   문자열 비교시의 순서는 유니코드에서 정의한 것으로 특정 언어나 지역에서 받아들이는 순서 개념과는 다를 수 있다. 또한, 영문 대/소문자를 구별하며 대문자의 ASCII 값이 소문자의 ASCII 값보다 작다는 것도 염두에 두어야 한다!


참고로, String.localeCompare(); 메서드는 해당 지역의 상식에 맞는 알파벳 순서를 판단 기준에 포함하며, Intl.Collator 속성을 쓰면 좀 더 상식적인 문자열 비교를 할 수 있다!

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

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

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

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

동등 연산자 ==null == undefined, "0" == 0, "0" == false, "1" == true를 모두 참으로 간주한다!

논리 연산자: &&, ||
1. 논리 연산자 ||는 가령, 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 ) 피연산자를 사용하면 논리 연산자는 항상 불린 값으로 반환한다. 그러나 피연산자가 불린이 아니라면; 그 결과를 결정한 값으로 반환한다!

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

                                        console.log(null && p.age) // null ← 앞쪽 null이 이미 거짓이므로, 뒤쪽은 건너뛴다(= 단축 평가)
                                        console.log({} && p.age) // 18 ← 빈 객체는 참이므로, 뒤쪽을 평가하여 반환한다(= 부수 효과)
                                    
                                
3. 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))


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 라면(곧, 값이 정의되지 않은 경우); 뒤쪽을(평가하여) 반환하며(= 부수 효과), 아니라면; 앞쪽 값을 (그대로)반환하는 것이다(= 단축 평가) 이는 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
                                    
                                

연산자의 우선순위

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

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

연산자의 우선 순위가 헷갈리는 경우에는; ()로 묶어주는 것이 최선이다!

콤마 연산자: ,
콤마 연산자 ,는 표현식을 앞에서부터 뒤쪽으로 순차적으로 평가해나간다:
                                    
                                        
                                    
                                

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


프로퍼티 접근 및 호출 표현식의 우선 순위는 다른 어떤 연산자보다도 우선 순위가 높다: 예컨대, 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이 된다!

                                            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 루프에서는 일단 루프로 들어가서 작업을 수행한 뒤, 루프를 나온 뒤에 조건 을 평가하여 다시 루프에 들어갈지 말지를 결정한다!

스크립트 문 연습 예제

탈출문
break 문은 (반복문과 switch 문에서, 더 이상 루프를 도는 것이 의미가 없을 때)중도에 루프를 빠져나가며, 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 루프에서는 (먼저, 증가식을 평가한 다음에)조건 검토로 넘어간다는 점에서 차이가 있다!


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

점프문
자바스크립트에서는 모든 문장에 라벨을 붙여 점프할 수 있는데, 반복문switch 문만 아니라 자신을 둘러싼 모든 문에서 사용 가능하다:
[ 점프문 사용법 ]
                                        
                                            
                                        
                                    

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

날짜/시간 연산

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() 메서드를 사용하면 된다:
                                    
                                        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
                                    
                                
                                    
                                        const toNum= new Date().valueOf() // 날짜 데이터의 숫자 변환
                                        console.log(toNum) // 1749692838181
                                    
                                

Array 배열

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

배열이란?
배열순서 있는 의 집합으로서, 각 은 배열 내에서의 위치를 가리키는 인덱스번호 를 갖는 요소들로서, 동적으로 커지거나 작아질 수 있고, '빈' 요소도 들어갈 수 있다. 배열은 언제든 요소(숫자, 문자열, 객체, 또 다른 배열 등)를 추가/제거하거나 각 요소의 값을 변경할 수 있으며, 배열의 요소 인덱스는 0 에서 시작하여 배열.length-1로 끝난다:
[ 배열의 선언 및 정의 ]
                                        
                                            const arr= [] // 빈 배열 선언
                                            arr[0]= "서울" // 배열의 요소에 값 넣기
                                            arr[1]= "부산"
                                            arr[3]= "경주"
                                            console.log(arr.length) // 4 (배열의 길이, 곧 배열 내 요소 수) ← arr[2]에는 배열의 '빈' 요소가 들어가 있다!
                                            console.log(arr) // ['서울', '부산', empty, '경주'] ← Array(4) 0: "서울" 1: "부산" 3: "경주" length: 4
                                        
                                    
                                        
                                            const ary= [[1, 2, 3], 1, '경주', false, {id: "이름", pw: "비번"}] // 리터럴 배열 선언 및 정의
                                            console.log(ary[2], ary[0][2]) // 경주 3 ← 대괄호 접근법으로 배열 요소의 값 가져오기
                                        
                                    
                                        
                                            const num= 1
                                            const nums= [num, num+1, num+2] // 표현식을 써서 만든 배열의 요소들 ← 콤마 연산자는 앞에서부터 순차적으로 나아간다!
                                            console.log(nums) // [1, 2, 3]
                                        
                                    

배열 생성 시 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이 된다!

1. 배열에 요소를 추가할 때는; 간단히 새 인덱스에 을 넣어주면 되고, 제거 시는; delete 연산자를 사용할 수 있다:
                                    
                                        let arr= [] // 빈 배열 선언
                                        arr[0]= 1 // 빈 배열에 요소 추가
                                        arr[1]= 2
                                        console.log(arr) // [1, 2]

                                        arr.push("three", undefined) // push() 메서드로 arr 배열 맨 뒤에 배열 요소를 추가한다
                                        console.log(arr) // [1, 2, 'three', undefined]

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

delete 연산자로 배열의 특정 요소 값을 삭제할 수는 있지만, 해당 요소의 값만 undefined 로 설정될 뿐(= '빈 요소') 배열의 길이는 달라지지 않는다 - 배열의 요소 자체를 삭제하려면; splice() 메서드를 써야 한다 참고로, 배열에서 '빈 요소'란 배열에 요소를 추가하거나 제거할 때, 또는 length 속성으로 길이를 설정해줄 때 undefined 값을 갖게 되는 요소를 말한다!

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

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

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

                                    
                                        console.log(typeof [], typeof {}, Array.isArray([])) // object object true
                                    
                                

Array 배열 (2)

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

배열의 인덱스 및 객체의 키 확인하기: in 연산자
delete 연산자는 객체의 프로퍼티나 배열의 요소를 삭제한다. 피연산자를 객체의 프로퍼티 키나 배열의 인덱스번호라고 간주한 채(아니라면; 그저 true를 반환할 뿐이다!) 작업을 수행하며, 성공하면 true 를 반환하고, 삭제할 수 없으면; false 를 반환한다:
                                    
                                        let arr= [1, 2, 3]

                                        delete arr[1]
                                        console.log(arr.length) // 3 ← 하나의 요소를 삭제해도 배열의 전체 길이는 변함이 없다!
                                    
                                
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 } // 객체의 키는 그 자체 문자열이다!

                                        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 연산자의 왼쪽 피연산자는 반드시 문자열 형태로 주어져야 한다!


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
                                    
                                

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님은 제 마음 속의 스타입니다!
                                    
                                

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

✓   객체의 속성값에 [] 문법으로 접근할 때는 따옴표를 필요로 하지만, 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 에는 할당 표현식의 왼쪽에 올 수 있는 것이라면 모두 가능한데, 배열인덱스번호객체속성키 에 해당한다(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 연산자로 객체 및 배열 자체를 삭제할 수는 없다!


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

Object 객체 (2)

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

프로퍼티 접근자: .
존재하지 않는 속성키(및 인덱스번호)로 접근하는 것은 에러가 아니다 - 단지 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는 없는 프로퍼티이므로 길이를 잴 수가 없다!
                                    
                                
➥ 조건부 프로퍼티 접근자 ?.

스크립트에서 프로퍼티를 가질 수 없는 값은 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