JavaScript) 2

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

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

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

함수란 무엇인가?

함수 function 는 특정 작업을 통해 어떤 결과물을 만들어내는 알고리즘으로서, 기본적으로 (언제든 불러서 재사용 가능한)서브 루틴으로서의 기능을 수행하는데, 작업한 결과물은 호출자에게 으로 반환된다!

함수의 선언 및 정의, 호출
함수function 함수명([매개변수]) { 함수객체 }로 구성되는데, 함수명 은 그 자체 변수이며 뒤따라 정의되는 { 함수객체 }의 작업수행 결과가 이 함수 '변수'의 이 된다. 따라서 함수는 선언과 동시에 렉시컬 스코프의 맨 위로 끌어올려지고(= '함수 호이스팅'), 호출될 때마다 { .. } 안의 내용을 실행하며, 함수의 매개변수{ .. } 안에서 지역변수처럼 사용된다:
[ 함수 선언 및 정의, 호출의 기본 ]
                                        
                                            const jc= "kjc"

                                            function fnc(name) { // 일반함수 fnc 선언 및 정의
                                                // 인자로 받은 name(매개변수)을 갖고 작업하여 호출자에게 반환한다(return 문)
                                                return name[0].toUpperCase() + name.slice(1); // 인자로 받은 'kjc'의 첫자를 대문자로 바꾸고, 'kjc'의 1번 인덱스부터 끝까지(= 'jc') 가져와 문자열로 연결한다
                                            }
                                            console.log(fnc(jc)) // Kjc ← 변수(jc)를 인자로 넘기면서 함수명(fnc)으로 함수를 호출하고, 그 결과값을 돌려받는다
                                        
                                    
[ 변수에 할당한 함수 표현식 ]
                                        
                                            const jc= "kjc"

                                            const fnc2= function(name) { // 변수에 할당된 함수 표현식 ← 함수에 이름이 없다!
                                                return name[0].toUpperCase() + name.slice(1);
                                            }
                                            fnc2(jc) // Kjc ← 변수(jc)를 인자로 넘기면서 변수 fnc2에 할당된 함수를 호출한다 ← 변수명(fnc2)으로 함수를 호출한다!

                                            const fnc3= (name) => { // 화살표 함수로 작성한 익명함수
                                                return name[0].toUpperCase() + name.slice(1);
                                            }
                                            fnc3(jc) // Kjc ← fnc3에 할당된 화살표 함수를 호출한다
                                        
                                    

변수에 할당한 (익명)함수 표현식은 해당 변수명 으로 호출하는데, 이름 없는 '익명함수'는 화살표 함수로 작성하면 좀 더 간결하고, 알아보기 쉽다!


함수 이름 뒤에 ()를 붙이면; 스크립트는 함수를 호출한다고 이해하여 함수객체 내부 코드를 실행하고(괄호가 없으면; 함수를 참조 하기만 하는 것이며, 그 함수를 실행 하지는 않는다!), 함수를 호출한 표현식은 return 문에 의해 반환되는 (리턴값이 없는 경우에는 undefined)을 돌려받게 된다 - 곧, 함수를 호출한 표현식은 리턴값이 된다!

➥ 순수 함수란?

일반적으로, 함수는 호출 시마다 값이 달라지는 '부수 효과'가 발생하며, 리턴값 또한 계속 달라질 수 있다. 반면, 오직 함수로서의 함수, 곧 순수 함수는 입력이 같으면 결과 또한 언제나 같고, '부수 효과'를 발생시키지 않는 함수를 말한다!

값으로서의 함수
함수는 객체이며, 따라서 함수도 일반 객체처럼 값으로 취급될 수 있다: 함수는 단순히 입력한 값을 받아 처리한 다음 그 결과를 반환하는 것만이 아니라 일반 객체처럼 으로 취급될 수 있다 - 곧, 함수를 변수, 객체의 프로퍼티, 배열의 요소 등에 할당할 수 있는 것이다!
                                    
                                        hello() // Hi, Kjc! ← 1. 함수명으로 함수를 호출한다
                                        function hello() { // 일반함수 선언 및 정의 ← 함수 '호이스팅'이 일어나 맨 위로 끌어올려진다!
                                            return "Hi, Kjc!" // 함수의 반환 값 ← 함수 내 실행 코드는 함수가 호출된 이후에 작동된다!
                                        }  // 여기서 함수의 이름 'hello'는 그저 { 함수객체 }를 참조하는 변수일 뿐이다!

                                        const fnc= hello // 함수명을 변수에 할당하여 참조한다 ← 함수명은 그 자체로 변수다!
                                        fnc() // Hi, Kjc! ← 2. 함수를 할당한 참조 변수 이름으로 함수를 호출한다

                                        const obj= {} // 빈 객체 생성
                                        obj.property= hello // 함수를 객체의 프로퍼티에 할당하여 참조한다 ← 괄호가 없으면; 함수를 '참조'하기만 하는 것이며, 그 함수를 '실행'하지는 않는다!
                                        obj.property() // Hi, Kjc! ← 3. 객체의 프로퍼티로 함수를 호출한다

                                        const arr= [] // 빈 배열 생성
                                        arr[0]= hello // 함수를 배열의 요소에 할당하여 참조한다 ← 괄호가 없으면; 함수를 '참조'하기만 하는 것이며, 그 함수를 '실행'하지는 않는다!
                                        arr[0]() // Hi, Kjc! ← 4. 배열의 요소로 함수를 호출한다
                                    
                                

함수 또한 객체인데, 매개변수로 받은 변수 f 가 함수인지 여부를 명확히 확인하고자 한다면; typeof 연산자를 사용할 수 있다:

                                    
                                        function fnc(ary, f) {
                                            // 함수가 인자로 전달되지 않았다면; (undefined 대신)매개변수를 그대로 반환하는 널함수
                                            if(typeof f != 'function') f= z => z; // 'f'가 함수가 아니라면; 그냥 건너뛴다!

                                            // 매개변수 f가 함수가 맞다면;
                                            .. 작업 수행
                                        }
                                    
                                
함수 표현식과 즉시실행 함수
1. 함수명 뒤에 ()를 붙여 '호출'하면; 함수를 '실행'한 뒤 그 결과값 을 돌려받게 된다. 반면, 단순히 함수명변수 에 할당하는 경우, (그 함수가 바로 실행되지는 않고)단지 해당 변수가 그 함수를 가리키고 있을 뿐이다. 한편, 함수를 () 안에 넣어 바로 '실행'되도록 할 수도 있다(= 즉시실행 함수):
                                    
                                        (function(a, b) { // 즉시실행 함수 ← 함수 전체를 괄호로 둘러싸준다!
                                            console.log(a + b) // 3
                                        }) (1, 2); // 함수 선언과 동시에 인자를 전달하면서 즉시 함수를 호출한다!
                                    
                                
2. 함수 표현식은 일반적인 함수 선언과 비슷하지만, 더 큰 표현식이나 의 일부로서 존재하고 이름을 붙이지 않아도 된다. 일반 함수 선언은 실제로 변수를 선언하며 그 변수에 함수 객체를 할당하지만('호이스팅'된다!), 함수 표현식은 곧바로 변수에 할당되지 않는다 - 나중에 실제 사용 시 변수나 상수에 '할당'하여 '호출'하게 된다:
                                    
                                        const add= function(x, y) { // 익명함수 함수 표현식: 익명함수를 정의하고 변수에 할당한다
                                            return x + y;
                                        } // 함수 표현식에서는 함수 호이스팅이 일어나지 않는다! 따라서, 호출 전에 작성되어야 한다!

                                        add // ƒ (x) { return x+x } ← 그냥 그 곳을 바라보고 있을 뿐, 아직 내부 코드는 실행되지 않았다!
                                        add(1, 2) // 3 ← 변수명 뒤에 ()를 붙여 함수를 호출한다!

                                        const add2= add // 또 다른 변수 add2로 (add 변수에 할당된)함수를 참조한다!
                                        add2(2, 3) // 5 ← 함수 호출
                                    
                                

일반 함수 선언으로 함수 f() 를 정의할 때 함수 객체는 실행되기 전에 존재하며 정의하기 전에 사용할 수 있다(= 함수 '호이스팅'). 반면, 함수 표현식에서의 함수 객체는 변수에 할당하기 전에는 '참조'할 수 없고, 따라서 '호출'할 수도 없다!

➥ 조건부 함수 호출

ES2020에서는 () 대신 ?.()로 함수를 호출할 수 있는데, 앞쪽의 표현식이 null 이나 undefined 일 때도 타입 에러를 일으키지 않고 호출된 함수 전체를 undefined 로 평가한다(= 단축 평가) - 곧, (부수 효과를 고려하지 않는다면;) f?.(x)(f !== null && f !== undefined) ? f(x) : undefined와 같다!

                                    
                                        let f= null, x= 0

                                        f?.(x++) // undefined ← 뒤는 평가하지 않는다(단축 평가!)
                                        console.log(x) // 0 ← 단축 평가되어 x 값은 변동이 없다!
                                    
                                

✓   함수호출 표현식 맨 앞에 있는 표현식이 프로퍼티 접근 표현식이라면; 이 호출은 메서드 호출이 되는데, 메서드 호출에서는 프로퍼티 접근 대상인 객체는 메서드가 실행되는 동안 this 값이 된다:

  • obj.m(); 프로퍼티 접근 및 메서드 호출 obj는 반드시 객체여야 하고, m() 메서드가 있어야 하며, 그 값도 함수여야 한다!
  • obj?.m(); 조건부 프로퍼티 접근 & 기본 메서드 호출 objnullundefined 라면; 표현식 전체가 undefined 로 된다!
  • obj.m?.(); 기본 프로퍼티 접근 & 조건부 메서드 호출 obj는 반드시 객체여야 하고, m 프로퍼티가 없거나 그 값이 null 이라면; 표현식 전체가 undefined 로 된다!

함수란 무엇인가 (2)

var 변수일반 함수(익명 함수 제외)는 선언하는 즉시 '호이스팅'이 일어나 선언 과 함께 초기화 가 실행되고(= 정의), 해당 스코프의 맨 위로 끌어올려진다!

함수 호이스팅
1. 렉시컬 스코프를 갖는 일반 함수선언 과 동시에 초기화 및 값 '할당'이 한번에 수행되지만('호이스팅'!), 함수 내부 변수의 값 할당 및 함수내 표현식 처리는 나중에 수행된다:
                                    
                                        for(var i=3; i >= 0; i--) { // var 변수 i는 호이스팅되어 스코프의 맨 위로 끌어올려지지만..
                                            setTimeout(function() { // 함수 내부 변수의 값 할당 및 함수내 표현식 처리는 나중에 수행되므로,
                                                console.log(i === 0 ? 'TheEnd!' : i) // -1 -1 -1 -1
                                            }, (4-i)*1000);
                                        } // ..루프를 다 돈 이후에 내부 함수객체의 처리가 이루어진다!
                                    
                                

곧, 변수에 할당한 함수 표현식 및 즉시호출 함수 표현식은 호이스팅되지 않고, 일반 변수의 규칙에 따라 처리된다!

2. 반면, 블록 스코프를 갖는 let 변수const 상수는 '호이스팅'이 일어나지 않으며, 블록 내부에서만 접근할 수 있다:
                                    
                                        function counter2() {
                                            for(let j=3; j >= 0; j--) { // for 루프 안의 블록변수 j는 호이스팅되지 않고, 루프의 매 단계마다 새로이 j의 복사본이 만들어진다!
                                                setTimeout(function() {
                                                    console.log(j===0 ? "Go!" : j) // 3 2 1 Go!
                                                }, (3-j)*1000);
                                            }
                                        }

                                        counter2() // 함수 호출
                                    
                                

참고로, 여기서도 변수의 선언 자체는 초기화 및 할당으로부터 분리되어 스코프의 맨 위로 끌어 올려진다. 따라서 값 할당없이 선언하는 경우에는 (초기화가 되지 않았기에)에러를 일으킨다!

이름붙인 함수 표현식과 재귀함수
함수는 자기 이름 으로 자기 자신을 호출 할 수 있는데, 주로 재귀 함수(= 자기 자신을 호출하는 함수)에서 쓰인다:
[ 팩토리얼 재귀함수 ]
                                        
                                            const fnc= function fact(x) { // 이름붙인 함수 표현식
                                                if(x <= 1) // x값이 1까지 내려가게 되면;
                                                    return 1; // 리턴한다!
                                                else
                                                    return x*fact(x-1); // 재귀함수 ← x에서 1을 뺀 뒤, 재차 자기 자신을 호출한다!
                                            }
                                            fnc(5) // 120 ← 5!(5x4x3x2x1)
                                        
                                    

이처럼 함수명 은 해당 {함수 객체} 안에서 지역 변수처럼 쓸 수 있다 함수 바깥에서는 이 내부 함수에 직접 접근할 수 없다!

                                    
                                        const countdown= (value, fn) => {
                                            fn(value) // 9 ← fn() 함수는 value 값을 돔에 출력한다
                                            return (value > 0) ? countdown(value-1, fn) : value; // countdown(8, fn), ..
                                        } /* value 값이 0보다 큰 동안은 현재 값을 출력한 뒤, value 값을 1 감소시켜서 재차 자기 자신을 호출한다
                                            ← 최종적으로 value 값이 0이 되고, 그 값을 돌려받으면; 호출 스택을 거슬러 올라가면서 값이 전달된다! */

                                        countdown(9, value => document.write(value)); // 9876543210
                                    
                                
                                    
                                        const countdown= (value, fn, delay=1000) => { // 시작: (9, log(), delay=1000)
                                            fn(value) // 9, ..  ← fn() 함수는 value 값을 콘솔에 표시한다
                                            return (value > 0) ? setTimeout(() => countdown(value-1, fn), delay) : value; // countdown(8, fn), countdown(7, fn), ..
                                        } /* value 값이 0보다 큰 동안은 현재 값을 출력한 뒤, value 값을 1 감소시켜서 재차 자기 자신을 호출한다
                                            ← 최종적으로 value 값이 0이 되고, 그 값을 돌려받으면; 호출 스택을 거슬러 올라가면서 값이 전달된다! */

                                        const log= value => console.log(value)
                                        countdown(9, log) // 9, .., 0
                                    
                                
➥ 템플릿 태그함수

템플릿 리터럴 앞에 함수 이름을 적으면 템플릿 리터럴의 내용을 인자로 받는 함수를 호출할 수 있다. 태그함수의 첫 번째 인자는 템플릿의 텍스트를 요소로 담은 배열(템플릿 리터럴 안의 문자열을 ${}를 기준으로 분할한 문자열)이며, 두번째 이후 인자로는 각 ${} 안에 지정된 표현식을 평가한 값이 순서대로 들어간다:

                                    
                                        const per= "Kim", age= 29

                                        function myTag(str, per, age) { // (str.raw, per, age)
                                            console.log(str.raw) // ['내 이름은 ', ', 나이는 ', '입니다!']

                                            let jen= ''
                                            age > 70 ? jen="노인" : jen="청춘"

                                            return str[0] + per + str[1] + jen + str[2]
                                        }

                                        const p= myTag `내 이름은 ${per}, 나이는 ${age}입니다!`; // 템플릿 태그로 함수 호출!
                                        console.log(p) // 내 이름은 Kim, 나이는 청춘입니다!
                                    
                                

태그함수의 첫 번째 인자는 읽기전용으로서 raw 속성이 있는데, 이 속성값에는 이스케이프되지 않은 문자열 배열이 들어간다 이 예제에서는, ${per}${age}가 빠진 문자열 배열로 들어간다!


이 태그함수 기능은 HTML이나 SQL을 텍스트에 붙이기 전에 이스케이프하는 용도로 사용할 수 있다!

함수의 매개변수

함수를 호출하면서 전달하는 인자 argument 는 함수에서 매개변수 parameter 로 받아 사용하는데, 이 매개변수는 함수가 호출되면서 전달된 (변수 자체가 아니라, 그 변수가 지닌) 을 받아 실제로 매개변수가 되며, 함수 내부에서만 존속하는 지역변수가 된다!

함수의 매개변수
함수 호출 시 객체나 배열을 전달하는 경우, 값에 의한 호출과는 달리, 참조에 의한 호출 방식으로 동작하기에 전달받은 참조값을 이용하여 (함수 내부에서)자신을 호출한 객체의 값을 변경할 수 있게 된다:
[ 값에 의한 전달 ]
                                        
                                            let x= 3

                                            function f(x) { // 매개변수 x는 (변수 자체가 아니라!)변수의 '값'으로 전달된다!
                                                console.log(`함수 내부: x= ${x}`) // x= 3 ← 새로운 값 할당 전
                                                x= 5 // 함수 내부에서 x에 새로운 값을 할당한다 ← 함수 내 지역변수 x
                                                console.log(`함수 내부에서 새로운 값 할당 후: x= ${x}`); // x= 5 ← 새로운 값 할당 후
                                            }

                                            f(x) // 변수 x를 인자로 하여 함수 f를 호출한다
                                            console.log(`함수 바깥: x= ${x}`) // x= 3 ← x 값은 여전히 3이다!
                                        
                                    

이 함수 내부 변수 x 는 함수 바깥의 변수 x 와는 (이름만 같은)별개의 변수이며, 함수 내부에서만 존속한다!

[ 참조에 의한 전달 ]
                                        
                                            let obj= { msg: '초기값' }

                                            function fnc(obj) {
                                                obj.msg= '함수 안에서 바깥의 obj 변수값을 변경함!'
                                            }

                                            fnc(obj) // 객체 obj(의 주소)를 인자로 하여 함수 fnc를 호출한다
                                            console.log(obj.msg) // 함수 안에서 바깥의 obj 변수값을 변경함! ← 바깥의 obj.msg 값이 변경되었다!
                                        
                                    
1. 전달하는 매개변수는 함수에 정의된 매개변수의 개수보다 적거나 많아도 상관없다(적으면; 나머지 인자에는 undefined 값이 할당되고, 남는 인자는 사용되지 않는다면; 버려진다). 이를 이용하면, 함수를 호출할 때 인자를 생략하고 함수 내에서 초기값을 설정하는 함수를 정의할 수도 있다:
                                    
                                        function multiply(a, b) { // (a= 3, b= undefined)
                                            b= b || 2; // 두번째 인자 b가 전달되면; 그것을 쓰고(= 단축평가), 인자가 a 하나면; b에 2를 할당한다(= 부수효과)
                                            return a*b; // 3x2
                                        }

                                        multiply(3) // 6
                                    
                                
                                    
                                        let o= {x: 1}, p= {y: 2, z: 3}

                                        function getp(obj, ary) {
                                            ary= ary || [] // 두번째 인자가 있으면; 단축 평가되고(ary= ary), 없으면; 부수효과가 일어난다(ary= [])
                                            for(let p in obj) { // obj 객체의 '키'로 루프를 돈다
                                                ary.push(p) // 배열의 뒤에 객체의 '키(이름)'를 덧붙인다 ← 파괴적!
                                            }

                                            return ary;
                                        }

                                        let a= getp(o)
                                        console.log(a) // ['x']

                                        let b= getp(p, a) // 참고로, 인자 a는 배열 ['x']이다
                                        console.log(b) // ['x', 'y', 'z']
                                    
                                

참고로, 선택 사항 인자를 받는 함수를 만들 때는 선택 사항 인자를 받는 매개변수를 마지막에 두어야 필요 시 생략하기 쉽다!

2. 함수의 매개변수초기값 을 줄 수도 있는데, 예컨대 const fnc(a=0, b=0) { .. }로 정의한 함수를 fnc(5)와 같이 호출할 경우; a 의 초기값은 5 로 대치되고, b 는 초기값으로 설정되어 있던 0 이 그대로 유지된다:
[ 매개변수 초기값 ]
                                        
                                            function getp(obj, ary=[]) { // 빈 배열([])을 두번째 매개변수의 초기값으로 넣는다
                                                for(let p in obj) {
                                                    ary.push(p)
                                                }

                                                return ary; // 리턴값은 배열이다!
                                            }
                                        
                                    

매개변수 기본값 표현식은 함수를 정의할 때가 아니라 호출할 때 평가된다 - 따라서, 위 함수가 인자 하나로 호출될 때마다 빈 배열이 새로 생성되어 전달된다!


매개변수 초기값에는 변수를 쓸 수도 있고, 함수 호출을 통해 받은 매개변수 초기값을 계산해서 전달할 수도 있다. 나아가, 앞의 매개변수의 값을 이용해 다음 매개변수의 초기값을 정의해줄 수도 있다:

                                    
                                        const rectangle= (width, height= width*2) => width * height;
                                    
                                
나머지 매개변수와 해체할당
함수의 매개변수로 나머지 매개변수를 사용하면; 첫번째 인자가 매개변수로 할당된 이후 나머지 인자들은 (하나로 묶인)배열로 할당된다:
                                    
                                        
                                    
                                

나머지 매개변수 ...는 맨 끝에 놓여져야 하며, {함수객체} 안에서 나머지 매개변수의 값은 항상 배열이다!

                                    
                                        function max(first= -Infinity, ...rest) {
                                            let maxVal= first

                                            for(let n of rest) { // ...rest가 배열이므로 for .. of 루프를 썼다 ← 배열의 값으로 루프를 돈다!
                                                if(n > maxVal) maxVal= n // 배열 요소의 값을 숫자로 변환하여 비교한다 ← 첫번째 매개변수 'first= -Infinity'가 숫자이다!
                                            }

                                            return maxVal // 가장 큰 값이 반환된다!
                                        }

                                        max(1, 10, 100, 2, 3, 1000, 4, 5) // 1000
                                    
                                

함수가 자체적으로 지닌 속성인 arguments(= 함수의 인수 리스트) 유사배열 객체와는 달리, 위와 같이 나머지 매개변수로 받는 경우는 함수 안에서 배열의 메서드들을 그대로 쓸 수 있다는 점에서 특히 유용하다!


배열 리터럴에서와 마찬가지로, 함수에서도 해체 할당...를 쓸 수 있는데, 이는 함수의 매개변수로 전달될 때의 ...와는 정반대로 동작한다 - 곧, 나머지 매개변수에서는 전달받은 여러 인자들을 하나의 배열로 모으지만, 이 경우에는 배열이나 문자열 같은 이터러블 객체를 각각의 요소들로 해체하여 사용하게 된다:

                                    
                                        function fnc([x, ...z], ...etc) { // [1, [2, 3]], ['a', 'b', 'c'] ← 나머지 매개변수
                                            return [x, ...etc, ...z]; // 1, 'a', 'b', 'c', 2, 3 ← 각 배열들을 '해체할당'하여 하나의 배열로 모아 돌려준다!
                                        }

                                        console.log(fnc([1, 2, 3], 'a', 'b', 'c')); // [1, 'a', 'b', 'c', 2, 3]
                                    
                                

fnc 함수를 호출할 때의 첫번째 인수는 배열이고([1, 2, 3]), fnc 함수의 첫번째 매개변수 내 ...z 또한 배열이지만([2, 3]) 스크립트는 스스로 이를 숫자 타입으로 간주한 채 작업을 수행하고 있다!


✓   자바스크립트는 함수의 매개변수 타입을 선언하지 않으며, 값을 전달할 때도 타입을 체크하지 않는다. 그럼에도, 스크립트는 전달받은 값이 사용되는 순간에 (필요하다면; 적절히)타입을 변환해서 작업을 수행해준다. 하지만, 항상 정확할 수는 없기에, 인자 타입을 체크하는 코드를 추가해주는 것도 좋다!

화살표 함수

화살표 함수에서는 함수의 인자함수 객체 가 분리되는데, 함수 이름은 사용할 수 없고 익명함수를 만들어 다른 곳에 전달하려 할 때 유용하게 쓰인다!

화살표 함수
아래는 일반 함수 대 화살표 함수의 간략 대차대조표다:
[ 일반 함수 대 화살표 함수 ]
                                        
                                            const fnc= function() { return "안녕!"; }

                                            /* 화살표 함수 */
                                            const fnc= () => "안녕!"; // 받는 인자가 없어도 ()는 넣어주어야 한다!
                                        
                                    
                                        
                                            const fnc= function(a, b) { return a + b; }

                                            /* 화살표 함수 */
                                            const fnc= (a, b) => a + b; // 한 줄로 작성할 때는; return 문은 사용하지 않아도 된다!
                                        
                                    
                                        
                                            (function(x) { // 즉시실행 함수
                                                x*x
                                            }) (3);

                                            /* 화살표 함수 */
                                            (x => x*x) (3); // 인자가 하나일 때는 ()는 없어도 된다!
                                        
                                    

화살표 함수 작성 시 인자=> 사이에 줄바꿈이 있어서는 안된다 - 아니라면; 이는 다음 줄로 이어지는 유효한 할당문이 만들어질 수 있다!

1. 화살표 함수의 인자객체 리터럴인 경우 반드시 ()로 묶어주어야 한다:
                                    
                                        const total= { first: 'Kim', last: 'jc', city: '경주시', state: '경상북도' }

                                        const getTotal= ({ first, last, city, state }) => ({
                                            fullName: `${first} ${last}`, location: `${state} ${city}`
                                        }); // 리턴문에서도 객체는 ()로 묶어주어야 한다!

                                        const total2= getTotal(total)
                                        console.log(total2.fullName + ": " + total2.location) // Kim jc: 경상북도 경주시
                                    
                                

함수의 리턴문에서도 return을 생략하는 경우, 반환하는 값이 객체 리터럴이면; 반드시 ()로 묶어주어야 한다. 한편, 함수의 리턴문은 기본적으로 한 줄로 작성해야 하지만, 화살표 함수 작성 시 return을 생략하고 ()로 묶어줄 경우에는 여러 줄에 걸쳐서 작성할 수도 있다!

2. 화살표 함수에서 인자 를 받을 때 ... 연산자를 쓰면; 화살표 함수에서도 온전히 배열 형태로 받아서 다룰 수 있다:
                                    
                                        
                                    
                                

스크립트 타이머

브라우저의 window 객체(및 노드의 global 객체)에는 스크립트의 타이밍을 제어하는 메서드가 기본 내장되어 있다!

스크립트 내장 타이머함수
1. setTimeout(function() { .. }, 지연시간)은 주어진 지연시간 후에(기본값인 0 은 현재 실행 중인 작업이 끝나는 즉시) 함수를 호출하여 스크립트 코드를 실행한다:
                                    
                                        function countdown() {
                                            console.log("시작: ")
                                            for(let i=3; i >= 0; i--) { // let 블록 변수 ← var 변수를 사용하면 안된다!
                                                setTimeout(function() {
                                                    console.log(i !== 0 ? i+" " : "끝!");
                                                }, (3-i)*1000); // var 변수를 쓸 경우, var 변수는 '호이스팅'되므로 '-1'만 4번 출력된다!
                                            }
                                        }

                                        countdown() // 시작: 3 2 1 끝!
                                    
                                
                                    
                                        console.log('시작..', new Date().toLocaleTimeString()) // 시작.. 오후 10:20:49

                                        setTimeout(() => {
                                            console.log('끝.', new Date().toLocaleTimeString())
                                        }, 3000); // 끝. 오후 10:20:52
                                    
                                

this의 예기치 않은 잘못된 참조를 막으려면; 화살표 함수를 사용하는 것이 보다 안전하다!

2. var timer= setInterval(function() { .. }, 시간간격)은 인자로 주어진 시간간격 (밀리초)으로 함수를 호출하여 스크립트 코드를 실행하며, clearInterval(timer)은 이를 취소한다:
                                    
                                        const interval= setInterval(timerFnc, 1000) // 단위: 밀리초

                                        let counter= 0
                                        function timerFnc() { // 이 함수는 '호이스팅'된다!
                                            ++counter
                                            console.log(counter, new Date().toLocaleTimeString()) // '1 오후 4:01:21' '2 오후 4:01:22' '3 오후 4:01:23'
                                            
                                            if(counter === 3) clearInterval(interval) // counter가 3이 되면; 빠져나간다
                                        }
                                    
                                
                                    
                                        let clock= setInterval(() => {
                                            console.clear() // 콘솔이 지워졌습니다 ← 매초마다 콘솔을 비우고,
                                            console.log(new Date().toLocaleTimeString()) // 오후 10:38:05, .. ← 새로 쓴다..
                                        }, 1000);

                                        setTimeout(() => { clearInterval(clock) }, 1000*10); // 10초가 지나면 해제한다
                                    
                                

This 바인딩

함수 호출 시에는 arguments(= 인자 리스트 )와 함께 this 또한 암묵적으로 전달되는데, 이 this 에는 함수의 호출 패턴에 따라 서로 다른 객체를 참조하게 되는 this 바인딩이 발생한다 - 곧, 함수는 '호출'될 때마다 호출되는 위치에 맞추어 자신의 this 값 을 재설정한다!

this란?
함수를 호출할 때마다 호출되는 위치를 바탕으로 this 바인딩이 만들어지는데.. 전역 코드에서의 this전역객체 를 가리키며, 함수 앞에 어떤 객체명을 붙여서 호출할 때 그 함수 내부 this 는 자신을 호출한 객체 를 가리키게 된다
1. 스크립트의 최상위 레벨에서 함수를 호출하면; 해당 함수 내부에서 사용된 this는 전역 객체에 바인딩된다. 따라서, 전역변수와 전역함수는 window 객체의 프로퍼티이다 - 이러한 특성은 내부 함수를 호출했을 경우에도(호이스팅된다!) 그대로 적용되므로 주의가 요구된다!
[ this가 가리키는 곳 ]
                                        
                                            const obj= {
                                                name: 'Kjc',
                                                say: function() { /* obj 객체의 메서드 say() */
                                                    console.log(this.name) // Kjc ← 여기서의 this는 자신을 내포한 블록인 obj 객체의 this, 곧 obj.this이다!

                                                    /* 일반 함수는 선언과 동시에 '호이스팅'되어 this를 (window.this로)새로 바인딩하게 된다! */
                                                    function f() { // 객체의 메서드 내부에서 정의된 이 일반 함수는 '호이스팅'되어 맨 위로 끌어올려진다
                                                        console.log(this.name) // undefined ← window.name
                                                    }
                                                    f() // 객체의 메서드 내부에서 f 함수 호출

                                                    /* 화살표 함수는 함수를 호출할 때 this 영역을 새로 만들어내지 않는다! */
                                                    const g= () => console.log(this.name) // Kjc ← obj.this
                                                    g() // 화살표 함수 g 호출
                                                }
                                            }

                                            obj.say() // obj 객체의 say() 메서드 호출
                                        
                                    
➥ 함수의 호출 표현식

함수는 호출 표현식을 통해 함수 또는 메서드로 호출된다. 일반적인 함수 호출에서는 return 문에 의한 반환값 (return 문 없이 끝나거나 반환값 없이 return한 경우는; undefined가 반환된다)이 호출 표현식의 이 되는데, 이때 함수의 호출 컨텍스트(곧, this)는 전역객체(곧, window 객체)가 된다!

2. 일반 함수의 this 값이 함수를 '선언'할 때 (호이스팅되어)결정되는 반면, 화살표 함수(및 익명 함수)는 (자신이 정의된)렉시컬 스코프 에서 this 값을 받아 사용하며, 이 값은 변경되지 않는다!
                                    
                                        const obj= { // 전역 obj 객체
                                            lang: 'js',
                                            greet: () => `Hi, ${this.lang}` // 이 this는 (자신이 정의된 스코프인)window의 this이다
                                        }

                                        console.log(obj.greet()) // Hi, undefined ← window 객체에는 lang 프로퍼티가 없다!
                                    
                                
                                    
                                        const obj= {
                                            name: 'cjK',
                                            g_back: function() { /* 화살표 함수를 포함하고 있는 블록 */
                                                const g_reverse= () => {
                                                    let back= ''
                                                    for(let i= this.name.length-1; i >= 0; i--) { // 역순 루프
                                                        back += this.name[i] // 이 this는 (자신이 정의된 스코프인)obj의 this이다!
                                                    }
                                                    return back;
                                                }

                                                return `${g_reverse()} 님!`
                                            }
                                        }

                                        obj.g_back() // Kjc 님!
                                    
                                
3. 객체의 프로퍼티가 함수인 경우(= 메서드), 이 메서드가 호출될 때 그 내부에서 사용된 this 는 해당 메서드를 호출한 객체에 묶이게 된다 - 곧, 함수를 어떻게 선언했느냐가 아니라, 누구로부터 호출되었는가에 따라 this가 가리키는 곳이 달라지는 것이다!
                                    
                                        const obj= { // 객체 obj
                                            name: 'jc',
                                            speak: function() {
                                                return `${this.name}`;
                                            }
                                        }

                                        obj.speak() // jc ← obj에서 speak를 호출하였다(obj.name)

                                        const other= { // 객체 other
                                            name: 'Kjc'
                                        }

                                        other.speak= obj.speak // other.speak와 obj.speak는 모두 같은 곳을 가리키고 있다!
                                        other.speak() // Kjc ← other에서 speak를 호출하였다(other.name)
                                    
                                

중첩된 함수 안에서의 this 는 다르게 작동하는데(함수 호이스팅!), 이 문제는 this 를 다른 변수에 저장하여 처리할 수 있다:

                                    
                                        const obj= {
                                            name: 'cjK',
                                            g_back: function() {
                                                const that= this // obj.this를 임시 변수 that에 할당한다!

                                                function g_reverse() { // 내부에 중첩된 함수
                                                    let back= ''

                                                    for(let i= that.name.length-1; i >= 0; i--) { // 역순 for 루프
                                                        back += that.name[i]
                                                    } // 바깥에 있는 that을 통해 obj.this에 접근한다!

                                                    return back;
                                                }

                                                return `${g_reverse()} 님!`
                                            }
                                        }

                                        console.log(obj.g_back()) // Kjc 님!
                                    
                                
메서드 호출과 체이닝
객체의 프로퍼티인 함수(= 메서드)는 객체.method()와 같이 호출하는데, 일반적인 함수 호출 시와 인자 및 반환값은 같지만, 호출 컨텍스트 가 다르다는 점에서 차이가 있다. 메서드 호출 표현식에서는 객체 Obj 가 호출 컨텍스트가 되는데, 함수 객체 메서드는 그 내부에서 this 를 통해 자신을 호출한 객체 Obj 를 참조할 수 있다:
[ 메서드 호출 ]
                                        
                                            let Obj= {
                                                p1: 1, p2: 2,

                                                add() { // 객체의 메서드 ← ES6의 약식 표기법
                                                    return this.p1 + this.p2; // 이 'this'는 Obj의 this다!
                                                }
                                            }

                                            Obj.add() // 3
                                        
                                    

✓   메서드 호출 시 대개는 . 접근자를 쓰지만, [] 접근자를 써서 calc["add"]() 식으로 접근하는 것도 가능하다. 한편, 접근하려는 메서드가 배열의 요소인 함수라면; ary[0](a, b) 식으로 접근할 수 있다!

1. 객체의 메서드로 사용되는 함수는 모두 자신을 호출한 객체 를 묵시적인 인자로 받아, 그 객체에서 움직이게 된다:
                                    
                                        fnc(obj, w, h) // obj 객체를 인자로 받아 (w, h) 인자로 조작하는 일반 fnc 함수
                                        ----------------
                                        obj.method(w, h) // obj 객체 안에서 (w, h) 인자로 작업하는 method 메서드
                                    
                                
2. 메서드가 객체 를 반환하면 그 반환값에서 다시 메서드를 호출할 수 있고, 이렇게 메서드 호출을 체인으로 이어서 하나의 표현식으로 만들 수 있다:
                                    
                                        let numbers= [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

                                        numbers
                                        .filter((val) => val%2 === 0) // numbers 배열에서 짝수를 찾아
                                        .map((val) => val*val) // 각각 제곱한 뒤
                                        .forEach((val) => {document.write(`${val} `)}); // 루프를 돌면서 결과 값들을 출력한다: 0 4 16 36 64
                                    
                                

명시적 This 바인딩

화살표 함수는 이미 문맥이 있고(곧, 자신이 정의된 렉시컬 스코프에 의해 this 값이 결정된다!), 따라서 다른 함수 내부에서 이 함수를 사용하고자 할 때 유용하다. 하지만, this 바인딩을 직접 설정할 필요가 있을 때는 문제가 된다!

명시적 this 바인딩: call()과 apply()
1. 함수를 호출하면서 call()apply()를 사용하고 this 로 사용할 객체(이 객체가 호출 컨텍스트가 된다)를 첫번째 인자로 넘기면 해당 함수가 주어진 객체의 메서드인 것처럼 사용할 수 있다:
                                    
                                        const Kjc= { name: 'jc' }

                                        function greet() { // 함수 호이스팅!
                                            return `Hi, ${this.name}`; // window.name
                                        }
                                        greet() // Hi, ← Window 객체에는 'name'이라는 이름의 프로퍼티가 없다!

                                        greet.call(Kjc) // Hi, jc ← 'Kjc' 객체의 'this'가 된다!
                                    
                                

여기서 call()apply()를 호출하는 주체는 greet 함수이며, call()apply()thisKjc 객체에 묶어줄 뿐, 그 본질적 기능은 함수 호출이다!

2. call()apply()함수.call(객체, 인자1, 인자2, ..), 함수.apply(객체, [요소1, 요소2, ..]) 식으로 호출할 수 있는데, 두번째 매개변수를 받는 모습만 다를 뿐 똑같은 일을 한다:
                                    
                                        const Kjc= { name: 'Kjc' }

                                        function ySang(year, sang) { // '함수 호이스팅'!
                                            this.y= year // this를 넣어주어야 한다!
                                            this.s= sang

                                            console.log(`${this.name}: ${this.y}, ${this.s}`)
                                        }

                                        ySang.call(Kjc, 2000, '장려상') // Kjc: 2000, 장려상
                                        ySang.apply(Kjc, [2001, '가수왕']) // Kjc: 2001, 가수왕
                                    
                                

배열의 요소들을 인자로 넘기는 경우라면; ...를 쓰면 코드가 한결 간결해진다:

                                    
                                        const numbers= [5, 1, 9, -1, 99, 0, 1]
                                        console.log(`최소값: ${Math.min(...numbers)}, 최대값: ${Math.max(...numbers)}`) // 최소값: -1, 최대값: 99
                                    
                                

Math 객체는 스크립트 전역 객체이므로 this 가 필요하지 않고, 따라서 명시적 this 바인딩 없이 펼침 연산자만 써도 충분하다!

➥ 함수형 프로그래밍

함수형 프로그래밍에서는 특정 데이터에 여러가지 함수를 적용시키는 방식으로 작업을 수행하는데, 여기서의 함수는 단지 입력을 받아 출력하는 기능만을 수행하는 것이 아니라, 인자 혹은 반환 값으로 전달된 함수를 특정 데이터에 적용시키는 개념으로 보아야 한다 - 곧, fnc.apply(Obj, Ary)와 같은 함수 호출을 fnc 함수를 Obj 객체와 Ary 배열에 적용시킨다로 이해해야 할 것이다!

명시적 this 바인딩: bind()
1. bind()는 함수의 this 값을 영구히 보존하려는 경우에 사용되는데(곧, 함수가 객체에 묶이게 된다), bind()인자 를 넘기면 항상 그 '매개변수'를 받으면서 호출되는 새 함수를 만드는 효과가 있다:
                                    
                                        const obj= { x: 1 }

                                        function fnc(y) {
                                            return this.x + y; // y는 bind 호출 시 이미 이 함수에 묶여있다!
                                        }

                                        const p= fnc.bind(obj) // fnc 함수가 this를 사용할 때마다 인자로 전달된 obj 객체에 묶인다!
                                        p(2) // 3 ← p(2) 호출은 obj.fnc(2) 호출과 같다!

                                        const obj2= { x: 10, p }
                                        console.log(obj2.p(2)) // 3 ← obj2.p(2)로 호출해도, 여전히 obj.fnc(2)이다!
                                    
                                

fnc 함수에서 bind() 메서드를 호출하면서 obj 객체를 전달하면; (obj 객체에 묶인)새 함수가 반환되는데, 이 함수를 나중에 호출해도(obj2.p(2)) 원래 함수 fncobj 객체의 메서드로 호출되는 것이다!

2. bind()는 단순히 함수를 객체에 겹합하는 데에만 쓰이는 것은 아니다. bind()에 전달되는 인자 중 첫번째 인자를 제외한 나머지는 this 값과 함께 결합되는데, 이러한 부분 적용 커링 은 화살표 함수에서도 마찬가지이다:
                                    
                                        const sum2= { x: 1 }

                                        function fnc(y, z) {
                                            return this.x + y + z;
                                        }

                                        const gc= fnc.bind(sum2, 2) // y= 2 ← this만 아니라 인자 y 값도 fnc 함수에 묶는다!
                                        console.log(gc(3)) // 6 ← 마지막 인자인 z 값으로 전달된다
                                    
                                
                                    
                                        const sum= (x, y) => x + y;

                                        const sc= sum.bind(null, 1) // x= 1 ← 화살표 함수에서는 this를 전달할 필요가 없으므로 null로 준 것이다!
                                        console.log(sc(2)) // 3 ← y= 2
                                    
                                
➥ 콜백함수와 고차함수

함수는 다른 함수에 인자로 넘겨져서 그 결과 값을 반환할 수도 있고(= 콜백함수), 함수가 다시 함수를 반환할 수도 있다(= 고차함수). 콜백 함수는 일반적으로 다른 함수에 인자로 넘기거나 객체의 프로퍼티로 사용된다. 따라서, 주로 익명 함수가 쓰이며 화살표 함수로 작성하는 것이 알아보기 쉽고 간결하다:

                                    
                                        function fnc(f1, f2) { // 함수(square, sum)를 인자로 받는다
                                            return function(...args) { // 내부 함수: 인자 (1, 2)를 받아서 처리한 뒤, 함수를 반환한다
                                                return f1.call(null, f2.apply(null, args)); // 받은 배열 인자로 f2에 적용되고(1+2), 그 값이 다시 f1에 적용되어(3x3) 반환된다
                                            }
                                        }

                                        const square= x => x*x;
                                        const sum= (x, y) => x+y;
                                        console.log(fnc(square, sum) (1, 2)); // 9 ← 내부 함수에 전달할 인자 (1, 2)와 함께 함수 인자(f1, f2)를 주어 fnc 함수를 호출한다
                                    
                                
                                    
                                        /* 고차함수: 함수를 받아서, 처리한 뒤.. 다시 함수를 반환한다 */
                                        const toUpper= (fnc) => (msg) => fnc(msg.toUpperCase()); // 함수를 받아서.. 작업의 결과는 다시 함수로 반환한다

                                        const ref= toUpper(msg => console.log(msg)); // toUpper에 함수를 전달하고.. 다시 함수로 반환받는다
                                        ref("Message") // MESSAGE
                                    
                                

함수 스코프

함수의 Scope그 함수가 어디서 작성되었는가 에 따라 명확한 경계를 지니며, 스크립트는 변수 객체를 찾을 때 자신부터 시작하여 자신을 둘러싼 상위 스코프 체인 으로 순차적으로 거슬러 올라가며 검색한다!

함수의 실행 컨텍스트와 스코프 체인
1. 함수의 실행 컨텍스트가 형성되면; 먼저 스크립트 내부적으로 변수 객체가 생성되고, arguments 객체가 만들어진다. 이어서, 현재 컨텍스트의 유효 범위를 나타내는 스코프 정보가 연결된 스코프 체인(__scope__)이 만들어지고, 지역변수 및 내부 함수 생성이 이루어진다. 마지막으로, this 바인딩이 이루어진다(여기서 this 가 참조하는 객체가 없으면; 전역 객체에 바인딩된다!). 이렇게 하나의 실행 컨텍스트가 생성되고, 변수 객체가 만들어진 후에 코드에 있는 여러가지 표현식 실행이 이루어진다(이 단계에서 변수의 값 할당 및 연산, 또 다른 함수 실행 등이 이루어진다!)
2. 함수가 실행 되는 순간 실행 컨텍스트 가 만들어지고, 이 실행 컨텍스트는 자신이 생성된 전역 객체 위에 현재 생성된 변수 객체를 쌓아 새로운 Scope 체인을 만든다. 이렇게 만들어진 스코프 체인으로 식별자 인식이 이루어지는데, 이 작업은 자신의 변수객체로부터 시작하여 찾지 못하면 스코프 체인에 따라 연결되는(__scope__) 상위 단계로 올라가면서 계속 수행된다 단, this 는 식별자가 아닌 키워드이므로 스코프 체인과 무관하게 접근할 수 있다!
➥ 스코프 체인과 변수 숨김

스코프의 계층적인 성격에서 생겨난 스코프 체인이란 개념은, 현재 스코프 체인에 있는 모든 변수는 스코프에 있는 것이며, 숨겨지지 않았다면; 접근할 수 있다는 것 이다 실행 흐름이 내부 블록에 들어가 같은 이름의 새 변수 x 를 정의하는 순간, 두 변수는 모두 같은 스코프 안에 있게 되고 외부 스코프에 있는 변수에는 접근할 수 없게 된다('변수 숨김')!


✓   전역 실행 컨텍스트는 arguments 객체가 없고, 자신이 최상위 변수 객체가 되어 곧바로 변수를 초기화하고 그 내부 함수는 최상위 레벨의 함수로 선언된다. 따라서, 이 전역 변수객체의 스코프 체인은 오직 자기 자신만을 가지며, 전역적으로 선언된 함수와 변수가 전역객체의 프로퍼티가 된다 또한, 여기서도 역시 this 를 전역 객체의 참조로 사용한다!

스코프와 클로저
1. 스코프는 변수와 상수, 매개변수가 어느 범위에서 존속하는 지를 의미하는데, 가령 특정 매개변수의 스코프가 어떤 함수라고 말할 때, 이 함수가 실제 호출되기 전까지는 그 함수 내에 해당 매개변수는 존재하지 않는다는 것을 먼저 알아야 한다 - 나아가 그 함수가 종료되면 해당 변수는 (여전히 존속은 하더라도)스코프 바깥으로 사라지게 된다. 다시 말하자면, 스코프는 프로그램의 현재 실행 컨텍스트에서 보이고 접근할 수 있는 식별자를 의미하는데, 그 식별자들은 스코프에서 벗어났더라도 (가비지 컬렉션에 의해 삭제되기 전까지는)여전히 메모리 상에 위치를 잡고 존속하고 있다!
                                    
                                        function checkscope() {
                                            const scope= "local scope" // 지역 변수

                                            function f() { // 클로저 함수 f() { .. }
                                                return scope; // 여기서는 scope 변수에 접근할 수 있다!
                                            }

                                            return f; // 이 f는 f() { .. } 함수의 참조를 돌려줄 뿐이다!
                                        }

                                        const check= checkscope()
                                        console.log(check) // f() { return scope } ← check 인스턴스는 함수 f() { .. }를 참조하고 있을 뿐이다!
                                        console.log(check()) // local scope ← 함수 호출

                                        console.log(checkscope() ()) // local scope ← 함수 f() { .. } 객체를 반환받아 즉시 실행한다!
                                    
                                
➥ 렉시컬 스코프와 클로저

렉시컬 스코프함수가 호출 시점의 스코프가 아니라 자신이 정의된 시점의 변수 스코프를 사용하여 실행된다는 것 인데, 이를 위해서는 스크립트 함수객체 내부에 함수의 코드(= 클로저 함수)만 아니라 함수가 정의된 스코프에 대한 참조(= 클로저 변수)도 반드시 포함되어야 한다 이렇게 함수객체와 스코프를 조합한 것이 바로 클로저다!

2. 클로저는 함수가 자신의 렉시컬 스코프를 기억하여 그 스코프를 벗어난 외부에서 실행될 때에도 자신이 정의된 렉시컬 스코프에 접근할 수 있게 해준다: 클로저를 써서 스코프 안에서 함수를 정의하면 해당 스코프는 더 오래 유지되며(스코프 바깥에서 클로저 내 함수를 참조하고 있음), 또한 일반적으로는 접근할 수 없는 것에 접근할 수 있는 효과도 있다 곧, 일반적으로는 자신의 스코프에 없는 것에 접근할 수 없지만, 함수를 정의해 클로저를 만들면 접근할 수 없었던 것들에 접근할 방법이 생겨난다!
                                    
                                        function outFnc(a, b) {
                                            let x= 2 // 지역변수 x

                                            function inFnc(inArg) { // 클로저 함수 inFnc ← 이 클로저 함수는 전역함수 outFnc의 스코프에서 실행된다!
                                                console.log((a+b)/(inArg+x)) // 3 ← x가 자기 스코프에 없으므로 스코프 체인을 따라 outFnc 함수의 스코프로 접근하여 x 값을 가져온다!
                                            }

                                            return inFnc; // 클로저 함수 inFnc의 참조를 돌려준다!
                                        }

                                        const ex= outFnc(4, 8) // 인자와 함께 outFnc 함수를 호출하여 inFnc 클로저 함수를 가리키는 참조로 돌려받는다!
                                        ex(2) // 이 인자는 클로저 함수 inFnc의 매개변수로 들어가며, 클로저 함수 inFnc를 실행하여 변수 x 값을 찾는다!
                                        // const ex= outFnc(4, 8) (2); // 위와 같다
                                    
                                
➥ 함수의 정적 프로퍼티

1. 함수는 기본값이 아니라 특별한 객체이므로 함수 또한 프로퍼티를 가질 수 있다 - 예컨대, 함수를 언제 호출하든 일정한 '정적' 변수가 필요하다면; 그 변수를 함수 자체의 프로퍼티로 정의하는게 편리할 수 있다:

                                    
                                        unique.ctr= 0 // 함수 객체의 ctr 프로퍼티 초기화

                                        function unique() { // 이 함수는 호출될 때마다 서로 다른 정수를 반환한다
                                            return unique.ctr++
                                        }
                                        console.log(unique(), unique(), unique()) // 0 1 2
                                    
                                

2. 위 코드는 아래와 같이 즉시실행 함수와 클로저 함수를 이용하여 보다 안전하게 다룰 수 있다:

                                    
                                        let unique= (function() { // 변수 unique에 할당되는 것은 이 즉시실행 함수가 반환하는 내부 클로저 함수이다!
                                            let ctr= 0 // 지역 변수

                                            return function() { // 클로저 함수 ← 이 클로저 함수는 자신의 스코프에 있는 변수에 접근할 수 있고, 외부 함수에 정의된 ctr 변수에도 접근할 수 있다!
                                                return ctr++ // 클로저 변수
                                            }
                                        } ()); // 이 즉시실행 함수가 종료되면; 외부에서는 변수 ctr에 접근할 수 없다!

                                        console.log(unique(), unique(), unique()) // 0 1 2
                                    
                                
비동기적 실행과 콜백 처리
함수를 호출하면 클로저가 만들어지고, 매개변수를 포함해 함수 안에서 만든 변수는 모두 무언가가 자신에 접근할 수 있는 한은 계속 존속하게 된다. 이때, 내부의 콜백은 자신을 선언한 스코프에 있는 것에만 접근할 수 있기에 콜백이 실제 실행되는 순간마다 자신이 접근하는 변수의 값이 달라질 수 있다 곧, 콜백 이 어느 스코프에서 선언됐느냐에 따라 변수값 이 각각 다를 수 있는 것이다!
                                    
                                        function counter() {
                                            let i // for 루프 바깥에서 let 변수 i 선언

                                            for(i=3; i >= 0; i--) { // for 루프 안의 i는 바깥의 변수 i를 참조한다!
                                                setTimeout(function() { // setTimeout() 안에 인수로 전달된 콜백 함수는 호이스팅되고, 바깥의 변수 i에 접근할 수 있다!
                                                    document.write(i === 0 ? "Go!" : (i+" ")) // -1 -1 -1 -1
                                                }, (3-i)*1000); // for 루프를 끝내고 i값이 -1이 된 이후 콜백 함수가 실행된다!
                                            }
                                        }
                                        counter()

                                        function counter2() {
                                            for(let j=3; j >= 0; j--) { // 루프 안의 블록변수 j는 호이스팅되지 않고, 루프의 매 단계마다 새로이 j의 복사본이 만들어진다!
                                                setTimeout(function() { // 이 즉시실행 콜백함수는 자신을 선언한 for 블록 안에서만 변수 j에 접근할 수 있다!
                                                    console.log(j===0 ? "Go!" : j) // 3 2 1 Go!
                                                }, (3-j)*1000);
                                            }
                                        }
                                        counter2()
                                    
                                

여기서 중요한 것은, 콜백이 어느 스코프에서 선언됐느냐 인데.. 위 코드에서 요점은, (여타 비동기식 프로그래밍과 마찬가지로)콜백은 자신을 선언한 스코프(클로저)에 있는 것에만 접근할 수 있다는 것이다!

스크립트 이름공간

스크립트에서 변수의 '이름 충돌'을 방지하기 위한 방식 중 하나로, 객체으로 가지는 전역 변수를 하나 생성하여 그 객체 에 프로그램 전체에서 사용할 모든 변수함수 를 프로퍼티로 정의해서 불러쓰는 방식이 있다!

스크립트 이름공간이란?
1. 이름 하나라도 스크립트 전역 공간에 남겨두고 싶지 않다면; 즉시호출 함수 표현식을 사용하면 된다. 즉시호출 함수 표현식은 내부에 있는 것들이 모두 (외부로부터의 접근을 막으면서)자신만의 스코프를 가지되, 그 자체 함수 이므로 스코프 바깥으로 무언가를 내보낼 수 있다는 점에서 클로저 를 만들어 무언가를 반환받을 때 유용하게 사용될 수 있다:
[ 즉시호출 함수 표현식 ]
                                        
                                            ( // ()로 감싼 즉시실행 함수
                                                function() { .. }
                                            ) (); // 함수를 즉시 호출한다!
                                        
                                    

함수()로 감싸면; 스크립트는 이를 함수 '선언'이 아니라 함수를 정의한 '표현식'으로 간주한다!


예컨대, 함수 안에서 선언된 변수의 유효 범위는 함수 내부로 국한되므로 가령, 외부 라이브러리를 불러들여서 사용할 때 라이브러리 안에 있는 전역 변수와의 이름 충돌을 피하기 위해서 전체 프로그램을 즉시실행 함수 안에 넣어서 실행하게 된다!

2. 함수 안에서 선언한 변수는 함수 바깥에서 보이지 않는다. 따라서, 전역 네임 스페이스를 어지럽히지 않으면서 임시 이름 공간 기능을 하는 함수를 정의하여 사용하는 것이 유용할 수 있다:
                                    
                                        const fnc= (function() {
                                            let count= 0 // 지역 변수

                                            return function() { // 클로저 함수
                                                return `${++count}번째 호출됨 !`
                                            }
                                        }) ();

                                        fnc() // 1번째 호출됨 ! ← 첫번째 함수 호출
                                        fnc() // 2번째 호출됨 ! ← 두번째 함수 호출
                                    
                                

이와 같이 함수의 이름 공간 안에 있는 변수를 사용하는 하나 이상의 함수를 정의하고, 정의된 함수를 리턴값으로 사용하는 것이 바로 클로저다!

➥ 스트릭트 모드: 'use strict'

스트릭트 모드('use strict') 지정은 (외부에서 불러온 라이브러리 등의)암시적 전역변수가 일으킬지 모르는 '이름 충돌' 문제를 막아준다:

                                    
                                        /* my.js 파일 */
                                        'use strict'; // 스트릭트 모드

                                        function myfnc() {
                                            ..
                                        }
                                    
                                

이 파일 내의 모든 코드는 스트릭트 모드로 작동하여, 이 코드와 함께 동작하는 다른 스크립트들과는 서로 간섭하지 않는다. 또한, 전역 객체와 같은 이름의 객체에 대해서는 window.객체로 설정하지 않고 undefined 값을 내보냄으로써 혹시 모를 '이름 충돌'을 방지해준다!


✓   전역 스코프에서는 모든 스크립트 전체에 스트릭트 모드가 강제되므로 전역 스코프에서는 사용하지 않는 것이 좋다. 꼭 사용해야 한다면; 아래와 같이 코드 전체를 즉시 실행되는 함수 하나로 감싸주면 된다:

                                    
                                        
                                    
                                

에러와 예외 처리

자바스크립트 Error 객체Error:에는 코드 실행 자체가 시작되지 못하는 SyntaxError (= 구문 에러)가 있고, 코드 실행 중에 발생하는 예외적인 에러인 RangeError (= 숫자값의 허용범위 초과), ReferenceError (= 잘못된 참조), TypeError (= 데이터타입 에러), URIError (= 부정확한 URI) 등이 있다

에러와 예외 처리
스크립트에 내장된 Error 객체의 인스턴스를 만들어(const err= new Error("에러 메시지")), Error 객체에 기본 내장된 message(오류 메시지) 프로퍼티 및 toString() 메서드를 써서 스크립트의 에러 정보를 다룰 수 있지만, 예상되는 에러를 처리하는 서브 클래스를 사용자 정의하여 사용할 수도 있다
1. Error 인스턴스는 주로 예상치 못한 에러 곧, '예외' 처리에 많이 사용되는데, 예외 가 발생하면; 스크립트는 즉시 프로그램 실행을 멈추고, 호출 스택을 따라 가장 가까운 예외 처리 핸들러(try .. catch .. finally 문)의 {catch} 블록을 찾아가는데, 끝까지 찾지 못한 경우에는 '예외'를 에러 로 간주하고 에러 메시지 를 보내게 된다:
                                    
                                        function fact(x) {
                                            if(x <= 0) throw new Error("똑바로 적으세요!"); // 1. 먼저, 예상되는 에러를 처리한다!

                                            let f
                                            for(f=1; x >= 1; f*=x, x--) { // 팩토리얼 계산
                                                ;
                                            }

                                            return f;
                                        }

                                        try {
                                            const n= Number(prompt("팩토리얼 계산, 양의 정수로 입력하세요: ", ""))
                                            const f= fact(n)
                                            console.log(n + "! = " + f)
                                        } catch(e) { // 2. {try 블록}에서 예상치 못한 에러, 즉 예외가 발생하면; ← 인자 e는 에러 내용인데, 보통 Error 객체가 된다
                                            alert(e) // 에러 내용을 표시한다 ← 로컬 변수 e는 Error 객체 또는 전달받은 값을 참조한다
                                        } finally { // 3. {finally 블록}이 있다면; 에러 유무를 떠나 불필요한 자원을 처리하기 위해서 마지막엔 반드시 여기로 온다!
                                            console.log(".. 여기서 최종 정리함.")
                                        }
                                    
                                

{catch} 블록과 {finally} 블록은 선택 사항이지만, 둘 중 하나는 반드시 있어야 한다!


참고로, {finally} 블록이 있다면; 이 블록은 반드시 실행된다 - 예컨대, {catch} 블록에서 return 문이나 continue 문, break 문을 만나는 경우에도 {finally} 블록을 먼저 실행한 뒤 다음 지점으로 이동하게 된다!

2. 예외를 감지하고 그 전파를 막을 뿐, 발생한 예외의 타입이나 값 등에 관심이 없다면; 아래와 같이 간결하게 작성할 수도 있다:
                                    
                                        function str_return(s) {
                                            try {
                                                return toString(s)
                                            } catch {
                                                return undefined // 위에서 뭔가 문제가 생기더라도; 신경쓰지 않고 그냥 undefined를 넘겨준다!
                                            }
                                        }
                                    
                                
3. 스크립트가 일으킨 에러 를 기다려서 캐치 하는 대신, 예상되는 에러에는 직접 throw를 던져서 '처리'하는 것이 바람직하다. throw의 표현식에는 어떤 타입의 값도 들어갈 수 있지만, 일반적으로는 Error 객체 및 그 인스턴스를 지정한다:
                                    
                                        if(찾을액수 > 계좌잔고) throw new Error("잔고 부족!")
                                    
                                

예상 가능한 에러는 예외 처리 이전에 이렇게 조건문으로 처리하는 것이 보다 안전하다!

forEach() 메서드

배열의 콜백 처리 메서드인 forEach/find/findIndex/some/every/map/filter() 등은 대부분 함수의 참조(= 콜백함수)를 첫번째 인수로 받는데, 이 콜백 함수는 배열의 요소마다 호출되어 순회하면서 작업을 수행하게 된다

배열의 순회: Ary.forEach(cb)
콜백함수의 매개변수는 콜백함수(요소[, 인덱스, Ary자체])가 된다. 각 메서드들은 추가적으로 두번째 매개변수로 this 값을 받을 수도 있는데, 이 경우 콜백함수는 자신이 그 두번째 인자의 메서드인 것처럼 호출된다 곧, 두번째 인자가 콜백함수의 this 가 된다!
1. Ary.forEach(콜백함수(요소)) 메서드는 콜백함수 를 인수로 받아 Ary 의 각 요소 를 순회하면서 특정 작업을 반복해나가게 된다:
                                    
                                        const letters= [... "hello world!"] // 문자열을 배열의 요소들로 분해하여 할당한다

                                        let toUC= ""
                                        letters.forEach(letter => { toUC += letter.toUpperCase() }); // 각 요소들을 대문자로 변환하여 문자열로 결합한다
                                        console.log(toUC) // HELLO WORLD!
                                    
                                

.forEach() 메서드는 for .. of 문과는 달리, '빈 요소' 및 존재하지 않는 요소에 대해서는 undefined 를 반환하지 않고 그냥 건너뛰어 다음 요소로 넘어간다는 점에서 더욱 유용하다!


forEach() 메서드는 배열의 요소를 순차적으로 검색하면서 반복 처리하는데, 유사배열 객체에서는 for .. of 문을 써서 이러한 반복 작업을 수행할 수 있다!

                                    
                                        const str= "hello world!" // 문자열은 유사배열 객체이다!

                                        for(let letter of str) {
                                            document.write(letter) // hello world!
                                        }
                                    
                                

2. Ary.forEach(콜백함수(요소[, 인덱스, Ary자체]) { .. })에서 인수로 주어진 콜백함수 는 주어진 Ary 의 각 요소 를 순차적으로 돌며 처리한 뒤, 그 결과를 (사본으로)반환하는데, for 문이나 for .. of 문과는 달리 map()이나 filter() 등의 메서드와 연결하여('메서드 체이닝') 그 결과값을 그대로 루프에 사용할 수 있다는 점에서 더욱 유용하다:

                                    
                                        const nums= [1, 5, 10]

                                        nums.forEach(function(val, idx) { // 매개변수: (요소, 인덱스번호)
                                            console.log(`${idx}:${val} `) // 0:1 1:5 2:10
                                        });

                                        nums.filter(value => value%2 !== 0).forEach(value => { // filter()로 홀수만 찾아서 forEach()로 넘긴다
                                            console.log(`${value} `) // 1 5
                                        });
                                    
                                
3. Ary.forEach() 메서드는 Ary 의 각 요소 를 돌면서 인수로 받은 콜백함수 를 순차적으로 실행하고, 그 작업 결과를 사본으로 반환하는데(단, Ary 자체를 콜백함수 의 3번째 인수로 넘기면; Ary 자신을 수정한다 - 파괴적!), 이를 활용하면 함수형 프로그래밍에서 for 문 대신 사용할 수 있게된다:
                                    
                                        const ary= [1, 2, 3, 4, 5]

                                        let sum= 0
                                        ary.forEach(v => sum += v); // 각 요소를 돌며 더한다 ← 복사본으로 작업한다
                                        console.log(sum) // 15

                                        ary.forEach((v, i, ary) => ary[i]= v*v) // 배열 자신을 세번째 인수로 주면; 배열 자체를 수정한다 ← 파괴적!
                                        console.log(ary) // [1, 4, 9, 16, 25]
                                    
                                

콜백처리 메서드

배열의 콜백 처리 메서드인 forEach/find/findIndex/some/every/map/filter() 등은 대부분 함수의 참조(= 콜백함수)를 첫번째 인수로 받는데, 이 콜백 함수는 배열의 요소마다 호출되어 순회하면서 작업을 수행하게 된다

배열의 변형: Ary.map(cb)
1. Ary 의 각 요소콜백함수 에 따라 새로운 형식(모든 형식이 가능하다)으로 변형하여 사본으로 반환하는데, 배열의 각 요소 를 하나씩 처리하므로 배열의 루프 처리에도 사용할 수 있다:
                                    
                                        const person= [{ name:'Kimjc', age:19 }, { name:'Lee', age:18 }]
                                        person.map(p => p.name).map(name => name.length); // [5, 3] ← 메서드 체인으로 연결
                                    
                                

메서드 체이닝에서는 항상 순서에 주의해야 한다 콜백 처리 배열 메서드의 대부분은 '빈 요소'에 대해서도 에러를 발생시키지 않고 단지 undefined를 반환할 뿐이다!

                                    
                                        const items= ['맥주', '소주'], prices= [2500, 2000]
                                        items.map((x, idx) => ({name:x, price: prices[idx]})); // [ {name: "맥주", price: 2500}, {name: "소주", price: 2000} ] ← 배열을 결합하여 객체(의 배열)로 변형하여 반환함
                                    
                                

화살표 함수에서 객체 는 반드시 ()로 감싸야 한다 아니라면; 블록으로 간주된다!

2. 객체를 배열로 변환하여 돌려받거나, 배열을 객체로 변환하여 그 사본을 돌려받을 수도 있다:
                                    
                                        let sans= ["남산 of 경주", "남산 of 서울"]

                                        // 배열에서.. 객체로 이루어진 배열을 생성한다:
                                        const objAry= sans.map(san => ({ city: san }));
                                        console.log(objAry) // [{city: '남산 of 경주'}, {city: '남산 of 서울'}]
                                    
                                
                                    
                                        const scores= { "김씨": 10, "이씨": 2, "박씨": 5 }

                                        // 객체에서.. 객체로 이루어진 배열을 생성한다:
                                        const scoreArr= Object.keys(scores).map(key => ({ name: key, wins: scores[key]}));
                                        console.log(scoreArr); // [{name: '김씨', wins: 10}, {name: '이씨', wins: 2}, {name: '박씨', wins: 5}]
                                    
                                
배열의 필터링: Ary.filter(cb)
Ary.filter()Ary 에서 조건에 맞는 요소 만 남겨 그 사본을 배열로 반환하는데, 조건에 맞는 값이 없을 때는; 빈 배열을 반환한다:
                                    
                                        const words= ['안드로이드', '금성', '해왕성', '목성', '코스모스']

                                        const long_words= words.filter(f => f.length > 3).join(" ");
                                        console.log(long_words) // 안드로이드 코스모스
                                    
                                
                                    
                                        let sans= ["경주", "서울", "서울남산", "경주남산"]

                                        const cut_san= (cut, list) => list.filter(san => san === cut);
                                        console.log(cut_san("경주남산", sans).join(" ")); // 경주남산
                                    
                                

필터링 시 undefinednull 요소 제거하기:

                                    
                                        const arr= [1, null, 'hello', 'world', true, false, undefined]

                                        const filters= arr.filter(x => x !== undefined && x !== null);
                                        console.log(filters) // [1, 'hello', 'world', true, false]
                                    
                                
배열의 검색: Ary.some/every(cb)
Ary.some()Ary 에서 (하나라도)조건에 맞는 요소가 있는지 확인하여, 첫 true 가 나오면; 바로 검색을 멈춘다. 반면, Ary.every()Ary의 모든 요소가 조건을 만족하는지 확인하여, 첫 false 가 나오면; 바로 검색을 멈춘다:
                                    
                                        const arr= [7, 11, 16, 17, 21]

                                        arr.some(x => x%2 === 0); // true ← 짝수가 있는가?
                                        arr.every(x => x%2 === 0); // false ← 모두 짝수인가?
                                    
                                
배열의 검색: Ary.find/findIndex(cb)
Ary.find()Ary 에서 조건(= 찾을 값, 또는 콜백함수)에 따라 검색하여 (처음 찾은) (찾지 못하면; undefined) 을 반환하며, Ary.findIndex()는 (처음 찾은 요소의)인덱스번호 (찾지 못하면; -1) 를 반환한다:
                                    
                                        const ary= [1, 2, 3, 4, 5]

                                        ary.findIndex(e => e > 3); // 3 ← 처음 찾은 값의 인덱스번호
                                        ary.findIndex(e => e > 5); // -1 ← 찾지 못함

                                        ary.find(e => e > 3); // 4 ← 처음 찾은 값
                                        ary.find(e => e > 5); // undefined ← 찾지 못함

                                        console.log(ary.find(e => e > 5) || "더 큰 값은 없습니다!"); // 더 큰 값은 없습니다! ← 부수효과!
                                    
                                
배열의 평면화: Ary.flatMap(cb)
Ary.flatMap()Ary 배열을 평면화하는데(1단계만), Ary.map()Ary.flat()을 합친 움직임을 보인다:
                                    
                                        let paras= ["Good bye", "jQuery"]

                                        let word= paras.map(para => para.split(" ")).flat();
                                        console.log(word) // ['Good', 'bye', 'jQuery']

                                        let words= paras.flatMap(para => para.split(" "));
                                        console.log(words) // ['Good', 'bye', 'jQuery']
                                    
                                

곧, Ary.flatMap(cb)Ary.map(cb).flat()와 같다 Ary.flat()은 중첩된 배열을 (1단계만)평탄화한다

배열의 분해/결합

Ary.concat(Ary2)
Ary 뒤에 Ary2 (또는, 콤마로 연결된 값들)를 분해(제공받은 배열은 한번만 분해하며, 배열 안에 있는 배열을 다시 분해하지는 않는다), 연결하여 사본으로 반환한다:
                                    
                                        const list= ["빨강", "파랑", "핑크"]

                                        const addColor= (color, list) => list.concat(color); // concat()은 작업한 결과물을 사본으로 반환한다!
                                        const ret= addColor(["초록", "검정"], list)
                                        console.log(ret) // ['빨강', '파랑', '핑크', '초록', '검정']
                                    
                                

참고로, 인수가 객체의 참조이면 그 참조 값을 복사하므로(= 얕은 복사) 원본 객체를 수정하면 반환되는 값 또한 바뀌게 된다!


concat() 대신에 push()...를 쓰면 좀 더 쉽게 배열을 결합할 수 있고, 코드도 더 간결하다:

                                    
                                        const ary= [1, 2, 3]

                                        ary.push(...["a", "b", "c"]);
                                        console.log(ary) // [1, 2, 3, 'a', 'b', 'c']

                                        ary.push(4, 5);
                                        console.log(ary) // [1, 2, 3, 'a', 'b', 'c', 4, 5]
                                    
                                
Ary.join()
Ary의 모든 요소를 ,로 구분하여( 연결문자 를 인수로 주면; 그 연결문자 로 연결하며, 빈 문자열 을 인수로 주면; 배열 요소의 모든 값을 그대로 붙여서) 그 사본을 문자열로 반환한다:
                                    
                                        const arr= [1, null, 'hello', 'world', true, false]
                                        delete arr[3]
                                        console.log(arr) // [1, null, 'hello', empty, true, false]

                                        console.log(arr.join()) // 1,,hello,,true,false ← null, undefined 자리는 비워둔다(= '빈 요소')
                                        console.log(arr.join('')) // 1hellotruefalse
                                        console.log(arr.join(' ')) // 1  hello  true  false
                                    
                                

요소를 합칠 때 정의되지 않은 요소, 삭제된 요소, null, undefined는 모두 빈 문자열로 처리한다!

                                    
                                        
                                    
                                

Ary.join()은 정확히 Str.split()에 역대응한다:

                                    
                                        console.log("010-1234-5678".split("-")) // ['010', '1234', '5678']
                                        console.log(['010', '1234', '5678'].join('-')) // 010-1234-5678
                                    
                                

✓   참고로, 배열의 toString() 메서드는 인자 없는 join() 메서드와 비슷하다:

                                    
                                        const ary= [1, [2, "K"]]

                                        console.log(ary.toString()) // 1,2,K
                                        console.log(ary.toLocaleString()) // 1,2,K
                                    
                                

toLocaleString() 메서드는 toString() 메서드의 지역별 버전인데, 각 배열 요소들을 문자열로 변환한 다음 지역에 맞는 구분자 문자열을 써서 병합한다!

Ary.flat()
Ary.flat()은 중첩된 배열을 평탄화하는데, 인수로 숫자 를 넣어 중첩된 배열의 깊이를 지정해줄 수도 있고, Infinity 로 주면 모든 깊이의 중첩을 해제할 수 있다:
                                    
                                        const arr=[1, [2, [3, 4]]]

                                        arr.flat() // [1, 2, [3, 4]]
                                        arr.flat(2) // [1, 2, 3, 4]
                                        arr.flat(Infinity) // [1, 2, 3, 4]
                                    
                                

배열의 검색/추출

Ary.indexOf/lastIndexOf(값[, 검색시작 인덱스번호])
Ary 의 앞/뒤쪽에서부터 주어진 과 일치하는(===) 첫번째 요소 를 찾아 그 인덱스 번호 (찾지 못하면; -1)을 반환한다:
                                    
                                        
                                    
                                
Ary.includes(값[, 검색시작 인덱스번호])
Ary에서 주어진 이 있는지 확인하여 그 결과를 true/false 값으로 반환한다 검색시작 인덱스번호 에는 음수도 가능하다!
                                    
                                        const cities= ['서울', '부산', '대구', '울산']

                                        function removeItem(items, r) {
                                            if(items.includes(r)) { // items에 r이 포함되어 있다면;
                                                const idx= items.indexOf(r) // 찾은 요소의 인덱스번호
                                                items.splice(idx, 1) // 배열에서 해당 인덱스번호가 가리키는 값을 제거한다 ← 파괴적!
                                            }
                                            return items; // 변경된 배열로 반환한다
                                        }

                                        console.log(removeItem(cities, '대구')) // ['서울', '부산', '울산']
                                    
                                

includes() 메서드는 NaN 이 자기 자신과는 같다고 간주한다. 반면, indexOf() 메서드는 NaN 이 자기 자신을 포함해 어떤 값과도 다르다고 간주한다!

                                    
                                        let ary= [1, true, 3, NaN]

                                        console.log(ary.includes(NaN)) // true
                                        console.log(ary.indexOf(NaN)) // -1

                                        console.log(NaN === NaN) // false
                                    
                                
Ary.slice(시작 인덱스번호[, 끝 인덱스번호])
Ary 에서 시작 인덱스번호 에 해당하는 요소부터 시작하여 끝 인덱스번호 직전까지 추출하는데, 음수는 맨 끝(= -1)부터 센다 - 끝 인덱스번호 를 생략하면 마지막까지 추출하여 그 사본을 반환한다 참고로, 인수가 객체의 참조이면 그 참조 값을 추출하므로(= 얕은 복사) 원본 객체를 수정하면 반환되는 값 또한 바뀌게 된다!
                                    
                                        const cities= ['서울', '부산', '대구', '울산', '경주']

                                        function removeItem2(items, r) {
                                            if(items.includes(r)) {
                                                const idx= items.indexOf(r) // 찾은 요소의 인덱스번호

                                                // 찾은 요소의 인덱스번호 직전까지 추출하고, ← items.slice(0, idx)
                                                // 다시 찾은 요소의 다음 인덱스번호부터 끝까지 추출하여 ← items.slice(idx+1)
                                                // 배열로 연결한다 ← A.concat(B)
                                                return items.slice(0, idx).concat(items.slice(idx+1));

                                                // 배열로 연결한다 ← [...A, ...B]
                                                // return [...items.slice(0, idx), ...items.slice(idx+1)]; // 좀 더 알아보기 쉽고 간결하다!
                                            }
                                        }

                                        console.log(removeItem2(cities, '대구')) // ['서울', '부산', '울산', '경주']
                                        console.log(cities) // ['서울', '부산', '대구', '울산', '경주'] ← 원본은 변함이 없다!
                                    
                                

배열의 추가/제거/수정

Ary.pop() 대 .push('요소[, 요소2, ..]')
Ary.push('요소[, 요소2, ..]')Ary 의 맨 뒤에 새 요소(들) 을 추가하고 변경된 Ary 의 길이를 반환하며, Ary.pop()Ary 의 맨 뒤에 위치한 요소를 제거하여 제거한 요소 값을 반환한다:
                                    
                                        let stack= []

                                        stack.push(1, 2) // [1, 2]
                                        stack.pop() // [1] ← 반환값: 2
                                        stack.push(3) // [1, 3]
                                        stack.pop() // [1]
                                        stack.push([4, 5]) // [1, [4, 5]] ← 배열을 해체하지는 않고, 통채로 넣는다!
                                        console.log(stack) // [1, [4, 5]] ← 파괴적!
                                    
                                

... 연산자는 배열의 요소들을 해체 할당하여 넣는다:

                                    
                                        let stack= []
                                        stack.push(1, 2) // [1, 2]

                                        const etc= [3, 4, 5]
                                        stack.push(...etc) // etc의 요소들을 해체할당하여 넣는다!
                                        console.log(stack) // [1, 2, 3, 4, 5]
                                    
                                
Ary.shift() 대 .unshift('요소[, 요소2, ..]')
Ary.shfit()Ary 의 맨 앞에 위치한 요소를 제거하고, 나머지 요소들을 앞으로 전진 배치한다(리턴값: 제거한 요소 의 값). 반면, Ary.unshift('요소[, 요소2, ..]')Ary 의 맨 앞에 새 요소(들) 을 추가하고, 나머지 요소들을 뒤로 밀어낸다(리턴값: 변경된 Ary 의 길이)
                                    
                                        let q= []

                                        q.push(1, 2) // [1, 2]
                                        q.shift() // [2]
                                        q.push(3) // [2, 3]
                                        console.log(q) // [2, 3] ← 파괴적!

                                        q.shift() // [3]
                                        q.shift() // []
                                        q.unshift(1, 2) // [1, 2] ← 뒤쪽 값부터 들어간다!
                                        console.log(q) // [1, 2] ← 파괴적!
                                    
                                

참고로, 제거할 요소가 없어도 에러는 발생하지 않으며, 단지 undefined 를 반환할 뿐이다!

Ary.splice(시작인덱스[, 제거할 개수, 추가할 요소1, 추가할 요소2, ..])
Ary시작인덱스 에 해당하는 요소부터(음수면; 뒤에서부터 세는데, 맨 끝이 -1 이다) 제거할 개수 만큼 삭제한 뒤(리턴값: 제거한 요소들의 배열), 추가할 요소(들) 을 덧붙여 기존 배열 Ary 자체를 수정한다 인수가 시작인덱스 하나이면; 거기서부터 끝까지 제거하고, 제거할개수0 이면; 삭제하지 않고 시작인덱스 위치에서부터 요소를 삽입하여 추가하기만 한다!
                                    
                                        const ary= [1, 2, 3, 4, 5]

                                        ary.splice(3, 1, "a", 7) // ary[3]번 요소(곧, 4)을 제거하고(ary[1, 2, 3, (4), 5]), 그 자리서부터 'a'와 7을 삽입한다 ← 5는 뒤로 밀려난다!
                                        console.log(ary) // [1, 2, 3, 'a', 7, 5] ← 파괴적!
                                    
                                
                                    
                                        const ary= [52, 273, 103, 32, 279, 129]

                                        for(let i= ary.length-1; i >= 0; i--) { // 역순 for 루프
                                            if(ary[i] > 100) ary.splice(i, 1) // 100보다 크다면; 그 인덱스번호(= i)의 해당 요소를 제거한다
                                        } // 요소가 삭제될 때마다 ary의 길이는 계속 줄어들기에 역 for 문을 써야한다!

                                        console.log(ary) // [52, 32] ← 파괴적!
                                    
                                

삽입이나 제거가 이루어진 후에는 다음 요소들을 앞이나 뒤로 밀어 배열을 빽빽하게 유지한다!

Ary.fill(값[, 시작인덱스, 끝인덱스])
주어진 으로 채워서 Ary 를 수정하는데, 일부만 채우려면; 시작인덱스끝인덱스(생략 시; 끝까지)를 지정해주면 된다 끝인덱스 의 바로 앞까지, 음수 또한 사용할 수 있다!
                                    
                                        const arr= new Array(5).fill(1); // [1, 1, 1, 1, 1] ← 5개 요소로 이루어진 배열을 생성하고 모두 '1'로 채운다

                                        console.log(arr.fill('*', 2)) // [1, 1, '*', '*', '*'] ← 파괴적! (따라서, 아래부터는 이 결과 값으로 시작된다)
                                        console.log(arr.fill(0, -3, -1)) // [1, 1, 0, 0, '*'] ← 뒤에서 3번째부터(-3) 시작하여 맨 뒤(-1) 바로 앞까지 '0'으로 채운다
                                    
                                
Ary.copyWithin(붙여넣을 인덱스번호, 시작인덱스[, 끝인덱스])
Ary 내부에서 시작인덱스 부터 끝인덱스 (직전)까지의(생략시; 끝까지) 요소를 붙여넣을 인덱스번호 위치에서부터 덮어씌운다:
                                    
                                        const arr= [1, 2, 3, 4]

                                        console.log(arr.copyWithin(1, 2)) // [1, 3, 4, 4] ← 파괴적!
                                        console.log(arr.copyWithin(2, 0, 2)) // [1, 3, 1, 3] ← 수정된 위 arr 값을 대상으로 작업한다!
                                        console.log(arr.copyWithin(0, -3, -1)) // [3, 1, 1, 3] ← 역시, 수정된 위 arr 값을 대상으로 작업한다!
                                    
                                

배열의 전체 길이는 달라지지 않는다!

배열의 정렬

Ary.reverse/sort()
Ary.sort()Ary 의 각 요소를 사전순으로 오름차순 정렬하여 수정하며, Ary.reverse()Ary 의 각 요소를 역순으로 정렬하여 수정한다:
                                    
                                        const cities= ['서울', '부산', '울산', '경주']

                                        console.log(cities.sort()) // ['경주', '부산', '서울', '울산'] ← 오름차순 정렬
                                        console.log(cities) // ['경주', '부산', '서울', '울산'] ← 파괴적!

                                        console.log([...cities].reverse()) // ['울산', '서울', '부산', '경주'] ← 역순 정렬
                                        console.log(cities) // ['경주', '부산', '서울', '울산'] ← 원본은 변함이 없다!
                                    
                                

모두 파괴적인데, ... 연산자를 이용하면 원본을 보존할 수 있다!

➥ 배열 요소의 순차 정렬

배열 관련 메서드의 순차 정렬은 유니코드 값(이것은 문자열이다!)에 따르는 오름차순 정렬이다. 가령, 숫자 배열을 정렬할 때 그 기준은 숫자의 크기가 아니라 그 숫자(곧, 첫 숫자 문자)의 유니코드 값이며, 또한 영문 대문자는 소문자보다 앞에 온다 곧, 대문자가 소문자보다 ASCII 코드값이 낮다!

                                        
                                            const ary= [9, 12, 1, 6, 2, 7]

                                            // ASCII 코드값(숫자 크기가 아님!)에 따르는 오름차순 정렬
                                            console.log(ary.sort()) // [1, 12, 2, 6, 7, 9] ← 첫 숫자 문자만으로 비교한다!
                                        
                                    

참고로, 비교함수 를 쓰면서, 그 리턴값에 localeCompare() 메서드를 활용하면; 대/소문자 구분없는 정렬이 가능해진다!

                                        
                                            const city= ['sla', 'silla', 'busan', 'Seoul']

                                            console.log(city.sort()) // ['Seoul', 'busan', 'silla', 'sla'] ← 유니코드에서는 대문자가 앞에 온다!

                                            // 지역 코드에 따르는 오름차순 정렬 ← 한국어에는 대/소문자 구분이 없다!
                                            console.log(city.sort((a, b) => a.localeCompare(b))); // ['busan', 'Seoul', 'silla', 'sla'] ← 대/소문자 구별없는 오름차순 비교
                                        
                                    

정렬 비교함수

비교함수를 이용한 정렬
Ary.sort(비교함수)Ary비교함수 의 정렬 알고리즘에 따라 (오름차순 및 내림차순으로)정렬하여 정렬된 새 배열로 대치하는데, 비교함수 는 두 인수 (a, b)의 크기를 비교하여 a-b 값이 0보다 작으면; a, b 순으로, 0보다 크면; b, a 순으로 정렬하고(= 오름차순 정렬), 0이면; 원래대로 놔둔다. 반대로, b-a로 비교하면; 내림차순 정렬이 된다:
                                    
                                        const ary= [3, 2, 1]

                                        ary.sort(function(a, b) { return a-b }); // 다른 함수의 인자로 사용된 익명 함수 표현식
                                        console.log(ary) // [1, 2, 3] ← 배열의 sort() 정렬 메서드에서 a-b는 오름차순으로, b-a는 내림차순으로 정렬된다!
                                    
                                
                                    
                                        const ary= [9, 12, 1, 6, 2, 7]

                                        // 비교함수에 의한 오름차순 정렬 ← 이는 ASCII 코드값이 아니라 숫자로 비교한다!
                                        // a-b가 음수이면(곧, b가 더 크다); 그대로 a, b 순으로 놓고, a-b가 양수이면(곧, a가 더 크다); b, a 순으로 놓는다  
                                        console.log(ary.sort((a, b) => a-b)); // [1, 2, 6, 7, 9, 12] ← 오름차순 정렬
                                        console.log(ary) // [1, 2, 6, 7, 9, 12] ← 파괴적!
                                    
                                
                                    
                                        const ary= [9, 12, 1, 6, 2, 7]

                                        // 비교함수에 의한 내림차순 정렬 ← 이는 ASCII 코드값이 아니라 숫자로 비교한다!
                                        // b-a가 양수이면(곧, b가 더 크다); b를 앞으로 옮기고, b-a가 음수이면(곧, a가 더 크다); 그대로 둔다  
                                        console.log(ary.sort((a, b) => b-a)); // [12, 9, 7, 6, 2, 1] ← 내림차순 정렬
                                        console.log(ary); // [12, 9, 7, 6, 2, 1] ← 파괴적!
                                    
                                
                                    
                                        const str= ['abced', 'abc', 'abcdefgh']
                                        const long_s= str[2].length; // 8
                                        
                                        // 각 문자열을 길이에 따라 오름차순으로 정렬한 뒤, long_s 길이로 (앞은 공백으로 채우면서)뒤쪽서부터 맞추어 표시한다
                                        str.sort((a, b) => a.length - b.length).forEach(s => console.log(s.padStart(long_s)));
                                    
                                
                                    
                                        let persons= [
                                            {name: "Kim", age: 17}, {name: "Park", age: 18}, {name: "Lee", age: 16}
                                        ]

                                        function cp_Func(key) { // 객체의 key 기준 정렬
                                            return function(a, b) {
                                                return a[key] - b[key]; // 나이순 오름차순 정렬
                                            }
                                        }

                                        persons.sort(cp_Func("age")) // {name: "Lee", age: 16}, {name: "Kim", age: 17}, {name: "Park", age: 18}
                                        console.log(`${persons[0].name} ${persons[0].age}`) // Lee 16 ← 파괴적!
                                    
                                

이처럼, 비교함수 를 쓰면 객체로 이루어진 배열을 정렬하는 것이 가능해진다!


Ary.sort(비교함수) 정렬에서 0 을 반환하는 함수를 사용하면(0 이 반환되면; sort는 요소가 순서상 같다고 간주하여 순서를 바꾸지 않는다!) 알파벳 순으로 정렬하되, k 로 시작하는 단어는 원래 순서를 유지한다는 식의 응용도 가능하다!

                                    
                                        const staff= [{name: 'Kim', years: 10}, {name: 'Pak', years: 5}, {name: 'Lee', years: 10}]

                                        function sortByYears(a, b) {
                                            if(a.years === b.years) return 0; // 같은 years가 나오면; 그냥 빠져나온다!

                                            return a.years - b.years; // 오름차순 정렬
                                        }

                                        console.log(staff.sort(sortByYears)) // {name: 'Pak', years: 5} {name: 'Kim', years: 10} {name: 'Lee', years: 10}
                                    
                                

reduce() 메서드

콜백처리: Ary.reduce()
Ary.reduce(콜백함수(누적값, 요소[, 인덱스, 배열]) { .. }[, 초기값]) 메서드는 인자로 받은 콜백함수 에 따라 처리하여 보통 단 하나의 값(객체나 또 다른 배열)으로 된 사본으로 반환하는데, 여타 콜백 매개변수들과는 달리 맨 먼저 오는 매개변수가 누적값 이며, 초기값 도 마지막 인자로 넣어줄 수 있다!
1. 누적값 은 마지막 호출에서의 결과 값인데, 배열만 아니라 객체나 문자열 등도 누적값 으로 사용할 수 있다. 한편, Ary 의 첫번째 요소가 그대로 초기값 이 될 수 있을 때는 초기값 을 생략해도 좋다:
                                    
                                        const ary= ["Bak", "Kim", "Lee"] // 배열

                                        // 누적값: p, 요소: v, 초기값: 빈 문자열("")
                                        const str2= ary.reduce((p, v) => p + " " + v, ""); // 문자열로 연결
                                        console.log(str2) // Bak Kim Lee
                                    
                                
                                    
                                        const arr= [['부산', '울산'], ['경주', '포항']] // 중첩 배열

                                        // (생략된)초기값은 배열의 첫번째 요소(['부산', '울산'])이다!
                                        const arr2= arr.reduce((pv, cv) => pv.concat(cv)); // 내부 중첩 배열을(해체하여 각각 누적값으로 붙인 뒤)하나의 배열로 반환한다
                                        console.log(arr2) // ['부산', '울산', '경주', '포항']
                                    
                                

초기값 이 주어지지 않으면; Ary 의 첫번째 요소초기값 으로 간주하여 작업한다!

                                    
                                        const colors= ["red", "red", "green", "blue", "green"]

                                        const dColors= colors.reduce((d, color) => d.indexOf(color) === -1 ? [...d, color] : d, []); // 초기값: []
                                        console.log(dColors) // ['red', 'green', 'blue'] ← 중복 요소 제거한 새 배열 생성
                                    
                                
                                    
                                        const nums= [21, 12, 43, 25, 50]

                                        const maxNum= nums.reduce((max, num) => num > max ? num : max, 0);

                                        /* 이 코드는 위 화살표 함수 코드를 풀어쓴 것임: */
                                        // const maxNum= nums.reduce((max, num) => { // max: 누적값, 현재 요소: num
                                        //     if(num > max) return num; // num이 누적값보다 크다면; 누적값으로 들어간다
                                        //     else return max; // num이 누적값보다 작다면; 그대로 누적값을 반환한다
                                        // }, 0) // 초기값: 0

                                        console.log('가장 큰수: ', maxNum) // 가장 큰수: 50
                                    
                                

Ary.reduceRight() 메서드는 뒤쪽 요소로부터 시작하여 작업한다는 점만 다르다!

2. Ary.reduce()가 받는 콜백함수 는 첫 번째 인자에 그때까지 행한 '축소' 작업의 결과물(= 누적값)이 계속해서 누적되어간다는 점에서, 또 this 로 사용될 다음번 인자 옵션이 없다는 점에서 forEach( .. ) 등과는 동작 방식에 차이가 있다:
                                    
                                        const colors= [
                                            { id: 'id1', color: "빨강", rate: 3 },
                                            { id: 'id2', color: "파랑", rate: 2 },
                                            { id: 'id3', color: "회색", rate: 5 },
                                            { id: 'id4', color: "파랑", rate: 7 }
                                        ]

                                        const mColors= colors.map(c => c['color']);
                                        console.log(mColors) // ['빨강', '파랑', '회색', '파랑']

                                        const rColors= colors.reduce((p, v) => {
                                            return [...p, v['color']];
                                        }, []);
                                        console.log(rColors) // ['빨강', '파랑', '회색', '파랑']

                                        const pColors= colors.reduce((p, v) => {
                                            if(p.includes(v.color)) { // 중복값은 제외한다
                                                return p; // 원래의 누적값을 그대로 리턴한다!
                                            }

                                            return [...p, v.color]; // 새로운 색상은 누적값 배열로 펼쳐넣는다
                                        }, []); // 초기값: 빈 배열
                                        console.log(pColors) // ['빨강', '파랑', '회색']

                                        const hColors= colors.reduce((h, {id, color, rate}) => {
                                            h[id]= { color, rate }
                                            return h;
                                        }, {}); // 초기값을 빈 객체로 주어 배열을 객체로 변환한다!
                                        console.log(hColors) // {id1: {color: '빨강', rate: 3}, id2: {…}, id3: {…}}
                                    
                                
                                    
                                        const ids= [
                                            { id: 'id1', color: "빨강" }, { id: 'id2', color: "파랑" }, { id: 'id3', color: "회색" }, { id: 'id4', color: "파랑" }
                                        ]

                                        const color= ids.reduce((p, c) => {
                                            const num= p[c.color] || 0; // 빈 객체가 초기값으로 주어졌으므로, '부수효과'가 일어나 처음에는 num에 0이 들어간다!

                                            return {
                                                ...p, [c.color]: num+1, // 처음 나오는 키에는 1이 들어가고, 같은 값을 만나면; num에 1이 추가된다!
                                            }
                                        }, {});

                                        console.log(color) // {빨강: 1, 파랑: 2, 회색: 1}
                                    
                                
                                    
                                        const cities= ['경주', '남산', '삼릉', '삼릉마트']

                                        const alp= cities.reduce((a, x) => {
                                            if(! a[x[0]]) { // a에 x[0] 프로퍼티가 없다면; (= 첫 단계에서는, a.경 프로퍼티가 없다면;)
                                                a[x[0]]= [] // 빈 배열을 추가한다
                                            }
                                            a[x[0]].push(x) // { 경: [ '경주' ] } (= 첫 단계의 누적값)

                                            return a; // 누적값을 리턴하지 않으면; 값은 완전히 사라진다!
                                        }, {}); // 빈 객체를 초기값으로 줌

                                        console.log(alp) // {경: ['경주'], 남: ['남산'], 삼: ['삼릉', '삼릉마트']}
                                    
                                

reduce() 사용 시; 항상 초기값 을 넣어주고, 누적값 또한 명시적으로 작성해주는 것이 코드의 가독성에 도움이 된다 참고로, 누적값 을 반환하지 않으면; undefined누적값 으로 들어가게 되어 에러가 발생한다!

고차함수

고차함수매개변수 를 가두는 방법을 통해 특별한 값을 제공하므로, 나중에 원래의 인자에 접근할 수 있도록 해두고 함수 실행을 마칠 수 있게 된다. 또한, 매개변수를 분리해 함수의 의도를 명확히 유지하도록 할 수 있다:

고차함수
1. 고차함수 userLogs는 일부 정보(userName)를 받아서 함수를 반환하는데, 나머지 정보(message)가 사용 가능해지면; 반환된 함수를 써서 추가 작업을 수행하게 된다:
                                    
                                        const userLogs= userName => message => console.log(`${userName}의 ${message}`);

                                        const log= userLogs("Kjc")
                                        log("메시지") // Kjc의 메시지
                                        log("메시지 2") // Kjc의 메시지 2
                                    
                                

위 코드에서 userLogs 함수는 고차함수로서, userLogs 를 호출해 만들어지는 log 함수를 호출할 때마다 메시지 맨 앞에 'Kjc'가 덧붙여진다!

2. 고차함수는 완전히 완료되기 전에 여러번 호출되어야 하는 함수인데, 연속된 ()로 호출하면 외부 함수가 호출된 후 바로 내부 함수가 호출된다!
                                    
                                        const going= ['남산', '남산'], cities= ['서울', '경주']

                                        const namsan= (...lt) => (...rt) => { // 고차함수
                                            return lt.map((val, idx) => [val, rt[idx]]);
                                        }
                                        console.log(namsan(...cities) (...going)) // [['서울', '남산'], ['경주', '남산']]
                                    
                                
                                    
                                    const sans= [
                                        { 지역: '경주', 산이름: '남산', 높이: '466m' }, { 지역: '서울', 산이름: '남산', 높이: '270m' },
                                    ]

                                    function getSan(sans, filter) {
                                        const [key, value]= filter

                                        return sans
                                        .filter(san => san[key] === value)
                                        .map(san => san['지역'] + " " + san['산이름'] + ": " + san['높이']);
                                    }

                                    console.log(getSan(sans, ['지역', '경주'])) // ['경주 남산: 466m']
                                
                                

셋 Set

Map 을 연결한다는 점에서 객체와 비슷하고, Set 은 중복을 허용하지 않는다는 점을 제외하면 배열과 비슷하다!

셋 Set
은 배열과 마찬가지로 값의 집합인데, 중복을 허용하지 않는 유일한 데이터를 수집하여 활용하기 위한 객체로서, 배열의 length 속성처럼 size 속성으로 (중복되지 않는)값의 개수를 확인할 수 있다:
                                    
                                        let hi= new Set("Hello Kjc"); // 인자와 함께 셋 생성

                                        console.log(hi) // Set(8) {H', 'e', 'l', 'o', ' ', 'K', 'j', 'c'} ← 중복된 값 제외
                                        console.log(hi.size) // 8 ← 셋에 포함된 값의 개수

                                        console.log([... new Set(hi)]) // ['H', 'e', 'l', 'o', ' ', 'K', 'j', 'c'] ← 전개 연산자를 써서 셋을 배열로 가져온다
                                    
                                

new Set()인자 로는 숫자나 문자열, 배열, 셋 객체를 포함한 모든 이터러블 객체가 들어갈 수 있는데, 셋은 배열과는 달리 순서도 없고 인덱스도 없으며, 중복된 값도 허용하지 않는다!

1. 셋은 생성 이후에도 언제든 add(), delete() 메서드로 요소를 추가/제거할 수 있으며, clear() 메서드를 쓰면 모든 셋 요소들을 한번에 비울 수 있다:
                                    
                                        const roles= new Set() // 빈 Set 객체를 생성한다

                                        roles.add('User')
                                        roles.add('Admin')
                                        console.log(roles) // Set(2) {'User', 'Admin'}

                                        roles.add('User')
                                        console.log(roles.size) // 2 ← 이미 있는 경우 아무 일도 하지 않는다!

                                        roles.delete('Admin') // true ← 있으면; 삭제하고 true를 리턴한다!
                                        roles.delete('Admin') // false ← 없으면; (에러는 내지 않고)그저 false를 리턴한다!
                                        console.log(roles) // Set(1) {'User'}
                                    
                                
2. 셋의 요소를 다룰 때 배열과 객체, 함수 등은 참조로 전달해야 하며, 특정 값이 셋의 요소인지 체크하는 has() 메서드는 요소의 존재 여부를 확인하는데 있어서(===로 일치 여부를 판단한다!) 배열의 includes() 메서드에 비해 더 엄격하고, 또한 더 효율적이다:
                                    
                                        let s= new Set()

                                        s.size // 0
                                        s.add(1).add(2).add(' ') // 셋에 요소를 추가할 때 메서드 체인으로 연결할 수 있다!
                                        s.add(['a', 'b', 'c']) // 배열은 각 요소별로가 아니라 하나의 배열 자체로 추가된다!
                                        console.log(s) // {1, 2, ' ', ['a', 'b', 'c']} ← 배열과 객체, 함수 등은 참조로 들어간다!

                                        s.add(['a', 'b', 'c']) // 이 배열은 위에서 추가한 배열과는 다른 배열이므로 셋에 추가된다 ← 배열은 참조로 들어간다!
                                        console.log(s) // {1, 2, ' ', ['a', 'b', 'c'], ['a', 'b', 'c']}

                                        s.delete(['a', 'b', 'c']); // 이 배열은 위에서 추가한 배열과는 다른 배열이므로 제거할 수 없다 ← 배열은 참조로 들어간다!
                                        console.log(s) // {1, 2, ' ', ['a', 'b', 'c'], ['a', 'b', 'c']}

                                        console.log(s.has('')) // false ← 셋은 일치 여부를 판단할 때 === 연산자를 사용하여 엄격하게 체크한다!
                                    
                                
                                    
                                        let nums= new Set([1, 2, 3]) // 숫자
                                        console.log(nums) // Set(3) {1, 2, 3}
                                        console.log(nums.has(3)) // true

                                        let arr= new Set(['a', 'b', 'c']) // 배열
                                        console.log(arr) // Set(3) {'a', 'b', 'c'}
                                        console.log(arr.has('c')) // true

                                        let str= new Set("namsan") // 문자열
                                        console.log(str) // Set(4) {'n', 'a', 'm', 's'}
                                        console.log(str.has('s')) // true
                                    
                                
셋에서 루프 돌기
1. 셋은 이터러블 이므로 당연히 for .. of 문으로 셋 요소를 열거할 수 있고, 나아가 배열의 forEach() 메서드 또한 지원한다. 또한, 펼침 연산자 ...를 써서 배열의 요소나 함수의 인자 리스트로 넘겨줄 수도 있다:
                                    
                                        const txt= "No no no no Batman!"
                                        let wordSet= new Set(txt.split(" ")) // 중복을 제외하면서 셋 이터러블을 생성한다

                                        let gh= []
                                        for(let word of wordSet) { // for .. of 루프로 셋을 순회한다
                                            gh.push(word)
                                        }

                                        console.log(gh) // ['No', 'no', 'Batman!']
                                    
                                
                                    
                                        const sep= new Set([1, 3, 5])
                                        console.log(sep) // Set(3) {1, 3, 5}
                                        console.log(Math.max(...sep)) // 5 ← 셋을 분해하여 max 함수의 인자 리스트로 전달한다!

                                        let pro= 1
                                        sep.forEach(n => { pro *= n }) // pro= 1x1, pro= 1x3, pro= 3x5 ← 셋에는 인덱스가 없으므로 요소값만 받아서 처리한다!
                                        console.log(pro) // 15
                                    
                                
2. 셋에는 순서도, 인덱스도 없지만 항상 요소가 삽입된 순서를 기억하면서 그 순서대로 순회한다 - 아래 코드들은 중복을 제외하면서 값을 가져오는 다양한 방법들이다:
                                    
                                        const dogs= [
                                            { name: '바둑이', size: '中', color: '검정' },
                                            { name: '귀요미', size: '小', color: '검정' },
                                            { name: '살살이', size: '大', color: '갈색' }
                                        ]

                                        /* 방법 1 */
                                        function getColors(dogs) {
                                            const colors= new Set() // 셋의 인스턴스 생성

                                            for(const dog of dogs) { // docs의 각 요소별로 루프를 돈다
                                                colors.add(dog.color) // 각 요소의 color 프로퍼티를 셋의 요소로 넣는다
                                            } // 루프를 돌면서 셋으로 넣으므로 중복되는 요소는 제외된다!

                                            return [...colors]; // 배열로 만들어 반환한다
                                        }

                                        console.log(getColors(dogs)) // ['검정', '갈색']
                                    
                                
                                    
                                        const dogs= [
                                            { name: '바둑이', size: '中', color: '검정' },
                                            { name: '귀요미', size: '小', color: '검정' },
                                            { name: '살살이', size: '大', color: '갈색' }
                                        ]

                                        function getColors(dogs) {
                                            return dogs.map(dog => dog.color); // 각 요소의 color 값을 뽑아서 배열로 반환한다
                                        }

                                        /* 방법 2-1 */
                                        const unique= new Set(getColors(dogs)) // 배열을 받아 셋을 생성한다 ← 셋에서 중복 항목은 제거된다
                                        console.log([... unique]) // ['검정', '갈색'] ← 전개 연산자 ...를 써서 배열로 만든다

                                        /* 방법 2-2 */
                                        const colors= getColors(dogs) // ['검정', '검정', '갈색']

                                        function getUnique(attr) {
                                            return [... new Set(attr)]; // 셋을 생성하여 전개 연산자 ...를 써서 배열로 변환한 뒤 반환한다
                                        }
                                        console.log(getUnique(colors)) // ['검정', '갈색']
                                    
                                
                                    
                                        const dogs= [
                                            { name: '바둑이', size: '中', color: '검정' },
                                            { name: '귀요미', size: '小', color: '검정' },
                                            { name: '살살이', size: '大', color: '갈색' }
                                        ]

                                        /* 방법 3 */
                                        function getReduce(dogs) {
                                            const colors= [...dogs.reduce((colors, { color }) => colors.add(color), new Set())]; // 초기값을 빈 셋으로 주었다
                                            return colors;
                                        }
                                        console.log(getReduce(dogs)) // ['검정', '갈색']
                                    
                                

맵 Map

Map은 데이터를 다양한 방식으로 수집하여 활용하기 위한 객체로서, 값의 고유한 식별 정보인 (문자열만 아니라 모든 데이터 타입이 가능한) 키 => 값Map 객체 안에 (일반 객체와는 달리 순서대로)저장해서 사용한다

맵 객체의 생성 및 정의
은 (특정 으로 연결되는) 집합으로서, 배열에서와 같은 인덱스 대신 (숫자 및 객체도 가능하다)를 인덱스처럼 사용할 수 있으며, 키(및 값)이 자주 추가/제거되거나 변경되는 경우에 특히 유용하다!
[ 맵 객체의 생성 및 초기화 ]
                                        
                                            // Map 생성자에 Set('키', '값')을 메서드 체인으로 연결하여 초기화한다
                                            let filters= new Map().set('견종', '동경이').set('크기', '대형견').set('색상', '갈색');
                                            console.log(filters.get('견종')) // 동경이

                                            // Map 생성자에 배열(의 배열)로 넘긴다
                                            let filters2= new Map([['견종', '동경이'], ['크기', '대형견'], ['색상', '갈색']]);
                                            console.log(filters2.get('색상')) // 갈색
                                        
                                    

Map 객체 생성과 함께 초기값으로 인자를 전달하는 경우, 그 인자는 [key, value] 배열을 전달하는 이터러블 객체여야 한다 - 곧, 배열의 배열 형태여야 한다!

1. new Map() 생성자로 빈 맵을 생성하거나 인자로 초기값을 넣어줄 수도 있는데, 초기값에 다른 맵을 불러오거나 기존 객체의 프로퍼티( )을 가져올 수도 있다:
                                    
                                        let m= new Map() // 빈 맵 생성

                                        let n= new Map([[1, "1st"], [2, "2nd"], [3, "3rd"]]) // 숫자를 키로 사용하는 맵 생성
                                        console.log(n) // Map(3) {1 => '1st', 2 => '2nd', 3 => '3rd'}

                                        m= new Map(n) // 기존 맵 n를 넣어 새 맵 m을 생성
                                        console.log(m) // Map(3) {1 => '1st', 2 => '2nd', 3 => '3rd'}

                                        let obj= { x: 1, y: 2 }
                                        let o= new Map(Object.entries(obj)) // 기존 객체의 키/값으로 초기화한 맵 생성
                                        console.log(o) // Map(2) {'x' => 1, 'y' => 2}
                                    
                                
2. Map 객체를 만들면 get(키)로 주어진 에 연결된 을 검색할 수 있고, set(키, 값)으로 키 => 값 을 추가할 수 있다. 이미 존재하는 키로 set(키, 값)을 호출하면(맵은 각 키가 값과 연결되어 있을 뿐, 키-값 쌍의 집합은 아니다!) 해당 키에 연결된 값을 수정한다:
                                    
                                        let m= new Map().set(1, "1st").set(2, "2nd");
                                        console.log(m.get(2)) // 2nd ← 키에 연결된 값 가져오기

                                        m.set(2, "Second") // 키에 연결된 값 변경하기
                                        console.log(m.get(2)) // Second

                                        m.delete(2) // 특정 키 삭제
                                        console.log(m.has(2)) // false ← 맵.has('키')는 맵에 해당 키가 존재하는지를 확인한다

                                        m.clear() // 모든 키 삭제
                                        console.log(m.size) // 0 ← 맵.size는 맵 요소의 길이를 확인한다
                                    
                                

맵은 셋에서 사용되는 메서드들 거의 모두 같은 방식으로 쓸 수 있고, 초기화 시에도 셋처럼 메서드 체인으로 값을 넣어줄 수 있다!

맵의 순회
Map 객체이터러블 이며, 키 => 값 으로 구성된 이터레이터로 구성된다. for .. of 문으로 순회할 때 반환되는 값의 첫번째 요소는 이고, 두번째 요소는 인 배열이다
                                    
                                        let m= new Map([["x", 1], ["y", 2]]);

                                        console.log(m.entries()) // MapIterator {'x' => 1, 'y' => 2}

                                        for(let entry of m) {
                                            console.log(entry) // (2) ['x', 1], (2) ['y', 2] ← 각각 다른 2개의 배열이다!
                                        }
                                    
                                
1. Map 객체에 펼침 연산자 ...를 사용하면 Map() 생성자에 전달했을 배열의 배열을 반환하는데, for .. of 문으로 맵을 순회할 때는 해체 할당을 써서 을 별도의 변수에 할당하여 쓰는 것이 일반적이다:
                                    
                                        let m= new Map([["x", 1], ["y", 2]]);

                                        console.log([...m]) // [['x', 1], ['y', 2]] ← 배열로 해체하여 나열하였다!

                                        for(let [key, value] of m) { // [키, 값]으로 해체 할당
                                            console.log(key, value); // x 1 y 2 ← 키, 값의 순서이다!
                                        }
                                    
                                
2. 맵 또한 셋처럼 삽입된 순서대로 순회한다: 중 하나만 순회하고자 하는 경우에는 keys()values(), 키와 값 모두 가져오고자 할 때는 entries() 메서드를 쓸 수 있다. forEach() 메서드 또한 사용 가능하다:
                                    
                                        let m= new Map([["x", 1], ["y", 2]]);

                                        console.log([... m.keys()]) // ['x', 'y']
                                        console.log([... m.values()]) // [1, 2]
                                        console.log([... m.entries()]) // [['x', 1], ['y', 2]]

                                        m.forEach((value, key) => { console.log(`${key}: ${value} `) }); // x: 1 y: 2
                                    
                                

중복 배열로 구성된 에서 forEach() 메서드는 배열 요소의 값, 키 를 세트로 가져와 처리하는데, 인자는 , 순서로 된다 forEach() 메서드는 첫번째 인자로 (인덱스 가 아니라!) 으로 받는다!


이터러블 객체보다 배열이 필요하다면; 전개 연산자 ...를 사용하면 코드가 보다 간결해진다:

                                    
                                        const m= new Map().set('경주', '남산').set('서울', '남산');

                                        function getStr(m) {
                                            const selected= [...m].map(([key, value]) => { return `[${key} ${value}]` });

                                            return `${selected.join(', ')}`
                                        }

                                        console.log(getStr(m)) // [경주 남산], [서울 남산]
                                    
                                
                                    
                                        const defs= new Map().set('색상', '갈색').set('견종', '동경이').set('지역', '경주');
                                        const filters= new Map().set('색상', '흰색');

                                        function applyDefs(filter, defs) { // filters는 참조가 아닌 값으로 전달된다!
                                            return new Map([...defs, ...filter]); // 맵을 병합하여 새로운 맵을 생성한다 ← 같은 키는 덮어씌워진다!
                                        }

                                        console.log(applyDefs(filters, defs)) // Map(3) {'색상' => '흰색', '견종' => '동경이', '지역' => '경주'}
                                    
                                
➥ Map 대 객체

은 객체로 을 연결할 때 생기는 프로토타입 체인에 의한 의도치 않은 연결, 객체 내 프로퍼티의 개수 확인 불가, 객체 내 프로퍼티의 순서가 보장되지 않는다는 점 등의 여러 단점들을 제거하였다. 나아가, 을 사용하면 객체 자체를 로 쓸 수도 있다:

                                        
                                            const user1= {name: 'Kim'}, user2= {name: 'Lee'}, user3= {name: 'Park'}

                                            const users= new Map().set(user1, 'User').set(user2, 'User').set(user3, 'Guest'); // 객체를 키로 하여 값 설정!
                                            users // [[Entries]] {user1 => "User"}, {user2 => "User"}, {user3 => "Guest"}

                                            for(const [key, value] of users.entries()) { // 맵을 키(객체)와 값으로 해체 할당한다
                                                console.log(`${key.name}: ${value} `) // Kim: User Lee: User Park: Guest
                                            }
                                        
                                    

Symbol 데이터

스크립트의 객체 타입은 프로퍼티의 순서없는 나열이며, 각 프로퍼티는 (문자열로 된)이름 및 그 을 갖고 있다. 프로퍼티의 이름 은 문자열이고, Symbol 또한 같은 목적으로(오직 이 하나의 용도로) 사용된다!

심볼 데이터타입
Symbol은 자기 자신을 제외한 그 어떤 값과도 다른 유일무이한 원시값을 나타내며, 심볼로 된 속성 키는 바깥에서는 읽을 수 없게 숨겨진다. 심볼 생성 시 넣어준 인수는 단지 심볼에 대한 설명일 뿐이고, 심볼의 내용은 심볼.toString() 메서드로 확인할 수 있다
[ 심볼의 생성 및 내용 확인 ]
                                        
                                            const BLACK= Symbol("블랙"), WHITE= Symbol("블랙") // 심볼의 생성

                                            if(BLACK === WHITE) console.log("절대 같을 수가 없습니다만..")
                                            else console.log(BLACK.toString() + " / " + WHITE.toString()) // Symbol(블랙) Symbol(블랙)
                                        
                                    
1. 심볼은 그 어떤 값과도 다른 유일무이한 값이기에 함수 안에서 심볼을 생성하여 그것을 속성 이름으로 사용하고, 그 프로퍼티에 값을 할당하면 함수 바깥에서 그 값을 읽거나 쓸 수 없게 된다. 따라서, 심볼을 사용하면 여타 라이브러리 등과 메서드 이름이 겹치는 것을 피하면서도 스크립트 기본 객체의 prototype을 확장할 수 있게 된다:
                                    
                                        const ext= Symbol("my extension symbol")

                                        let obj= {
                                            [ext]: { /* 확장 데이터 저장 */ }
                                        }
                                        obj[ext].x= 0 // obj의 다른 프로퍼티와 충돌하지 않는다!                                
                                    
                                
2. Symbol()은 선택 사항인 인자로 문자열을 받고, 고유한 심볼 값을 반환한다. 문자열 인자를 전달하면; 그 문자열은 심볼의 toString() 메서드 결과에 포함된다. 하지만 같은 문자열로 재차 Symbol()을 호출하더라도 그 결과는 완전히 다른 값이다:
                                    
                                        const people= { Kim: "onner", Lee: "ceo", Lee: "cio" }

                                        for(per in people) {
                                            console.log(per + " ") // Kim Lee
                                        }

                                        const people2= {
                                            [Symbol("Kim")]: "onner", [Symbol("Lee")]: "ceo", [Symbol("Lee")]: "cio"
                                        }

                                        const symPeople= Object.getOwnPropertySymbols(people2) // people2에서 심볼로 된 속성들을 가져온다

                                        const value= symPeople.map(sym => people2[sym]) // map() 메서드를 사용하여 속성값을 얻어야 한다!
                                        console.log(value) // ['onner', 'ceo', 'cio']
                                    
                                

심볼은 항상 고유하며(같은 값이 들어가 있어도 서로 겹치지 않는다 - 따라서, 같은 이름으로 된)객체 속성의 식별자로 사용할 수 있게 된다

wave